[STM32]Day7ADC模数转换器+ADC单通道+ADC多通道 ADC模数转换器ADCAnalog-Digital Converter模拟-数字转换器可以将引脚上连续变化的模拟电压转化为内存中存储的数字变量简历模拟电路到数字电路的桥梁。12位逐次逼近型ADC1us转换时间输入电压范围0 - 3.3V转换结果范围0 - 409518个输入通道可测量16个外部和2个内部信号源内部信号源内部温度传感器和内部参考电压。规则组和注入组两个转换单元。普通的AD流程是启动一次转换读一次值然后再启动再读值…STM32的ADC可以列一个组一次性启动一个组连续转换多个值。这样的组有两个一个是用于常规使用的规则组一个是用于突发事件的注入组。模拟看门狗自动监测输入电压范围。可以用于测量光线强度、温度等模拟量实现模拟量高于/低于某个阈值自动执行特定操作。STM32F103C8T6 ADC资源ADC1、ADC210个外部输入通道。逐次逼近型ADC下面是8位逐次逼近型ADC内部结构IN0 - IN7共8个输入通道地址锁存和译码通过输入的ADDA - ADDC信号控制通道选择开关选择输入信号源。DACDigital-Analog Converter数字-模拟转换器可以根据数值通过加权电阻网络输出模拟信号与模数转换器的输入信号INx作为比较器的两个输入进行比较大小。如果DAC输出的信号值大就减小DAC的值反之增大DAC的值直到比较器的两个输入信号相等此时DAC的数值就是模拟量INx的编码值了。逐次逼近寄存器SAR通常通过二分查找快速找到INx的编码值。找到之后将值送入三态锁存缓冲器中输出转换结束信号EOCEnd of Conversion。CLOCK是模数转换器的时钟信号START是开始信号Vcc和GND给模数转换器供电Vref和Vref-决定了数模转换器DAC的输出范围进而决定了模数转换器ADC可编码的范围。STM32ADCADCx_IN0 - ADCx_IN15共16个外部输入通道温度传感器和Vrefint**(V Reference Internal内部参考电压)是2个内部通道共18个输入通道送入模拟多路开关进行输入选择然后送入模数转换器进行逐次逼近型模数转换转换结果送入数据寄存器包括注入通道数据寄存器和规则通道**数据寄存器等待读取。规则通道可以同时选择16个输入通道启动模数转换但是规则通道用于存放结果的数据寄存器只有一个如果一次性选择16个输入通道进行转换后来的结果会覆盖之前的结果导致最终只能得到最后通道的转换值。因此规则通道经常配合DMA使用每完成一次转换DMA进行一次数据转运最终得到所有的转换值。注入通道的数据寄存器有4个可以同时保存4个输入通道的转换值。左下角是STM32ADC触发转换信号相当于8位逐次逼近ADC的START信号。对于STM32的ADC开始转换的信号有两种一种是软件触发在程序中通过代码实现一种是硬件触发通过左下角的触发源触发注入组和规则组开始转换。输入信号可以选择定时器通道也可以选择定时器触发输出信号TRGO实现硬件触发转换避免频繁中断阻塞主进程。与8位逐次逼近型模数转换器相同Vref和Vref-决定了模数转换的转换范围。VDDA和VSSA是供电信号。一般情况下Vref接VDDAVref-接VSSA即模数转换器转换范围等于工作电压。ADDCLK是驱动模数转换的时钟来自ADC预分频器。ADC预分频器的输入时钟是APB272MHz而ADDCLK的最大频率为14MHz因此ADC预分频的分频系数只能选择6或8。DMA请求信号用于触发DMA进行数据转运。模拟看门狗可以存放一个阈值高限和阈值低限如果启动了模拟看门狗并指定了看门的通道模拟看门狗就会关注看门的通道一旦超出阈值范围就申请一个模拟看门狗中断。规则组和注入组在转换完成后都会产生一个完成信号规则组为EOC注入组为JEOC这两个信号会改变标志位通过读取标志位可以判断转换是否结束同时EOC和JEOC可以触发中断。STM32ADC基本结构规则组转换模式单次转换非扫描模式非扫描模式下只能选择一个输入通道转换完成后结果存入数据寄存器同时产生EOC结束信号。如果想开启下一次转换就要再产生一个触发信号如果希望更换输入通道就要在转换前进行操作。连续转换非扫描模式由于还是非扫描模式所以只能选择一个输入通道。连续转换的特点是在一次转换完成产生EOC信号后不会停止而是立刻开始下一轮的转换一直持续下去。好处是想读AD值时不用检查EOC信号直接从数据寄存器读取即可。单次转换扫描模式由于是单词转换因此每转换一次都会停下来下次转换需要重新设置触发信号。扫描模式下可以选择多个输入通道开始转换后会将所有已选择通道的输入信号进行转换并存放到数据寄存器此时需要配合DMA进行数据转运所有通道转换完成后产生结束信号EOC停止本次转换。连续转换扫描模式在单次转换扫描模式上实现了转换的连续不断地对所选通道进行转换并存放到数据寄存器一直持续。触发控制数据对齐数据转换器是12位的因此转换结果也为12位但是存放结果的数据寄存器位16位因此存放时存在数据对齐问题。可以根据补零位置分为数据右对齐和数据左对齐数据右对齐比较常用。转换时间AD转换步骤采样 - 保持 - 量化 - 编码STM32 ADC的总转换时间为Tconv 采样时间 12.5个ADC周期例如当ADCCLK 14MHz采样时间位1.5个ADC周期时Tconv 1.5 12.5 14个ADC周期 1us校准ADC有一个内置的自校准模式。校准可大幅减小因内部电容器组的变化而造成的精准度误差。校准期间在每个电容器上都会计算出一个误差修正码数字值这个码用于消除在随后的转换中每个电容器上产生的误差。建议在每次上电后执行一次校准启动校准前ADC必须处于关电状态超过至少两个ADC周期。硬件电路以下电路可以为ADC产生一个模拟输入信号。ADC单通道ADC基本结构给ADC数模转换器新建一个模块为了避免和标准库中的函数冲突起名ADConverterADC初始化整体流程开启时钟GPIOADCADC预分频器 - 配置GPIO - 配置多路选择器 - 配置ADC转换器 - ADC_cmd使能ADC看门狗和中断部分暂时不需要。涉及到的函数// 配置ADC预分频器决定ADC逐次逼近转换工作频率voidRCC_ADCCLKConfig(uint32_tRCC_PCLK2);// ADC校准相关函数voidADC_ResetCalibration(ADC_TypeDef*ADCx);FlagStatusADC_GetResetCalibrationStatus(ADC_TypeDef*ADCx);voidADC_StartCalibration(ADC_TypeDef*ADCx);FlagStatusADC_GetCalibrationStatus(ADC_TypeDef*ADCx);// 软件触发ADC函数voidADC_SoftwareStartConvCmd(ADC_TypeDef*ADCx,FunctionalState NewState);// 检查转换是否完成FlagStatusADC_GetFlagStatus(ADC_TypeDef*ADCx,uint8_tADC_FLAG);// 规则组配置voidADC_RegularChannelConfig(ADC_TypeDef*ADCx,uint8_tADC_Channel,uint8_tRank,uint8_tADC_SampleTime);// 获取转换值uint16_tADC_GetConversionValue(ADC_TypeDef*ADCx);初始化ADConvertervoidADConverter_Init(void){// 开启时钟GPIOADC配置ADC预分频器RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);RCC_ADCCLKConfig(RCC_PCLK2_Div6);// 配置GPIOGPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_ModeGPIO_Mode_AIN;// 模拟输入GPIO_InitStructure.GPIO_PinGPIO_Pin_0;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);// 配置多路选择器ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);// 配置ADCADC_InitTypeDef ADC_InitStructure;ADC_StructInit(ADC_InitStructure);ADC_InitStructure.ADC_ContinuousConvModeDISABLE;// 单次ADC_InitStructure.ADC_DataAlignADC_DataAlign_Right;ADC_InitStructure.ADC_ExternalTrigConvADC_ExternalTrigConv_None;// 不使用外部触发源ADC_InitStructure.ADC_ModeADC_Mode_Independent;// 单个ADCADC_InitStructure.ADC_NbrOfChannel1;// 使用1个通道ADC_InitStructure.ADC_ScanConvModeDISABLE;// 非扫描模式ADC_Init(ADC1,ADC_InitStructure);// ADC使能ADC_Cmd(ADC1,ENABLE);// ADC校准ADC_ResetCalibration(ADC1);// 复位校准寄存器while(ADC_GetResetCalibrationStatus(ADC1)SET);// 等待复位完成ADC_StartCalibration(ADC1);// 开始校准while(ADC_GetCalibrationStatus(ADC1)SET);// 等待校准完成}启动转换并获取转换值的流程软件启动转换 - 等待转换完成 - 从数据寄存器读取转换值uint16_tADConverter_GetVal(void){// 软件启动转换ADC_SoftwareStartConvCmd(ADC1,ENABLE);// 等待转换完成while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)!SET);// 读取规则组数据寄存器的值returnADC_GetConversionValue(ADC1);}ADC多通道在扫描模式下启动转换后每个通道完成转换不会有标志位产生因此难以手动判断是否转换完成从而转运结果要配合DMA使用。并且每个通道转换速度极快只有几微秒难以转运完成。使用单次非扫描方式实现AD多通道每次转换之前更改输入通道。// ADConverter.c#includestm32f10x.h// Device headervoidADConverter_Init(void){// 开启时钟GPIOADC配置ADC预分频器RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);RCC_ADCCLKConfig(RCC_PCLK2_Div6);// 配置GPIOGPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_ModeGPIO_Mode_AIN;// 模拟输入GPIO_InitStructure.GPIO_PinGPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);// 配置ADCADC_InitTypeDef ADC_InitStructure;ADC_StructInit(ADC_InitStructure);ADC_InitStructure.ADC_ContinuousConvModeDISABLE;// 单次ADC_InitStructure.ADC_DataAlignADC_DataAlign_Right;ADC_InitStructure.ADC_ExternalTrigConvADC_ExternalTrigConv_None;// 不使用外部触发源ADC_InitStructure.ADC_ModeADC_Mode_Independent;// 单个ADCADC_InitStructure.ADC_NbrOfChannel1;// 使用1个通道ADC_InitStructure.ADC_ScanConvModeDISABLE;// 非扫描模式ADC_Init(ADC1,ADC_InitStructure);// ADC使能ADC_Cmd(ADC1,ENABLE);// ADC校准ADC_ResetCalibration(ADC1);// 复位校准寄存器while(ADC_GetResetCalibrationStatus(ADC1)SET);// 等待复位完成ADC_StartCalibration(ADC1);// 开始校准while(ADC_GetCalibrationStatus(ADC1)SET);// 等待校准完成}uint16_tADConverter_GetVal(uint8_tADC_Channel){// 配置多路选择器ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);// 软件启动转换ADC_SoftwareStartConvCmd(ADC1,ENABLE);// 等待转换完成while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)!SET);// 读取规则组数据寄存器的值returnADC_GetConversionValue(ADC1);}// main.c#includestm32f10x.h// Device header#includeDelay.h#includeOLED_Hardware.h#includeADConverter.huint16_tAD0,AD1,AD2,AD3;intmain(void){OLED_Init_H();ADConverter_Init();OLED_ShowString_H(1,1,AD0:);OLED_ShowString_H(2,1,AD1:);OLED_ShowString_H(3,1,AD2:);OLED_ShowString_H(4,1,AD3:);while(1){AD0ADConverter_GetVal(ADC_Channel_0);AD1ADConverter_GetVal(ADC_Channel_1);AD2ADConverter_GetVal(ADC_Channel_2);AD3ADConverter_GetVal(ADC_Channel_3);OLED_ShowNum_H(1,5,AD0,4);OLED_ShowNum_H(1,5,AD1,4);OLED_ShowNum_H(1,5,AD2,4);OLED_ShowNum_H(1,5,AD3,4);Delay_ms(100);}});AD1ADConverter_GetVal(ADC_Channel_1);AD2ADConverter_GetVal(ADC_Channel_2);AD3ADConverter_GetVal(ADC_Channel_3);OLED_ShowNum_H(1,5,AD0,4);OLED_ShowNum_H(1,5,AD1,4);OLED_ShowNum_H(1,5,AD2,4);OLED_ShowNum_H(1,5,AD3,4);Delay_ms(100);}}