前言
由于在STM32单片机中,主要是数字电路,而数字电路没有多少伏电压的概念,只有高电平和低电平两个概念,如果想要读取电压值,则需要经过ADC模数转换来读取对应引脚的模拟电压,然后存放到对应的寄存器种,通过变量来读取从而进行显示、判断等操作。
1.ADC(Analog-Digital Converter)模拟-数字转换器
12位ADC是一种逐次逼近型模拟数字转换器。它有多达18个通道,可测量16个外部和2个内部 信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右 对齐方式存储在16位数据寄存器中。
上述为手册对ADC的介绍,接下来对上面一一进行解释:
首先ADC是12位的,而一般的寄存器是16位的,所以在对ADC输出的数据进行存储的时候需要选择对齐方式,左对齐还是右对齐,在后续进行介绍。
ADC模数转换的方法是一种逐次逼近的比较方法,实际上是用二分法来实现的,在后续进行介绍。
然后ADC有18个通道输入,其中两个和芯片内部相连接,分别是温度传感器以及内部电压模块相连接,其他的16个通道就对应的接通到其他Pin端口,用于片外外设上的模拟信号读取,如下图。
最后,这18个通道,都可以分别单独地配置 单次/连续、扫描/非扫描、间断/不间断模式,同样在后续进行介绍。
同时ADC一般为1us转换时间 (根据产品型号,时钟频率有所不同)
输入电压范围:0~3.3V,转换结果范围:0~4095(2的12次方-1)
对应的是一个线性关系,用图来表示就是:
1.1ADC结构
接下来看到ADC的结构图:
首先看到主通道,ADCx_IN0~15,也就对应16个外部通道通过GPIO最终进入到ADC模块中,其中会经过一个数据选择器,同时输入还包含之前提到的温度传感器和内部电压模块:
他的名称为模拟多路开关,也就是决定哪个几个通道输出,可以看到输出可以输出至注入通道最多4通道,也可以输出至规则通道最多16通道。
1.1.1注入组和规则组
首先需要明确注入组和规则组:
这里引用江科大的例子:
这一整个过程类比为写菜单点菜,规则组菜单上最多可以写16道菜,如果写16个菜(输入16个规则通道),老板会一次性把16个菜做好(16个通道按顺序依次一次性执行),然而放菜的桌子仅能放一盘菜也就其他前15盘菜会被挤掉(覆盖)(对应的就是AD转换后的前15个规则通道数据都会被覆盖,只会留下IN15通道的数据。
改进方法,通过DMA转运,防止转换后的数据被覆盖,也就叫服务员先把菜移至别的地方,在后续对DMA的会提到。
然后是规则组,注入组则是菜单只能够写满四个菜(四个注入通道),同样老板也会一次性做好,在注入组的餐桌上正好能够摆满四个菜(四个注入通道转换后的数据都能够被存储),不会出现有菜被挤掉(覆盖)的情况。
1.1.2ADCCLK时钟
同时能看到ADCCLK对ADC提供时钟,对应的时钟树部分:
可以看到,一般都是由系统时钟72MHz输入到ADC预分频器,同时ADCCLK最大为14MHz,所以在系统时钟72MHz输入的情况下,ADC预分频器的值只能为6分频和8分频,对应输出ADCCLK为12MHz、9MHz。
1.1.3VREF+和VREF-
VREF+和VREF-为参考电压的正负极,下图为各引脚的说明解释:
实际上,VREF+和VREF-分别接上VDDA和VSSA,然后VDDA和VSSA分别接到VDD和VSS,结果就是VREF+和VREF-分别接到VDD和VSS。
1.1.4中断触发
可以看到,超过看门狗的上阈值或是低于下阈值会产生AWD标志位,规则组、注入组AD转换结束分别会产生EOC和JEOC标志位,在对应寄存器的位上设置就可以实现转换结束后产生中断:
上述寄存器是用来存储对应标志位的,需要注意的是,需要软件清除,也即手动清除。当需要使用对应的结束标志位来启动中断的时候,还需要手动清除一下对应的标志位。
对应的,还需要再下面的寄存器种设置对应的位:
对应在ADC_ITConfig函数进行产生中断源的选择:
具体是ADC_IT这个参数,可以选择ADC_IT_EOC、ADC_IT_AWD、ADC_IT_JEOC,分别对应规则组转换完成、模拟看门狗触发、注入组转换完成。
对应的中断通道IRQ的选择,需要根据自身芯片的型号来选择对应的中断通道枚举类型:
这里我采用的是STM32F103C8T6,在该芯片中,只有ADC1和ADC2两个模块,即对应MD中容量类型如下图:
由以上的解释,NVIC_IRQChannel=ADC1_2_IRQn;这样即完成对中断通道的配置。
最终代码如下:
ADC_ITConfig(ADC1,ADC_IT_EOC,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC1;
NVIC1.NVIC_IRQChannel=ADC1_2_IRQn;
NVIC1.NVIC_IRQChannelCmd=ENABLE;
NVIC1.NVIC_IRQChannelPreemptionPriority=2;
NVIC1.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC1);
这里我定义的结构体变量为NVIC1,对于抢占优先级和响应优先级的设置,要求不高的情况下,随意配置。
最后配置一下中断函数需要执行的内容,即完成对中断的配置:
1.1.5外部触发输入
也就是ADC结构的下半部分,同时可以看到在右下角,说明了ADC3外设下的触发结构,于ADC1和ADC2左边的结构略有不同,在使用ADC3的时候需要注意一下。
可以看到输入进注入通道和规则通道之前,有一个与门(相当于开关),与门的一位为触发信号输入,另一位则为控制位,通过对控制位的控制来使能或失能触发输入。
对应寄存器:
这两位分别控制两个与门的控制位,类似于“开关”。
同时在这个寄存器种,还含有对外部触发输入的数据寄存器控制的位:
可以看到,基本上都是由TIM定时器产生的触发事件,所以我们可以使用TIM定时器在隔多少秒后使用ADC采集并转换一下数据。
上述的寄存器位描述用图表表示就是,注入组:
对应的选择触发函数:
对应的注入组与门控制位函数:
规则组:
规则组与门的控制位函数:
奇怪的是,并没有规则组的外部触发选择函数,如ADC_ExternalTrigConvConfig,并没有在固件库中找到,而却含有规则组与与门控制位的函数,这里个人推测是ST公司漏装了^ ^,这里就不在深究了,毕竟他们只需要写写函数,而我们考虑的东西就更多了。
这里说一下JSWSTART、SWSTART:
对应函数:
对应函数:
这两位可以通过软件设置,即通过上述的两个函数,设置后就会开启对应规则/注入通道的转换,同时在转换启动后,该位会由软件自动清除。
同样的,在外部触发源数据选择器中,置111,对应启动规则/注入通道的转换,同时还需打开与门的控制位,这样才能够真正启动ADC转换。
再来看看数据手册对触发注入的描述:
同时看到时序图来理解下面的注:
当使用触发的注入转换时,必须保证触发事件的间隔长于注入序列。例如:序列长度为28个ADC时钟周期(即2个具有1.5个时钟间隔采样时间的转换),触发之间最小的间隔必须是29个ADC时钟周期。
这意味着,从一次触发事件开始到下一次触发事件开始,这段时间必须足够长,以确保当前的注入转换序列能够完全执行完成,以避免不同注入转换序列之间的冲突或中断。
看到COUNT位:
自动注入需要禁止外部触发的注入模式,因为外部触发的注入转换会中断正在执行的规则转换,对应的置JAUTO为1,如果置COUNT位为1,也即选择连续转换模式,当规则通道后面有注入通道在“排队”的时候,当执行完规则转换,自动执行后面的转换。
需要注意自动注入不能和间断模式共同作用。
2.ADC的模式
前面提到过,ADC的模式有很多种,连续/非连续、扫描/非扫描、间断/非间断,以及在后面还有一个双ADC模式。
2.1连续/非连续模式
2.2扫描/非扫描模式
上图会依次序列顺序进行转换,直到转换到最后一位序列,然后置EOC标志位。
非连续就是,只转换第一位序列的通道,而忽略下面序列的通道,第一位序列转换完成直接置EOC标志位。
2.3间断/非间断模式
ADC中的间断模式是一种特殊的转换模式,它允许将一组通道分成多个短序列进行转换,而不是像扫描模式那样连续转换整组通道。
此模式在规则组和注入组之间有所不同:
规则组,此模式通过设置ADC_CR1寄存器上的DISCEN位激活。它可以用来执行一个短序列的n次转换(n<=8),此转换是ADC_SQRx寄存器所选择的转换序列的一部分。数值n由ADC_CR1寄存器的DISCNUM[2:0]位给出。
位DISCEN在规则通道组上决定失能还是使能。
DISCNUM[2:0]用来决定短序列的转换次数n。
这里看起来难以理解,举个例子,例如接下来我需要转换规则通道组:0、1、2、3、5、6、7、8、9、11、15、16这12个通道。
如果n=4(也即每次转换的次数4),第一次转换为0、1、2、3,第二次转换为5、6、7、8,第三次转换为9、11、15、16。
再来看n=3的情况,第一次转换为0、1、2,第二次转换为3、5、6,第三次转换为7、8、9,第四次转换为11、15、16。
这样看来,是不是更好理解规则组下的间断模式。
当n=4时,如果执行完三次,第四次就会重新执行第一次0、1、2、3规则通道的转化。
有三个SQR寄存器,处理SQR1中包含L[3:0]确定规则组通道数目,其他都是每五位确定一个转换通道,SQ16~1对应序列16~1。
接下里介绍注入组,也是类似的:
小结
这样在当需要同时监控多个模拟信号,但每个信号的采样频率不同时,可以使用间断模式来优化资源使用,同时相比扫描模式减少不必要的转换,间断模式有助于降低ADC的功耗。
最后在某些应用场景中,可能需要在不同的时间点获取不同通道的数据,间断模式提供了这种灵活性。
2.4混合模式
上面连续/非连续(单次)可以和扫描/非扫描混合出四种:连续扫描、连续非扫描、单次扫描、单次非扫描。
而扫描/非扫描不能和间断/非间断混合,也即剩下连续/非连续混合间断/非间断:连续间断、连续非间断、非连续间断、非连续非间断。
这样就有八种模式共ADC使用,然而大部分情况下不会用到间断模式,在特殊情况下有可能会用到。
然而上述只是单ADC的模式,对应的有双ADC模式,同样对应特殊情况,例如需要采样频率较高的场合下,需要的可以自行查阅手册,这里不进行展开。
3.ADC的校准及温度传感器
3.1ADC的校准
每次使用ADC转换之前,都应该进行ADC的校准,校准可以减小一些误差,对应的CAL为校准位:
在校准期间置CAL,校准完成置0,同时校准结束会将校准码存储在ADC_DR寄存器中:
对于库函数,只需要完成如下的代码,即可实现校准:
ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
3.2温度传感器
前面提到过,再ADC1中,温度传感器和内部电源模块分别再ADC通道的17和18中:
- 选择ADC1_IN16输入通道
- 选择采样时间为17.1 μs
- 设置ADC控制寄存器2(ADC_CR2)的TSVREFE位,以唤醒关电模式下的温度传感器
- 通过设置ADON位启动ADC转换(或用外部触发)
- 读ADC数据寄存器上的VSENSE 数据结果
4.模拟看门狗
5.数据对齐以及采样时间的计算
5.1数据对齐
数据对齐是因为ADC为12位,而寄存器位16位,所以就存在一个数据对齐的问题。
分为,左对齐和右对齐,对于注入组:
一般使用的是右对齐,这样读取到的结果就直接是转换的结果。
左对齐会是转换后的数据增大,左移一位×2,对应规则组就是扩大16倍。
如果你不需要这右对齐这么高的精度(分辨率)也就是0~4095(2的12次方-1),就是做一个简单的判断,将数据左移4位,然后舍弃低八位的数据,这样12位的ADC就被当作8位ADC来使用。
5.2采样时间的计算
AD转换的步骤:采样,保持,量化,编码
STM32 ADC的总转换时间为:
TCONV = 采样时间 + 12.5个ADC周期
例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期
TCONV = 1.5 + 12.5 = 14个ADC周期 = 1μs
6.逐次逼近比较法
接下里通过对一个8位ADC来介绍逐次逼近比较法:
7.ADC的配置
首先是开启时钟,以及初始化ADC采样输入的接口。
/*开启时钟*/
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_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为模拟输入
模式采用模拟输入模式(GPIO_Mode_AIN),GPIO引脚直接接入ADC中:
最后还需关注一个模式输入的引脚重映射,来选择GPIO端口:
接下来配置ADC规则组通道:
/*规则组通道配置*/
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //规则组序列1的位置,配置为通道0
上述采用的ADC规则组,使用的函数是ADC_RegularChannelConfig ,对应的如果想要使用ADC的注入组,使用函数ADC_InjectedChannelConfig,接下来对ADC_RegularChannelConfig 规则组进行展开。
跳转到定义:
参数较多,首先是ADCx,也就是选择ADC1~3外设。
然后是ADC_Channel,也就选择规则组的通道,每个外设ADC都会有所差异,PAx为默认重映射的引脚如下图:
下一个参数是选择第几行,行数值小的先执行,如下图先:
先选择通道二ADC_Channel_2,然后放到第一行(rank)1。
最后一个ADC_SampleTime转换时间,根据自身的需求来选择,如果你需要更快的转换,则选择更小的参数,也即ADC_SampleTime_1Cycles5;
需要更稳定的转换,则选择更大的参数ADC_SampleTime_71Cycles5,相应的转换时间也会更长。
如果都没要求,可以任意选择。
这样就配置好规则组通道的配置,如果还想再设置一个通道,就可以对上述代码进行复制,修改一下通道和RANK就行。
然后配置ADC通道的模式、数量等参数:
/*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_Mode,跳转定义:
其中,除了第一个的单ADC模式,其他的都为双ADC模式下的选择。
接着是数据对齐方式,一般是右对齐。
然后是外部触发ADC来源选择,选择需要要外部触发,也即软件触发。
接着是扫描和连续的选择,这个也是根据自身的需求来的,同时结合扫描和连续的作用,这里转换一个通道一次,选择非扫描、非连续(单次)转换的模式。
然后是通道数量,根据配置的通道数量来选择,这里选1,1个通道。
ADC_Cmd(ADC1, ENABLE); //使能ADC1,ADC开始运行
/*ADC校准*/
ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
接着是ADC使能以及ADC校准,这里就不在进行展开了。
最后设置一个返回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转换的结果
}
通过读取AD_GetValue函数的返回值,就可以得到AD转换后的数值。
8.总结
以上就是对ADC模数转换器的全部介绍了,其中没有介绍到的双ADC模式,自行查阅资料进行展开,不过最重要的还是得回归数据手册来进行理解,最后欢迎大家来进行讨论以及指正文章错误的地方。