江协科技STM32课程笔记(五)— ADC模数转换器
一、ADC简介
stm32F103C8T6有ADC1和ADC2,10个外部输入通道。
逐次逼近型ADC:
左边IN0~IN7是输入端口,通过ADDA、ADDB和ADDC来决定通道选择,ALE为锁存信号。DAC通常us级,所以如果要对多个信号进行转换,不需要设计多个ADC,只需要通过通道地址,将通道选择器开关拨到对应通道即可。
后面是个比较器,上面为待测信号,下面为DAC输出的已知编码的电压值,通过SAR寄存器,二分法的方式逐渐查找电压对应的量化值。比如这里是8位的ADC,那编码就是从0~255,第一次比较的时候,我们就给DAC输入255的一半进行比较,那就是128,然后看谁大谁小,如果DAC电压大了,那第二次比较的时候,给128的一半,64,如果还大,第三次比较的时候就给32,如果这次DAC电压小了,那就给32到64中间的值,依次进行下去,就能最快找到未知电压的编码。128、64、32这些数据,正好是二进制每一位的位权,这个判断过程就相当于是,对二进制高位到低位依次判断是1还是0的过程。对于12位ADC就要判断12次。
ADC结束后,SAR输出到锁存缓冲器。EOC是转换结束信号,START是开始转换,给一个输入脉冲开始转换。CLOCK为时钟。VREF是参考电压,通常等于VCC。
STM32的ADC:
1、首先左边的ADC_IN为输入通道,还有两个内部输入通道温度传感器和Vrefint参考电压。
2、注入通道和规则通道,注入通道最多4通道,规则通道最多16通道。规则组可以转换16个通道,但是因为数据寄存器16位只有一个,所以后面来的通道数据会覆盖前一个的数据,通常结合DMA来使用,将数据挪到内存中。注入组可以同时存储4个通道数据,不用担心被覆盖。
3、左下角是触发转换信号。STM32触发ADC的信号有两种,软件触发(代码)和硬件触发。硬件触发主要来源于定时器触发,如TRGO主模式输出,用定时器每隔一段时间触发ADC转换,不用定时中断。也可以选择EXTI外部中断引脚触发。
4、
5、ADC时钟来自ADC预分频器。最大支持14MHz,所以分频器只能选6或者8。
6、模拟看门狗。可以存上下限阈值,如果该通道超过阈值,就会产生看门狗中断,通向NVIC的ADC中断。
7、EOC是规则组完成信号;JEOC是注入组完成信号;如果NVIC开启了相关通道,他们俩也能产生中断。
STM32F103C8的ADC:
ADC1和ADC2输入通道引脚相同,用于双ADC交叉模式,提高采样频率。STM32F103C8只有通道0到9
规则组的4种转换模式:
单次转换,非扫描模式:一次转一个通道,转换前修改通道,等待下一次触发。
连续转换,非扫描模式:一次转一个,但是一次转换后立刻开始下一次转换
单次转换、扫描模式:一次转多个通道,每次触发后等待下一次触发。转换完成后需要通过DMA及时保存寄存器中的结果。
连续转换、扫描模式:
还有一个间断扫描模式,可以每隔几个转换就暂停一次,需要等待触发再次转换。
触发控制:
规则组触发源:
注入组触发源:
数据对齐:
ADC为12位的,但是数据寄存器是16位的,存在数据对齐问题。一般采用右对齐,直接读取寄存器即可。左对齐用于可以读取寄存器只取高位,如高8位,舍弃低4位精度,12位ADC退化为8位ADC。
转换时间:
AD步骤采样、保持、量化和编码。由于AD转换需要一定时间,所以需要保持输入电压不变,通过一个采样开关控制,可以通过一个电容来实现,那这个采样并保持的过程就需要时间。如果提高ADCCLK提高了,ADC处于超频状态,但是稳定性不能保证。
校准:
ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差。建议在每次上电后执行一次校准。启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期。对于我们来说就是需要初始化后利用代码调用自校准。
二、AD单通道
/*RCC库函数里*/
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2); // 配置ADCCLK分频器的,可以对APB2的72MHz时钟选择2、4、6、8分频,输入到ADCCLK/*ADC库函数里*/
void ADC_DeInit(ADC_TypeDef* ADCx); // 恢复缺省配置
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct); // 初始化
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct); // 结构体初始化
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState); // 用于给ADC上电,也就是开关控制
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState); // 用于开启DMA输出信号,若果使用DMA转运数据,就得调用这个函数
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState); // 中断输出控制,用于控制某个中断,能不能通往NVIC
/*下面4个函数是用于获取控制校准的函数、在ADC初始化完成之后,一次调用就可以了*/
void ADC_ResetCalibration(ADC_TypeDef* ADCx); // 复位校准
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx); // 获取复位校准状态
void ADC_StartCalibration(ADC_TypeDef* ADCx); // 开始校准
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx); // 获取开始校准状态
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// ADC_软件开始转换控制,这个就是用于软件触发的函数了,调用一下,就能软件触发转换了
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);
//ADC获取软件开始转换状态,给SWSTART位置1,以开始转换,这个函数一般不用
/*下面2个函数是用来配置ADC间断模式的*/
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);//每隔几个通道间断一次
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState); //是不是启用间断模式
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
/*ADC规则组通道配置,很重要,作用就是给序列的每个位置填写指定的通道
ADC_Channel:要指定的通道;
Rank:序列几的位置;
ADC_SampleTime:指定通道的采样时间。
*/
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
//ADC外部触发转换控制,就是是否允许外部触发转换
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx); // ADC获取转换值,获取ADC转换的数据寄存器,读取转换结果就是要用这个函数
uint32_t ADC_GetDualModeConversionValue(void);//ADC获取双模式转换通道,这个是双ADC模式读取转换结果的函数,暂时不用
/*下面带Injected的函数都是对ADC注入组进行配置的*/
void ADC_AutoInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_InjectedDiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_ExternalTrigInjectedConvConfig(ADC_TypeDef* ADCx, uint32_t ADC_ExternalTrigInjecConv);
void ADC_ExternalTrigInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_SoftwareStartInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
FlagStatus ADC_GetSoftwareStartInjectedConvCmdStatus(ADC_TypeDef* ADCx);
void ADC_InjectedChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
void ADC_InjectedSequencerLengthConfig(ADC_TypeDef* ADCx, uint8_t Length);
void ADC_SetInjectedOffset(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel, uint16_t Offset);
uint16_t ADC_GetInjectedConversionValue(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel);
/*下面三个函数是对模拟看门狗进行配置的*/
void ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog); //是否启动模拟看门狗
void ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold, uint16_t LowThreshold); //配置高低阈值
void ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel); //配置看门的通道
void ADC_TempSensorVrefintCmd(FunctionalState NewState);//ADC温度传感器、内部参考电压控制,用来开启内部的两个通道的
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
//获取标志位状态,参数给EOC标志位,就可以判断EOC标志位是不是置1了,如果转换结束,EOC标志位置1
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG); //清除标志位
ITStatus ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT); //获取中断状态
void ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT); //清除中断挂起位
三、AD多通道
江协科技是规则组一个一个转换,自己尝试改的4通道注入组的
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引脚初始化为模拟输入ADC_InjectedSequencerLengthConfig(ADC1,4);ADC_InjectedChannelConfig(ADC1, ADC_Channel_0 , 1, ADC_SampleTime_55Cycles5);ADC_InjectedChannelConfig(ADC1, ADC_Channel_1 , 2, ADC_SampleTime_55Cycles5);ADC_InjectedChannelConfig(ADC1, ADC_Channel_2 , 3, ADC_SampleTime_55Cycles5);ADC_InjectedChannelConfig(ADC1, ADC_Channel_3 , 4, ADC_SampleTime_55Cycles5);/*ADC初始化*/ADC_InitTypeDef ADC_InitStructure; //定义结构体变量ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //模式,选择独立模式,即单独使用ADC1ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐,选择右对齐ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发,使用软件触发,不需要外部触发ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //连续转换,失能,每转换一次规则组序列后停止ADC_InitStructure.ADC_ScanConvMode = ENABLE; //扫描模式,失能,只转换规则组的序列1这一个位置ADC_InitStructure.ADC_NbrOfChannel = 4; //通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1ADC_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);while (ADC_GetCalibrationStatus(ADC1) == SET);
}/*** 函 数:获取AD转换的值* 参 数:无* 返 回 值:AD转换的值,范围:0~4095*/
void AD_GetValue(void)
{ADC_ExternalTrigInjectedConvConfig(ADC1,ADC_ExternalTrigInjecConv_None);ADC_SoftwareStartInjectedConvCmd(ADC1, ENABLE); //软件触发AD转换一次while (ADC_GetFlagStatus(ADC1, ADC_FLAG_JEOC) == RESET); //等待EOC标志位,即等待AD转换结束ADC_ClearFlag(ADC1,ADC_FLAG_JEOC);
}int main(void)
{uint16_t AD0,AD1,AD2,AD3; //定义AD值变量float Voltage; //定义电压变量OLED_Init(); //LED初始化AD_Init(); //PWM初始化/*显示静态字符串*/OLED_ShowString(1, 1, "AD0:");OLED_ShowString(2, 1, "AD1:");OLED_ShowString(3, 1, "AD2:");OLED_ShowString(4, 1, "AD3:");int i;while(1){for(i = 0;i<10;i++){AD_GetValue(); //软件触发AD转换一次AD0 += ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_1); //获取AD转换的值AD1 += ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_2);AD2 += ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_3);AD3 += ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_4);}OLED_ShowNum(1, 5, AD0/10, 4); //显示通道0的转换结果AD0OLED_ShowNum(2, 5, AD1/10, 4); //显示通道1的转换结果AD1OLED_ShowNum(3, 5, AD2/10, 4); //显示通道2的转换结果AD2OLED_ShowNum(4, 5, AD3/10, 4); //显示通道3的转换结果AD3AD0 = 0;AD1 = 0;AD2 = 0;AD3 = 0;sysDelayms(300); //延时100ms,手动增加一些转换的间隔时间}}
连续测量需要DMA,放在下一节