文章目录
- 一、ADC模数转化器
- ADC简介
- 逐次逼近型ADC
- ADC框图
- 二、ADC基本结构
- 三、触发转换控制
- 四、输入通道
- 五、规则组的四种转换模式
- 单次转换,非扫描模式
- 连续转换,非扫描模式
- 单次转换,扫描模式
- 连续转换,扫描模式
- 六、数据对齐
- 七、转换时间(可编程的通道采样时间)
- 八、校准
- 九、硬件电路
- 十、电压值在抖动的解决方案:
- 十一、AD单通道
- 电路设计
- 关键代码
- 十二、AD多通道
- 电路设计
- 关键代码
一、ADC模数转化器
ADC简介
- ADC(Analog-Digital Converter)模拟-数字转换器
- ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
- ADC其实是一个
电压表
,将引脚输入的高低电平间的任意电压进行量化,放在数据寄存器里面
12位
【分辨率】逐次逼近型ADC,1us转换时间【转化频率1MHZ】- 输入电压范围:0~ 3.3V,转换结果范围:0~4095【2^12-1】
- 18个输入通道,可测量16个外部和2个内部信号源【内部温度传感器和参考电压VREFINT=1.2V】
- 规则组和注入组两个转换单元
- 模拟看门狗
自动监测输入电压范围
【应用于中断】 STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道
- PWM也属于数字信号到模拟信号的桥梁,在某些方面优于DAC,没有功率损耗,DAC应用于信号发生器,音频解码,PWM用于控制电机速度
逐次逼近型ADC
通过ADC0809外挂芯片了解逐次逼近型ADC
- 电压比较器,未知编码电压与已知编码电压比较,采用二分法比较,
二分法选取的电压值恰好是二进制的位权
,所以8位只要分8次就可以找到对应编码电压,8位的二级制最大值是255 - 电压比较器比较未知编码电压与已知编码电压,大于时调小DAC的模拟电压,反之调大,DAC的输出数据就是未知编码电压
- DAC参考电压决定ADC参考电压,因为两者相互比较,通常ADC的供电电压(Vcc)和DAC参考电压(VREF+)连接在一起
- DAC内部是由加权电阻网络实现
- EOC输出转换结束信号
- CLOCK为ADC时钟,因为比较是需要时钟推动
- START信号:触发转化的信号源,如定时器,外部中断等
ADC框图
- 常规情况使用规则组,突发事件的注入组
- 开启注入通道,可以同时接收4个通道的输入,经过逐次比较后输出到四个数据寄存器,不会有覆盖问题,而规则通道会有覆盖问题,只能存放一个通道的值在一个数据寄存器中【配合DMA实现,避免数据被覆盖】
- VREF+和VREF-为参考电压,是进行逐次比较的已知编码电压
- VDDA和VSSA是供电引脚,是内部模拟部分的电源,比如ADC,RC振荡器,锁相环等
- ADCCLK:来自ADC预分频器,通过RCC时钟树可以知道最大是频率是14MHZ,
只能选择6分频【12MHZ】或8分频【9MHZ】
- EOC是规则组的完成信号,JEOC是注入组的完成信号,通过读取状态标志位了解是否完成转换。可以使能中断功能,由NVIC管理外设ADC中断
- ADC触发方式有软件触发和硬件触发,此处是硬件触发,包含定时器触发,外部中断引脚触发,规则组合注入组都不一样。
- 定时器中断可以触发ADC、DAC转换,但是频繁进入中断会消耗资源,通过定时器的更新事件映射到TRGO输出,此时就是通过硬件自动触发ADC转换
二、ADC基本结构
关键函数
ADC_RegularChannelConfig()//模拟多路开关函数
ADC_SoftwareStartConvCmd()//软件触发函数,转换开启的函数
ADC_Cmd()//开关控制
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//ADCCLK函数在rcc.h文件里面
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);//等待转换结果的完成,通过判断EOC标志位
ADC输入引脚必须配置为模拟输入
- 在模拟输入模式下,会断开GPIO口,GPIO口输入的电压失效,防止对模拟输入造成影响
三、触发转换控制
规则组合注入组触发控制的方式:
规则组触发控制的方式
四、输入通道
stm32f103c8t6的10个输入通道
- ADC1和ADC2通道的引脚相同,可以用于组成双ADC模式:配合组成同步模式,交叉模式(ADC1和ADC2交叉对一个通道进行采样,提高采样频率)
五、规则组的四种转换模式
单次转换,非扫描模式
连续转换,非扫描模式
单次转换,扫描模式
- 扫描模式只有在列表里面所有的通道都转换完成才会发出EOC信号
连续转换,扫描模式
六、数据对齐
因为数据寄存器16位,而ADC寄存器是12位,所以涉及数据对齐
数据右对齐:
数据左对齐:
- 数据左移四次,等效于乘以16
- 左对齐的好处是当不需要12位的精度而只需要8位精度时,采用左对齐,然后只取出前8位即可
七、转换时间(可编程的通道采样时间)
AD转换的步骤:采样,保持,量化,编码
- 采样保持:闭合采样开关,存储需要量化的电压
- 量化编码:比较器比较电压
STM32 ADC的总转换时间为:
-
TCONV = 采样(保持)时间 + 12.5个ADC周期【ADCCLK】
-
例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期
- TCONV = 1.5 + 12.5 = 14个ADC周期 = 1μs
//采样保持时间在这个函数的最后一个参数里面设置
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);//配置通道选择器
//ADCCLK设置
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//ADCCLK函数在rcc.h文件里面
//参数
@arg RCC_PCLK2_Div6: ADC clock = PCLK2/6//6分频为12MHZ
@arg RCC_PCLK2_Div8: ADC clock = PCLK2/8//8分频为9MHZ
八、校准
- ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差
- 建议在每次上电后执行一次校准
- 启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期
固定代码
ADC_ResetCalibration(ADC1);//校准寄存器
while (ADC_GetResetCalibrationStatus(ADC1) == SET);//获取所选ADC重置校准寄存器状态。当校准完成会变为RESET,此时跳出循环
ADC_StartCalibration(ADC1);//启动选定的ADC校准过程。
while (ADC_GetCalibrationStatus(ADC1) == SET);//获取所选ADC校准状态。
九、硬件电路
- 传感器输出电压电路输出引脚是AO
- 电压转换电路中输入的电压范围为0-5V,通过串联两个电阻将电压转化为0-3.3V
十、电压值在抖动的解决方案:
当设定了阈值与电压值进行比较时会遇到抖动导致功能无法正常实现,可以采用如下方法:
- 采用迟滞比较方式:设置两个阈值,优于一个阈值的方式,在两个阈值中间抖动不会影响
- 采用滤波:均值滤波
- 裁剪分辨率
十一、AD单通道
电路设计
关键代码
AD.c
#include "stm32f10x.h" // Device header
void AD_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//ADCCLK函数在rcc.h文件里面
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//ADC输入引脚必须配置为模拟输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//ADC12_IN0
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);//配置规则组通道选择器,第三个参数是通道里面的序列
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//独立通道or双通道
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//指定ADC数据对齐是左对齐还是右对齐
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//触发方式,外部触发None表示采用软件触发
//设置成单次转换非扫描
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//连续转换or单次转换
ADC_InitStructure.ADC_ScanConvMode = DISABLE;//扫描模式or非扫描模式
ADC_InitStructure.ADC_NbrOfChannel = 1;//指定ADC通道数量,在扫描模式下才有
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
//校准固定代码
ADC_ResetCalibration(ADC1);//校准寄存器
while (ADC_GetResetCalibrationStatus(ADC1) == SET);//获取所选ADC重置校准寄存器状态。当校准完成会变为RESET,此时跳出循环
ADC_StartCalibration(ADC1);//启动选定的ADC校准过程。
while (ADC_GetCalibrationStatus(ADC1) == SET);//获取所选ADC校准状态。
//ADC_SoftwareStartConvCmd(ADC1, ENABLE);//当选择连续单通道时可以将触发函数写在初始化函数内部,在while循环里面就不会频繁触发。此时EOC标志位也可以不用判断是否置1,直接返回数据寄存器里面的ADC转换结果值即可
}
uint16_t AD_GetValue(void)
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//启用或禁用所选ADC的软件触发
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);//等待转换结果的完成,
return ADC_GetConversionValue(ADC1);//返回常规通道的最后ADCx转换结果数据。读取DR寄存器会自动清除EOC标志位,不用手动清除
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t ADValue;
float Voltage;
int main(void)
{
OLED_Init();
AD_Init();
OLED_ShowString(1, 1, "ADValue:");
OLED_ShowString(2, 1, "Volatge:0.00V");
while (1)
{
ADValue = AD_GetValue();
Voltage = (float)ADValue / 4095 * 3.3;
OLED_ShowNum(1, 9, ADValue, 4);
//showNum显示的是整数,通过计算获取小数部分
OLED_ShowNum(2, 9, Voltage, 1);
OLED_ShowNum(2, 11, (uint16_t)(Voltage * 100) % 100, 2);
Delay_ms(100);
}
}
十二、AD多通道
电路设计
关键代码
AD多通道采集一般采用扫描模式,在本案例中可以用单次转换非扫描模式,在每次触发转换之前,更改列表第一个序列的通道
AD.c
#include "stm32f10x.h" // Device header
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
}
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
//在规则组通道选择器配置函数中,将采样通道设置为形参
ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5);//第三个参数表示序列1
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//切换序列上的通道后在开启转换
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
return ADC_GetConversionValue(ADC1);
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t AD0, AD1, AD2, AD3;
int main(void)
{
OLED_Init();
AD_Init();
OLED_ShowString(1, 1, "AD0:");
OLED_ShowString(2, 1, "AD1:");
OLED_ShowString(3, 1, "AD2:");
OLED_ShowString(4, 1, "AD3:");
while (1)
{
AD0 = AD_GetValue(ADC_Channel_0);
AD1 = AD_GetValue(ADC_Channel_1);
AD2 = AD_GetValue(ADC_Channel_2);
AD3 = AD_GetValue(ADC_Channel_3);
OLED_ShowNum(1, 5, AD0, 4);
OLED_ShowNum(2, 5, AD1, 4);
OLED_ShowNum(3, 5, AD2, 4);
OLED_ShowNum(4, 5, AD3, 4);
Delay_ms(100);
}
}