【STM32】ADC(模拟/数字转换) 您所在的位置:网站首页 stm32单片机adc采样电压原理 【STM32】ADC(模拟/数字转换)

【STM32】ADC(模拟/数字转换)

2024-07-10 02:45| 来源: 网络整理| 查看: 265

一、ADC的简介

1)PWM---》实际上就是使用DAC(数字-->模拟)。因为PWM只有完全断开和完全接通,所以在大功率电机中损耗很小,所以使用PWM比较合适。

2)规则阻和注入组:实际上就是可以一次性转换多个ADC值

3)当测量到的AD值超过某一个阈值的时候,就可以使用看门狗来申请中断,就可以在中断函数中执行相应的操作。

1.什么是ADC

1)将【电信号】-->【电压】-->【数字量】

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

3)12位逐次逼近型ADC,1us转换时间(表示从产生电压到转换得出结果所使用的时间)

2.常见的ADC

3.并联比较型工作示意图

1)比较器:当两个数值相同时才会生成信号传输给编码器

2)D0,D1,D2(从低位--》高位)---》二进制数(分辨率)---》2^3

4.逐次逼近型工作示意图(DAC工作原理)

如果是n位的锁存缓冲器(数码控制器),就需要进行n次的判断

逐次逼近:就是如果是12位的DAC转换,则我们先将DAC赋值为2^12的一半,然后逐次的按照一半递减(增加)。所以12位DAC转换就需要比较12次。

如果DAC输出的电压比较小,就增大DAC

如果DAC输出的电压比较大,就减小DAC

直到DAC输出的电压和外部通道输入的电压近似相等,这样DAC输入的数据就是外部电压的编码数据

2)通道选择开关:通过输入的8路ADC来选择使用输入哪一路 

CLOCK:ADC的转换是需要通过时间来进行逐步执行的

START:置为高电平表示此时要进行ADC转换

EOC:表示结束信号

Vref+/Vref-:这个是DAC的参考电压,同时也是ADC的参考电压(如果这个参考电压值越大,则表示ADC的范围越大)

5.ADC特性参数 1.分辨率:刻度划分

表示ADC能分辨的最小模拟量,用二进制位数进行表示,比如;8,10,12

比如此时电压为3.3V,我们使用12位进行表示   2^12=4096   3.3%4096=0.0008V,表示当数字量为1的时候,输出电压为0.0008V。

输入电压范围:0-3.3V,转换结果范围:0-4095(2^12)

2.转换时间

表示完成一次A/D转换所需要的时间,转换时间越短,采用频率就越高

假设1s的时间中转换时间为200ms,则表示可以转换5次

3.精度:物理量的精确程度

4.量化误差

6.STM32各系列ADC主要特性

7.ADC基本结构

二、ADC工作原理 1.ADC框图简介 1.参考电压/模拟部分电压

1) 输入电压的范围在参考电压的两个范围之间。

2)Vref+和Vref-分别接着Vdda(3.3V)和Vssa(0V)【这两个参考电压同时也是DAC的参考电压范围】

2.输入通道

输入的GPIO必须具有模拟输入功能的IO口才可以。

GPIO通道:快速通道VS慢速通道

内部ADC源直接绑定,外部ADC绑定GPIO

18个输入通道,可测量16个外部和2个内部信号源(温度传感器 && 内部的参考电压)

 ADC1和ADC2两个的通道都使用同一个引脚。---->双ADC

3.转换序列(转换顺序)

1)转换被组织分为2组:规则组&&注入组【注入组可以打断规则组的转换】

2)规则组最多可以有16个转换(通道),注入组最多有4个转换(通道)【规则组一次性可以转换16个,但是只能】

 规则组和注入组执行优先级对比

注入组(类似于中断)的优先级比规则组的优先级高

规则序列:(regular channel)

1)规则组有16个通道

2)这里的意思理解为,有16个规则通道,即为16个规则的不同编号的盘子,每个盘子可以放18个通道即为GPIO其中的一个口

3)必须按顺序来执行(如果想要执行通道3,则通道1和通道2都要执行)

4)我们有3个ADC,表示可以设置3个最高的优先级【可以同步进行】

5)一个菜单,可以点16个菜,也可以只写一个菜。【同时上16个菜,但是只能一个一个上(因为只有一个寄存器),否则前面会被覆盖--->所以我们使用DMA】

注入序列:(injected channel)

1)注入组有4个通道

2)注入组的寄存器写入位是反向写入的

3)一次性最多可以点4个菜,且可以同时上4个菜,不会被覆盖【因为有4个寄存器】

4.触发源

1)触发转换分为:a.ADON位触发转换  b.外部事件触发转换(规则组和注入组)

2)如果想要1ms进入一次中断来对ADC进行值的获取,我们可以使用TIM3的TIM3_TRGO,将TIM3设置为1ms进入,然后将TIM3的更新事件选择为TIM3_TRGO输出,然后在ADC这里,选择开始触发信号信号TIM3的TRGO,这样TIM3的更新事件就可以通过硬件自动的触发ADC转换。整个过程不需要进入中断。

规则组外部触发

注入组外部触发

5.转换时间/输入频率

如何设置ADC时钟?

1)ADCCLK的最大时钟频率是:14MHZ

如何设置ADC转换时间

1)ADC中的最短转换时间为:1us【在ADC时钟频率为:14MHZ,采样时间为1.5个ADC时钟周期,12.5个周期(固定值-->12位寄存器)的情况下】

2)采样时间(可以进行编程的)越大,就可以尽量避免毛刺信号的干扰,精确度越高

为什么会有采样时间??

因为我们在获取ADC输入信号的时候,电压都是在不断的变化,无法准确的判断比较准确的电压。所以设置一个采样开关,先打开采样开关,收集一下外部的电压,使用一个小容量的电容存储器存储这个电压,存储好了后,断开采样开关,在进行后面的AD转换。这样在量化编码器件,电压始终不变。【在采样的过程中,需要闭合采样开关,等待一段时间】

6.数据寄存器

规则组只有一个寄存器,注入组有四个寄存器。在没有DMA的帮助下,规则组只能一次输入ADC,如果多几个,会被覆盖。

数据对齐

7.中断

1)我们会给数据寄存器中加入模拟看门狗,给看门狗添加阈值高限和低限,当寄存器中的数值超过阈值,则看门狗会叫,就会在上面,申请一个模拟看门狗的中断,然后通向NVIC

2)当我们的数据寄存器正常结束的时候也会给EOC(规则通道)和JEOC(注入通道)置上标志位,表示转换结束,然后通向NVIC,申请中断。

DMA请求(只适用于规则组)

规则组每一个通道转换结束后,除了可以产生中断外,还可以产生DMA请求,我们利用DMA及时把转换好的数据传输到指定的内存中,防止数据被覆盖。

2.单次转换模式VS连续转换模式

单次转换模式:如果我们不需要实时检测,则使用单次

连续转换模式:如果需要实时检测,则使用连续

3.转换/扫描模式

关闭扫描模式:只能扫描第一个通道

使用扫描模式:表示扫描全部通道

连续是一个通道多次采集,扫描是每个通道依次采集

不同模式组合的作用

扫描:切换通道(遍历),连续:多次

单次转换,非扫描模式 

当我们要使用通道2进行扫描,则直接触发ADC转换,然后当获取到数值后就,将数据放入数据寄存器中,然后将EOC位置1,表示此时转换结束。

 单次转换,扫描模式

需要给通道数目,表示这个菜单中有多少个通道,当通道1转换结束后,需要使用DMA将这个通道1转移走,然后在继续下一个通道的读取,当整个菜单中的通道执行完后,才会将EOC置1表示转换结束。

连续转换,非扫描模式 

这里不需要手动开启转换,当第一个转换通道2结束后,会自动的转换到下一个列表中进行下一个通道2的转换

连续转换模式 

4.ADC校准

如果需要使用到精确计算,则需要校准

影响ADC转换的因素:

1)温飘(温度影响)

2)基准电压值

/*ADC校准 1)先重置校准位:ADC_ResetCalibration -->重置所选ADC校准寄存器。 2)判断校准位是否被清除: ADC_GetResetCalibrationStatus--> 获取所选ADC复位校准寄存器状态。 3)进行ADC校准: ADC_StartCalibration--->启动选定的ADC校准过程。 4)判断校准是否完成: ADC_GetCalibrationStatus-->获取所选ADC校准状态 */ ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准 while (ADC_GetResetCalibrationStatus(ADC1) == SET);//判断是否校准完成【1表示未完成校准】 ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1) == SET); 5.ADC与低功耗

外部引脚输入也会耗电--->采样时间

采样间隔周期

要采样则唤醒,不需要就进入睡眠。

 三、ADC采集实验 1.实验简要

2.寄存器描述 1.ADC控制寄存器 1(ADC_CR1)

2.ADC控制寄存器 2(ADC_CR2)

3.ADC采样时间寄存器 1(ADC_SMPR1)

通道10-通道17的设置

4.ADC采样时间寄存器 2(ADC_SMPR2)

通道0-通道9的设置

5.ADC规则序列寄存器 1(ADC_SQR1)

设置第13-第16个转换

6.ADC规则序列寄存器 2(ADC_SQR2)

设置通道12-通道7

7.ADC规则序列寄存器 3(ADC_SQR3)

设置通道6-通道0

8.ADC规则数据寄存器(ADC_DR)

9.ADC状态寄存器(ADC_SR)

EOC:当规则组或者注入组转换结束后,会由硬件设置1表示此时转换结束并且已经将数据存放入数据寄存器中。当我们去读取数据寄存器中的数值后,硬件会自动将EOC置为0.

3.ADC采集实验配置步骤

1)开启RCC时钟,包括ADC和GPIO时钟,ADCCLK的分频器也需要配置【因为ADC的频率不能高于14MHZ,所以这里需要再一次分频】

2)配置GPIO,把需要用到的GPIO配置成模拟输入的模式

3)配置这里的多路开关,把左边的通道接入到右边的规则组列表中

4)配置ADC转换器,包括ADC是单次转换还是连续转换,扫描还是非扫描,有几个通道

5)如果需要模拟看门狗,有几个函数配置阈值和检测通道,如果想开启中断,就在中断输出控制里用ITConfig函数开启对应的中断输出,然后在NVIC中配置优先级,就可以触发中断。

6)开启开关控制,调用ADC_Cmd()

相关HAL库介绍

关键结构体介绍 ADC句柄

ADC通道设置

四、AD单通道 1.硬件接线

通过控制接入的阻值来读取引脚上的电压值,来输出模拟信号

2.代码编写

1)ADC输入的通道对应的是GPIO的AIN(模拟输入)模式。【使用AIN模式,是因为在AIN模式下是断开GPIO,防止GPIO口的输入输出对模拟电压造成干扰】

2)我们在使用【ADC_GetConversionValue】的时候,内部会读取DR寄存器的值,而读取DR寄存器的值会自动清除EOC标志【所以我们在后面不需要手动清除】

adc.c /** * 函 数:AD初始化 * 参 数:无 * 返 回 值:无 */ 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_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为模拟输入 /*规则组通道配置*/ ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //规则组序列1的位置,配置为通道0 /*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使能*/ ADC_Cmd(ADC1, ENABLE); //使能ADC1,ADC开始运行 /*ADC校准 1)先重置校准位:ADC_ResetCalibration -->重置所选ADC校准寄存器。 2)判断校准位是否被清除: ADC_GetResetCalibrationStatus--> 获取所选ADC复位校准寄存器状态。 3)进行ADC校准: ADC_StartCalibration--->启动选定的ADC校准过程。 4)判断校准是否完成: ADC_GetCalibrationStatus-->获取所选ADC校准状态 */ ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准 //我们上面手动的将该位置为1,表示此时要开始校准,当校准结束后硬件会自动清零,表示校准结束 while (ADC_GetResetCalibrationStatus(ADC1) == SET);//判断是否校准完成【1表示未完成校准】 ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1) == SET); } /** * 函 数:获取AD转换的值 * 参 数:无 * 返 回 值:AD转换的值,范围:0~4095 */ uint16_t AD_GetValue(void) { //启用或禁用所选ADC软件启动转换 ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发AD转换一次 //判断ADC转换是否完成 /** 上面我们设置了【将时钟进行RCC_ADCCLKConfig(RCC_PCLK2_Div6); 6分频--->则我们最后的结果为72 MHZ / 6= 12 然后我们设置ADC采用频率为【ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);55个时间周期,整个周期--->12.5+55=68个周期】 则我们需要的时间是--->f=1/T=1/12 --》时间=f*周期=1/12*68=5.6us */ while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待EOC标志位,即等待AD转换结束 //获取转换值,读取数据寄存器的置后,会自动清除EOC的位为0. return ADC_GetConversionValue(ADC1); //读数据寄存器,得到AD转换的结果 } main.c uint16_t ADValue; //定义AD值变量 float Voltage; //定义电压变量 int main(void) { /*模块初始化*/ OLED_Init(); //OLED初始化 AD_Init(); //AD初始化 /*显示静态字符串*/ OLED_ShowString(1, 1, "ADValue:"); OLED_ShowString(2, 1, "Voltage:0.00V"); while (1) { ADValue = AD_GetValue(); //获取AD转换的值 Voltage = (float)ADValue / 4095 * 3.3; //将AD值线性变换到0~3.3的范围,表示电压 OLED_ShowNum(1, 9, ADValue, 4); //显示AD值 OLED_ShowNum(2, 9, Voltage, 1); //显示电压值的整数部分 OLED_ShowNum(2, 11, (uint16_t)(Voltage * 100) % 100, 2); //显示电压值的小数部分 Delay_ms(100); //延时100ms,手动增加一些转换的间隔时间 } } 五、AD多通道

这里的AD多通道并没有使用到DMA进行数据的运输,而是使用【单次转换,非扫描模式】

如果我们使用多次转换,当一个序列中的多个通道转换结束后只会置EOC一次为1,所以我们并不知道每一个通道的转换结束时间,如果我们没有及时将一个通道产生的数据转移走,则会导致数据被覆盖。

1.硬件接线

2.代码编写 ad.c /** * 函 数:AD初始化 * 参 数:无 * 返 回 值:无 */ 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、PA1、PA2和PA3引脚初始化为模拟输入 /*不在此处配置规则组序列,而是在每次AD转换前配置,这样可以灵活更改AD转换的通道*/ /*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使能*/ ADC_Cmd(ADC1, ENABLE); //使能ADC1,ADC开始运行 /*ADC校准*/ ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准 while (ADC_GetResetCalibrationStatus(ADC1) == SET); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1) == SET); } /** * 函 数:获取AD转换的值 * 参 数:ADC_Channel 指定AD转换的通道,范围:ADC_Channel_x,其中x可以是0/1/2/3 * 返 回 值:AD转换的值,范围:0~4095 */ 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转换的结果 }

main.c uint16_t AD0, AD1, AD2, AD3; //定义AD值变量 int main(void) { /*模块初始化*/ OLED_Init(); //OLED初始化 AD_Init(); //AD初始化 /*显示静态字符串*/ 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); //单次启动ADC,转换通道0 AD1 = AD_GetValue(ADC_Channel_1); //单次启动ADC,转换通道1 AD2 = AD_GetValue(ADC_Channel_2); //单次启动ADC,转换通道2 AD3 = AD_GetValue(ADC_Channel_3); //单次启动ADC,转换通道3 OLED_ShowNum(1, 5, AD0, 4); //显示通道0的转换结果AD0 OLED_ShowNum(2, 5, AD1, 4); //显示通道1的转换结果AD1 OLED_ShowNum(3, 5, AD2, 4); //显示通道2的转换结果AD2 OLED_ShowNum(4, 5, AD3, 4); //显示通道3的转换结果AD3 Delay_ms(100); //延时100ms,手动增加一些转换的间隔时间 } } 四、使用CubeMX创建ADC和DMA

将外部0-3.3V的模拟信号接入到单片机底座脚P11(PA1)口

1.CubeMX使用

因为ADC1和ADC2所使用的通道对应的GPIO引脚是一致的,所以使用ADC1或者ADC2都可以。

0.其他相关设置

1)选择外部晶振

2)启动DMA

1.通道选择

2.中断选择

此时我们的实验是将从ADC获取到的模拟信号通过转换为电压传输给DAM,然后DMA在通知CPU。

1)ADC不需要中断:因为当ADC采样到的结果直接丢给DMA,而不需要停下来告诉DMA,ADC不需要考虑是否传输成功。因为他们两个之间有专门的传输通道。

2)DMA需要中断:因为当接收到ADC传输过来的数据后,DMA需要告诉CPU,我接收到ADC的数据了。

3.ADC中的DMA Setting

绑定ADC

我们一般要将结果计算出来,我们都会多测量几次,然后求平均值,所以我们的Memory地址要递增,要不然会将上一个数据覆盖掉。 4.ADC中的Parameter Settings

5.总结

本实验使用了APB2(ADC1的时钟频率为14MhZ)

使用了ADC1的通道1,测试外部0-3.3V的模拟信号

2.代码编写

3.MDA 中​​​​​​断处理的内部逻辑 1.HAL_ADC_Start_DMA

这个函数实际上是ADC中的函数,通过DMA来传输ADC数据

ADC传输完的中断入口

2.HAL_DMA_Start_IT

设置DMA中断的相关事宜

3.DMA_SetConfig

完成DMA和DAC之间的联系

4.HAL_ADC_ConvCpltCallback

DMA中断实际上是调用了ADC【HAL_ADC_ConvCpltCallback】--->真正的中断回调函数

如果想要在中断中处理什么,就重写这个函数

5.DMA1_Channel1_IRQHandler

我们往里面查看发现并没有什么真正有执行的代码

      /* Half transfer callback */       hdma->XferHalfCpltCallback(hdma);

      /* Transfer complete callback */       hdma->XferCpltCallback(hdma);

分析可知【HAL_UART_IRQHandler】实际上并没有做什么

五、STM32随机数生成器 1.什么是随机数

1.真正的随机数

2.伪随机数

2.随机数的生成

1.用纯软件算法

伪随机数生成算法 - shine-lee - 博客园 (cnblogs.com)

2.采集随机事件为元素生成

3.用Soc内置伪随机数发生模块生成:使用硬件方法【HAL_RNG】



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有