MQ-2传感器数据飘得厉害?手把手教你用STM32的ADC+DMA+TIM定时采集,实现稳定滤波(基于HAL库)

烟雾检测系统的稳定性往往取决于传感器数据的可靠性。MQ-2作为常见的半导体气敏元件,其输出信号容易受环境温度、供电波动等因素影响,导致原始ADC值出现明显抖动。本文将分享一套基于STM32 HAL库的工程级解决方案,通过硬件定时触发+DMA传输+软件滤波的组合拳,实现低CPU占用率的高稳定性数据采集。

1. 系统架构设计思路

传统轮询式ADC采集存在两个致命缺陷:一是MCU需要持续等待转换完成,造成资源浪费;二是单次采样难以消除噪声干扰。我们采用的方案核心在于三模块协同工作:

  • TIM定时器:以精确的时间间隔触发ADC转换(例如100ms)
  • DMA控制器:自动搬运ADC数据到内存缓冲区,完全解放CPU
  • 多级滤波算法:对原始数据进行滑动平均+中值滤波处理

这种架构下,CPU仅在需要读取最终结果时介入,其余时间可处理其他任务。实测显示,相比传统轮询方式,CPU占用率可从70%降至不足5%。

2. 硬件环境搭建

2.1 元器件清单

组件型号/参数备注
主控芯片STM32F103C8T6蓝色pill开发板
传感器MQ-2需预热3分钟
电阻10kΩ分压电路使用
电容0.1μF电源滤波

2.2 电路连接要点

  1. 供电部分:
    • 开发板5V输出接MQ-2的VCC引脚
    • 共地连接必须可靠
  2. 信号调理:
    // 分压电路计算(MQ-2电阻随浓度变化)
    float voltage = adc_value * 3.3f / 4095;
    float rs_air = (5.0f - voltage) / voltage * RL; // RL=10kΩ
    
  3. ADC接口:
    • 推荐使用PA0-PA7等ADC1通道
    • 走线尽量远离数字信号线

3. STM32CubeMX配置

3.1 时钟树设置

  • 主频设为72MHz
  • APB2分频保持1:1(ADC时钟上限14MHz)

3.2 外设参数

/* ADC1 Configuration */
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;

/* TIM3 Configuration */
htim3.Instance = TIM3;
htim3.Init.Prescaler = 7200-1;  // 10kHz
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 1000-1;     // 100ms触发

/* DMA Configuration */
hdma_adc1.Instance = DMA1_Channel1;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;

关键提示:TIM3的TRGO信号需要设置为Update Event触发,CubeMX中在TIM3参数页的Trigger Output选项里设置。

4. 滤波算法实现

4.1 滑动窗口滤波

#define SAMPLE_SIZE 10
uint16_t adc_buffer[SAMPLE_SIZE];

float moving_average_filter() {
    static uint8_t index = 0;
    static uint32_t sum = 0;
    
    sum -= adc_buffer[index];
    sum += adc_buffer[index] = HAL_ADC_GetValue(&hadc1);
    index = (index + 1) % SAMPLE_SIZE;
    
    return (float)sum / SAMPLE_SIZE;
}

4.2 中值滤波优化

int compare(const void *a, const void *b) {
    return (*(uint16_t*)a - *(uint16_t*)b);
}

uint16_t median_filter(uint16_t *buf, uint8_t size) {
    uint16_t temp[size];
    memcpy(temp, buf, size*sizeof(uint16_t));
    qsort(temp, size, sizeof(uint16_t), compare);
    return temp[size/2];
}

两种滤波方式可组合使用:先对DMA缓冲区的原始数据做中值滤波,再对连续多个中值结果进行滑动平均。

5. 完整工程代码框架

5.1 主循环结构

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_ADC1_Init();
    MX_TIM3_Init();
    
    HAL_TIM_Base_Start(&htim3);
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, SAMPLE_SIZE);
    
    while (1) {
        if(data_ready_flag) {
            float filtered = post_process_data();
            printf("Filtered: %.2f\n", filtered);
            data_ready_flag = 0;
        }
        // 其他任务...
    }
}

5.2 DMA完成回调

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
    data_ready_flag = 1;
    // 可在此处添加临界区保护
}

6. 性能优化技巧

  1. 采样时序调整:

    • 加热阶段提高采样率(1Hz)
    • 稳定后降低至0.1Hz
  2. 动态阈值算法:

    #define ALPHA 0.1f
    float dynamic_threshold(float new_sample) {
        static float baseline = 0;
        baseline = ALPHA * new_sample + (1-ALPHA) * baseline;
        return baseline * 1.5f; // 安全系数
    }
    
  3. 低功耗模式:

    • 在采样间隔期间进入STOP模式
    • 通过TIM唤醒

实际项目中,这套方案将MQ-2的数据波动幅度从±15%降低到±3%以内。需要注意的是,传感器预热期间的数据应丢弃,且每隔2-3个月需要进行校准。

更多推荐