一、ADC的简介
PWM---》实际上就是使用DAC(数字-->模拟)。因为PWM只有完全断开和完全接通,所以在大功率电机中损耗很小,所以使用PWM比较合适。
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输出的电压和外部通道输入的电压近似相等。
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)
2.输入通道
输入的GPIO必须具有模拟输入功能的IO口才可以。
GPIO通道:快速通道VS慢速通道
内部ADC源直接绑定,外部ADC绑定GPIO
18个输入通道,可测量16个外部和2个内部信号源(温度传感器 && 内部的参考电压)
ADC1和ADC2两个的通道都使用同一个引脚。---->双ADC
3.转换序列(转换顺序)
1)转换被组织分为2组:规则组&&注入组【注入组可以打断规则组的转换】
2)规则组最多可以有16个转换(通道),注入组最多有4个转换(通道)
规则组和注入组执行优先级对比
注入组(类似于中断)的优先级比规则组的优先级高
规则序列:(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.外部事件触发转换(规则组和注入组)
规则组外部触发
注入组外部触发
5.转换时间
如何设置ADC时钟?
1)ADCCLK的最大时钟频率是:14MHZ
如何设置ADC转换时间
1)ADC中的最短转换时间为:1us【在ADC时钟频率为:14MHZ,采样时间为1.5个ADC时钟周期,12.5个周期(固定值-->12位寄存器)的情况下】
2)采样时间(可以进行编程的)越大,就可以尽量避免毛刺信号的干扰,精确度越高
6.数据寄存器
规则组只有一个寄存器,注入组有四个寄存器。在没有DMA的帮助下,规则组只能一次输入ADC,如果多几个,会被覆盖。
数据对齐
7.中断
DMA请求(只适用于规则组)
规则组每一个通道转换结束后,除了可以产生中断外,还可以产生DMA请求,我们利用DMA及时把转换好的数据传输到指定的内存中,防止数据被覆盖。
2.单次转换模式VS连续转换模式
单次转换模式:如果我们不需要实时检测,则使用单次
连续转换模式:如果需要实时检测,则使用连续
3.转换/扫描模式
关闭扫描模式:只能扫描第一个通道
使用扫描模式:表示扫描全部通道
连续是一个通道多次采集,扫描是每个通道依次采集
不同模式组合的作用
扫描:切换通道(遍历),连续:多次
单次转换,非扫描模式
单次转换,扫描模式
连续转换,非扫描模式
连续转换模式
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)
3.ADC采集实验配置步骤
相关HAL库介绍
关键结构体介绍
ADC句柄
ADC通道设置
四、AD单通道
1.硬件接线
通过控制接入的阻值来读取引脚上的电压值,来输出模拟信号
2.代码编写
1)ADC输入的通道对应的是GPIO的AIN(模拟输入)模式。
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); //固定流程,内部有电路会自动执行校准
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转换结束
//获取转换值
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多通道
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】
文章来源:https://www.toymoban.com/news/detail-789720.html
文章来源地址https://www.toymoban.com/news/detail-789720.html
到了这里,关于【STM32】ADC(模拟/数字转换)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!