基于GD32VW553的MQ-9可燃气体传感器驱动移植与浓度检测实战

最近在做一个环境监测的小项目,需要检测空气中的一氧化碳和甲烷浓度,正好手头有GD32VW553开发板和MQ-9气体传感器模块。很多刚开始接触嵌入式传感器的朋友可能会觉得,把传感器用起来挺复杂的,既要接硬件又要写驱动。其实没那么难,今天我就以这个组合为例,手把手带你走一遍完整的驱动移植和浓度检测流程,让你也能快速上手。

咱们的目标很明确:让GD32VW553开发板通过MQ-9传感器,既能读取模拟量浓度值,也能判断数字量报警信号,最终在串口上看到实时的检测结果。我会从硬件接线讲起,到代码编写、调试,最后完成功能验证。只要你跟着步骤走,保证能调通。

1. 认识你的“鼻子”:MQ-9传感器模块

在动手接线写代码之前,咱们先花几分钟了解一下MQ-9传感器是怎么工作的。这能帮你更好地理解后面的数据。

MQ-9的核心是一层二氧化锡(SnO2)气敏材料。这东西在清洁空气里导电性很差,电阻很大。但是,当它遇到一氧化碳(CO)或者甲烷(CH₄)这类可燃气体时,气体分子会吸附在材料表面,导致材料的电导率上升,电阻下降。电阻变化了,我们就能通过测量电压变化来感知气体浓度。

这个模块比较聪明,它采用高低温循环检测的方式:

  • 低温加热(1.5V):主要用来检测一氧化碳。
  • 高温加热(5.0V):用来检测甲烷、丙烷等可燃气体,同时还能“清洗”掉低温时吸附的其他杂散气体,让传感器恢复状态。

模块给我们提供了两种读取结果的方式:

  1. AO(模拟输出):输出一个模拟电压,气体浓度越高,电压值一般也越高。我们需要用开发板的ADC(模数转换器)功能来读取这个电压,并换算成浓度。
  2. DO(数字输出):模块上有个电位器可以调节报警阈值。当气体浓度超过你设定的阈值时,DO引脚会输出一个数字信号(高电平或低电平),可以直接接到单片机的普通IO口上判断,实现简单的超标报警。

模块的基本参数如下,接线和供电时要注意:

参数项规格说明
工作电压3.3V ~ 5V
工作电流约150mA(加热时电流较大,供电要足)
输出信号AO(模拟量输出)、DO(数字量输出)
读取方式ADC读取AO,GPIO读取DO
接口4 Pin 2.54mm排针

2. 硬件连接:给开发板装上“鼻子”

知道了原理,咱们就来接线。这一步千万不能错,接错了可能烧模块或者读不到数据。

根据原始资料,我们需要用到开发板的以下资源:

  • 一个ADC通道 来读取AO的模拟电压。
  • 一个普通GPIO 来读取DO的数字电平。
  • 电源和地 给模块供电。

具体的引脚连接方案已经有人帮我们验证好了,直接采用这个最稳妥的接法:

传感器模块引脚GD32VW553开发板引脚说明
VCC5V0接5V电源引脚。虽然模块支持3.3V,但接5V加热更充分,响应可能更好。
GNDGND接地,和开发板共地。
DOPB2数字输出,接普通GPIO口,配置为输入模式。
AOPB0模拟输出,必须接在具有ADC功能的引脚上。PB0对应ADC的通道8。

注意:接线时最好断开电源。接好后检查一下,别把VCC和GND接反了。

3. 软件驱动编写:让开发板学会“闻气味”

硬件准备好了,接下来就是重头戏——写代码。我们会新建两个文件 bsp_mq9.c 和 bsp_mq9.h,把所有的硬件操作封装成函数,这样主程序用起来就非常清爽。

3.1 引脚与宏定义 (bsp_mq9.h)

首先在头文件里,我们把用到的硬件资源用宏定义好,这样以后想换引脚修改起来也方便。

#ifndef BSP_CODE_BSP_MQ9_H_
#define BSP_CODE_BSP_MQ9_H_

#include "gd32vw55x.h"
#include "systick.h"

// 延时函数的宏定义,方便使用
#ifndef delay_ms
#define delay_ms(x)    delay_1ms(x)
#endif
#ifndef delay_us
#define delay_us(x)    delay_1us(x)
#endif

// 一键使能所有相关时钟的宏
#define MODULE_BSP_RCU_ENABLE()   \
        rcu_periph_clock_enable(BSP_ADC_GPIO_RCU); \
        rcu_periph_clock_enable(BSP_DO_GPIO_RCU); \
        rcu_periph_clock_enable(BSP_ADC_RCU);

/* 模拟输入AO引脚定义 (PB0 -> ADC 通道8) */
#define BSP_ADC_GPIO_RCU     RCU_GPIOB
#define BSP_ADC_GPIO_PORT    GPIOB
#define BSP_ADC_GPIO_PIN     GPIO_PIN_0

#define BSP_ADC_RCU         RCU_ADC
#define BSP_ADC             ADC
#define BSP_ADC_CHANNEL     ADC_CHANNEL_8

/* 数字输入DO引脚定义 (PB2) */
#define BSP_DO_GPIO_RCU     RCU_GPIOB
#define BSP_DO_GPIO_PORT    GPIOB
#define BSP_DO_GPIO_PIN     GPIO_PIN_2

// 读取DO引脚电平的快捷宏
#define MQ_DO   gpio_input_bit_get(BSP_DO_GPIO_PORT, BSP_DO_GPIO_PIN)

// ADC采样次数,多次采样取平均可以减小波动
#define SAMPLES   5

// 函数声明
void Module_BSP_Init(void);
int Get_Adc_MQ9_Value(void);
int Get_MQ9_Percentage_value(void);
char Get_MQ9_DO(void);

#endif /* BSP_CODE_BSP_MQ9_H_ */

3.2 初始化函数 (bsp_mq9.c)

任何外设使用前都要初始化,ADC和GPIO也不例外。Module_BSP_Init 函数就是干这个的。

void Module_BSP_Init(void)
{
    /* 1. 打开时钟开关 */
    MODULE_BSP_RCU_ENABLE();

    /* 2. 配置DO引脚为浮空输入模式 */
    gpio_mode_set(BSP_DO_GPIO_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, BSP_DO_GPIO_PIN);

    /* 3. 配置ADC时钟为PCLK2的4分频 */
    adc_clock_config(ADC_ADCCK_PCLK2_DIV4);

    /* 4. 配置AO引脚(PB0)为模拟输入模式 */
    gpio_mode_set(BSP_ADC_GPIO_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, BSP_ADC_GPIO_PIN);

    /* 5. 配置ADC工作模式 */
    adc_special_function_config(ADC_SCAN_MODE, ENABLE); // 使能扫描模式(多通道时用)
    adc_data_alignment_config(ADC_DATAALIGN_RIGHT);    // 数据右对齐,方便读取
    adc_resolution_config(ADC_RESOLUTION_12B);         // 12位分辨率,值范围0-4095

    /* 6. 配置ADC规则组(我们只用1个通道) */
    adc_channel_length_config(ADC_ROUTINE_CHANNEL, 1); // 规则组通道数为1
    adc_external_trigger_config(ADC_ROUTINE_CHANNEL, EXTERNAL_TRIGGER_DISABLE); // 禁用外部触发
    adc_software_trigger_enable(ADC_ROUTINE_CHANNEL); // 使能软件触发(即用代码启动转换)

    /* 7. 最后,使能ADC */
    adc_enable();
}

提示:初始化顺序有个小讲究,一般是先开时钟,再配置GPIO,最后配置和使能ADC核心。另外,我们把ADC配置为软件触发,这样主动权就在我们的程序手里,想什么时候采样就什么时候采样。

3.3 核心数据读取函数

初始化好了,就能读数据了。我们分三层来实现,从最底层的单次ADC读取,到稳定的平均值获取,再到直观的百分比计算。

第一层:单次ADC读取 (ADC_GET) 这是一个静态函数,只在文件内部使用,负责完成一次完整的ADC转换。

static int ADC_GET(void)
{
    int gAdcResult = 0;
    int timeOut = 1000; // 超时计数器,防止卡死

    // 1. 指定要采样的通道(通道8)和采样时间
    adc_routine_channel_config(0, BSP_ADC_CHANNEL, ADC_SAMPLETIME_14POINT5);
    // 2. 软件触发,启动一次转换
    adc_software_trigger_enable(ADC_ROUTINE_CHANNEL);

    // 3. 等待转换完成标志位
    while(!adc_flag_get(ADC_FLAG_EOC) && timeOut--)
    {
        delay_us(1);
    }

    // 4. 清除标志位,为下次转换准备
    adc_flag_clear(ADC_FLAG_EOC);

    if(!timeOut) // 如果超时了,说明转换可能出了问题
    {
        printf("ADC_GET Failed!!!\r\n");
        return 0;
    }

    // 5. 读取转换结果
    gAdcResult = adc_routine_data_read();
    return gAdcResult;
}

第二层:获取稳定的ADC平均值 (Get_Adc_MQ9_Value) 传感器读数可能会有微小波动,连续采样5次然后取平均,得到的值会更稳定。

int Get_Adc_MQ9_Value(void)
{
    uint32_t Data = 0;
    int i;

    for(i = 0; i < SAMPLES; i++)
    {
        Data += ADC_GET(); // 累加5次采样值
        delay_ms(5);       // 每次采样间隔5ms,让传感器稳定一下
    }

    Data = Data / SAMPLES; // 计算平均值
    return Data;
}

第三层:计算浓度百分比 (Get_MQ9_Percentage_value) ADC读出来的是0-4095的数字,我们把它转换成0-100%的百分比,看起来更直观。注意:这个百分比是相对ADC量程的,不是精确的ppm浓度值。要得到实际浓度,需要根据传感器数据手册进行标定和公式换算。

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

    adc_new = Get_Adc_MQ9_Value(); // 获取稳定的ADC值
    Percentage_value = ((float)adc_new / (float)adc_max) * 100.f; // 转换为百分比
    return Percentage_value;
}

第四层:读取数字报警信号 (Get_MQ9_DO) 这个就简单了,直接读取PB2引脚的电平状态。模块上的电位器拧到不同位置,报警阈值就不同。

char Get_MQ9_DO(void)
{
    if( MQ_DO == 0 ) // 根据你的模块逻辑判断,这里假设DO输出0表示正常
    {
        return 0; // 浓度未超标
    }
    else
    {
        return 1; // 浓度超标,报警
    }
}

注意:DO引脚输出0还是1表示报警,取决于模块的具体设计。最好用万用表量一下,或者看模块说明书。上面的代码是一个示例,你可能需要根据实际情况调整判断逻辑。

4. 功能验证:在主程序中调用并打印结果

驱动函数都写好了,最后就是在主函数里把它们用起来。我们在 main.c 里创建一个简单的循环,每隔半秒读取一次传感器数据并打印到串口,这样就能在电脑的串口助手上实时观察了。

#include "gd32vw55x.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "gd32vw553h_eval.h"
#include "bsp_mq9.h" // 包含我们刚写的驱动头文件

int main(void)
{
    // 系统初始化(时钟、延时、LED、串口等)
    systick_config();
    eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL3_PRIO1);
    gd_eval_led_init(LED1);
    gd_eval_com_init(EVAL_COM0); // 初始化串口0,用于打印

    // 初始化MQ-9传感器
    Module_BSP_Init();
    printf("MQ9 Sensor Initialization Complete\r\n");

    while(1)
    {
        // 读取并打印所有数据
        printf("\n"); // 换行,让每次输出更清晰
        printf("MQ9 ADC Value = %d\r\n", Get_Adc_MQ9_Value());
        printf("MQ9 Percentage = [%d%%]\r\n", Get_MQ9_Percentage_value());
        printf("MQ9 DO State = %d\r\n", Get_MQ9_DO());

        delay_ms(500); // 延时500ms
    }
}

将代码编译、下载到GD32VW553开发板,打开串口调试助手(波特率通常为115200),你就能看到类似下面的输出:

MQ9 Sensor Initialization Complete

MQ9 ADC Value = 2048
MQ9 Percentage = [50%]
MQ9 DO State = 0

MQ9 ADC Value = 2150
MQ9 Percentage = [52%]
MQ9 DO State = 0

恭喜你! 到这里,你已经成功完成了MQ-9传感器在GD32VW553平台上的驱动移植。你可以尝试向传感器附近呼一口气(含二氧化碳)或者使用打火机释放少量气体(注意安全,远离明火!),观察ADC值和百分比的变化。同时,调节模块上的蓝色电位器,改变报警阈值,看看DO State是否会在浓度变化时从0跳变为1。

这个实战项目把硬件连接、ADC配置、数据滤波和状态判断都串起来了,是一个很好的嵌入式传感器入门案例。你可以基于这个框架,去尝试驱动其他的模拟或数字传感器。

更多推荐