一、ADC的介绍
1.1什么是ADC
ADC(Analogto-Digital Converter)模拟数字转换器,是将模拟信号转换成数字信号的一种外设。比如某一个电阻两端的是一个模拟信号,单片机无法直接采集,此时需要ADC先将短租两端的电压这个模拟信号转化成数字信号,单片机才能够进行处理。
1.2 ADC的用途
ADC具有将模拟信号转换成数字信号的能力,比如将模拟的电压转换成数字信号,单片机进行处理。可以用作温度监测或者电流监测等方面,用途极广。
1.3 STM32F1系列的ADC介绍
STM32F1的ADC为12位ADC,是一种逐次逼近型模拟数字转换器。它有多达18个通道,可测量16个外部和2个内部 信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右 对齐方式存储在16位数据寄存器中。 模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。 ADC的输入时钟不得超过14MHz,它是由PCLK2经分频产生。
STM32的ADC其转换时间为1us。
二、STM32的ADC时钟介绍
下图是截取的时钟树关于ADC部分,至于时钟树可以在编程参考手册的56页找到或者直接搜索时钟树就可以找到了。
可以看见其时钟来源于APB2总线,其频率最大可以达到72MHz,而ADC的时钟最大只能为14MHz,故先经过ADC预分频器经行6分频得到12MHz的时钟。
三、ADC框图介绍:
对于ADC的框图,下图以经把各个功能模块以及分好了,见下图:
3.1 ADC的18个通道介绍
首先其16个外部通道接口对应着某些GPIO口,至于哪些就不介绍了,可以查看引脚定义。
而内部通道一个为内部温度传感器,可以用于查看CPU温度,另一个则是内部参考电压了,为一个1.2V左右的基准电压(不会随外部供电电压变化而变化)。有没有想过为什么ADC初始化需要校准呢?假如你芯片的供电电压不是标准的3.3V,那测量外部引脚的电压就可能不对,这时就可以读取这个基准电压经行校准,就能得到正确的电压值了,这就是校准的作用。
3.2 ADC转换单元的介绍
规则组和注入组两个转换单元,在图中可以看见注入组最多四个通道,而规则组最多16个通道。如何选择这两种转换单元呢?下面我来说明这两种的区别:
首先是通道数:**规则组可以包含最多16个转换,而注入组最多只能有4个转换。**这意味着规则组适用于更多的通道同时进行转换,而注入组则更适合于顺序执行的单次转换。注:注入通道虽然只有4个通道,但是每一个通道都有自己的数据寄存器,即注入组有四个数据寄存器。而规则组虽然有16个转换通道,但只有一个数据寄存器。
使用场景:规则组通常用于大规模的连续转换条件下,而注入组则多用于执行单次的转换,仅在需要时进行转换。
触发方式:**注入组可以打断规则组的转换,具有更高的优先级。**具体来说,如果注入组在规则组转换期间被触发,当前规则组的转换会被中断,并开始执行注入组的转换。注入组转换完成后,规则组的转换会继续。此外,注入组可以在规则通道之后自动进行转换,这种模式允许最多20个转换序列的组合,其中规则通道在前,注入通道在后。
综上所述,规则组和注入组的选择取决于具体的应用需求和场景。规则组适合于需要连续、大规模数据采集的场景,而注入组则适用于需要快速响应、单次数据采集的情况。
3.3 模拟看门狗的介绍
模拟看门狗检测输入电压范围:用于检测ADC值,当检测值低于或高于某个阈值时,用来执行某些操作(也就相当于报警,而监测不需要人为去看守,不需要自己手动读值再用if判断,相当于家里养了狗,有陌生人来了狗就会叫,而这个陌生人代指超阈值)。
3.4 ADC触发源的介绍
其触发源可以来自定时器、外部引脚以及软件控制位来触发。即两种触发源:软件触发以及外部触发(内部定时器、外部中断)。
3.4.1 定时器触发:
ADC在平常使用过程中经常需要过一个固定时间转换一次,比如每个1ms转换一次,正常思路是在定时器中断中用软件控制位来控制触发。但是这样频繁进入中断对主程序是有一定影响的,当你有很多中断时由于优先级不同也会导致某些中断不能及时得到响应,还是有很大影响,所以对于这种需要频繁进入中断且在中断只完成简单工作的情况的一般都有硬件的支持。在这里便可以这样使用:先给定时器设置定时时间,并把定时器更新事件选择为TRGO输出,然后在ADC这里选择开始触发信号为定时器的TRGO,这样定时器的更新事件就能自动触发ADC转换了且无需进入中断,节省了中断资源。
3.4.2 外部中断触发
外部中断可以产生一个触发脉冲,触发Adc转换。先设置EXTTRIG控制位,则外部事件就能够触发转换。
3.4.3 软件自动触发
软件控制位触发
3.5 ADC时钟介绍
上文有介绍到其时钟最大不超过14MHz,那么为什么有这个规定呢?
我们可以看到其ADC的转换时间为1us,如果其频率超过了14MHz,会导致转换时间少于1us,也就导致其采样不精确。ADC的总转换时间如下:
Tconv = 采样时间 + 12.5个周期;
例如:当其频率ADCCLK = 14MHz,采样时间为1.5个周期(最小为1.5个周期)见下图:
那么此时转换时间为 :Tconv = 1.5 + 12.5 = 14个周期,而频率为14MHz,则时间T = 14 * (单个周期时间)1/14MHz = 1MHz,也就是1us。这下你应该明白了吧?
四、ADC转换模式的介绍
在ADC的配置过程中会遇到配置两个参数,即是否使用连续转换(ADC_ContinuousConvMode)以及是否启用扫描模式(ADC_ScanConvMode),而这两个配置也就产生了四种结果:单次转换非扫描、连续转换非扫描、单次转换扫描、连续转换扫描模式。
单次转换: 只执行转换一次
连续转换: 转换一次后立马进行下一次转换。
**非扫描模式:**只转换ADC_SQRx或ADC_JSQR选中的第一个通道。
扫描模式: 一次性转换所有被选中的通道
下面介绍ADC的几种转换模式:
4.1 单次转换非扫描模式
即一次只转换选中的某一个通道,且只转换一次。其转换结果放在数据寄存器里面,给EOC标志位置1表示转换完成。想获取转换结果只需要判断EOC标志位,为1时便可以读取数据了。如果还想继续转换,就得重新再触发一次。
如果想换一个通道转换就在转换之前将第一个位置的通道改成其他通再启动转换。
4.2 连续转换非扫描模式
**一次只转换选中的某一个通道,开始时只需要触发一次便可以一直转换。**与上面的区别就是不需要你再次触发了,且读取AD值直接去数据寄存器里面取,无需判断转换是否结束。
4.3 单次转换、扫描模式
即触发转换后只转换一次,每触发一次转换结束后就会停下来,再次转换得重新触发一次。单和单次转换非扫描模式的区别就是一次可以转换多个指定的通道,即按你指定的顺序依次转换并放入到数据寄存器中。但数据寄存器只有一个,所以为了防止覆盖需要及时将数据挪走(不及时木挪走读取到的便是被后面通道所覆盖的数据)。当转换完成也会产生一个EOC信号,即转换结束。
4.4 连续转换扫描模式
讲到这里你应该已经明白了,无非是单次转换扫描模式换成连续扫描而已。即一次转换完成继续下一次转换且可以转换多个通道而已。
五、ADC的对齐模式:
由于此芯片的ADC为12位的,而数据寄存器为16位的,如果选择左对齐也就是相当于往左位移了4位,也就是扩大了AD采集结果。二进制特点就是数据左移一次就等效于把这个数据乘2,而左移4次也就是扩大了16倍了。
而数据右对齐则没有这种情况,且平常都是使用右对齐。
六、ADC配置流程
GPIO的初始化(开启时钟包括ADC的时钟,配置为模拟输入)
选择ADC的预分频系数(很重要!)
ADC的初始化:ADC的模式(ADC_Mode:选择是否独立模式)、
数据对齐(ADC_DataAlign:左对齐或右对齐)、
外部触发转换选择(ADC_ExternalTrigConv:三种触发源)、
是否为连续转换模式(ADC_ContinuousConvMode)、
是否为扫描模式(ADC_ScanConvMode)、
通道数目(ADC_NbrOfChannel)。最后初始化ADC_Init();
ADC通道配置: 选择通道以及采样时间
ADC使能以及校准:ADC_Cmd(ADCx,ENABLE);
使能或者失能指定的ADC的软件转换启动功能
七、ADC的代码例程展示
cpp
/*
*==============================================================================
*函数名称:ADC1_Init
*函数功能:初始化ADCx
*输入参数:无
*返回值:无
*备 注:无
*==============================================================================
*/
void ADC1_Init(void)
{
// 结构体定义
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1,ENABLE);
// 设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// GPIO配置
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1; //ADC1通道1
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; // 模拟输入
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
// ADC参数配置
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 非扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 关闭连续转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 禁止触发检测,使用软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; // 1个转换在规则序列中 也就是只转换规则序列1
ADC_Init(ADC1, &ADC_InitStructure); // ADC初始化
ADC_RegularChannelConfig(ADC1,ADC_Channel_1, 1, ADC_SampleTime_239Cycles5 );
ADC_Cmd(ADC1, ENABLE); // 开启AD转换器
// ADC校准
ADC_ResetCalibration(ADC1); // 重置指定的ADC的校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); // 获取ADC重置校准寄存器的状态
ADC_StartCalibration(ADC1); // 开始指定ADC的校准状态
while(ADC_GetCalibrationStatus(ADC1)); // 获取指定ADC的校准程序
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 使能或者失能指定的ADC的软件转换启动功能
}
uint16_t AD_GetValue(void)
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//开启转换
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);//等待转换完成标志位
return ADC_GetConversionValue(ADC1);// 返回获取转换结果
}
这里附上一张ADC库函数图:
由于扫描模式得配合DMA使用,否则数据来不及移走导致清空,所以这里先不介绍,在下篇DMA的文章中将会介绍。