目录
1.什么是ADC采样?
ADC可以被称做二进制模数转换器,也即将模拟信号转换成数字信号。通俗理解ADC采样就是采集电路中的电压,将电压的波形通过二进制的方式表现出来。以STM32F103系列为例,它可以反应0~4095,换句话说,它采集的电压数值上表现为0~4095,也就是12位ADC(2^12)。
(1)ADC采样的原理
①采样的数值
以STM32F103xxxx芯片为例,供电电压是3.3V,它能检测的电压也是0~3.3V。而0~3.3V电压对应的是0~4095数值。所以可以理解为把3.3V分成了4096份,采集上来多少份占3.3V的比例。当然,当供电电压改变,或芯片变更,或许就不止4096份,这要根据数据手册上的数据来改变!
例如:采集电压=(采集数值/4095)*3.3V
②ADC的18个通道
12位ADC是一种逐次逼近型模拟数字转换器。它有多达18个通道,可测量16个外部和2个内部信号源(不同引脚数的STM32外部通道数可能有所差异)。运用时多数在使用外部的16个信号源各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。
ADC的输入时钟不得超过14MHz,它是由PCLK2经分频产生。
如下图:
③通道与引脚的对应表(以stm32F103C8T6为例)
如图:
④ADC校准原理
ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。在校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差。通过设置ADC_CR2寄存器的CAL位启动校准。一旦校准结束,CAL位被硬件复位,可以开始正常转换。建议在上电时执行一次ADC校准。
校准阶段结束后,校准码储存在ADC_DR中。
注意:
1.建议在每次上电后执行一次校准。
2.启动校准前,ADC必须处于关电状态(ADON=’0’)超过至少两个ADC时钟周期。
如下图所示代码:
/*ADC校准*/
ADC_ResetCalibration(ADC1); //开启复位校准,固定流程,
//内部有电路会自动执行校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);//等待复位校准结束
ADC_StartCalibration(ADC1); //开启 AD 校准
while (ADC_GetCalibrationStatus(ADC1) == SET); //等待校准结束
⑤数据对齐
AD转换后数字量保存在ADCH,ADCL两个寄存器中
左对齐的AD值的最高位是ADCH的最高位,ADCL的低位有的用不到,读出来为0。
而右对齐的AD值的最低位是ADCL的最低位,ADCH的高位有的用不到,读出来为0。
⑥数据读取时可编程的通道采样时间
ADC使用若干个ADC_CLK周期对输入电压采样,采样周期数目可以通过ADC_SMPR1和 ADC_SMPR2寄存器中的SMP[2:0]位更改。每个通道可以分别用不同的时间采样。
总转换时间如下计算:
Tconv = 采样时间+ 12.5个周期
例如:
当ADCCLK=14MHz,采样时间为1.5周期
Tconv = 1.5 + 12.5 = 14周期 = 1μs
2.ADC采样的常用代码
(1)初始化ADC
void AD_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*设置ADC时钟*/
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
/*GPIO初始化*/
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); //将PA0、PA1、PA2和PA3引脚初始化为模拟输入
/*不在此处配置规则组序列,而是在每次AD转换前配置,这样可以灵活更改AD转换的通道*/
/*ADC初始化*/
ADC_InitTypeDef ADC_InitStructure; //定义结构体变量
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //模式,选择独立模式,即单独使用ADC1
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐,选择右对齐
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发,使用软件触发,不需要外部触发
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //连续转换,失能,每转换一次规则组序列后停止
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //扫描模式,失能,只转换规则组的序列1这一个位置
ADC_InitStructure.ADC_NbrOfChannel = 1; //通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
ADC_Init(ADC1, &ADC_InitStructure); //将结构体变量交给ADC_Init,配置ADC1
/*ADC使能*/
ADC_Cmd(ADC1, ENABLE); //使能ADC1,ADC开始运行
/*ADC校准*/
ADC_ResetCalibration(ADC1); //开启复位校准,固定流程,内部有电路会自动执行校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启 AD 校准
while (ADC_GetCalibrationStatus(ADC1) == SET); //等待校准结束
}
(2)读取ADC数据
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5); //在每次转换前,根据函数形参灵活更改规则组的通道1
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发AD转换一次
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待EOC标志位,即等待AD转换结束
return ADC_GetConversionValue(ADC1); //读数据寄存器,得到AD转换的结果
}
3.ADC采样与实际应用的区别
在实际上,外部模块往往有安装有ADC转化模块,例如HX711芯片就内部有ADC数模转化模块,仅仅只要将其中的ADC数据读取就可以了,所以就用不到在内部程序中进行ADC初始化,但也不要担心,例如在HX711中,如果用stm32读取,写程序的时候就必须考虑到通道采样时间与左右对齐的问题了如下程序,同样是ADC采样,但写法却不一样,相当于是IIC协议简单读取数据。
unsigned long HX711_Read(void)//unsigned long 为无符号整数,不能表示负数
{
unsigned long Count;
unsigned char i;//为无符号字符,不能表示负
W_SCK(0);//拉低SCK时钟
Count=0;//相当于清空Count里的所有数
while(R_DT);//由于DT是上拉输入,所以当DT口没有信号输入或者悬空
//则IO口默认为高电平,即R_DT==1,
//直到DT口检测到了信号输入,才置为低电平,进入下面程序
for (i=0;i<24;i++)//这里由于是128增益,所以要循环24次
{
W_SCK(1);
Count=Count<<1;//这里指的是将Count左位移一位
W_SCK(0);
if(R_DT) //如果有DT电平输入,那空的Count那一位就由0变1
//否则Coun那一位任然为0,等下一次Count位移继续判断
Count++;
}
W_SCK(1);
Count=Count^0x800000; //最高位取反,其他位不变
//在HX71芯片中,count是一个32位的有符号整数,
//用于存储称重传感器的读数。
//当count的最高位为1时,表示读数为负数,
//而HX711芯片不支持负数的读数。
//因此,为了将负数转换为正数,需要将count的最高位取反,
//即将count与0x800000进行异或操作。
//具体来说,0x800000的二进制表示为100000000000000000000000,
//与count进行异或操作后,
//可以将count的最高位从1变为0,从而得到对应的正数读数。
W_SCK(0);
return(Count);
}
总结:
以上就是我最近对于ADC数模转化的总结,总的来说ADC转换对于嵌入式是至关重要的一环,不可或缺,也希望看到文章的各位有所收益,加油!!!