stm32-ADC
2026/7/31 13:17:20 网站建设 项目流程

关于stm32的ADC

12位就是ADC的分辨率,分辨率越高转换越精细;1us是开始AD转换到取得结果的时间,对应转换频率1MHZ。

输入电压的范围是负极到正极的范围,转换范围即0~2^12-1,这两个范围每个数是一一对应的线性关系,0对应0,3.3对应4095,中间的数也一一对应。

stm32的型号中最多有18个通道,外部信号源即模拟电压输入GPIO口,几个外部信号源就是几个GPIO口,可以直接测电压;2个内部信号源分别是内部的温度传感器和参考电压。温度传感器可以测CPU的温度;参考电压一般在1.2v左右,不受外部供电影响,在一些情况下比如电机驱动或者大功率负载时单片机供电电压3.3v会被拉低,参考电压的作用就是依靠它算出准确的供电电压

普通的AD转换流程是启动->转换,来一个就启动转换一次,但stm32可以将信号分成组,一次性转换一个组,其中规则组是用于平时转换,注入组是用于突发情况下转换。

AD转换时可以设置转换值的上下阈值,模拟看门狗可以在指定通道监测值的变化,一旦超出阈值范围就会申请中断

关于DAC

stm32有一些型号是没有DAC的,比如F103C8T6,这时候可以用PWM代替,同样都是数字信号转换成模拟信号输出;实际上PWM使用时没有功率损耗,在这方面比DAC更加常用,DAC主要用于波形生成,这是PWM不能代替的。

ADC内部结构

先来看一个8位的ADC芯片

可以连接8个输入通道,因为AD转换时间是微秒级别的,所以可以一个ADC接多个输入通道,提高效率。把想选择的通道地址放在ADDA/B/C这三个引脚上并给一个ALE锁存的信号,这个通道就选择了,

输入的是模拟电压,怎么能知道对应的数字呢?需要使用DAC,逐次逼近寄存器SAR给DAC一个值,DAC会将其转换成相应的模拟电压,拿去和输入电压比较,借助电压比较器,看输出的高低电平就知道谁大谁小,然后相应改变DAC的值,不断逼近输入电压,使用了类似于数学上的二分法,最后得到相似的值作为结果,这个DAC的值就会被输出。

8位的转换范围就是0~255,最终输出的数据就是8位的,逼近也需要逼近8次;START就是开始ADC转换的信号,EOC是转换结束的信号,由于逼近是一次一次的,这需要时钟来驱动,CLOCK就是时钟,VREF是DAC的参考电压,它也会影响ADC,所以同时也是ADC转换的电压范围,VCC,GND是芯片供电,一般和参考电压相同,所以这四个引脚是相连接的,正接正,负接负。

stm32中的ADC和这个差不多

先来看看信号转换部分的结构

16个外部信号源加上两个内部信号源的输入,经过多路开关选择一路通道,规则组是最常用的,可以同时选择16路通道,进行ADC转换,转换过程和上面ADC芯片差不多,然后结果传到寄存器中,但规则组的数据寄存器只能保持一个结果,旧结果会被新结果覆盖,所以需要搭配DMA使用,将前一个数据转移,防止被覆盖;注册组可以选择4路通道,并且寄存器也可以存四个结果。ADCCLK就是驱动逐次逼近的时钟,来源于APB2总线的RCC时钟,经过分频再给ADC。VREF,VDDA,VSSA就是参考电压和供电。

什么时候开始AD转换呢?再来看看触发源

软件触发方面可以通过代码触发;硬件触发方面,ADC的触发源一般都来自定时器,其中TRGO就是定时器通过主模式控制ADC自动转换,也可以来自于EXTI外部中断触发。

最后来看看AD转换完成后

不管是规则组还是注入组,AD转换完成后都会发出完成信号,并挂起标志位,规则组是EOC,注入组是JEOC,通过读取这些标志位就能知道转换是否完成。前面提到过模拟看门狗,电压超出阈值时它也会挂起标志位,它们都可以连接NVIC申请中断。

基本流程

其他相关点

输入通道和GPIO口复用关系

ADC规则组四种转换模式

单次转换:触发一次就转换一次,转换结束之后还想转换就要先再次触发

非扫描模式:不管选择了多少通道都只会转换排在第一个的通道,相当于普通的AD转换了

连续转换:触发一次之后就会连续转换,每次转换完成置EOC标志位后会自动再次转换上一次的通道,不用再触发。

扫描模式:需要设置通道数目x,触发后就会转换次序前x个通道。注意在扫描模式下,所有通道全部转换完毕后才会置一次EOC标志位,所以你不知道前面的通道是什么时候转换完成的

这些模式需要自己配置。

数据对齐

分辨率是12位的,转换结果就是12位的数据,但是数据寄存器是16位的,空出来四位,这时候就需要数据对齐。一般常用数据右对齐,这样读出来的数据就直接是转换结果;

数据左对齐相当于将数据左移了四位,扩大了16倍。左对齐可以用于改变分辨率,比如不需要2^12这么高的分辨率,那就读取前八位就行了,相当于将分辨率变成了2^8,就像数字13557,只需要知道是一万多就行了。

转换时间

量化编码就是ADC中逐次逼近的过程;这段时间里,如果输入电压还在不停变化,无法定位,结果会错乱;采样保持就是输入电压时闭合开关,给电容充电,充好之后断开开关,电容没有放电的回路,所以电压不变,就能进行转换了。

采样需要时间,12位的ADC逐次逼近也要12次,所以是12个周期,还有半个周期做一些别的事,这就是总转换时间。ADCCLK最高频率是14MHZ,如果高于这个频率时间会短一些,但是超频不能保持稳定。

校准

了解即可

ADC外围电路

为了输入可变电压,可以搭建一下电路

AD单通道代码

使用AD单通道,先开启AD和GPIO的时钟,再预分频得到ADCCLK(ADCLK最高14MHZ,所以只能选择对72MHZ6分频或8分频),然后初始化GPIO,再选择通道,然后配置ADC,最后开启ADC,后面还需要校准

void AD_init(void) { //开启时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); //预分频 RCC_ADCCLKConfig(RCC_PCLK2_Div6);//将72MHZ 6分频了,因为ADCCLK最高频率是14MHZ //初始化GPIO GPIO_InitTypeDef GPIO_InitSturcture; GPIO_InitSturcture.GPIO_Mode=GPIO_Mode_AIN;//需要模拟输入模式 GPIO_InitSturcture.GPIO_Pin=GPIO_Pin_0; GPIO_InitSturcture.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_InitSturcture); GPIO_SetBits(GPIOA,GPIO_Pin_0); //选择通道 ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_1Cycles5);//哪个ADC,哪个通道,让它排在第几个转换,采样时间多久 //配置ADC ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;//是否开启连续转换模式,不开就是单次转换 ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//数据右对齐 ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//不用外部触发源,也就是使用软件触发 ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//ADC独立模式,不使用双模式 ADC_InitStructure.ADC_NbrOfChannel=1;//转换通道总数,非扫描模式下其实没用 ADC_InitStructure.ADC_ScanConvMode=DISABLE;//是否开启扫描模式 ADC_Init(ADC1,&ADC_InitStructure); //开启ADC ADC_Cmd(ADC1,ENABLE); //校准 ADC_ResetCalibration(ADC1);//复位校准 while (ADC_GetResetCalibrationStatus(ADC1)==SET);//等待复位完成,当是SET的时候说明复位没结束,结束了会自动RESET ADC_StartCalibration(ADC1);//开始校准 while(ADC_GetCalibrationStatus(ADC1)==SET);//等待校准完成,完成会自动RESET }

初始化完成之后ADC准备就绪,接下来需要触发,以及读取结果

uint16_t ADC_get(void) { //ADC准备好了,接下来就触发转换,这里使用软件触发 ADC_SoftwareStartConvCmd(ADC1,ENABLE); //ADC开始转换了,需要通过读取标志位来确定是否转换完成 if(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==SET) { return ADC_GetConversionValue(ADC1);//读取结果,之后应该清除标志位,但是这个函数里已经清除了,就不用手动清除了 } }

AD多通道代码(不使用DMA)

不使用DMA时转换多通道,考虑到数据覆盖的问题,可以借助单次转换、非扫描模式,在每次转换完成置EOC标志位后手动换成下一个通道继续转换

void AD_init(void) { //开启时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); //预分频 RCC_ADCCLKConfig(RCC_PCLK2_Div6);//将72MHZ 6分频了,因为ADCCLK最高频率是14MHZ //初始化GPIO GPIO_InitTypeDef GPIO_InitSturcture; GPIO_InitSturcture.GPIO_Mode=GPIO_Mode_AIN;//需要模拟输入模式 GPIO_InitSturcture.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3; GPIO_InitSturcture.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_InitSturcture); //配置ADC ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;//是否开启连续转换模式,不开就是单次转换 ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//数据右对齐 ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//不用外部触发源,也就是使用软件触发 ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//ADC独立模式,不使用双模式 ADC_InitStructure.ADC_NbrOfChannel=4;//转换通道总数 ADC_InitStructure.ADC_ScanConvMode=DISABLE;//是否开启扫描模式 ADC_Init(ADC1,&ADC_InitStructure); //开启ADC ADC_Cmd(ADC1,ENABLE); //校准 ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)==SET); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)==SET); } uint16_t ADC_get(uint8_t ADC_Channel) { //选择通道 ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_1Cycles5);//哪个ADC,哪个通道,让它排在第几个转换,采样时间多久,在这里选择通道,主函数中调用这个函数并给通道值就能在单次转换完成后换成下一个通道了 //ADC准备好了,接下来就触发转换,这里使用软件触发 ADC_SoftwareStartConvCmd(ADC1,ENABLE); //ADC开始转换了,需要通过读取标志位来确定是否转换完成 if(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==SET) { return ADC_GetConversionValue(ADC1);//读取结果,之后应该清除标志位,但是这个函数里已经清除了,就不用手动清除了 } }

主函数中

uint16_t AD0,AD1,AD2,AD3; int main() { 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=ADC_get(ADC_Channel_0); AD1=ADC_get(ADC_Channel_1); AD2=ADC_get(ADC_Channel_2); AD3=ADC_get(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(500); } }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询