第16章 ADC介绍及应用
第十六章 ADC介绍及应用
1. ADC简介
ADC 即模拟数字转换器,英文详称 Analog-to-digital converter,可以将外部的模拟信号转换为数字信号。
STM32F4xx 系列芯片拥有 3 个 ADC,这些 ADC 可以独立使用,其中 ADC1 和 ADC2 还可以组成双重模式(提高采样率)。 STM32 的 ADC 是 12 位逐次逼近型的模拟数字转换器。它有 19 个通道,可测量 16 个外部和 2 个内部信号源和 Vbat 通道的信号 ADC 中的各个通道的A/D 转换可以单次、连续、扫描或间断模式执行。 ADC 的结果可以以左对齐或者右对齐存储在16 位数据寄存器中。 ADC 具有模拟看门狗的特性,允许应用检测输入电压是否超过了用户自定义的阈值上限或下限。
STM32F407 的 ADC 主要特性我们可以总结为以下几条:
-
可配置 12 位、 10 位、 8 位或 6 位分辨率;
-
转换结束、注入转换结束和发生模拟看门狗事件时产生中断
-
单次和连续转换模式
-
自校准
-
带内嵌数据一致性的数据对齐
-
采样间隔可以按通道分别编程
-
规则转换和注入转换均有外部触发选项
-
间断模式
-
双重模式(带 2 个或以上 ADC 的器件)
-
ADC 转换时间: 最大转换速率为 2.4MHz,转换时间为 0.41us
-
ADC 供电要求: 2.4V 到 3.6V
-
ADC 输入范围: VREF–≤VIN≤VREF+
-
规则通道转换期间有 DMA 请求产生
下面来介绍 ADC 的框图:

1.1 输入电压
在前面 ADC 的主要特性也对输入电压有所提及, ADC 输入范围 VREF–≤VIN≤VREF+,最终还是由 VREF–、 VREF+、 VDDA和 VSSA决定的。下面看一下这几个参数的关系

从上图可以知道, VDDA 和 VREF+接 VCC3.3,而 VSSA和 VREF-是接地,所以 ADC 的输入范围即 0~3.3V。 R55 默认焊接, R54 默认不焊接。
1.2 输入通道
在确定好了 ADC 输入电压后,如何把外部输入的电压输送到 ADC 转换器中呢,在这里引入了 ADC 的输入通道,在前面也提及到了 ADC1 有 16 个外部通道和 3 个内部通道,而 ADC2和 ADC3 只有有 16 个外部通道。 ADC1 的外部通道是通道 17、通道 18 和通道 19,分别连接到内部温度传感器、内部Vrefint和Vbat。

1.3 转换顺序
当任意 ADCx 多个通道以任意顺序进行一系列转换就诞生了成组转换,这里就有两种成组转换类型:规则组和注入组。规则组就是图上的规则通道,注入组也就是图上的注入通道。为了避免大家对输入通道加上规则通道和注入通道理解有所模糊,后面规则通道以规则组来代称,注入通道以注入组来代称。
规则组允许最多 16 个输入通道进行转换,而注入组允许最多 4 个输入通道进行转换。这里讲解一下规则组和注入组。
规则组(规则通道)
规则组,按字面理解,“规则”就是按照一定的顺序,相当于正常运行的程序,平常用到最多也是规则组。
注入组(注入通道)
注入组,按字面理解,“注入”就是打破原来的状态,相当于中断。当程序执行的时候,中断是可以打断程序的执行。同这个类似,注入组转换可以打断规则组的转换。假如在规则组转换过程中,注入组启动,那么注入组被转换完成之后,规则组才得以继续转换。

1.4 触发源
在配置好输入通道以及转换顺序后,就可以进行触发转换了。 ADC 的触发转换有两种方法:分别是通过软件或外部事件(也就是硬件)触发转换。
我们先来看看通过写软件触发转换的方法。方法是:通过写 ADC_CR2 寄存器的 ADON 这个位来控制,写 1 就开始转换,写 0 就停止转换,这个控制 ADC 转换的方式非常简单。
另一种就是通过外部事件触发转换的方法,有定时器和输入引脚触发等等。这里区分规则组和注入组。方法是:通过 ADC_CR2 寄存器的 EXTSET[2:0]选择规则组的触发源, JEXTSET[2:0]选择注入组的触发源。通过 ADC_CR2 的 EXTTRIG 和 JEXTTRIG 这两位去激活触发源。
1.5 转换时间
STM32F407 的 ADC 总转换时间的计算公式如下:
TCONV = 采样时间 + 12 个周期
采样时间可通过 ADC_SMPR1 和 ADC_SMPR2 寄存器中的 SMP[2:0]位编程, ADC_SMPR2控制的是通道 0~9, ADC_SMPR1 控制的是通道 10~18。所有通道都可以通过编程来控制使用不同的采样时间,可选采样时间值如下:
-
SMP = 000: 3 个 ADC 时钟周期
-
SMP = 001: 15 个 ADC 时钟周期
-
SMP = 010: 28 个 ADC 时钟周期
-
SMP = 011: 56 个 ADC 时钟周期
-
SMP = 100: 84 个 ADC 时钟周期
-
SMP = 101: 112 个 ADC 时钟周期
-
SMP = 110: 144 个 ADC 时钟周期
-
SMP = 111: 480 个 ADC 时钟周期
12 个周期是 ADC 输入时钟 ADC_CLK 决定的。 ADC_CLK 是由 APB2 经过分频产生,分频系数是由 RCC_CFGR 寄存器中的 PPRE2[2:0]进行设置,有 2/4/6/8/16 分频选项。
采样时间最小是 3 个时钟周期,这个采样时间下,我们可以得到最快的采样速度。 举个例子,我们采用最高的采样速率,使用采样时间为 3 个 ADC 时钟周期,那么得到:
TCONV = 3 个 ADC 时钟周期 + 12 个 ADC 时钟周期 = 15 个 ADC 时钟周期
一般 APB2 的时钟是 84MHz,经过 ADC 分频器的 4 分频后, ADC 时钟频率就为 21MHz。通过换算可得到:
TCONV = 15 个 ADC 时钟周期 = ( 1/21000000) ∗ 15 s = 0.71us
1.6 数据寄存器
ADC 转换完成后的数据输出寄存器。根据转换组的不同,规则组的完成转换的数据输出到ADC_DR 寄存器,注入组的完成转换的数据输出到 ADC_JDRx 寄存器。假如是使用双重模式,规则组的数据也是存放在 ADC_DR 寄存器。
1.7 中断
规则和注入组转换结束时能产生中断,当模拟看门狗状态位被设置时也能产生中断。它们在 ADC_SR 中都有独立的中断使能位,后面讲解 ADC_SR 寄存器时再进行展开。这里讲解一下,模拟看门狗中断以及 DMA 请求。
模拟看门狗中断
模拟看门狗中断发生条件:首先通过ADC_LTR和ADC_HTR寄存器设置低阈值和高阈值,然后开启了模拟看门狗中断后,当被 ADC 转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断。例如我们设置高阈值是 3.0V,那么模拟电压超过 3.0V 的时候,就会产生模拟看门狗中断,低阈值的情况类似。
DMA 请求
规则组和注入组的转换结束后,除了产生中断外,还可以产生 DMA 请求,把转换好的数据存储在内存里面,防止读取不及时数据被覆盖。
2. ADC使用示例
2.1 单通道ADC采集
2.1.1 ADC初始化
#include "adc.h"
#include "delay.h"
ADC_HandleTypeDef adc_handle;
void adc_init(void)
{
adc_handle.Instance = ADC1;
adc_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; // 4分频
adc_handle.Init.Resolution = ADC_RESOLUTION_12B; // 12位精度
adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
adc_handle.Init.ScanConvMode = DISABLE;
adc_handle.Init.ContinuousConvMode = DISABLE;
adc_handle.Init.NbrOfConversion = 1; // 单次转换
adc_handle.Init.DiscontinuousConvMode = DISABLE;
adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
adc_handle.Init.DMAContinuousRequests = DISABLE;
HAL_ADC_Init(&adc_handle);
}
2.1.2 ADC通道配置
// ADC通道配置
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
if(hadc->Instance==ADC1)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_ADC1_CLK_ENABLE();
/* PA5 */
GPIO_Initure.Pin = GPIO_PIN_5;
GPIO_Initure.Mode = GPIO_MODE_ANALOG; // 输入模式
GPIO_Initure.Pull = GPIO_NOPULL; // 上拉
HAL_GPIO_Init(GPIOA, &GPIO_Initure);
}
}
2.1.3 设置ADC通道采样时间
/**
* @brief 设置ADC通道采样时间
* @param adcx : adc句柄指针,ADC_HandleTypeDef
* @param ch : 通道号, ADC_CHANNEL_0~ADC_CHANNEL_17
* @param stime: 采样时间 0~7, 对应关系为:
* @arg ADC_SAMPLETIME_3CYCLES, 3个ADC时钟周期 ADC_SAMPLETIME_15CYCLES, 15个ADC时钟周期
* @arg ADC_SAMPLETIME_28CYCLES, 28个ADC时钟周期 ADC_SAMPLETIME_56CYCLES, 56个ADC时钟周期
* @arg ADC_SAMPLETIME_84CYCLES, 84个ADC时钟周期 ADC_SAMPLETIME_112CYCLES,112个ADC时钟周期
* @arg ADC_SAMPLETIME_144CYCLES,144个ADC时钟周期 ADC_SAMPLETIME_480CYCLES,480个ADC时钟周期
* @param rank: 多通道采集时需要设置的采集编号,
假设你定义channel1的rank=1,channel2的rank=2,
那么对应你在DMA缓存空间的变量数组AdcDMA[0] 就i是channel1的转换结果,AdcDMA[1]就是通道2的转换结果。
单通道DMA设置为 ADC_REGULAR_RANK_1
* @arg 编号1~16:ADC_REGULAR_RANK_1~ADC_REGULAR_RANK_16
* @retval 无
*/
void adc_channel_set(ADC_HandleTypeDef *adc_handle, uint32_t ch, uint32_t rank, uint32_t stime)
{
ADC_ChannelConfTypeDef adc_channel;
adc_channel.Channel = ch; // 设置ADCX对通道ch
adc_channel.Rank = rank; // 设置采样序列
adc_channel.SamplingTime = stime; // 设置采样时间
HAL_ADC_ConfigChannel( adc_handle, &adc_channel);
}
2.1.4 获取采样值
// 获取ADC通道ch的转换值
uint32_t adc_get_result(uint32_t ch)
{
adc_channel_set(&adc_handle, ch, 1, ADC_SAMPLETIME_480CYCLES); // 设置通道,序列和采样时间
HAL_ADC_Start(&adc_handle); // 开启ADC
HAL_ADC_PollForConversion(&adc_handle, 10); // 轮询转换
return (uint16_t)HAL_ADC_GetValue(&adc_handle); // 返回最近一次ADC1规则组的转换结果
}
// 通道ch的times次转换结果平均值
uint32_t adc_get_result_average(uint32_t ch, uint8_t times)
{
uint32_t temp_val = 0;
uint8_t t;
for (t = 0; t < times; t++)
{
temp_val += adc_get_result(ch);
delay_ms(5);
}
return temp_val / times;
}
2.1.5 主函数测试
#include "bsp_init.h"
#include "stdio.h"
#include "adc.h"
int main(void)
{
uint16_t adc_value;
float temp;
bsp_init();
adc_init();
LCD_ShowString(30,50,200,16,16,"STM32F4 ADC Test");
LCD_ShowString(30,110,200,16,16, "ADC1_CH5_VAL:");
LCD_ShowString(30,130,200,16,16, "ADC1_CH5_VOL:0.000V");
while(1)
{
adc_value = adc_get_result_average(ADC_CHANNEL_5, 10); // 10次采样取平均值
LCD_ShowxNum(134,110,adc_value,5,16,0);
temp = (float)adc_value*(3.3/4096); // 计算电压值
adc_value = temp;
LCD_ShowxNum(134,130,adc_value,1,16,0); // 显示电压整数部分
temp -= adc_value;
temp *= 1000; // 计算电压小数部分
LCD_ShowxNum(150,130,temp,3,16,0x80);
LED_TOGGLE(LED0_GPIO_Pin);
delay_ms(100);
}
}
2.2 单通道ADC采集(DMA读取)
2.2.1 ADC初始化
#include "adc.h"
#include "delay.h"
ADC_HandleTypeDef adc_handle;
DMA_HandleTypeDef dma_handle;
uint8_t adc_dma_sta = 0; // 0:未完成,1:完成
void adc_init(void)
{
adc_handle.Instance = ADC1;
adc_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; // 4分频
adc_handle.Init.Resolution = ADC_RESOLUTION_12B; // 12位精度
adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
adc_handle.Init.ScanConvMode = DISABLE; // 单通道扫描模式禁用
adc_handle.Init.ContinuousConvMode = DISABLE; // 连续转换模式禁用 (在DMA模式下会通过CR2_CONT使能)
adc_handle.Init.NbrOfConversion = 1; // 单次转换
adc_handle.Init.DiscontinuousConvMode = DISABLE;
adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
adc_handle.Init.DMAContinuousRequests = ENABLE; // 使能DMA连续请求 (ADC生成DMA请求后,DMA会持续请求直到传输完成)
if (HAL_ADC_Init(&adc_handle) != HAL_OK)
{
// 初始化失败处理,例如错误打印或死循环
while(1);
}
}
2.2.2 ADC通道初始化
// ADC通道配置
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
if(hadc->Instance == ADC1)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_ADC1_CLK_ENABLE();
/* PA5 作为ADC输入 */
GPIO_Initure.Pin = GPIO_PIN_5;
GPIO_Initure.Mode = GPIO_MODE_ANALOG; // 模拟输入模式
GPIO_Initure.Pull = GPIO_NOPULL; // 无上拉/下拉
HAL_GPIO_Init(GPIOA, &GPIO_Initure);
// 关联ADC和DMA中断
HAL_NVIC_SetPriority(DMA2_Stream4_IRQn, 2, 2); // DMA中断优先级
HAL_NVIC_EnableIRQ(DMA2_Stream4_IRQn);
}
}
2.2.3 设置ADC通道采样时间
/**
* @brief 设置ADC通道采样时间
* @param adc_handle : adc句柄指针,ADC_HandleTypeDef
* @param ch : 通道号, ADC_CHANNEL_0~ADC_CHANNEL_17
* @param rank: 多通道采集时需要设置的采集编号, 单通道DMA设置为 ADC_REGULAR_RANK_1
* @param stime: 采样时间
* @retval 无
*/
void adc_channel_set(ADC_HandleTypeDef *adc_handle, uint32_t ch, uint32_t rank, uint32_t stime)
{
ADC_ChannelConfTypeDef adc_channel;
adc_channel.Channel = ch; // 设置ADCX对通道ch
adc_channel.Rank = rank; // 设置采样序列
adc_channel.SamplingTime = stime; // 设置采样时间
if (HAL_ADC_ConfigChannel(adc_handle, &adc_channel) != HAL_OK)
{
// 通道配置失败处理
while(1);
}
}
2.2.4 DMA配置
// DMA配置
void adc_dma_init(uint32_t address)
{
// 确保ADC先初始化,因为DMA需要关联到ADC句柄
adc_init();
// 确定DMA时钟使能
__HAL_RCC_DMA2_CLK_ENABLE(); // 对于DMA2_Stream4,直接使能DMA2时钟即可
dma_handle.Instance = DMA2_Stream4;
dma_handle.Init.Channel = DMA_CHANNEL_0; // 对应ADC1的DMA请求,通常是通道0
dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; // 外设到存储器
dma_handle.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址不递增
dma_handle.Init.MemInc = DMA_MINC_ENABLE; // 存储器地址递增
dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; // 外设数据宽度半字 (16位)
dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; // 存储器数据宽度半字 (16位)
dma_handle.Init.Mode = DMA_CIRCULAR;
dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM; // DMA优先级中等
dma_handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; // 禁用FIFO
dma_handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; // FIFO阈值
dma_handle.Init.MemBurst = DMA_MBURST_SINGLE; // 存储器突发模式单次传输
dma_handle.Init.PeriphBurst = DMA_PBURST_SINGLE; // 外设突发模式单次传输
if (HAL_DMA_Init(&dma_handle) != HAL_OK)
{
// DMA初始化失败处理
while(1);
}
// 关联DMA句柄到ADC句柄
__HAL_LINKDMA(&adc_handle, DMA_Handle, dma_handle);
// 设置ADC通道用于DMA传输 (这里我们只用一个通道,PA5/ADC_CHANNEL_5)
adc_channel_set(&adc_handle, ADC_CHANNEL_5, 1, ADC_SAMPLETIME_56CYCLES); // 设置通道,序列和采样时间
// 开启ADC连续转换模式(对于DMA通常需要,以便ADC自动开始下一次转换)
SET_BIT(adc_handle.Instance->CR2, ADC_CR2_CONT); // 使能连续转换
// 启动DMA ADC转换
// 第三个参数是数据传输的次数。在DMA_CIRCULAR模式下,DMA会自动循环。
if (HAL_ADC_Start_DMA(&adc_handle, (uint32_t*)address, ADC_DMA_BUFFSIZE) != HAL_OK)
{
// 启动DMA转换失败处理
while(1);
}
// 开启DMA传输完成中断 (在DMA_CIRCULAR模式下,每次缓冲区填充满时会触发)
__HAL_DMA_ENABLE_IT(&dma_handle, DMA_IT_TC);
}
// 在DMA_CIRCULAR模式下,此函数通常只用于首次启动DMA,或从停止状态重新启动。
// 一旦DMA以CIRCULAR模式启动,它会自行循环,无需重复调用。
void adc_dma_enable(uint16_t times)
{
// 检查DMA是否已经处于活跃状态,如果已经活跃,则不执行任何操作。
// 如果DMA处于HAL_DMA_STATE_READY或HAL_DMA_STATE_BUSY_TC,则不应再次启动。
// 这里简单地禁用和重新使能,以确保状态被重置。
// 在DMA_CIRCULAR模式下,HAL_ADC_Start_DMA 已经启动了,所以这个函数可能变得多余。
// 如果您想使用它来停止和重新启动,可以保留逻辑。
__HAL_ADC_DISABLE(&adc_handle); // 禁用ADC
__HAL_DMA_DISABLE(&dma_handle); // 禁用DMA
// 在DMA_CIRCULAR模式下,NDTR会被HAL_ADC_Start_DMA设置,并自动重置。
// 重新设置NDTR在这里可能没有必要,甚至可能导致问题,因为HAL_ADC_Start_DMA会处理它。
// dma_handle.Instance->NDTR = times; // 考虑注释掉或删除此行
__HAL_DMA_ENABLE(&dma_handle); // 重新使能DMA
__HAL_ADC_ENABLE(&adc_handle); // 重新使能ADC
// 启动ADC规则组转换(软件触发),这将启动DMA传输
SET_BIT(adc_handle.Instance->CR2, ADC_CR2_SWSTART); // 启动规则转换通道
}
// DMA中断函数
void DMA2_Stream4_IRQHandler(void)
{
// HAL库中断处理函数会自动处理DMA中断标志和调用回调函数
HAL_DMA_IRQHandler(&dma_handle);
}
// DMA传输完成回调函数
// 在DMA_CIRCULAR模式下,每次整个缓冲区被填充完毕后,此回调函数都会被调用。
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
if (hadc->Instance == ADC1)
{
adc_dma_sta = 1; // 完成标志位置1
// 在DMA_CIRCULAR模式下,DMA会自动重新开始传输,无需在此处手动重新启动。
}
}
2.2.5 主函数测试
#include "bsp_init.h" // 假设包含了LCD初始化和显示函数,以及LED控制
#include "stdio.h"
#include "adc.h"
uint16_t adc_dma_buf[ADC_DMA_BUFFSIZE]; // DMA缓冲区
extern uint8_t adc_dma_sta; // DMA传输完成标志
int main(void)
{
uint16_t i;
uint32_t sum;
float voltage_val;
bsp_init();
// 初始化ADC DMA采集。
// 在此函数内部,DMA已经被设置为CIRCULAR模式并启动。
adc_dma_init((uint32_t)adc_dma_buf);
LCD_ShowString(30, 50, 200, 16, 16, "STM32F4 ADC DMA Test");
LCD_ShowString(30, 110, 200, 16, 16, "ADC1_CH5_VAL:");
LCD_ShowString(30, 130, 200, 16, 16, "ADC1_CH5_VOL:0.000V");
// 由于 adc_dma_init 已经通过 HAL_ADC_Start_DMA 启动了DMA和ADC
// 并且DMA是CIRCULAR模式,这里不需要再调用 adc_dma_enable。
// 如果您在 adc_dma_init 中没有调用 HAL_ADC_Start_DMA,那么在这里调用一次是必需的。
// 目前的 adc_dma_init 已经包含了 HAL_ADC_Start_DMA,所以这行可以注释掉或移除。
// adc_dma_enable(ADC_DMA_BUFFSIZE); // 此行在此方案中不再需要
while(1)
{
// 检查DMA传输是否完成一个缓冲区的数据
if(adc_dma_sta)
{
sum = 0;
// 遍历DMA缓冲区,计算平均值
for(i = 0; i < ADC_DMA_BUFFSIZE; i++)
{
sum += adc_dma_buf[i];
}
// 计算平均ADC值
uint16_t avg_adc_value = sum / ADC_DMA_BUFFSIZE;
LCD_ShowxNum(134, 110, avg_adc_value, 4, 16, 0); // 显示平均ADC值
// 计算电压值 (3.3V 参考电压,12位ADC,最大值为4095)
voltage_val = (float)avg_adc_value * (3.3f / 4095.0f); // 12位ADC最大值为4095
// 显示电压整数部分
uint16_t voltage_int = (uint16_t)voltage_val;
LCD_ShowxNum(134, 130, voltage_int, 1, 16, 0);
// 显示电压小数部分 (取小数点后三位)
uint16_t voltage_frac = (uint16_t)((voltage_val - voltage_int) * 1000);
LCD_ShowxNum(150, 130, voltage_frac, 3, 16, 0x80);
adc_dma_sta = 0; // 清除完成标志,等待下一次DMA传输完成回调
}
LED_TOGGLE(LED0_GPIO_Pin);
delay_ms(100);
}
}
2.3 多通道ADC采集(DMA读取)
2.3.1 ADC相关参数宏定义
#ifndef __ADC_H
#define __ADC_H
#include "sys.h"
// 定义ADC相关的GPIO和ADC外设
#define SINGLE_ADC_GPIO_PORT GPIOA
#define SINGLE_ADC_GPIO_PIN GPIO_PIN_5
#define SINGLE_ADC_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define ADC_INSTANCE ADC1
#define ADC_CLK_ENABLE() __HAL_RCC_ADC1_CLK_ENABLE()
// 定义ADC DMA相关的宏
#define ADC_DMA_STREAM DMA2_Stream4
#define ADC_DMA_CHANNEL DMA_CHANNEL_0
#define ADC_DMA_IRQn DMA2_Stream4_IRQn
// 多通道ADC DMA采集通道数
#define MULTI_ADC_CHANNEL_NUM 6
// 外部声明
extern uint8_t g_adc_dma_transfer_complete_flag;
extern uint16_t g_adc_dma_buffer[]; // 声明外部缓冲区
void adc_multi_channel_dma_init(void);
void adc_multi_channel_dma_enable(uint16_t num_transfers);
void DMA2_Stream4_IRQHandler(void);
#endif /* __ADC_H */
2.3.2 GPIO初始化
#include "delay.h"
#include "adc.h"
#include "led.h"
ADC_HandleTypeDef g_adc_handle;
DMA_HandleTypeDef g_dma_adc_handle;
uint8_t g_adc_dma_transfer_complete_flag = 0;
// 外部声明缓冲区
extern uint16_t g_adc_dma_buffer[];
// 多通道 GPIO 初始化
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
if (hadc->Instance == ADC_INSTANCE)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStructure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 |
GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
GPIO_InitStructure.Mode = GPIO_MODE_ANALOG;
GPIO_InitStructure.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
}
}
2.3.3 设置ADC通道采样时间
// 设置ADC通道采样时间
void adc_set_channel_config(ADC_HandleTypeDef *hadc, uint32_t channel, uint32_t rank, uint32_t sample_time)
{
ADC_ChannelConfTypeDef adc_channel_config;
adc_channel_config.Channel = channel; // 通道
adc_channel_config.Rank = rank; // 通道序号
adc_channel_config.SamplingTime = sample_time; // 采样时间
HAL_ADC_ConfigChannel(hadc, &adc_channel_config);
}
2.3.4 ADC多通道DMA初始化
// ADC多通道DMA初始化函数
void adc_multi_channel_dma_init(void)
{
// 使能ADC时钟和DMA时钟
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_DMA2_CLK_ENABLE();
// 初始化ADC
g_adc_handle.Instance = ADC_INSTANCE; // ADC1
g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; // 4分频
g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B; // 12位精度
g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; // 数据右对齐
g_adc_handle.Init.ScanConvMode = ENABLE; // 扫描模式
g_adc_handle.Init.ContinuousConvMode = ENABLE; // 连续转换模式
g_adc_handle.Init.DiscontinuousConvMode = DISABLE; // 不连续转换模式
g_adc_handle.Init.NbrOfConversion = MULTI_ADC_CHANNEL_NUM; // 转换通道数-6
g_adc_handle.Init.NbrOfDiscConversion = 0; // 不连续转换通道数
g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; // 软件触发
g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; // 无外部触发信号
g_adc_handle.Init.DMAContinuousRequests = ENABLE; // DMA连续请求模式
HAL_ADC_Init(&g_adc_handle);
// 初始化DMA
g_dma_adc_handle.Instance = ADC_DMA_STREAM; // DMA2_Stream4
g_dma_adc_handle.Init.Channel = ADC_DMA_CHANNEL; // DMA_CHANNEL_0
g_dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; // 从外设到内存
g_dma_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址不增
g_dma_adc_handle.Init.MemInc = DMA_MINC_ENABLE; // 内存地址增
g_dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
g_dma_adc_handle.Init.Mode = DMA_CIRCULAR; // 循环模式
g_dma_adc_handle.Init.Priority = DMA_PRIORITY_HIGH; // 高优先级
g_dma_adc_handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; // 禁用FIFO
HAL_DMA_Init(&g_dma_adc_handle);
// 链接DMA到ADC
__HAL_LINKDMA(&g_adc_handle, DMA_Handle, g_dma_adc_handle);
// 设置多通道采样规则序列
adc_set_channel_config(&g_adc_handle, ADC_CHANNEL_0, 1, ADC_SAMPLETIME_480CYCLES);
adc_set_channel_config(&g_adc_handle, ADC_CHANNEL_1, 2, ADC_SAMPLETIME_480CYCLES);
adc_set_channel_config(&g_adc_handle, ADC_CHANNEL_2, 3, ADC_SAMPLETIME_480CYCLES);
adc_set_channel_config(&g_adc_handle, ADC_CHANNEL_3, 4, ADC_SAMPLETIME_480CYCLES);
adc_set_channel_config(&g_adc_handle, ADC_CHANNEL_4, 5, ADC_SAMPLETIME_480CYCLES);
adc_set_channel_config(&g_adc_handle, ADC_CHANNEL_5, 6, ADC_SAMPLETIME_480CYCLES);
// 配置DMA中断
HAL_NVIC_SetPriority(ADC_DMA_IRQn, 0, 0); // 最高优先级
HAL_NVIC_EnableIRQ(ADC_DMA_IRQn);
}
// 使能ADC DMA传输
void adc_multi_channel_dma_enable(uint16_t num_transfers)
{
// 启动ADC DMA传输
HAL_ADC_Start_DMA(&g_adc_handle, (uint32_t*)g_adc_dma_buffer, num_transfers);
// 软件触发启动转换
SET_BIT(g_adc_handle.Instance->CR2, ADC_CR2_SWSTART);
}
2.3.5 中断函数
// ADD DMA中断回调函数
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
if (hadc->Instance == ADC_INSTANCE) // 确保是ADC1
{
static uint32_t callback_count = 0;
callback_count++;
g_adc_dma_transfer_complete_flag = 1; // 标记DMA传输完成
if(callback_count == 100)
{
LED_TOGGLE(LED1_GPIO_Pin); // 测试用
callback_count = 0;
}
}
}
// DMA中断函数
void DMA2_Stream4_IRQHandler(void)
{
HAL_DMA_IRQHandler(&g_dma_adc_handle);
}
2.3.6 主函数测试
#include "bsp_init.h"
#include "adc.h"
#include <stdio.h>
// 定义ADC DMA缓冲区
#define ADC_DMA_SAMPLES_PER_CHANNEL 50 // 单通道时DMA缓冲区大小
#define ADC_DMA_BUFFER_SIZE (ADC_DMA_SAMPLES_PER_CHANNEL * MULTI_ADC_CHANNEL_NUM) // 多通道时DMA缓冲区大小:单通道大小 * 通道数
uint16_t g_adc_dma_buffer[ADC_DMA_BUFFER_SIZE]; // DMA缓冲区
// LCD显示文本
const char *lcd_title[] = {
"STM32F407",
"ADC 6CH DMA TEST",
"There are 6 channels"
};
const char *lcd_channel_info_val[] = {
"ADC1_CH0_VAL:",
"ADC1_CH1_VAL:",
"ADC1_CH2_VAL:",
"ADC1_CH3_VAL:",
"ADC1_CH4_VAL:",
"ADC1_CH5_VAL:",
};
const char *lcd_channel_info_vol[] = {
"ADC1_CH0_VOL:0.000V",
"ADC1_CH1_VOL:0.000V",
"ADC1_CH2_VOL:0.000V",
"ADC1_CH3_VOL:0.000V",
"ADC1_CH4_VOL:0.000V",
"ADC1_CH5_VOL:0.000V",
};
int main(void)
{
uint32_t channel_sum; // 通道数
uint16_t adc_raw_value; // ADC原始值
float voltage_value; // 计算得来电压值
// 启动ADC DMA采集
bsp_init();
adc_multi_channel_dma_init();
adc_multi_channel_dma_enable(ADC_DMA_BUFFER_SIZE);
// 初始化LCD显示
LCD_ShowString(30, 50, 200, 16, 16, (char*)lcd_title[0]);
LCD_ShowString(30, 70, 200, 16, 16, (char*)lcd_title[1]);
LCD_ShowString(30, 90, 200, 16, 16, (char*)lcd_title[2]);
for (uint8_t i = 0; i < MULTI_ADC_CHANNEL_NUM; i++)
{
LCD_ShowString(30, 110 + (i * 30), 200, 12, 12, (char*)lcd_channel_info_val[i]);
LCD_ShowString(30, 122 + (i * 30), 200, 12, 12, (char*)lcd_channel_info_vol[i]);
}
while (1)
{
if(g_adc_dma_transfer_complete_flag) // DMA采集完成
{
// 处理并显示每个通道的数据
for (uint8_t channel_index = 0; channel_index < MULTI_ADC_CHANNEL_NUM; channel_index++)
{
channel_sum = 0;
// 计算每个通道的平均值
for (uint16_t sample_index = 0; sample_index < ADC_DMA_SAMPLES_PER_CHANNEL; sample_index++)
{
channel_sum += g_adc_dma_buffer[sample_index * MULTI_ADC_CHANNEL_NUM + channel_index];
}
adc_raw_value = channel_sum / ADC_DMA_SAMPLES_PER_CHANNEL;
// 显示ADC原始值
LCD_ShowxNum(108, 110 + (channel_index * 30), adc_raw_value, 4, 12, 0);
// 计算并显示电压值
voltage_value = (float)adc_raw_value * (3.3f / 4096.0f);
// 显示电压值
uint16_t integer_part = (uint16_t)voltage_value;
uint16_t decimal_part = (uint16_t)((voltage_value - integer_part) * 1000);
LCD_ShowxNum(108, 122 + (channel_index * 30), integer_part, 1, 12, 0);
LCD_ShowxNum(120, 122 + (channel_index * 30), decimal_part, 3, 12, 0X80);
}
g_adc_dma_transfer_complete_flag = 0; // 开始下一轮
}
LED_TOGGLE(LED0_GPIO_Pin);
HAL_Delay(100);
}
}
2.4 单通道ADC过采样(16位)
2.4.1 主函数
#include "bsp_init.h"
#include "stdio.h"
#include "adc.h"
extern uint8_t adc_dma_sta; // DMA传输完成标志
extern ADC_HandleTypeDef adc_handle;
/* ADC过采样技术, 是利用ADC多次采集的方式, 来提高ADC精度, 采样速度每提高4倍
* 采样精度提高 1bit, 同时, ADC采样速度降低4倍, 如提高4bit精度, 需要256次采集
* 才能得出1次数据, 相当于ADC速度慢了256倍. 理论上只要ADC足够快, 我们可以无限
* 提高ADC精度, 但实际上ADC并不是无限快的, 而且由于ADC性能限制, 并不是位数无限
* 提高结果就越好, 需要根据自己的实际需求和ADC的实际性能来权衡.
*/
#define ADC_OVERSAMPLE_TIMES 256 // 过采样次数
#define ADC_DMA_BUF_SZIE ADC_OVERSAMPLE_TIMES*10 // DMA缓冲区大小
uint16_t ADC_DMA_BUF[ADC_DMA_BUF_SZIE];
int main(void)
{
uint16_t i;
uint32_t adc_value,sum;
float temp;
bsp_init();
// 初始化ADC DMA采集。
// 在此函数内部,DMA已经被设置为CIRCULAR模式并启动。
adc_dma_init((uint32_t)&ADC_DMA_BUF);
LCD_ShowString(30, 50, 200, 16, 16, "STM32F4 ADC DMA Test");
LCD_ShowString(30, 110, 200, 16, 16, "ADC1_CH5_VAL:");
LCD_ShowString(30, 130, 200, 16, 16, "ADC1_CH5_VOL:0.000V");
// 由于 adc_dma_init 已经通过 HAL_ADC_Start_DMA 启动了DMA和ADC
// 并且DMA是CIRCULAR模式,这里不需要再调用 adc_dma_enable。
// 如果您在 adc_dma_init 中没有调用 HAL_ADC_Start_DMA,那么在这里调用一次是必需的。
// 目前的 adc_dma_init 已经包含了 HAL_ADC_Start_DMA,所以这行可以注释掉或移除。
adc_dma_enable(ADC_DMA_BUF_SZIE); // 此行在此方案中不再需要
while(1)
{
if(adc_dma_sta)
{
sum=0;
for(i=0;i<ADC_DMA_BUF_SZIE;i++)
{
sum += ADC_DMA_BUF[i];
}
adc_value = sum/(ADC_DMA_BUF_SZIE/ADC_OVERSAMPLE_TIMES); // 计算平均值
adc_value >>= 4; // 除以2^4倍, 得到12+4位 ADC精度值, 注意: 提高 N bit精度, 需要 >> N
LCD_ShowxNum(134,110,adc_value,5,16,0);
temp = (float)adc_value*(3.3/65536);
adc_value = temp;
LCD_ShowxNum(134,130,adc_value,1,16,0);
temp -= adc_value;
temp *= 1000;
LCD_ShowxNum(150,130,temp,3,16,0x80);
adc_dma_sta = 0;
adc_dma_enable(ADC_DMA_BUF_SZIE);
}
LED_TOGGLE(LED0_GPIO_Pin);
delay_ms(100);
}
}
3. ADC常见函数(HAL库)
3.1 ADC 初始化与配置
3.1.1 HAL_ADC_Init()
函数原型:
HAL_StatusTypeDef HAL_ADC_Init(ADC_HandleTypeDef *hadc)
参数:
hadc: ADC 句柄指针
配置结构体:
typedef struct {
uint32_t ClockPrescaler; // 时钟分频:
// ADC_CLOCK_SYNC_PCLK_DIV2/4/6/8
uint32_t Resolution; // 分辨率:
// ADC_RESOLUTION_12B/10B/8B/6B
uint32_t DataAlign; // 数据对齐:
// ADC_DATAALIGN_RIGHT/LEFT
uint32_t ScanConvMode; // 扫描模式:
// ENABLE/DISABLE
uint32_t EOCSelection; // EOC 标志选择:
// ADC_EOC_SINGLE_CONV
// ADC_EOC_SEQ_CONV
uint32_t ContinuousConvMode; // 连续转换模式:
// ENABLE/DISABLE
uint32_t DMAContinuousRequests; // DMA 连续请求:
// ENABLE/DISABLE
uint32_t NbrOfConversion; // 转换序列长度 (1-16)
uint32_t DiscontinuousConvMode; // 间断模式: ENABLE/DISABLE
uint32_t NbrOfDiscConversion; // 间断序列长度 (1-8)
uint32_t ExternalTrigConv; // 外部触发源:
// ADC_SOFTWARE_START
// ADC_EXTERNALTRIGCONV_T1_CC1-3
// ADC_EXTERNALTRIGCONV_T2_CC2-4
// ADC_EXTERNALTRIGCONV_T3_CC1-4
// ADC_EXTERNALTRIGCONV_T4_CC4
// ADC_EXTERNALTRIGCONV_T8_CC1-4
// ADC_EXTERNALTRIGCONV_EXT_IT11
uint32_t ExternalTrigConvEdge; // 外部触发边沿:
// ADC_EXTERNALTRIGCONVEDGE_NONE
// ADC_EXTERNALTRIGCONVEDGE_RISING
// ADC_EXTERNALTRIGCONVEDGE_FALLING
// ADC_EXTERNALTRIGCONVEDGE_RISINGFALLING
} ADC_InitTypeDef;
功能: 初始化 ADC 基础参数
示例配置:
ADC_HandleTypeDef hadc1;
void ADC_Init(void) {
__HAL_RCC_ADC1_CLK_ENABLE();
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.NbrOfConversion = 4;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
if (HAL_ADC_Init(&hadc1) != HAL_OK) {
Error_Handler();
}
}
3.2 ADC 通道配置
3.2.1 HAL_ADC_ConfigChannel()
函数原型:
HAL_StatusTypeDef HAL_ADC_ConfigChannel(
ADC_HandleTypeDef *hadc,
ADC_ChannelConfTypeDef *sConfig)
通道配置结构体:
typedef struct {
uint32_t Channel; // 通道号: ADC_CHANNEL_0 到 19
uint32_t Rank; // 转换序列中的位置 (1-16)
uint32_t SamplingTime; // 采样时间:
// ADC_SAMPLETIME_3CYCLES
// ADC_SAMPLETIME_15CYCLES
// ADC_SAMPLETIME_28CYCLES
// ADC_SAMPLETIME_56CYCLES
// ADC_SAMPLETIME_84CYCLES
// ADC_SAMPLETIME_112CYCLES
// ADC_SAMPLETIME_144CYCLES
// ADC_SAMPLETIME_480CYCLES
uint32_t Offset; // 偏移值 (用于偏移校准)
} ADC_ChannelConfTypeDef;
示例配置 (多通道扫描):
ADC_ChannelConfTypeDef sConfig = {0};
// 通道0配置 (PA0)
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_56CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
// 通道1配置 (PA1)
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = 2;
sConfig.SamplingTime = ADC_SAMPLETIME_56CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
// 通道2配置 (PA2)
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = 3;
sConfig.SamplingTime = ADC_SAMPLETIME_56CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
3.3 ADC 校准
3.3.1 校准函数
// 执行ADC校准
HAL_StatusTypeDef HAL_ADCEx_Calibration_Start(
ADC_HandleTypeDef *hadc,
uint32_t SingleDiff);
// 获取校准因子
uint32_t HAL_ADCEx_Calibration_GetValue(
ADC_HandleTypeDef *hadc,
uint32_t SingleDiff);
示例:
// 执行校准
if (HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED) != HAL_OK) {
Error_Handler();
}
// 获取校准因子
uint32_t calib_factor = HAL_ADCEx_Calibration_GetValue(&hadc1, ADC_SINGLE_ENDED);
3.4 ADC 转换控制
3.4.1 启动转换
// 启动ADC转换 (阻塞模式)
HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef *hadc);
// 启动ADC转换 (中断模式)
HAL_StatusTypeDef HAL_ADC_Start_IT(ADC_HandleTypeDef *hadc);
// 启动ADC转换 (DMA模式)
HAL_StatusTypeDef HAL_ADC_Start_DMA(
ADC_HandleTypeDef *hadc,
uint32_t *pData,
uint32_t Length);
3.4.2 停止转换
// 停止ADC转换
HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef *hadc);
// 停止ADC转换 (中断模式)
HAL_StatusTypeDef HAL_ADC_Stop_IT(ADC_HandleTypeDef *hadc);
// 停止ADC转换 (DMA模式)
HAL_StatusTypeDef HAL_ADC_Stop_DMA(ADC_HandleTypeDef *hadc);
3.5 ADC 数据获取
3.5.1 获取转换结果
// 获取ADC转换值 (阻塞模式)
uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef *hadc);
// 检查转换是否完成
HAL_StatusTypeDef HAL_ADC_PollForConversion(
ADC_HandleTypeDef *hadc,
uint32_t Timeout);
示例 (单次转换):
HAL_ADC_Start(&hadc1); // 启动转换
// 等待转换完成
if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) {
// 获取转换结果
uint32_t adc_value = HAL_ADC_GetValue(&hadc1);
float voltage = (adc_value * 3.3f) / 4095.0f; // 转换为电压
}
3.6 ADC 中断处理
3.6.1 中断服务函数
// ADC全局中断服务函数
void ADC_IRQHandler(void) {
HAL_ADC_IRQHandler(&hadc1);
}
3.6.2 回调函数 (用户实现)
// 转换完成回调
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
if (hadc == &hadc1) {
// 处理转换完成事件
}
}
// 转换半完成回调 (DMA双缓冲)
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) {
if (hadc == &hadc1) {
// 处理半转换完成事件
}
}
// 错误回调
void HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc) {
// 处理ADC错误
uint32_t error = HAL_ADC_GetError(hadc);
if (error & HAL_ADC_ERROR_OVR) {
// 溢出错误处理
}
}
3.7 ADC 多模式配置 (双ADC)
3.7.1 HAL_ADCEx_MultiModeConfigChannel()
函数原型:
HAL_StatusTypeDef HAL_ADCEx_MultiModeConfigChannel(
ADC_HandleTypeDef *hadc,
ADC_MultiModeTypeDef *multimode)
多模式配置结构体:
typedef struct {
uint32_t Mode; // 多ADC模式:
// ADC_MODE_INDEPENDENT
// ADC_DUALMODE_REGSIMULT
// ADC_DUALMODE_INTERL
// ADC_DUALMODE_INJECSIMULT
// ADC_DUALMODE_ALTERTRIG
// ADC_TRIPLEMODE_REGSIMULT
// ADC_TRIPLEMODE_INTERL
// ADC_TRIPLEMODE_INJECSIMULT
// ADC_TRIPLEMODE_ALTERTRIG
uint32_t DMAAccessMode; // DMA访问模式:
// ADC_DMAACCESSMODE_DISABLED
// ADC_DMAACCESSMODE_1
// ADC_DMAACCESSMODE_2
// ADC_DMAACCESSMODE_3
uint32_t TwoSamplingDelay; // 采样延迟 (0-31个时钟周期)
} ADC_MultiModeTypeDef;
示例 (双ADC同步规则模式):
ADC_MultiModeTypeDef multimode;
multimode.Mode = ADC_DUALMODE_REGSIMULT;
multimode.DMAAccessMode = ADC_DMAACCESSMODE_1;
multimode.TwoSamplingDelay = ADC_TWOSAMPLINGDELAY_5CYCLES;
HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode);
3.8 ADC 注入通道
3.8.1 注入通道配置
// 配置注入通道
HAL_StatusTypeDef HAL_ADCEx_InjectedConfigChannel(
ADC_HandleTypeDef *hadc,
ADC_InjectionConfTypeDef *sConfigInjected);
// 启动注入转换
HAL_StatusTypeDef HAL_ADCEx_InjectedStart(ADC_HandleTypeDef *hadc);
注入配置结构体:
typedef struct {
uint32_t InjectedChannel; // 注入通道号
uint32_t InjectedRank; // 注入序列位置 (1-4)
uint32_t InjectedSamplingTime; // 注入采样时间
uint32_t InjectedOffset; // 注入偏移值
uint32_t InjectedNbrOfConversion; // 注入转换数量
FunctionalState AutoInjectedConv; // 自动注入: ENABLE/DISABLE
FunctionalState InjectedDiscontinuousConvMode; // 间断模式
} ADC_InjectionConfTypeDef;
3.9 ADC 看门狗
3.9.1 模拟看门狗配置
HAL_StatusTypeDef HAL_ADC_AnalogWDGConfig(
ADC_HandleTypeDef *hadc,
ADC_AnalogWDGConfTypeDef *AnalogWDGConfig);
看门狗配置结构体:
typedef struct {
uint32_t WatchdogMode; // 看门狗模式:
// ADC_ANALOGWATCHDOG_NONE
// ADC_ANALOGWATCHDOG_SINGLE_REG
// ADC_ANALOGWATCHDOG_ALL_REG
// ADC_ANALOGWATCHDOG_ALL_INJEC
// ADC_ANALOGWATCHDOG_SINGLE_INJEC
uint32_t Channel; // 监控通道
uint32_t ITMode; // 中断模式: ENABLE/DISABLE
uint32_t HighThreshold; // 高阈值 (12位)
uint32_t LowThreshold; // 低阈值 (12位)
} ADC_AnalogWDGConfTypeDef;
示例:
ADC_AnalogWDGConfTypeDef AnalogWDGConfig;
AnalogWDGConfig.WatchdogMode = ADC_ANALOGWATCHDOG_SINGLE_REG;
AnalogWDGConfig.Channel = ADC_CHANNEL_5;
AnalogWDGConfig.ITMode = ENABLE;
AnalogWDGConfig.HighThreshold = 3000; // 约2.4V (3.3V参考)
AnalogWDGConfig.LowThreshold = 1000; // 约0.8V
HAL_ADC_AnalogWDGConfig(&hadc1, &AnalogWDGConfig);
3.10 温度传感器与内部参考电压
3.10.1 内部通道配置
// 启用温度传感器
void HAL_ADCEx_EnableVREFINT(void);
void HAL_ADCEx_EnableVREFINTTemp(void);
// 配置内部通道
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_TEMPSENSOR; // 温度传感器
// 或 ADC_CHANNEL_VREFINT // 内部参考电压
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
3.10.2 温度计算
// 获取温度值
float Get_Temperature(uint32_t adc_value) {
// 使用工厂校准值
uint32_t *ts_cal1 = (uint32_t *)0x1FFF7A2C; // 30°C校准值地址
uint32_t *ts_cal2 = (uint32_t *)0x1FFF7A2E; // 110°C校准值地址
float temp = ((float)adc_value - *ts_cal1) * (110.0 - 30.0);
temp = temp / (*ts_cal2 - *ts_cal1) + 30.0;
return temp;
}
3.11 ADC DMA 双缓冲模式
3.11.1 DMA 双缓冲配置
#define ADC_BUF_SIZE 256
uint16_t adc_buffer1[ADC_BUF_SIZE];
uint16_t adc_buffer2[ADC_BUF_SIZE];
// 启动ADC DMA双缓冲
HAL_ADC_Start_DMA(&hadc1,
(uint32_t*)adc_buffer1,
(uint32_t*)adc_buffer2,
ADC_BUF_SIZE);
3.11.2 双缓冲回调处理
// 缓冲区1转换完成
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
if (hadc == &hadc1) {
// 处理adc_buffer1数据
Process_ADC_Data(adc_buffer1, ADC_BUF_SIZE);
}
}
// 缓冲区2转换完成
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) {
if (hadc == &hadc1) {
// 处理adc_buffer2数据
Process_ADC_Data(adc_buffer2, ADC_BUF_SIZE);
}
}
3.12 ADC 其他配置
3.12.1 采样时间选择
// 根据输入阻抗选择采样时间
#define INPUT_IMPEDANCE 10e3 // 10KΩ
void Set_Sampling_Time(ADC_HandleTypeDef *hadc, uint32_t channel) {
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = channel;
sConfig.Rank = 1;
// 采样时间计算: T_sampling = (SamplingTime + 12.5) / ADC_CLK
if (INPUT_IMPEDANCE > 50e3) {
sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
} else if (INPUT_IMPEDANCE > 10e3) {
sConfig.SamplingTime = ADC_SAMPLETIME_144CYCLES;
} else {
sConfig.SamplingTime = ADC_SAMPLETIME_15CYCLES;
}
HAL_ADC_ConfigChannel(hadc, &sConfig);
}
3.12.2 软件过采样
// 通过软件过采样提高分辨率
uint32_t Oversample_ADC(ADC_HandleTypeDef *hadc, uint8_t oversample_bits) {
uint32_t sum = 0;
uint16_t samples = 1 << (2 * oversample_bits); // 4^N 次采样
for (int i = 0; i < samples; i++) {
HAL_ADC_Start(hadc);
HAL_ADC_PollForConversion(hadc, 10);
sum += HAL_ADC_GetValue(hadc);
}
// 右移N位得到(12+N)位结果
return sum >> oversample_bits;
}
3.12.3 自动校准偏移
// 自动校准ADC偏移
void Auto_Calibrate_Offset(ADC_HandleTypeDef *hadc) {
// 短路ADC输入到地
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_VREFINT; // 使用内部通道
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
HAL_ADC_ConfigChannel(hadc, &sConfig);
// 采集多个样本
uint32_t sum = 0;
for (int i = 0; i < 16; i++) {
HAL_ADC_Start(hadc);
HAL_ADC_PollForConversion(hadc, 10);
sum += HAL_ADC_GetValue(hadc);
}
uint32_t avg = sum / 16;
// 设置偏移校准
hadc->Instance->OFR1 = (avg << ADC_OFR1_OFFSET1_Pos) | ADC_OFR1_OFFSET1_EN;
}

浙公网安备 33010602011771号