基于天空星STM32F407的MQ-5液化气传感器驱动移植与浓度检测实战

最近在做一个智能家居安全监测的小项目,需要检测厨房的天然气泄漏,于是就用上了MQ-5这款传感器。很多刚开始接触STM32和传感器开发的朋友,拿到模块后往往对着几根线不知道从何下手,网上的代码又七零八落,移植起来总报错。今天我就以手头的“天空星STM32F407”开发板为例,带大家从头到尾走一遍MQ-5传感器的驱动移植和浓度读取过程,保证你跟着做一遍就能点亮模块、读到数据。

咱们的目标很明确:让STM32通过ADC读取MQ-5输出的模拟电压(AO引脚),换算成气体浓度百分比;同时还能读取数字报警信号(DO引脚),实现一个双保险的检测方案。下面咱们就一步步来。

1. 认识你的“哨兵”:MQ-5传感器模块

在写代码之前,得先了解你手里的工具。MQ-5模块是一个四引脚的小板子,长这样:

  • VCCGND:接电源和地,工作电压很宽,3.3V或5V都行,咱们的开发板通常用3.3V。
  • AO(Analog Output):模拟量输出。这是传感器的核心输出,引脚上的电压值会随着周围可燃气体(如天然气、液化气)浓度的变化而连续变化。浓度越高,电压越高。我们需要用STM32的ADC(模数转换器)功能来读取这个电压。
  • DO(Digital Output):数字量输出。这个引脚输出不是高电平就是低电平。模块上有个蓝色的电位器(可调电阻),你可以用它设定一个浓度阈值。当气体浓度超过这个阈值时,DO引脚就会从一种电平跳变到另一种电平(比如从高变低),相当于一个简单的开关报警信号。我们用普通的GPIO输入功能就能读取它。

简单来说,AO给你的是“浓度有多高”的详细报告,DO给你的是“是否超标”的警报。在实际项目中,两个都用上会更可靠。

2. 硬件连接与引脚规划

现在,把MQ-5模块和天空星STM32F407开发板连起来。连接很简单,关键是选对STM32的引脚。

接线表:

MQ-5模块引脚连接至说明
VCC开发板 3.3V供电
GND开发板 GND共地
AO开发板 PC1关键! 必须连接到具有ADC功能的引脚
DO开发板 PA1连接到任意普通GPIO引脚即可

引脚选择思路(为什么是PC1和PA1?):

  1. AO引脚(PC1):STM32的ADC功能不是所有引脚都有的,需要查数据手册。对于STM32F407,PC1这个引脚正好对应ADC1第11通道。我们计划用ADC1来采集,所以选它。
  2. DO引脚(PA1):这个就随意多了,只要是普通的GPIO输入引脚都行。这里我们选PA1,方便记忆(和PC1数字一样)。

注意:接线时务必确保电源正负极不要接反,接反很可能烧坏传感器模块。

3. 软件驱动代码编写与解析

硬件连好了,接下来就是重头戏——写代码。我们会创建两个文件:bsp_mq5.c(驱动源文件)和bsp_mq5.h(驱动头文件)。

3.1 头文件定义 (bsp_mq5.h)

头文件里主要做宏定义,把硬件连接关系用代码“告诉”单片机。这样以后如果想换引脚,只需要改这里的一行代码,非常方便。

#ifndef _BSP_MQ5_H_
#define _BSP_MQ5_H_

#include "stm32f4xx.h"

/* 时钟宏定义 */
#define RCC_MQ5_GPIO_AO    RCC_AHB1Periph_GPIOC // PC1的GPIO时钟
#define RCC_MQ5_GPIO_DO    RCC_AHB1Periph_GPIOA // PA1的GPIO时钟
#define RCC_MQ5_ADC        RCC_APB2Periph_ADC1  // ADC1的时钟

/* ADC相关宏定义 */
#define PORT_ADC           ADC1                 // 使用ADC1外设
#define CHANNEL_ADC        ADC_Channel_11       // 使用第11通道,对应PC1

/* AO引脚(PC1)宏定义 */
#define PORT_MQ5_AO        GPIOC
#define GPIO_MQ5_AO        GPIO_Pin_1

/* DO引脚(PA1)宏定义 */
#define PORT_MQ5_DO        GPIOA
#define GPIO_MQ5_DO        GPIO_Pin_1

// 采样次数,用于软件滤波,取平均值使读数更稳定
#define SAMPLES         30

/* 函数声明 */
void ADC_MQ5_Init(void);                      // 初始化函数
unsigned int Get_Adc_MQ5_Value(void);         // 获取ADC原始值
unsigned int Get_MQ5_Percentage_value(void);  // 获取浓度百分比
char Get_MQ5_DO_value(void);                  // 获取DO报警状态

#endif

3.2 初始化函数 (ADC_MQ5_Init)

这是最核心的配置函数,负责开启硬件时钟、设置引脚模式、配置ADC参数。STM32的库函数配置看起来复杂,其实是有固定套路的。

void ADC_MQ5_Init(void)
{
    /* 1. 开启相关时钟 —— 不开时钟,外设不工作 */
    RCC_AHB1PeriphClockCmd (RCC_MQ5_GPIO_AO, ENABLE); // 使能GPIOC时钟
    RCC_AHB1PeriphClockCmd (RCC_MQ5_GPIO_DO, ENABLE); // 使能GPIOA时钟
    RCC_APB2PeriphClockCmd (RCC_MQ5_ADC, ENABLE);     // 使能ADC1时钟

    GPIO_InitTypeDef  GPIO_InitStructure;

    /* 2. 配置AO引脚(PC1)为模拟输入模式 */
    GPIO_InitStructure.GPIO_Pin = GPIO_MQ5_AO;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;      // 关键!必须设为模拟输入
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;  // 模拟输入通常不上下拉
    GPIO_Init(PORT_MQ5_AO, &GPIO_InitStructure);

    /* 3. 配置DO引脚(PA1)为浮空输入模式 */
    GPIO_InitStructure.GPIO_Pin = GPIO_MQ5_DO;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;      // 普通输入模式
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;  // 浮空输入,依赖外部电平
    GPIO_Init(PORT_MQ5_DO, &GPIO_InitStructure);

    /* 4. ADC公共配置 (ADC1和ADC2/3共用的一些设置) */
    ADC_CommonInitTypeDef ADC_CommonInitStruct;
    ADC_DeInit(); // 先复位ADC,恢复到默认状态

    ADC_CommonInitStruct.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; // 不用DMA
    ADC_CommonInitStruct.ADC_Mode = ADC_Mode_Independent;                // 独立模式
    ADC_CommonInitStruct.ADC_Prescaler = ADC_Prescaler_Div4;             // ADC时钟分频
    ADC_CommonInitStruct.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
    ADC_CommonInit(&ADC_CommonInitStruct);

    /* 5. ADC特定配置 (配置ADC1本身) */
    ADC_InitTypeDef ADC_InitStruct;
    ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;   // 单次转换模式
    ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;// 数据右对齐
    ADC_InitStruct.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; // 软件触发
    ADC_InitStruct.ADC_NbrOfConversion = 1;            // 只转换1个通道
    ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;// 12位分辨率
    ADC_InitStruct.ADC_ScanConvMode = DISABLE;         // 非扫描模式(单通道)
    ADC_Init(PORT_ADC, &ADC_InitStruct);

    /* 6. 指定ADC的转换通道和采样时间 */
    // 告诉ADC:你要转换的是第11通道,采样时间设为480个周期(采样时间越长,精度一般越高,但速度慢)
    ADC_RegularChannelConfig( PORT_ADC, CHANNEL_ADC, 1, ADC_SampleTime_480Cycles );

    /* 7. 使能ADC */
    ADC_Cmd(PORT_ADC, ENABLE);
}

提示GPIO_Mode_AN(模拟输入)是配置ADC引脚的关键,如果错设为GPIO_Mode_IN(数字输入),将无法正确读取模拟电压。

3.3 数据读取函数

初始化完成后,就可以编写函数来读取数据了。

a) 获取ADC原始值 这个函数负责启动一次ADC转换,并读取结果。我们采用了简单的软件滤波——多次采样取平均值,来减少偶然的读数跳动。

unsigned int Get_Adc_MQ5_Value(void)
{
    uint32_t Data = 0;

    // 循环采样SAMPLES次(在头文件中定义为30次)
    for(int i = 0; i < SAMPLES; i++)
    {
        ADC_SoftwareStartConv(PORT_ADC); // 软件触发一次转换
        while(!ADC_GetFlagStatus(PORT_ADC, ADC_FLAG_EOC )); // 等待转换完成
        Data += ADC_GetConversionValue(PORT_ADC); // 累加转换结果
        delay_ms(5); // 每次采样间隔5ms,让传感器和电路稳定一下
    }
    Data = Data / SAMPLES; // 计算平均值
    return Data; // 返回12位ADC原始值(0~4095)
}

b) 将ADC值转换为浓度百分比 STM32的ADC是12位的,最大值是4095(2^12 - 1),对应参考电压(通常是3.3V)。这个函数把0-4095的原始值,映射到0-100%的浓度百分比,非常直观。

unsigned int Get_MQ5_Percentage_value(void)
{
    int adc_max = 4095; // 12位ADC最大值
    int adc_new = 0;
    int Percentage_value = 0;

    adc_new = Get_Adc_MQ5_Value(); // 先获取原始ADC值

    // 核心计算公式:(当前ADC值 / 最大ADC值) * 100%
    Percentage_value = ((float)adc_new / (float)adc_max) * 100.f;
    return Percentage_value;
}

注意:这个“百分比”是相对于ADC量程的百分比,不是真实的气体浓度ppm值。要得到精确的ppm浓度,需要根据传感器数据手册的曲线进行复杂的标定和公式计算。这里的百分比用于相对浓度的比较和阈值判断已经足够。

c) 读取数字报警输出(DO) 这个函数就简单多了,直接读取我们之前配置的GPIO输入引脚的电平状态。

char Get_MQ5_DO_value(void)
{
    // 读取PA1引脚的电平
    if( GPIO_ReadInputDataBit(PORT_MQ5_DO, GPIO_MQ5_DO) == RESET ) // 如果是低电平
    {
        return 0; // 返回0,表示浓度未超过模块设定的阈值
    }
    else // 如果是高电平
    {
        return 1; // 返回1,表示浓度已超过阈值,报警!
    }
}

提示:DO引脚输出的高低电平所代表的意义(1报警还是0报警),取决于模块上比较器电路的接法。上述代码是基于原文示例的,如果你的模块逻辑相反,修改这里的判断条件即可。你可以用打火机在传感器附近轻轻放点气(注意安全!),观察DO返回值的变化来确定。

4. 在工程中调用与验证

驱动写好了,最后就是在主函数里调用它,并把数据打印出来看看。

  1. bsp_mq5.cbsp_mq5.h文件添加到你的MDK或IAR工程中。
  2. main.c中包含头文件并编写以下代码
#include "board.h"     // 开发板初始化
#include "bsp_uart.h"  // 串口初始化,用于打印
#include <stdio.h>
#include "bsp_mq5.h"   // 包含我们刚写的MQ-5驱动

int main(void)
{
    board_init();        // 初始化系统时钟等
    uart1_init(115200U); // 初始化串口1,波特率115200,用于连接电脑串口助手
    ADC_MQ5_Init();      // 初始化MQ-5传感器(ADC和GPIO)

    printf("MQ-5 Sensor Demo Start\r\n");

    while(1)
    {
        // 读取并打印气体浓度百分比
        printf("Gas Concentration = %d%%\r\n", Get_MQ5_Percentage_value());

        // 读取并打印数字报警状态
        char do_status = Get_MQ5_DO_value();
        if(do_status == 1) {
            printf("Warning: Gas level EXCEEDED threshold!\r\n");
        } else {
            printf("Status: Gas level normal.\r\n");
        }
        printf("-----------------\r\n");

        delay_ms(1000); // 每隔1秒读取一次
    }
}
  1. 编译、下载到天空星开发板
  2. 打开串口助手(如XCOM、SSCOM),设置好对应的串口号和波特率(115200)。
  3. 你应该能看到串口每隔一秒打印出当前的“气体浓度百分比”和报警状态。对着MQ-5传感器吹口气或者用打火机(远离传感器,小心操作)释放少量气体,观察数值和报警状态的变化。

5. 调试心得与常见问题

  • 读数跳动:这是正常的,传感器本身和环境都有噪声。我们代码里已经做了30次取平均的软件滤波。如果跳动依然很大,可以尝试增加SAMPLES次数,或者调整ADC_SampleTime_480Cycles为更长的采样时间。
  • ADC值始终为0或4095
    • 检查硬件接线,尤其是AO引脚是否接对了PC1。
    • 检查bsp_mq5.h中的引脚宏定义是否正确。
    • 确认ADC_MQ5_Init函数中,AO引脚的模式是否配置为GPIO_Mode_AN(模拟输入)。
  • DO状态不变化:调节模块上的蓝色电位器。顺时针或逆时针旋转,可以改变触发报警的浓度阈值。一边调节一边向传感器吹气,观察DO返回值的变化。
  • 百分比数值的理解:再次强调,这个百分比是电压百分比。如果需要精确监测,你需要查阅MQ-5的数据手册,找到其灵敏度特性曲线,将ADC读取的电压值代入公式,才能换算成以ppm为单位的气体浓度。对于一般的报警和趋势监测,百分比完全够用。

好了,整个从硬件连接到软件驱动,再到实际测试的流程就走完了。代码我都实际在天空星的板子上跑过,你可以直接拿去用。遇到问题,先别慌,按照上面“常见问题”的思路排查,大部分都能解决。嵌入式开发就是这样,动手调一遍,印象最深。

更多推荐