16.1STM32_ADC
STM32_ADC
数字信号分为高/低电平两种状态
模拟信号就是任意的电压值
STM32芯片内就是一整套的数字逻辑电路,来实现我们的程序执行,以及各种各样的外设功能,
ADC(模拟-数字转换技术)的功能就是将模拟信号转化为数字信号来进行处理的
转化采用的方法是:逐次逼近法 即二分比较确定电压值
STM32F1采用12位的ADC ,12位指的是最总结果是以12个二进制位存储
从0000 0000 0000 - – – 1111 1111 1111
即 0 4095
对应 0V 3.3V(测量最大值)
线性对应如 2048对应 1.65V
这12位 称为ADC的分辨率
逐次逼近法
STM32首先通过GPIO口将待测电压采样到电容上 随后切点与待测电压的连接,将待测电压保持到电容上 ,这样可以防止待测信号的波动影响测量结果,随后ADC先将参考电压3.3V的一半,也就是2048所代表的电压1.65V 与待测电压进行比较,待测电压小,结果的第十二位上一定为0
随后将2048的一半1088(第十一位为1)与待测电压比较,待测电压更大,因此第十一位一定为1
一直循环后,就确定了电压值的比较结果,随后ADC便将此结果放入到专门用来放ADC转换结果的数据寄存器中,然后我们的程序就可以从此寄存器中取到转换结果
将其 套入公式
在编写程序时只需要考虑一下三部:
- 启动ADC
- 采样 & 转换
- 获取 & 计算
在STM32F103中一共有16个gpio口可以进行ADC的采样工作, 有内部2个,和外部14个 称为通道(内部通道和外部通道)
有两个用于ADC转换的工作 ADC1 与 ADC2 ,每个ADC中有注入组和规则组两种ADC通道组
规则组对来自外部通道的信号进行采样,采样完毕后的结果放到规则通道数据寄存器中
根据想要测量的GPIO口上的信息,开启ADC的相应通道
将ADC的时钟频率控制在14MHz以下,这里我们选择6分频
保存并生成代码
/* USER CODE BEGIN 2 */
int adc_value = 0;
float adc_vol = 0.0;
HAL_ADCEx_Calibration_Start(&hadc1); //校准ADC,在程序开始前先对ADC进行校准
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_ADC_Start(&hadc1); //开启ADC1
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);//等待ADC1测量完成
adc_value = HAL_ADC_GetValue(&hadc1); //获取ADC的测量值
adc_vol = (float)adc_value*(3.3/4095); //计算对应的电压值
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
每次程序都要重新启动,我们可以配置
开启连续转换模式,ADC会持续不断的进行测量,因而只需要触发一次测量,需要时随时取值就好了
/* USER CODE BEGING 2 */
HAL_ADCEx_Calibration_Start(&hadc1); //校准ADC,在程序开始前先对ADC进行校准
HAL_ADC_Start(&hadc1); //开启ADC1
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);//等待ADC1测量完成
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
adc_value = HAL_ADC_GetValue(&hadc1); //获取ADC的测量值
adc_vol = (float)adc_value*(3.3/4095); //计算对应的电压值
sprintf(tbuf1,"ADC值:%d",adc_value);
sprintf(tbuf2,"模拟值:%.2fV",adc_vol);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
上述方法为软件的方式触发ADC转换
但如果要求精确周期性ADC转换,上述方式的周期肯定时不够精确的
ADC可以使用定时器的触发输出TRGO信号或捕获比较事件信号作为ADC的转换启动信号,而TRGO信号可以设置为定时器的更新事件UEV信号,也就是定时器溢出信号,这样每次ADC的采样间隔就是精确的,下面进行定时器触发ADC转换
定时器触发ADC转换
我们使用TIM3的TRGO信号作为ADC1的外部触发信号,TIM3的定时器周期为500ms,ADC1以中断模式启动转换,在ADC转换完成中断里读取结果数据
- 复制上述工程改名,将外部触发源改为TIM3的触发信号
- 勾选ADC的中断向量
/* USER CODE BEGIN 0 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
if (hadc == &hadc1) {
adc_value = HAL_ADC_GetValue(&hadc1);
adc_vol = (float)adc_value*(3.3/4095); //计算对应的电压值
sprintf(tbuf1,"ADC值:%d",adc_value);
sprintf(tbuf2,"模拟值:%.2fV",adc_vol);
OLED_NewFrame();
OLED_PrintString(10, 10, (uint8_t *)tbuf1, &font16x16, OLED_COLOR_NORMAL);
OLED_PrintString(10, 35, (uint8_t *)tbuf2, &font16x16, OLED_COLOR_NORMAL);
HAL_UART_Transmit(&huart1, tbuf2, sizeof(tbuf2), 100);
HAL_UART_Transmit_IT(&huart1, (uint8_t *)tbuf1, sizeof(tbuf1));
OLED_ShowFrame();
}
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1); //校准ADC,在程序开始前先对ADC进行校准
HAL_ADC_Start_IT(&hadc1); //开启ADC1
HAL_TIM_Base_Start(&htim3);
/* USER CODE END 2 */
这样每当TIM3触发了自动重装载更新后输出一个TRGO触发信号(时间为:预分频器*自动重装载值/时钟频率(秒))(并非自动重装载中断)就会触发一次ADC测量,ADC测量完成后会触发ADC测量完成中断,重写HAL_ADC_ConvCpltCallback()回调函数并显示在屏幕上即可。
- TRGO为一个短时正脉冲信号,上升沿即可
使用DMA方式实现多通道ADC扫描模式
对于一个规则组中的多个通道的转换,不会再每一个通道完成就输出一个EOC信号,而是全部完成后才会输出EOC信号,此时前一个通道的结果会被下一个通道转换结果所覆盖,那么就要采用DMA通道对每个通道转换结果后马上进行搬运,数据就能通过DMA传输自动保存到缓冲区就能得到多通道的转换值,
在一个规则组转换结束后,在对数据进行处理,或者在采集多次数据后在处理
实验要求:为规则组设置三个输入通道,使用扫描转换模式,并通过DMA方式输出ADC转换结果数据,
选择3个通道
将3个通道放入规则组
确保扫描模式为开启
/* USER CODE BEGIN PV */
uint8_t tbuf1[40];
uint8_t tbuf2[40];
uint8_t tbuf3[40];
#define BATCH_DATA_LEN 3
uint32_t dmaDataBuffer[BATCH_DATA_LEN];
int adc_value = 0;
float adc_vol = 0.0;
/* USER CODE END PV */
/* USER CODE BEGIN 0 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
if (hadc == &hadc1) {
for (int i = 0 ; i < 3; ++ i) {
adc_value = dmaDataBuffer[i];
adc_vol = (float)adc_value*(3.3/4095);
if (i %3 ==0) {
sprintf(tbuf1,"PA2:%.2f V",adc_vol);
}
else if (i %3 ==1) {
sprintf(tbuf2,"PA6:%.2f V",adc_vol);
}
else if (i %3 ==2) {
sprintf(tbuf3,"PA7:%.2f V",adc_vol);
}
}
OLED_NewFrame();
OLED_PrintString(10, 5, (uint8_t *)tbuf1, &font16x16, OLED_COLOR_NORMAL);
OLED_PrintString(10, 25, (uint8_t *)tbuf2, &font16x16, OLED_COLOR_NORMAL);
OLED_PrintString(10, 45, (uint8_t *)tbuf3, &font16x16, OLED_COLOR_NORMAL);
//HAL_UART_Transmit(&huart1, tbuf2, sizeof(tbuf2), 100);
//HAL_UART_Transmit_IT(&huart1, (uint8_t *)tbuf1, sizeof(tbuf1));
OLED_ShowFrame();
}
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1); //校准ADC,在程序开始前先对ADC进行校准
HAL_ADC_Start_DMA(&hadc1,dmaDataBuffer,BATCH_DATA_LEN); //开启ADC1
HAL_TIM_Base_Start(&htim3);
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);//等待A DC1测量完成
/* USER CODE END 2 */
首先定义一个保存数据的数组uint32_t dmaDataBuffer[通道个数]
只需要简单的启动以DMA的形式开启ADC,DMA通道会自动将ADC测得的结果传输到数组中,在HAL_ADC_ConvCpltCallback回调函数中将对应通道的数据取出并显示即可。
##如何使用TIM3的定时器更新中断作为ADC的开启采集信号,并实现每隔一秒采集一次adc的值。
关闭ADC Continuous Conversion Mode 才能定时器每更新一次,adc采集一次。
记得打开adc的中断向量
TIM3的中断不需要打开
开启外设,重写HAL_ADC_ConvCpltCallback() adc采集完成中断。
HAL_ADC_Start_IT(&hadc1);
HAL_TIM_Base_Start(&htim3);
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
if (hadc == &hadc1) {
//相关代码逻辑。
}
}
同理,如果开启了Continuous Conversion Mode ,则在TIM3发送一个更新信号之后,adc开始工作,每采集完成就会触发一次采集完成中断,事件间隔为采样时间(极短!)
通过DMA的两个中断实现伪双缓冲区的ADC读取
DMA传输具有双缓冲区功能,也就是交替使用两个缓冲区。DMA的HAL驱动程序中,也有使用双缓冲区的相关函数,但是初始化配置和使用比较麻烦 ,其实可以利用DMA流的传输半完成事件中断和传输完成事件中断实现类似于双缓冲区的功能,称为伪双缓冲区方法
只需要将dmaDataBuffer的BATCH_DATA_LEN设置的长一点如60
双ADC同步转换
我们将使用ADC1和ADC2同步采集两个通道的信号,双重ADC同步采集时,不能采集同一个通道,多种ADC模式只能使用DMA方式传输数据。
三重ADC扫描连续采样+DMA双缓冲区存储
dmaDataBuffer的BATCH_DATA_LEN设置的长一点如60
双ADC同步转换
我们将使用ADC1和ADC2同步采集两个通道的信号,双重ADC同步采集时,不能采集同一个通道,多种ADC模式只能使用DMA方式传输数据。
https://bbs.21ic.com/icview-3343492-1-1.html