自学内容网 自学内容网

ADC模数转换器

参考视频:[7-1] ADC模数转换器_哔哩哔哩_bilibili

ADC(Analog-Digital Converter)模拟-数字转换器

ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁

12位逐次逼近型ADC,1us转换时间

输入电压范围:0~3.3V,转换结果范围:0~4095

18个输入通道,可测量16个外部和2个内部信号源

规则组和注入组两个转换单元

模拟看门狗自动监测输入电压范围

STM32F103C8T6 ADC资源:ADC1、ADC2、10个外部输入通道

逐次逼近型ADC

ADC0809是8位逐次逼近型ADC。

IN0~IN7是八路输入通道,通过通道选择开关,选中一路,输入到这个点进行转换。

地址锁存器和译码,就是你想选中哪个通道,就把通道号放在ADDA、ADDB、ADDC三个脚上,然后给一个锁存信号,上面对应的通路开关就可以自动拨好了。

上面那两部分就相当于一个可以通过模拟信号的数据选择器。

因为ADC转换是一个很快的过程,给个开始信号,过几个us就转换完成了,所以如果想转换多路信号,不必设计多个AD转换器。只需要一个AD转换器,然后加一个多路选择开关。想转换哪一路,就先拨一下开关,选中对应通道,然后再开始转换就行了。

接下来是电压比较器,可以判断两个输入信号电压的大小关系,输出一个高低电平指示谁大谁小,两个输入段,一个是待测的电压,另一个是这里DAC的电压输出端。(DAC是数模转换器,给他一个数据,他就能输出数据对应的电压)

如果DAC输出的电压比较大,就调小DAC数据;如果DAC输出的电压比较小,就增大DAC数据。直到DAC输出的电压和外部通道输入的电压近视相等,这样DAC输入的数据就是外部电压的编码数据了。

这个电压调节的过程就是逐次逼近SAR来完成的。为了最快找到未知电压的编码,通常使用二分法进行寻找。

AD转换结束后,DAC的输入数据,就是未知电压的编码,通过向右进行输出,因为是八位的,所以有八根线。

EOC是end of Convert,转换结束信号;START是开始转换,给一个输入脉冲,开始转换;CLOCK是ADC时钟,因为ADC内部是一步一步进行判断的,所以需要时钟来推动这个过程。VREF+和VREF-是DAC的参考电压,比如给一个数据255,是对应5v还是3.3v,就由参考电压决定,DAC的参考电压也决定了ADC的输入范围,所以也是ADC参考电压。

STM32逐次逼近型ADC

左边是ADC的16个输入通道,包括16个GPIO口(IN0~IN15),和两个内部通道(一个内部温度传感器,另一个是VREFINT内部参考电压)。

接下来是模拟多路开关,可以指定想要选择的通道,右边是多路开关的输出,进入到模数转换器(执行逐次比较的过程),转换结果会直接放在数据寄存器里,读取寄存器就能知道ADC转换的结果了。

对于普通的ADC,多路开关一般都是只选中其中的一个,但是这里就比较高级了,可以同时选中多个,而且在转换的时候,分成了两个组,规则通道组和注入通道组。规则组可以一次性最多选中16个通道,注入组最多可以选中4个通道。规则组只有一个数据寄存器,所以对于规则组来说,最好配合DMA来实现。注入组有4个数据寄存器,所以注入组不用担心数据覆盖的问题。

左下角是触发转换的部分,也就是START信号(开始转换)。对于STM32的ADC,触发ADC开始转换的信号有两种,一种是软件触发,就是你在程序中手动调用一条代码,就可以启动转换了;另一种是硬件触发,就是这些触发源,分为注入组的触发源和规则组的触发源,这些触发源主要来自定时器(定时器可以通向ADC、DAC这些外设,用于触发转换),有定时器的各个通道,还有TRGO定时器主模式的输出。

因为ADC经常需要过一个固定时间段转换一次,比如1ms转换一次,正常的思路就是,用定时器,每隔1ms申请一次中断,在中断里手动开始一次转换,但是频繁进入中断对程序是有一定影响的。

对于频繁进入中断,并且在中断里只完成简单工作的情况,一般都有硬件支持,比如给TIM3定个1ms的时间,并且把TIM3的更新事件选择为TRGO输出,然后在ADC这里,选择开始触发信号为TIM3的TRGO,这样TIM3的更新事件就能通过硬件自动触发ADC转换了。

VREF+和VREF-是DAC的参考电压,决定了ADC输入电压的范围。ADCCLK是ADC的时钟,来源于ADC预分频器。

对于规则组和注入组,它们转换完成之后,也会有一个EOC转换完成的信号,EOC是规则组的完成信号,JEOC是注入组完成的信号,这两个信号会在状态寄存器里置一个标志位,读取这个标志位,就可以知道有没有转换结束,同时这两个标志位也可以去到NVIC,申请中断,如果开启了NVIC对应的通道,它们就会触发中断。

ADC基本结构

左边是输入通道,16个GPIO口,外加两个内部的通道。

然后进入AD转换器,一个是规则组,一个是注入组,规则组最多选中16个通道,注入组最多可以选择4个通道。

然后转换的结果可以存放在AD数据寄存器里,其中规则组只有一个数据寄存器,注入组有4个数据寄存器。

然后AD转换器下面有触发控制START,触发控制可以选择软件触发和硬件触发,硬件触发主要来自于定时器,当然也可以选择外部中断的引脚,右边是来自于RCC的ADC时钟CLOCK,ADC逐次比较的过程就是由这个时钟推动的。

然后AD数据寄存器上面可以布置一个模拟看门狗用于监测转换结果的范围,如果超出设定的阈值,就通过中断输出控制,向NVIC申请中断。

规则组和注入组转换完成后会有个EOC信号,它会置一个标志位,当然也可以通向NVIC。

在右下角有一个开关控制,在库函数中,就是ADC_Cmd函数,用于给ADC上电的。

输入通道

ADC通道和引脚定义对应的表

规则组的四种转换模式

单次转换,非扫描模式

在非扫描模式下,这个菜单只有第一个序列1的位置有效,这时,菜单同时选中一组的方式就退化为简单地选中一个的方式了,在这里可以在序列1的位置指定想转换的通道,比如通道2,然后就可以触发转换,ADC就会对这个通道2进行模数转换,过一小段时间,转换完成,转换结果放在数据寄存器里,同时给EOC标志位置1,判断EOC标志位,如果转换完了,就可以在数据寄存器里读取结果了。

如果想再启动一次转换,就需要再触发一次,转换结束,置EOC标志位,读结果。

如果想换一个位置转换,把第一个位置的通道2改成其他通道,然后再启动转换,这样就可以了。

连续转换,非扫描模式

首先,还是非扫描模式,所以菜单列表只用第一个,与上一种单次转换不同的是,它在一次转换结束后不会停止,而是立刻开始下一轮的转换,然后一直持续下去,这样就只需要最开始触发一次,然后就可以一直转换了。

单次转换,扫描模式

这个模式也是单次转换,所以每次转换结束后,就会停下来,下次转换就得再触发才能开始。然后是扫描模式,就需要用到这个菜单列表了,你可以在这个菜单里点菜,每个位置的通道可以任意指定,并且也是可以重复的。

然后初始化结构体里还会有个参数,就是通道数目,比如通道数目为7,那它就只看前七个位置,每次触发之后,就依次对前7个位置进行AD转换,转换结果都放在数据寄存器里,这里为了防止数据被覆盖,就需要用DMA及时将数据挪走,那7个通道转换完成之后,产生EOC信号,转换结束。然后再触发下一次,就又开始新一轮的转换。

连续转换,扫描模式

这个模式就是在上个模式的基础上变了一点,就是一次转换完成之后,立刻开始下一次的转换。

规则组触发源

这个类型有来自片上定时器的内部信号,外部引脚/来自片上定时器的内部信号,软件控制位。这些控制位的选择可以通过右边的寄存器来选择,使用库函数,直接给一个参数就可以了。

数据对齐

这个ADC是12位的,转换结果就是一个12位的数据。但是数据寄存器是16位的,所以就存在一个数据对齐的问题。

第一种是数据右对齐,就是12位的数据向右靠,高位多出来的几位就补0。

第二种是数据左对齐,就是12位的数据向左靠,低位多出来的几位补0。

一般选择的是数据右对齐,这样就直接是结果。

转换时间

AD转换的步骤:采样,保持,量化,编码

量化编码就是ADC逐次比较的过程。

AD转换,就是量化编码,是需要一小段时间的,如果在这段时间里,输入的电压还在不断变化,所以在量化编码之前,要设置一个采样开关,先打开采样开关,收集一下外部的电压,比如可以用一个小容量的电容存储一下这个电压,存储好了之后,断开采样开关,再进行后面的AD转换。这样在量化编码的期间,电压始终保持不变,这样才能精确地定位未知电压的位置。

STM32 ADC的总转换时间为:

因为是12位的ADC,所以需要12个周期,还有半个周期是其他东西花费的时间。

ADC周期就是从RCC分频过来的ADCCLK,这个ADCCLK最大是14MHZ。

 例如:当ADCCLK=14MHz,采样时间为1.5ADC周期

         

校准

ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差

建议在每次上电后执行一次校准

启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期

这部分不需要理解,校准过程是固定的,只需要在ADC初始化的最后,加几条代码就可以了。

硬件电路

第一个是电位器产生一个可调的电压,一端接3.3V,一端接GND,这样中间的滑动端就可以输出一个0~3.3V可调的电压输出了,这里可以接ADC的输入通道,比如PA0口。当滑动端往上滑时,电压增大,往下滑时,电压减小。

中间是传感器输出电压的电路,一般来说,像光敏电阻、红外接收管、麦克风等等,都可以等效为一个可变电阻。

最右边是一个电压转换电路,比如想测一个0~5V的VIN电压,但是ADC只能接收0~3.3V的电压,那就可以搭建一个这样的简易转换电路。

ADC相关的库函数

// 在rcc库函数里
// 配置ADCCLK分频器的,可以对APB2的72MHz时钟选择2、4、6、8分频,输入到ADCCLK
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);

// 在adc库函数里
void ADC_DeInit(ADC_TypeDef* ADCx);
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);
// 给ADC上电的,就是上面ppt里的开关控制
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// 开启DMA输出信号的,如果使用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软件开始转换控制,这个就是用于软件触发的函数,给SWSTART位置1,以开始转换的
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// ADC获取软件开始转换状态,返回SWSTART的状态,(一般不用,没啥用)
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);

// 获取标志位的状态,然后参数给EOC的标志位,判断EOC标志位是不是置1了。
// 如果转换结束,EOC标志位置1,然后调用这个函数,判断标志位,这样才是正确的判断转换是否结束的方法。
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);

// 配置间断模式的函数
// 每隔几个通道间断一次
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
// 是不是启用间断模式
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

// ADC规则组通道配置,它的作用是给序列的每个位置填写指定的通道,就是填写点菜菜单的过程。
// 第一个参数是ADCx,第二个参数是你想指定的通道,第三个参数是序列几的位置,第四个就是指定通道的采样时间
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);

// 对模拟看门狗进行配置的
// 是否启动模拟看门狗
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);


AD单通道代码

接线图

步骤

  1. 开启RCC时钟,包括ADC和GPIO的时钟,配置ADCCLK的分频器。
  2. 配置GPIO,把需要用的GPIO配置成模拟输入的模式。
  3. 配置多路开关,把左边的通道接入到右边的规则组列表里,就是上面说的点菜,把各个通道的菜列在菜单里。
  4. 配置ADC转换器,在库函数里,是用结构体来配置的,包括ADC是单次转换还是连续转换,扫描还是非扫描,有几个通道,触发源是什么,数据是左对齐还是右对齐。
  5. 开关控制,调用一下ADC_Cmd函数,开启ADC,这样ADC就配置完成了,就能正常工作了。
  6. 开启ADC之后,还可以对ADC进行校准,可以减小误差。

代码

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"

uint16_t ADValue;
float Voltage;

int main(void)
{
OLED_Init();
AD_Init();

OLED_ShowString(1, 1, "ADValue:");
OLED_ShowString(2, 1, "Voltage:0.00V");

while(1)
{
ADValue = AD_GetValue();
Voltage = (float)ADValue / 4095 * 3.3;

OLED_ShowNum(1, 9, ADValue, 4);
OLED_ShowNum(2, 9, Voltage, 1);
OLED_ShowNum(2, 11, (uint16_t)(Voltage * 100) % 100 , 2);

Delay_ms(100);
}
}

AD.c

#include "stm32f10x.h"                  // Device header

void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

RCC_ADCCLKConfig(RCC_PCLK2_Div6);

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);

ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);

ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;  // 非连续转换模式
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;  // 对齐方式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 触发方式
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_NbrOfChannel = 1;  // 通道数目
ADC_InitStructure.ADC_ScanConvMode = DISABLE;  // 扫描转换模式
ADC_Init(ADC1, &ADC_InitStructure);

ADC_Cmd(ADC1, ENABLE);

// 调用复位校准
ADC_ResetCalibration(ADC1);
// 等待复位校准完成,该函数是返回复位校准的状态,所以要等待复位校准完成的话,要加一个while循环
while (ADC_GetResetCalibrationStatus(ADC1) == SET) ;
// 开始校准
ADC_StartCalibration(ADC1);
// 等待校准完成
while (ADC_GetCalibrationStatus(ADC1) == SET);
}

// 首先软件触发,然后等待转换完成,也就是等待EOC标志位置1,最后读取ADC数据寄存器
uint16_t AD_GetValue(void)
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
return ADC_GetConversionValue(ADC1); // 获取转换值
}

修改转换模式

上面用的是单次转换,非扫描模式。如果要换成连续转换,扫描模式,首先需要在ADC初始化的时候,设置

之后因为,连续转换只需要刚开始的时候开启软件触发就可以了,之后会自动开启,所以把红色框内的内容放到了初始化部分。

AD多通道代码

使用单次转换,非扫描模式。在每次触发转换之前,手动更改一下列表第一个位置的通道。

接线图

代码

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"

uint16_t AD0, AD1, AD2, AD3;

int main(void)
{
OLED_Init();
AD_Init();

OLED_ShowString(1, 1, "AD0:");
OLED_ShowString(2, 1, "AD1:");
OLED_ShowString(3, 1, "AD2:");
OLED_ShowString(4, 1, "AD3:");

while(1)
{
AD0 = AD_GetValue(ADC_Channel_0);
AD1 = AD_GetValue(ADC_Channel_1);
AD2 = AD_GetValue(ADC_Channel_2);
AD3 = AD_GetValue(ADC_Channel_3);

OLED_ShowNum(1, 5, AD0, 4);
OLED_ShowNum(2, 5, AD1, 4);
OLED_ShowNum(3, 5, AD2, 4);
OLED_ShowNum(4, 5, AD3, 4);

Delay_ms(100);
}
}

AD.c

#include "stm32f10x.h"                  // Device header

void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

RCC_ADCCLKConfig(RCC_PCLK2_Div6);

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);

ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;  // 非连续转换模式
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;  // 对齐方式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 触发方式
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_NbrOfChannel = 1;  // 通道数目
ADC_InitStructure.ADC_ScanConvMode = DISABLE;  // 扫描转换模式
ADC_Init(ADC1, &ADC_InitStructure);

ADC_Cmd(ADC1, ENABLE);

// 调用复位校准
ADC_ResetCalibration(ADC1);
// 等待复位校准完成,该函数是返回复位校准的状态,所以要等待复位校准完成的话,要加一个while循环
while (ADC_GetResetCalibrationStatus(ADC1) == SET) ;
// 开始校准
ADC_StartCalibration(ADC1);
// 等待校准完成
while (ADC_GetCalibrationStatus(ADC1) == SET);
}

// 首先软件触发,然后等待转换完成,也就是等待EOC标志位置1,最后读取ADC数据寄存器
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
return ADC_GetConversionValue(ADC1); // 获取转换值
}

原文地址:https://blog.csdn.net/m0_63690570/article/details/142689265

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!