ADC 基本原理
STM32F103 的 ADC(Analog-to-Digital Converter,模数转换器)为 12 位分辨率,最大采样率 1Msps。ADC 将模拟电压转换为数字量,转换结果为 0-4095(0xFFF),对应 0-VREF+ 电压。
ADC 关键参数:
- 分辨率:12 位,4096 级
- 参考电压:VREF+(通常 3.3V)
- 输入范围:0 至 VREF+
- 转换公式:
ADC_Value = (Vin / VREF+) × 4095
电压计算:
#define VREF 3.3f
float ADC_To_Voltage(uint16_t adc_value) {
return (adc_value * VREF) / 4095.0f;
}
采样时间选择
采样时间(Sampling Time)决定 ADC 输入电容充电时间,影响转换精度和速度。采样时间越长,精度越高但速度越慢。
STM32F103 采样时间选项:
- 1.5 周期:最快,适合低阻抗信号源
- 7.5 周期:平衡速度与精度
- 13.5 周期:中等精度
- 28.5 周期:高精度
- 41.5 周期、55.5 周期、71.5 周期、239.5 周期:更高精度
采样时间计算:
总转换时间 = 采样时间 + 12.5 周期(12 位转换)
72MHz ADC 时钟下,7.5 周期采样时间转换时间约为 1μs。
hadc1.Init.SamplingTimeCommon = ADC_SAMPLETIME_7CYCLES5;
规则组与注入组
STM32 ADC 分为规则通道(Regular Channels)和注入通道(Injected Channels)。
规则组
规则通道按顺序转换,最多 16 个通道。适用于常规采样。
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_7CYCLES5;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
注入组
注入通道可中断规则组转换,优先级高,最多 4 个通道。适用于紧急采样(如过流保护)。
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_INJECTED_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_7CYCLES5;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
单次/连续/扫描模式
单次模式
每次触发转换一个通道,转换完成后停止。
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
连续模式
转换完成后自动开始下一次转换,无需重新触发。
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
扫描模式
按顺序转换多个通道,配合 DMA 使用。
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
DMA 搬运多通道数据
多通道采集使用 DMA 自动搬运数据,避免 CPU 干预。
#include "stm32f1xx_hal.h"
#define ADC_CHANNEL_COUNT 4
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
uint32_t adc_values[ADC_CHANNEL_COUNT]; // DMA 缓冲区
void MX_ADC1_Init(void) {
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = ADC_CHANNEL_COUNT;
HAL_ADC_Init(&hadc1);
// 配置通道 0-3
sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES5;
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_2;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = ADC_REGULAR_RANK_3;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_3;
sConfig.Rank = ADC_REGULAR_RANK_4;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}
void MX_DMA_Init(void) {
__HAL_RCC_DMA1_CLK_ENABLE();
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (hadc->Instance == ADC1) {
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
// PA0-PA3 作为 ADC 输入
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// ADC1 DMA 配置
hdma_adc1.Instance = DMA1_Channel1;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hdma_adc1);
__HAL_LINKDMA(hadc, DMA_Handle, hdma_adc1);
}
}
void ADC_Start(void) {
HAL_ADC_Start_DMA(&hadc1, adc_values, ADC_CHANNEL_COUNT);
}
// 读取各通道电压
void ADC_Read_All_Channels(float *voltages) {
for (int i = 0; i < ADC_CHANNEL_COUNT; i++) {
voltages[i] = (adc_values[i] * 3.3f) / 4095.0f;
}
}
分压电阻计算
工业设备常用 24V 供电,需通过分压电阻将 0-24V 映射到 ADC 输入范围 0-3.3V。
分压电路:
24V --- R1 ---+--- ADC --- GND
|
R2
|
GND
分压公式:
Vadc = Vin × R2 / (R1 + R2)
选择 R1 = 68kΩ,R2 = 10kΩ:
Vadc = 24V × 10k / (68k + 10k) = 3.16V < 3.3V(安全)
反向计算实际电压:
#define R1 68000.0f
#define R2 10000.0f
float ADC_To_Actual_Voltage(uint16_t adc_value) {
float v_adc = (adc_value * 3.3f) / 4095.0f;
return v_adc * (R1 + R2) / R2;
}
分压电阻功耗计算:
最大功耗 = (24V)^2 / (R1 + R2) = 576 / 78000 = 7.4mW
选用 1/8W 或 1/4W 电阻
ADC 校准流程
STM32 ADC 存在偏移误差,需校准以提高精度。HAL 库提供校准函数。
void ADC_Calibrate(void) {
HAL_ADCEx_Calibration_Start(&hadc1);
}
校准注意事项:
- 校准前 ADC 必须停止
- 校准期间禁止中断
- 每次上电或参考电压变化后需重新校准
完整初始化流程:
void ADC_Init_With_Calibration(void) {
HAL_ADC_DeInit(&hadc1);
MX_ADC1_Init();
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADC_Start_DMA(&hadc1, adc_values, ADC_CHANNEL_COUNT);
}
软件滤波算法
ADC 采样存在噪声,需通过软件滤波提高稳定性。
均值滤波
连续采样 N 次取平均值,适合消除随机噪声。
#define FILTER_SAMPLES 10
float ADC_Mean_Filter(uint8_t channel) {
uint32_t sum = 0;
for (int i = 0; i < FILTER_SAMPLES; i++) {
sum += adc_values[channel];
HAL_Delay(1);
}
return (sum * 3.3f) / (FILTER_SAMPLES * 4095.0f);
}
中值滤波
连续采样 N 次,取中位数,适合消除脉冲干扰。
#define MEDIAN_SAMPLES 5
uint16_t ADC_Median_Filter(uint8_t channel) {
uint16_t samples[MEDIAN_SAMPLES];
for (int i = 0; i < MEDIAN_SAMPLES; i++) {
samples[i] = adc_values[channel];
HAL_Delay(1);
}
// 冒泡排序
for (int i = 0; i < MEDIAN_SAMPLES - 1; i++) {
for (int j = 0; j < MEDIAN_SAMPLES - i - 1; j++) {
if (samples[j] > samples[j + 1]) {
uint16_t temp = samples[j];
samples[j] = samples[j + 1];
samples[j + 1] = temp;
}
}
}
return samples[MEDIAN_SAMPLES / 2];
}
滑动平均滤波
维护固定长度队列,新数据替换旧数据后计算平均值。
#define SLIDING_WINDOW_SIZE 8
typedef struct {
uint16_t buffer[SLIDING_WINDOW_SIZE];
uint8_t index;
uint32_t sum;
} SlidingAverage_t;
SlidingAverage_t sliding_avg;
void Sliding_Average_Init(void) {
memset(&sliding_avg, 0, sizeof(sliding_avg));
}
float Sliding_Average_Update(uint16_t new_value) {
sliding_avg.sum -= sliding_avg.buffer[sliding_avg.index];
sliding_avg.buffer[sliding_avg.index] = new_value;
sliding_avg.sum += new_value;
sliding_avg.index = (sliding_avg.index + 1) % SLIDING_WINDOW_SIZE;
return (sliding_avg.sum * 3.3f) / (SLIDING_WINDOW_SIZE * 4095.0f);
}
实际应用:读取拨码开关地址
通过 ADC 读取拨码开关设置的地址,避免占用多个 GPIO。
拨码开关电路:
3.3V --- 拨码开关(4位) --- 分压网络 --- ADC
不同组合产生不同电压,通过查表确定地址。
#define DIP_ADC_CHANNEL ADC_CHANNEL_4
typedef struct {
uint16_t adc_min;
uint16_t adc_max;
uint8_t address;
} DipSwitch_Map_t;
const DipSwitch_Map_t dip_map[] = {
{0, 100, 0},
{300, 500, 1},
{600, 800, 2},
{900, 1100, 3},
{1200, 1400, 4},
{1500, 1700, 5},
{1800, 2000, 6},
{2100, 2300, 7},
{2400, 2600, 8},
{2700, 2900, 9},
{3000, 3200, 10},
{3300, 3500, 11},
{3600, 3800, 12},
{3900, 4100, 13},
{4200, 4400, 14},
{4500, 4095, 15}
};
uint8_t Read_Dip_Switch_Address(void) {
uint16_t adc_value = adc_values[DIP_ADC_CHANNEL];
for (int i = 0; i < sizeof(dip_map) / sizeof(dip_map[0]); i++) {
if (adc_value >= dip_map[i].adc_min && adc_value <= dip_map[i].adc_max) {
return dip_map[i].address;
}
}
return 0; // 默认地址
}
完整应用示例
以下代码实现多通道 ADC 采集、滤波和拨码开关读取:
#include "stm32f1xx_hal.h"
#include <string.h>
#define ADC_CHANNEL_COUNT 5
#define FILTER_SAMPLES 10
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
uint32_t adc_raw_values[ADC_CHANNEL_COUNT];
float adc_filtered_values[ADC_CHANNEL_COUNT];
volatile uint8_t adc_conversion_complete = 0;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_ADC1_Init(void);
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
ADC_Calibrate();
HAL_ADC_Start_DMA(&hadc1, adc_raw_values, ADC_CHANNEL_COUNT);
while (1) {
if (adc_conversion_complete) {
adc_conversion_complete = 0;
// 滤波处理
for (int i = 0; i < ADC_CHANNEL_COUNT - 1; i++) {
adc_filtered_values[i] = ADC_Mean_Filter(i);
}
// 读取拨码开关地址
uint8_t device_addr = Read_Dip_Switch_Address();
// 处理数据
float voltage_ch0 = ADC_To_Actual_Voltage(adc_filtered_values[0]);
float voltage_ch1 = ADC_To_Actual_Voltage(adc_filtered_values[1]);
}
HAL_Delay(100);
}
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
if (hadc->Instance == ADC1) {
adc_conversion_complete = 1;
}
}
void ADC_Calibrate(void) {
HAL_ADC_Stop_DMA(&hadc1);
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADC_Start_DMA(&hadc1, adc_raw_values, ADC_CHANNEL_COUNT);
}
float ADC_Mean_Filter(uint8_t channel) {
uint32_t sum = 0;
for (int i = 0; i < FILTER_SAMPLES; i++) {
sum += adc_raw_values[channel];
HAL_Delay(1);
}
return (sum * 3.3f) / (FILTER_SAMPLES * 4095.0f);
}
float ADC_To_Actual_Voltage(float adc_voltage) {
return adc_voltage * 7.8f; // 分压比 (68k+10k)/10k = 7.8
}
static void MX_ADC1_Init(void) {
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = ADC_CHANNEL_COUNT;
HAL_ADC_Init(&hadc1);
sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES5;
// 通道 0-3:电压采集
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_2;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = ADC_REGULAR_RANK_3;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_3;
sConfig.Rank = ADC_REGULAR_RANK_4;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
// 通道 4:拨码开关
sConfig.Channel = ADC_CHANNEL_4;
sConfig.Rank = ADC_REGULAR_RANK_5;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}
static void MX_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 |
GPIO_PIN_3 | GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
static void MX_DMA_Init(void) {
__HAL_RCC_DMA1_CLK_ENABLE();
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc) {
if (hadc->Instance == ADC1) {
__HAL_RCC_ADC1_CLK_ENABLE();
hdma_adc1.Instance = DMA1_Channel1;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hdma_adc1);
__HAL_LINKDMA(hadc, DMA_Handle, hdma_adc1);
}
}
常见问题
ADC 读数不稳定
原因:采样时间过短、输入阻抗过高、电源噪声。
解决:增加采样时间、降低输入阻抗、加去耦电容。
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES5; // 最长采样时间
多通道数据错位
原因:DMA 配置错误或通道顺序不对。
解决:检查 DMA 数据对齐和通道 Rank 配置。
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
校准失败
原因:ADC 未停止或参考电压不稳定。
解决:校准前确保 ADC 停止,等待电源稳定。
HAL_ADC_Stop(&hadc1);
HAL_ADCEx_Calibration_Start(&hadc1);