在嵌入式开发领域,模拟信号采集是最基础也最核心的功能之一。STM32系列MCU凭借其丰富的外设资源和稳定的性能,成为工业控制、传感器数据采集等场景的首选方案。而STM32CubeIDE作为ST官方推出的集成开发环境,为开发者提供了从硬件配置到代码生成的一站式解决方案。
这个项目将带你从零开始,在STM32CubeIDE环境下实现模拟信号电压值的精确采集。不同于简单的ADC读数,我们会深入探讨如何通过合理的硬件设计、软件配置和数据处理,获得稳定可靠的电压测量结果。无论你是刚接触STM32的新手,还是需要优化现有采集系统的开发者,都能从中获得实用价值。
STM32系列通常内置12位逐次逼近型(SAR)ADC,以STM32F103C8T6为例,其ADC主要特性包括:
实际应用中需注意:
关键提示:虽然标称12位,但实际有效位数(ENOB)通常为10-11位,受噪声、电源波动等因素影响。
可靠的电压测量离不开合理的硬件设计:
电压分压电路:
code复制Vout = Vin * R2/(R1+R2)
滤波设计:
参考电压处理:
PCB布局建议:
时钟配置:
ADC参数设置:
c复制hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = DISABLE; // 单通道禁用扫描
hadc1.Init.ContinuousConvMode = ENABLE; // 连续转换
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; // 软件触发
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; // 数据右对齐
hadc1.Init.NbrOfConversion = 1; // 转换通道数
通道配置:
DMA配置(可选):
ADC初始化:
c复制HAL_ADC_Start(&hadc1); // 启动ADC
HAL_Delay(1); // 等待稳定
单次采集函数:
c复制uint32_t read_ADC(ADC_HandleTypeDef* hadc) {
HAL_ADC_Start(hadc);
HAL_ADC_PollForConversion(hadc, 10); // 超时10ms
return HAL_ADC_GetValue(hadc);
}
电压计算:
c复制float adc_to_voltage(uint32_t adc_value) {
const float VREF = 3.3f; // 实际参考电压
return (adc_value * VREF) / 4095.0f;
}
均值滤波实现:
c复制#define SAMPLE_COUNT 16
float get_avg_voltage(ADC_HandleTypeDef* hadc) {
uint32_t sum = 0;
for(int i=0; i<SAMPLE_COUNT; i++) {
sum += read_ADC(hadc);
HAL_Delay(1);
}
return adc_to_voltage(sum / SAMPLE_COUNT);
}
偏移校准:
c复制HAL_ADCEx_Calibration_Start(&hadc1); // 执行内部校准
两点校准法:
c复制float calibrated_voltage(uint32_t adc) {
static float scale = (V2-V1)/(D2-D1);
static float offset = V1 - D1*scale;
return adc*scale + offset;
}
移动平均滤波:
c复制#define FILTER_SIZE 8
float moving_avg_filter(float new_val) {
static float buffer[FILTER_SIZE] = {0};
static uint8_t index = 0;
static float sum = 0;
sum -= buffer[index];
buffer[index] = new_val;
sum += new_val;
index = (index+1) % FILTER_SIZE;
return sum / FILTER_SIZE;
}
中值滤波:
c复制float median_filter(float new_val) {
static float buffer[5] = {0};
static uint8_t count = 0;
buffer[count++ % 5] = new_val;
float temp[5];
memcpy(temp, buffer, sizeof(temp));
bubble_sort(temp, 5); // 实现排序算法
return temp[2];
}
卡尔曼滤波(简单实现):
c复制typedef struct {
float q; // 过程噪声
float r; // 观测噪声
float x; // 估计值
float p; // 估计误差
float k; // 卡尔曼增益
} KalmanFilter;
float kalman_update(KalmanFilter* kf, float measurement) {
// 预测
kf->p = kf->p + kf->q;
// 更新
kf->k = kf->p / (kf->p + kf->r);
kf->x = kf->x + kf->k * (measurement - kf->x);
kf->p = (1 - kf->k) * kf->p;
return kf->x;
}
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 读数跳动大 | 电源噪声 | 增加LC滤波,使用线性稳压 |
| 测量值偏小 | 信号源阻抗过高 | 增加电压跟随器或降低分压电阻 |
| ADC值固定为0 | 通道配置错误 | 检查CubeMX引脚分配 |
| 读数不稳定 | 采样时间不足 | 增加采样周期数 |
| 线性度差 | 参考电压不稳 | 使用外部参考电压源 |
基准电压验证:
信号完整性检查:
代码调试方法:
c复制printf("Raw ADC: %lu, Voltage: %.3f\n", adc_val, voltage);
DMA传输验证:
CubeMX配置:
DMA配置:
c复制__HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, BUFFER_SIZE);
数据处理:
c复制void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
for(int i=0; i<CHANNEL_COUNT; i++) {
voltages[i] = adc_to_voltage(adc_buffer[i]);
}
}
间歇采样模式:
c复制HAL_ADC_Start_IT(&hadc1);
HAL_SuspendTick();
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
SystemClock_Config(); // 唤醒后重新配置时钟
参数优化:
动态参考电压:
隔离设计:
抗干扰措施:
温度补偿:
c复制float temp_compensated_voltage(float raw_voltage, float temp) {
// 根据温度传感器读数进行补偿
return raw_voltage * (1.0 + 0.0005*(25.0 - temp));
}
经过多个项目的实践验证,稳定的ADC采集系统往往需要在硬件设计和软件处理之间取得平衡。特别是在工业环境中,单纯的软件滤波可能无法完全解决干扰问题,必须结合适当的硬件滤波和良好的接地设计。一个实用的建议是:在PCB上为ADC输入部分预留π型滤波电路的位置,在实际调试中根据噪声情况决定是否安装相关元件。