本内容基于江协科技STM32视频学习之后整理而得。
1. ADC模拟-数字转换器
1.1 ADC模拟-数字转换器
- ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁;(DAC数字模拟转换器,PWM是数字到模拟的转换,使用PWM来控制LED的亮度、电机的速度,这就是DAC的功能,同时PWM只有完全导通和完全断开两种状态,在这两种状态上都没有功率损耗,所以在直流电机调速这种大功率的应用场景中,使用PWM来等效模拟量,是比DAC更好的选择,并且PWM电路更加简单,更加常用,所以可以看出PWM还是挤占了DAC的很多应用空间,目前DAC的应用主要是在波形生成这些领域,比如信号发生器、音频解码芯片。)
- 12位逐次逼近型ADC,1us转换时间。(12位表示分辨率,范围0-2^12-1=0~4095,位数越高,量化结果就越精细,对应分辨率就越高。转换时间即转换频率,转换需要时间,1us表示AD从转换开始到产生结果,需要花1us的时间,对应的AD转换频率就是1MHz)
- 输入电压范围:0-3.3V,转换结果范围:0~4095
- 18个输入通道,可测量16个外部和2个内部信号源
- 规则组和注入组两个转换单元
- 模拟看门狗自动监测输入电压范围(ADC一般可以用于测量光线强度、温度这些值,如果光线高于某个阈值,低于某个阈值,或者温度高于某个阈值,低于某个阈值时,执行一些操作,低于某个阈值、高于某个阈值的判断就可以用模拟看门狗来自动执行,模拟看门狗可以监测指定的某些通道,当AD值高于它设定的上阈值或低于下阈值时,它就会申请中断,就可以在中断函数里执行相应的操作,这样就不用不断地手动读值,再用if进行判断了。)
- STM32103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道(就是最多只能测量10个外部引脚的模拟信号)
1.2 逐次逼近型ADC
- ADC0809:独立8位逐次逼近型ADC芯片
- IN0~IN7:8路输入通道,通过通道选择开关,选中一路,输入到比较器进行转换。
- 地址锁存和译码:想选中哪个通道,就把通道号放在ADDA~ADDC上,然后给一个锁存信号,对应的通路开关就可以自动拨好。通路选择开关相当于一个可以通过模拟信号的数据选择器。
- 比较器:电压比较器,可以判断两个输入信号电压的大小关系,输出一个高低电平指示谁大谁小。其输入:一个是通道选择开关输出的待测电压,另一个是DAC的电压输出端。
- DAC是数模转换器,给DAC一个数据,就能输出对应的电压值;内部是使用加权电阻网络实现的转换。
- 现在有了一个外部通道输入的未知编码的电压,和一个DAC输出的已知编码的电压,它俩同时输入到电压比较器,进行大小判断。如果DAC输出的电压比较大,就调小DAC数据;反之,输出电压比较小,就增大DAC数据。直到DAC输出的电压和外部通道输入的电压近似相等。这样DAC输入的数据就是外部电压的编码数据了,这就是DAC的实现原理,该电压调节过程是逐次逼近寄存器SAR完成,为了最快找到未知电压的编码,采用二分法,0~255,每次对半分,128、64、32这些数据,正好是二进制每一位的位权。该判断过程,相当于对二进制从高位到低位依次判断是1还是0的过程。对于8位的ADC,从高位到低位依次判断8次就能找到未知电压的编码。AD转换结束后,DAC的输入数据,就是未知电压的编码,通过8位三态锁存缓冲器输出。
- EOC是End of Convert,转换结束信号;
- START是开始转换,给一个输入脉冲,开始转换;
- CLOCK是ADC时钟,因ADC内部是一步一步进行判断的,因此需要时钟来推动这个过程。
- VREF+和VREF-是DAC的参考电压,该参考电压也决定了ADC的输入范围,所以也是ADC参考电压。
1.3 ADC框图
- 对于普通的ADC,多路开关一般都是只选中一个的,就是选中某一个通道、开始转换、等待转换完成、取出结果。
- 但是在这里可以选中多个,而且在转换的时候,还分成了两个组,规则通道组和注入通道组,其中规则组可以一次性最多选中16个通道,注入组最多可以选中4个通道,但是规则组只有一个数据寄存器,而注入组有4个数据寄存器,用规则组需要使用DMA配合转运数据。
- 规则组和注入组的触发源主要来自定时器,有定时器的各个通道,还有TRGO定时器主模式的输出,可以选择TIM3定一个1ms的时间,并且把TIM3的更新事件选择为TRGO输出,然后在ADC里,选择开始触发信号为TIM3的TRGO,这样TIM3的更新事件就能通过硬件自动触发ADC转换了。也可以选择外部中断引脚来触发转换。
- VREF+和VREF-是ADC的参考电压,VDDA和VSSA是ADC的供电引脚,一般VREF+要接VDDA,VREF-要接VSSA,
- ADCCLK是ADC的时钟,用于驱动内部逐次比较的时钟,是来自ADC预分频器,这个ADC预分频器是来源于RCC的。
1.4 ADC基本结构
- 规则组最多可以选中16个通道;注入组最多可以选择4个通道;
- 转换结果存放在AD数据寄存器里,规则组只有1个数据寄存器,注入组有4个;
- 触发控制,提供了开始转换START信号;可以选择软件触发和硬件触发。
- 硬件触发主要来自于定时器,也可以选择外部中断的引脚。
来自于RCC的ADC时钟CLOCK,ADC逐次比较的过程就是由这个时钟推动的; - 可以布置一个模拟看门狗用于检测转换结果的范围,若超出设定的阈值,就通过中断输出控制,向NVIC申请中断;
- 规则组和注入组转换完成后会有个EOC信号,会置一个标志位,也可以通向NVIC。
- 开关控制:在库函数中,就是ADC_Cmd函数,用于给ADC上电的。
1.5 输入通道
1.6 规则组的转换模式
在ADC初始化的结构体里,会有两个参数:参1是选择单次转换还是连续转换,参2是选择扫描模式还是非扫描模式。
1.6.1 单次转换,非扫描模式
- 在非扫描模式下,该菜单只有第一个序列1的位置有效。菜单同时选中一组的方式就退化为简单地选中一个的方式。
- 在序列1可以指定要转换的通道,之后就可以触发转换,ADC就会对这个通道2进行模数转换。
- 转换完成后,结果存放在数据寄存器里,同时给EOC标志位置1,转换结束。
- 判断转换结束后,就可以在数据寄存器里读取结果。若想再启动一次转换,就需要再触发一次,转换结束,置EOC标志位,读结果。
- 若想换一个通道转换,则在转换之前,把第一个位置的通道2改为其他通道,然后再启动转换。
- 流程:触发转换–>判断转换结束(置EOC标志位)–>获取转换值
1.6.2 连续转换,非扫描模式
它在一次转换结束后,不会停止,而是立刻开始下一轮的转换,然后一直持续下去。因此只需最开始触发一次,之后就可以一直转换。优点是开始转换之后不需要等待一段时间,想要读AD值的时候,直接从数据寄存器取就是了。
1.6.3 单次转换,扫描模式
每触发一次,转换结束后,就会停下来,下次转换就得再触发才能开始。初始化结构体中还会有个参数:通道数目,若为7,就是在每次触发之后,依次对前7个位置进行AD转换,转换结果都放在数据寄存器里。为了防止数据被覆盖,就需要用DMA及时将数据挪走。7个通道转换完成之后,产生EOC信号,转换结束。然后再触发下一次,就又开始新一轮的转换。
1.6.4 连续转换,扫描模式
一次转换完成后,立刻开始下一次的转换。
在扫描模式的情况下,还有一种模式:间断模式,在扫描的过程中,每隔几个转换,就暂停一次,需要再次触发,才能继续。
1.7 触发控制
类型:外部引脚/来自片上定时器的内部信号,具体是引脚还是定时器,需要用AFIO重映射来确定。
软件控制位:软件触发
触发信号的选择可以通过设置右边的寄存器来完成。也可以使用库函数实现。
1.8 数据对齐
ADC是12位的,其转换结果就是一个12位的数据。但数据寄存器是16位的,所以存在数据对齐的问题
1.9 转换时间
- 量化编码即逐次比较
- 采样保持:是因为量化编码是需要一小段时间的,如果在这一小段时间里,输入的电压不断变化,则无法定位输入电压的位置,所以在量化编码之前,需要设置一个采样开关,先打开采样开关,收集一下外部的电压,之后断开采样开关,再进行AD转换,这样在量化编码的期间,电压始终保持不变,这样才能精确地定位未知电压的位置。
- 采样时间就是采样保持的时间,采样时间越大,越能避免一些毛刺信号的干扰。但转换时间也会相应延长。
- 12.5个ADC周期是量化编码花费的时间,因为是12位的ADC,所以需要花费12个周期,0.5个周期是做其他用的。ADC周期就是从RCC分频过来的ADCCLK,ADCCLK最大是14MHz。
1.10 校准
- ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差
- 建议在每次上电后执行一次校准
- 启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期
1.11 硬件电路
- 第一个:电位器可调电压的电路,就是接一个电位器,当滑动端往上滑时,电压增大,往下滑时,电压减小。
- 第二个:传感器输出电压的电路,如光敏电阻、热敏电阻、红外接收管、麦克风等都可以等效为一个可变电阻。传感器阻值变小时,下拉作用变强,输出端电压就下降;传感器阻值变大时,下拉作用变弱,输出端受上拉电阻的作用,电压就会升高。
- 第三个:电压转换电路,若想测一个0-5V的VIN电压,但ADC只能接收0-3.3V的电压,就可以搭一个转换电路,上面阻值17K,下面阻值33K,总共50K,根据分压公式,中间的电压就是VIN/50K*33K,得到的电压范围就是0~3.3V,就可以进入ADC转换了。
2. AD库函数及代码
2.1 AD库函数
// 配置ADCCLK分频器
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);
// DeInit恢复缺省配置、Init初始化、StructInit结构体初始化
void ADC_DeInit(ADC_TypeDef* ADCx);
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);
// 给ADC上电的,即开关控制
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// 开启DMA输出信号
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// 中断输出控制,用于控制某个中断,能不能通往NVIC
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);
// 复位校准、获取复位校准状态
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
// 开始校准、获取开始校准状态
void ADC_StartCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);
// ADC软件开始转换控制,用于软件触发的函数
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// ADC获取软件开始转换状态
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);
// 判断转换是否结束。获取标志位状态,参数给EOC的标志位,判断EOC标志位是不是置1了
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
// 配置间断模式,函数1:每隔几个通道间断一次;函数2:是不是启用间断模式
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// ADC规则组通道配置,给序列的每个位置填写指定的通道
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
// ADC 外部触发转换控制,就是是否允许外部触发转换
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// ADC换取转换值。就是获取AD转换的数据寄存器,读取转换结果使用该函数
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
// ADC获取双模式转换值,是ADC模式读取转换结果的函数
uint32_t ADC_GetDualModeConversionValue(void);
// 对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);
// 对模拟看门狗进行配置,函数1:是否启动看门狗;函数2:配置高低阈值;函数3:配置看门的通道
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);
// ADC温度传感器、内部参考电压控制,用于开启内部的两个通道
void ADC_TempSensorVrefintCmd(FunctionalState NewState);
// 获取中断状态、清除中断挂起位
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
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);
2.2 7-1AD单通道代码
2.2.1 硬件电路
实现功能:接一个电位器,即滑动变阻器,用该电位器产生一个0~3.3V连续变化的模拟电压信号,接到STM32的PA0口上,之后用STM32内部的ADC读取电压数据,显示在屏幕上。屏幕上第一行显示的是AD转换后的原始数据,第二行是经过处理后实际的电压值。往左拧,AD值减小,电压值也减小,AD值最小为0,对应的电压就是0V,往右拧,AD值变大,对应电压值也变大。STM32的ADC是12位的,所以AD结果最大值是4095(2^12 - 1),对应的电压是3.3V。