1.ADC的简介
ADC是Analog-to-Digital Converter的缩写,意为“模数转换器”,他的作用呢,就是将连续变化的模拟量,类似于
例如左侧的,他会一直连续变化,他就是模拟信号,而左侧的数字信号只有0和1两种情况.
好的既然大家知道了,模拟信号和数字信号的样子,那么他们是如何在STM32内部如何转换的呢,接下来就要介绍一下
逐次逼近型ADC(SAR ADC),
逐次逼近型ADC(SAR ADC)
在我们的STM32内部有ADC外设,我们就是通过这个ADC外设来进行转换的,那么我们的这个ADC是逐次逼近型(SAR)ADC,就是采取二分法,逐次逼近待测量的电压值.
SAR ADC图示:
那么这个SAR ADC是如何工作的呢,假设我们的基准电压是32mv的话,假设测量21.5mv,我们的输入电压第一次与32mv的一半进行比较,也就是16mv,比16mv大,我们就输出1,第二次我们就与32mv与16mv之间的一半进行比较,那么那么这个值就是24mv,我们的电压小于24mv,则输出0,依次比下去,得出结果为10101,在这里我们的基准电压就是外部的需要测量的电压,我们这里需要通过不断地逼近测量出电压,然后输出给芯片
如图所示:
规则组与注入组的区别
image-20240912154552830
规则组上菜,就是一次只能上一个菜,假如上了第十六个菜,那么之前的十五个菜,都会被挤掉,相当于桌子比较小
,那么想要数据不被覆盖,就得及时用DMA运走
注入组相当于VIP餐厅的座位,他一次能上四个菜,相当于老板桌
转换模式
第一个模式 :
单次转换非扫描模式,在非扫描模式下,只有第一个序列1的位置有效,比如说我们想获取通道2的数据,我们就在序列1的位置写上通道2,假如我们还想再次转换的话,我们就还得在触发
第二个模式:
连续转,换非扫描模式 他与上一次,单次转换的不同的是,他在转换一次后不会停止,而是立刻开始新一轮的转换,这样就可以只出发一次,然后一直转换了
第三个模式:
单次转换,扫描模式,这个则是与第一种模式完全相反了
第四个模式:
连续扫描转换模式 在扫描模式下,前七个序列都是有效的,都是可以被读取数据的,比如说我们想获取任意几个通道的数据,我们就在序列1-7的位置写依次写入想转换的数据2,假如我们还想再次转换的话,就不用触发了,他会连续起来,这样就只需要最看是触发一次,然后就可以一直转换了,这个模式的好处就是开始转换后,不需要等待一段时间了,因为他再一直触发
触发控制
触发控制分为硬件触发和软件触发
数据对齐模式
数据对齐 ,因为我们的ADC是12位的,但是寄存器有16位数据,那么就产生了,数据对齐,是左对齐还是右对齐
数据右对齐就是这些数据都向右靠齐,高位多出来的,直接补零
数据左对齐就是数据都向左对齐,低位多出来的补零,这样一来跟我们真是采集的数据就有所不同了,还需通过计算来算出真实的采集值,二进制有个特点就是数据左移一次,相当于把数据乘以2,那我们的数据左移了四位,那么就是相当于乘以16了,
转换时间
介绍完转换时间后在介绍一下时钟源的选择
-
选择时钟源:ADC需要一个稳定的时钟源来进行采样。这里选择了APB2总线时钟作为ADC的时钟源。
-
调整时钟频率:通过将APB2总线时钟除以6,可以得到一个适合ADC工作的较低频率。这是因为ADC对时钟频率有特定的要求,过高的时钟频率可能会导致采样不稳定或其他问题。
校准
ADC的预分频器来自RCC ,ADC 的ADCCLK最大为14MHZ,所以说他只能用6分频,8分频
2.代码的书写
下面我们将利用ADC来采集电压,通过USART DMA来显示在屏幕上
我们主要是根据这个图片来书写代码,
第一步初始化GPIO,初始化时钟
//首先开启时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//初始化时钟 //ADC分频 RCC_ADCCLKConfig(RCC_PCLK2_Div6); //采用6分频 //初始化模拟输入的引脚 GPIO_InitTypeDef LED_InitStruct; //定义GPIO结构体 LED_InitStruct.GPIO_Pin = GPIO_Pin_0; //确定引脚 LED_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //确定速度 LED_InitStruct.GPIO_Mode = GPIO_Mode_AIN; //确定为模拟输入 GPIO_Init(GPIOA,&LED_InitStruct);
第二步选择规则组
//选择规则组的输入通道 //首先选择为ADC1,然后选择ADC通道0,接着选择规则组里面的次序号为1,最后这个就是采样时间的参数 //ADC_SampleTime_55Cycles5决定了ADC在开始转换之前对输入信号的采样时间长度 //较长的采样时间可以提高ADC的精度,特别是在处理快速变化的信号或存在噪声的情况下。 //然而,较长的采样时间也会增加每次转换的总时间,从而影响ADC的吞吐量。 ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
给大家翻译了一份ADC结构体的中文注释版
typedef struct { uint32_t ADC_Mode; /*!< 配置ADC以独立模式或双模式运行。 此参数可以是 @ref ADC_mode 中定义的值 */ FunctionalState ADC_ScanConvMode; /*!< 指定转换是在扫描模式(多通道)还是单通道模式下执行。 此参数可以设置为 ENABLE 或 DISABLE */ FunctionalState ADC_ContinuousConvMode; /*!< 指定转换是在连续模式还是单次模式下执行。 此参数可以设置为 ENABLE 或 DISABLE */ uint32_t ADC_ExternalTrigConv; /*!< 定义用于启动常规通道组模拟到数字转换的外部触发源。 此参数可以是 @ref ADC_external_trigger_sources_for_regular_channels_conversion 中定义的值 */ uint32_t ADC_DataAlign; /*!< 指定ADC数据是对齐到左侧还是右侧。 此参数可以是 @ref ADC_data_align 中定义的值 */ uint8_t ADC_NbrOfChannel; /*!< 指定使用常规通道组进行转换的ADC通道数量。 此参数必须在1到16之间。 */ }ADC_InitTypeDef;
第三部开始配置ADC结构体
ADC_InitTypeDef ADC_Init_Strct; ADC_Init_Strct.ADC_ContinuousConvMode = DISABLE; //选择单次模式 ADC_Init_Strct.ADC_DataAlign = ADC_DataAlign_Right; //选择右对齐模式 ADC_Init_Strct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //选择软件触发 ADC_Init_Strct.ADC_Mode = ADC_Mode_Independent; //选择独立模式,这个就是ADC1和ADC2各自转换 ADC_Init_Strct.ADC_NbrOfChannel = 1; ADC_Init_Strct.ADC_ScanConvMode = DISABLE; //选择非扫描模式 ADC_Init(ADC1,&ADC_Init_Strct); ADC_Cmd(ADC1,ENABLE);//使能ADC
最后一步进行校准
//第一步调用复用校准函数 ADC_ResetCalibration(ADC1); //第二步等待校准完成 while(ADC_GetResetCalibrationStatus(ADC1) == 1); //第三步开始校准 ADC_StartCalibration(ADC1); //第四步等待校准完成 while(ADC_GetCalibrationStatus(ADC1) == 1);
我们现在完成了按照上述流程图配置ADC的初始化了,接下来还得写一个获取ADC数据函数,按照下面这个流程
uint16_t ADC_GetVaul() { ADC_SoftwareStartConvCmd(ADC1,ENABLE); //软件启动ADC开始转换 while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == 0); //等待转换完成,EOC标志位置1 return ADC_GetConversionValue(ADC1); //返回获取的值 }
接下来就开始书写main函数了
float Valu = 0; int main(void) { Usart_Init(); ADC_Init_Star(); Valu = (float)ADC_GetVaul() / 4095 *3.3; // 假设这里返回的是一个 uint16_t 类型的值 printf("%f\n ", Valu); // 在格式字符串中指明要打印一个无符号短整型 // 添加 \n 以换行,使输出更加清晰 }
在按照这样接线,就可以在屏幕上成功显示电压了
image-20240916174157881