第4章:ADC采样调试
ADC采样,说白了就是把模拟信号变成数字信号。理疗仪里那些电极信号、温度传感器、压力传感器,最终都要靠ADC来读取。我调试过不下十款理疗仪,ADC这块出问题的概率其实挺高的。今天咱们就把ADC初始化的整个流程捋一遍,顺便聊聊那些坑。
4.1 ADC初始化流程
ADC初始化,我习惯按这个顺序来:
- 时钟使能 — 先给ADC模块和对应的GPIO口开时钟
- GPIO配置 — 把引脚设为模拟模式
- ADC工作模式配置 — 单次转换还是连续转换
- 分辨率与对齐方式 — 12位还是10位,左对齐还是右对齐
- 采样时间设置 — 这个很关键,后面细说
- 触发源选择 — 软件触发还是定时器触发
- 使能ADC — 最后再校准一下
嗯,这里要注意:校准必须在ADC使能之后做。我见过有人把顺序搞反了,结果采样值一直飘。
// 以STM32为例,ADC1初始化片段
void ADC_Init(void)
{
// 1. 时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
// 2. GPIO配置为模拟模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. ADC配置
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 单通道
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; // 软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfConversion = 1;
ADC_Init(ADC1, &ADC_InitStructure);
// 4. 采样时间设置
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
// 5. 使能ADC并校准
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
}
4.2 参考电压选择
参考电压决定了ADC的量程。理疗仪里常用的参考电压有几种:
| 参考电压 | 适用场景 | 注意事项 |
|---|---|---|
| 内部VREF(如3.3V) | 一般传感器信号 | 精度一般,受电源波动影响 |
| 外部VREF(如2.5V) | 高精度测量 | 需要额外基准芯片 |
| 内部带隙基准 | 电池电压检测 | 精度较低,但省成本 |
我个人建议:如果理疗仪里有精密传感器(比如压力传感器),一定要用外部基准。我之前有个项目,用内部VREF做温度检测,结果电源一波动,温度值跟着跳,差点把加热控制搞出问题。
4.3 采样率设置
采样率 = ADC时钟频率 / (采样周期 + 转换周期)。
举个例子:ADC时钟12MHz,采样周期55.5个时钟周期,转换周期12.5个时钟周期(12位分辨率)。
那么采样率 = 12MHz / (55.5 + 12.5) ≈ 176.5 kHz。
理疗仪里常见的信号频率都不高:
- 心电信号:0.05~100Hz,采样率200~500Hz就够了
- 肌电信号:10~500Hz,采样率1~2kHz
- 温度传感器:变化很慢,10Hz都嫌多
所以别把采样率设太高。采样率越高,数据量越大,DMA负担也重。我一般按信号最高频率的5~10倍来设采样率。
4.4 DMA传输配置
ADC用DMA,说白了就是让数据自己跑,CPU不用管。配置要点:
- DMA通道选择 — 查手册,ADC对应哪个DMA通道
- 传输方向 — 外设到内存
- 数据宽度 — 12位ADC用半字(16位)就够了
- 循环模式 — 连续采样必须开循环模式
- 缓冲区大小 — 根据你的滤波算法来定
// DMA配置示例
void DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 100; // 100个采样点
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
}
4.5 数据滤波算法
ADC原始数据直接拿来用?别闹。理疗仪里干扰多,50Hz工频、肌肉抖动、呼吸起伏,都会让采样值跳。我常用的滤波方法:
4.5.1 中值滤波
取连续N个采样值,排序后取中间值。对脉冲干扰特别有效。N一般取3、5、7,奇数。
uint16_t MedianFilter(uint16_t *buf, uint8_t len)
{
uint16_t temp;
// 简单冒泡排序
for(uint8_t i = 0; i < len - 1; i++) {
for(uint8_t j = 0; j < len - 1 - i; j++) {
if(buf[j] > buf[j+1]) {
temp = buf[j];
buf[j] = buf[j+1];
buf[j+1] = temp;
}
}
}
return buf[len / 2]; // 返回中值
}
4.5.2 滑动平均滤波
维护一个队列,新数据进来,旧数据出去,取平均值。对周期性干扰效果好。
#define FILTER_LEN 10
uint16_t MovingAverageFilter(uint16_t new_value)
{
static uint16_t buffer[FILTER_LEN] = {0};
static uint8_t index = 0;
static uint32_t sum = 0;
sum -= buffer[index];
buffer[index] = new_value;
sum += new_value;
index = (index + 1) % FILTER_LEN;
return (uint16_t)(sum / FILTER_LEN);
}
4.5.3 一阶低通滤波
说白了就是加权平均。新值占一点,旧值占大部分。适合变化缓慢的信号。
#define ALPHA 0.1 // 滤波系数,越小越平滑
uint16_t LowPassFilter(uint16_t new_value)
{
static uint16_t last_value = 0;
uint16_t result;
result = (uint16_t)((uint32_t)new_value * ALPHA +
(uint32_t)last_value * (256 - ALPHA)) >> 8;
last_value = result;
return result;
}
4.6 采样值跳变问题排查
采样值跳变,这是ADC调试里最头疼的问题。我总结了几种常见原因:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 单个值突然跳变 | 电源干扰、静电 | 加滤波电容、检查接地 |
| 周期性跳变 | 50Hz工频干扰 | 加陷波滤波器、屏蔽线 |
| 整体漂移 | 参考电压不稳、温度变化 | 检查VREF、加温度补偿 |
| 固定值跳变 | ADC引脚虚焊、接触不良 | 重新焊接、检查连接器 |
我曾经遇到过一个案例:理疗仪电极信号采样值每隔几十个点就跳一下。查了半天,发现是DMA传输时,CPU刚好在更新缓冲区,数据被覆盖了。解决办法很简单:用双缓冲区,一个给DMA写,一个给CPU读,交替使用。
还有一个容易被忽略的点:采样时间不够。如果信号源内阻大,采样电容充不满电,采样值就会偏低且不稳定。我一般把采样时间设成手册推荐值的2~3倍,稳得很。
好了,ADC采样调试就聊到这儿。下一章咱们讲定时器,理疗仪里那些脉冲波形全靠它来生成。