当前位置: 首页 > news >正文

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

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

最近在做一个智能家居的环境监测项目,需要检测厨房的可燃气体泄漏。我选用了MQ-9传感器,搭配国产的天空星HC32F4A0PITB开发板。整个过程从看资料、配置引脚、写驱动到数据读取,踩了几个小坑,但最终调通了。今天我就把整个驱动移植和浓度检测的实战过程分享出来,如果你也在用HC32或者类似的ARM Cortex-M芯片做传感器开发,这篇教程应该能帮你省不少时间。

咱们的目标很明确:让HC32F4A0能通过MQ-9传感器,既读取模拟量浓度值,也能判断数字量报警信号,最终在串口上打印出气体浓度的百分比。

1. 认识MQ-9传感器:它如何“闻”到气体?

在写代码之前,咱们得先搞清楚手头的“兵器”。MQ-9模块其实挺简单的,核心是一小片二氧化锡(SnO2)材料。这材料有个特点:在干净空气里导电性很差,但一旦遇到一氧化碳、甲烷这类可燃气体,它的电导率就会蹭蹭往上涨。模块通过测量这个电导率的变化,就能知道气体浓度了。

模块上有两个输出口,这也是我们编程要处理的两个关键点:

  • AO (Analog Output) 模拟输出:这个引脚会输出一个电压信号,电压值随着气体浓度变化。浓度越高,电压越高。我们需要用单片机的ADC(模数转换器)功能来读取这个电压,并换算成浓度值。这是做定量分析的关键。
  • DO (Digital Output) 数字输出:这个引脚更“直白”,它内部有个比较器。当气体浓度超过你通过模块上蓝色电位器设定的阈值时,DO引脚就直接输出高电平(比如3.3V);低于阈值就输出低电平(0V)。我们直接用单片机的GPIO读取高低电平就行,适合做简单的报警开关。

模块需要供电,工作电压是3.3V到5V,和咱们的开发板是兼容的。它工作时电流大约150mA,加热丝需要一定的功率来维持敏感材料的工作温度。

注意:MQ-9传感器需要一段预热时间(俗称“烧机”),刚上电时读数不稳定,通常需要预热几十秒到几分钟,具体看数据手册。实际项目中,上电后最好先延时一段时间再开始正式采样。

2. 硬件连接与引脚规划

接线很简单,模块就4根线(VCC, GND, DO, AO)。关键是要把AO和DO接到开发板正确的引脚上。

根据原始资料里的代码,作者是这么规划的:

  • AO引脚:连接到了HC32F4A0的PC1引脚。为什么选这个脚?因为PC1复用了ADC1的第11输入通道(ADC_CH11),正好用来接模拟信号。
  • DO引脚:连接到了PA1引脚。这就是一个普通的GPIO输入引脚,用来读取高低电平。

在你的实际项目中,完全可以换成其他具有ADC功能的引脚和任意GPIO,只要在代码里把引脚定义改对就行。下面这个表格是代码里用到的引脚定义,一目了然:

信号名称开发板引脚功能对应宏定义
AO (模拟输出)PC1ADC1 通道11输入PORT_MQ9_AO,GPIO_MQ9_AO
DO (数字输出)PA1GPIO 输入PORT_MQ9_DO,GPIO_MQ9_DO
VCC3.3V/5V电源-
GNDGND-

3. 驱动代码移植与解析(手把手教学)

拿到原始代码,咱们不能直接照搬,得理解每一行在干什么。我把它拆解成几个核心函数,咱们一个一个来看。

3.1 头文件定义 (bsp_mq9.h)

头文件就像一份“配置清单”,把所有用到的参数和函数声明都放在这里。

#ifndef _BSP_MQ9_H_ #define _BSP_MQ9_H_ #include "hc32_ll.h" // 1. ADC相关配置 #define FCG_MQ9_ADC FCG3_PERIPH_ADC1 // 使能ADC1的时钟 #define PORT_ADC CM_ADC1 // ADC1外设实例 #define CHANNEL_ADC ADC_CH11 // 使用第11通道 // 2. 引脚定义 #define PORT_MQ9_AO GPIO_PORT_C // AO引脚所在端口:C口 #define GPIO_MQ9_AO GPIO_PIN_01 // AO引脚:PC1 #define GPIO_MS1100_AO_REMAP ADC12_PIN_PC1 // PC1的ADC功能重映射(这个名称可能是笔误,但功能是映射) #define PORT_MQ9_DO GPIO_PORT_A // DO引脚所在端口:A口 #define GPIO_MQ9_DO GPIO_PIN_01 // DO引脚:PA1 // 3. 采样参数 #define SAMPLES 30 // ADC采样次数,用于求平均值滤波 // 4. 函数声明 void ADC_MQ9_Init(void); // 初始化ADC和GPIO unsigned int Get_Adc_MQ9_Value(void); // 获取ADC原始值(已滤波) unsigned int Get_MQ9_Percentage_value(void); // 获取气体浓度百分比 char Get_MQ9_DO_value(void); // 获取DO引脚数字状态 #endif

这里有个细节,GPIO_MS1100_AO_REMAP这个宏的名字看起来像是其他传感器(MS1100)的,但在MQ-9的代码里它被用来做PC1的ADC通道重映射。咱们理解它的作用就行,在实际项目中如果改名会让代码更清晰。

3.2 初始化函数 ADC_MQ9_Init

这个函数是驱动的核心,负责把单片机的ADC和GPIO配置好,让它们能正确读取传感器信号。

void ADC_MQ9_Init(void) { // 第一步:解锁寄存器写保护(HC32系列很多外设寄存器默认是锁定的) LL_PERIPH_WE(LL_PERIPH_ALL); stc_gpio_init_t stcGpioInit; (void)GPIO_StructInit(&stcGpioInit); // 用默认值初始化结构体 // 第二步:配置AO引脚(PC1)为模拟输入模式 stcGpioInit.u16PinAttr = PIN_ATTR_ANALOG; // 关键!必须设为模拟属性,否则ADC读不到 stcGpioInit.u16PinDir = PIN_DIR_IN; // 方向为输入 // 其他参数如上下拉、中断等,在模拟模式下通常无效,但按规范关闭 stcGpioInit.u16PullUp = PIN_PU_OFF; stcGpioInit.u16ExtInt = PIN_EXTINT_OFF; GPIO_Init(PORT_MQ9_AO, GPIO_MQ9_AO, &stcGpioInit); // 第三步:配置DO引脚(PA1)为数字输入模式,并开启内部上拉 stcGpioInit.u16PinAttr = PIN_ATTR_DIGITAL; // 数字属性 stcGpioInit.u16PullUp = PIN_PU_ON; // 开启上拉,保证引脚有确定电平 GPIO_Init(PORT_MQ9_DO, GPIO_MQ9_DO, &stcGpioInit); // 第四步:使能ADC1的时钟(外设要工作,必须先给时钟) FCG_Fcg3PeriphClockCmd(FCG_MQ9_ADC, ENABLE); // 第五步:配置ADC1的基本参数 stc_adc_init_t stcAdcInit; (void)ADC_StructInit(&stcAdcInit); stcAdcInit.u16ScanMode = ADC_MD_SEQA_SINGLESHOT; // 单次扫描模式,适合单通道采样 stcAdcInit.u16Resolution = ADC_RESOLUTION_12BIT; // 12位分辨率,读数值范围0-4095 stcAdcInit.u16DataAlign = ADC_DATAALIGN_RIGHT; // 数据右对齐,方便处理 ADC_Init(PORT_ADC, &stcAdcInit); // 第六步:将ADC通道11映射到PC1引脚(告诉ADC,第11通道的信号来自PC1) ADC_ChRemap(PORT_ADC, CHANNEL_ADC, GPIO_MS1100_AO_REMAP); // 第七步:使能我们需要的ADC通道(序列A,通道11) ADC_ChCmd(PORT_ADC, ADC_SEQ_A, CHANNEL_ADC, ENABLE); }

提示:配置AO引脚时,PIN_ATTR_ANALOG(模拟属性)是必须的。如果错误地配置为数字属性,ADC将无法正确读取引脚上的模拟电压,你可能会一直读到0或者一个固定的错误值。这个坑我踩过,调试了半天才发现是引脚模式设错了。

3.3 核心数据读取函数

初始化完成后,就要开始读数据了。原始代码提供了几个层次的数据读取函数,咱们来捋一捋。

第一层:单次ADC采样 (adc_GET)这个函数负责启动一次ADC转换,并等待转换完成。它加入了超时机制,防止程序卡死。

unsigned int adc_GET(void) { static uint16_t adcValue; __IO uint32_t TimeOut = 0UL; ADC_Start(PORT_ADC); // 发出启动转换命令 // 等待转换完成标志位EOCA(End Of Conversion A)置位 while(SET != ADC_GetStatus(PORT_ADC, ADC_FLAG_EOCA)) { if(TimeOut > 500) // 超时判断,大约500ms { ADC_ClearStatus(PORT_ADC, ADC_FLAG_EOCA); ADC_Stop(PORT_ADC); printf("ERROR = ADC 等待序列 A 转换【超时】!!\r\n"); return 0; // 超时返回0 } TimeOut++; delay_ms(1); // 延时1ms,注意这个delay_ms需要你自己实现或由系统提供 } ADC_ClearStatus(PORT_ADC, ADC_FLAG_EOCA); // 清除标志位,为下次转换准备 adcValue = ADC_GetValue(PORT_ADC, CHANNEL_ADC); // 读取转换结果 return adcValue; }

第二层:均值滤波 (Get_Adc_MQ9_Value)传感器信号难免有毛刺,直接读一次值跳动会很大。常见的做法是连续采样多次,然后取平均值。这个函数就干了这件事。

unsigned int Get_Adc_MQ9_Value(void) { uint32_t Data = 0; for(int i = 0; i < SAMPLES; i++) // SAMPLES在头文件里定义为30 { Data += adc_GET(); // 累加30次采样值 delay_ms(5); // 每次采样间隔5ms,避免过于密集 } Data = Data / SAMPLES; // 求平均值 return Data; }

注意SAMPLES(采样次数)和delay_ms(5)(采样间隔)这两个值是滤波的关键参数。30次平均对于MQ-9这类变化相对缓慢的气体信号通常足够了。如果觉得响应速度慢,可以适当减少次数,比如10次;如果觉得数据跳动大,可以增加次数或调整间隔。这需要在实际环境中测试调整。

第三层:换算为百分比 (Get_MQ9_Percentage_value)ADC读出来的是原始数字量(0-4095),我们需要把它转换成更有意义的浓度百分比。

unsigned 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值 // 核心换算公式: (当前ADC值 / 最大ADC值) * 100% Percentage_value = ((float)adc_new / (float)adc_max) * 100.f; return Percentage_value; }

这里需要强调一下:这个百分比是电压百分比,不是气体浓度的真实ppm值!它表示当前传感器输出电压占ADC量程(通常是VREF,比如3.3V)的比例。要得到真实的ppm浓度,你需要根据MQ-9的数据手册,找到其灵敏度特性曲线(Rs/R0 vs. ppm),并通过公式进行换算。这个换算比较复杂,涉及对数计算和温度补偿。对于很多报警应用,用这个电压百分比设定一个阈值已经够用了。

第四层:读取数字报警信号 (Get_MQ9_DO_value)这个就简单了,直接读GPIO电平。

char Get_MQ9_DO_value(void) { if( GPIO_ReadInputPins(PORT_MQ9_DO, GPIO_MQ9_DO) == RESET ) // 读取PA1引脚电平 { return 0; // 低电平,表示气体浓度未超过模块设定的阈值 } else { return 1; // 高电平,表示浓度超标,报警! } }

模块上那个蓝色的电位器就是用来调节这个报警阈值的。顺时针拧,灵敏度降低(需要更高浓度才报警);逆时针拧,灵敏度升高。

4. 在main函数中调用与验证

驱动写好了,最后就是在主函数里把它们用起来。原始代码给的例子很清晰:

#include "board.h" #include "bsp_uart.h" #include "stdio.h" #include "bsp_mq9.h" // 包含我们刚写的驱动头文件 int32_t main(void) { board_init(); // 开发板基础初始化(系统时钟、滴答定时器等) uart1_init(115200U); // 初始化串口1,用于打印数据 ADC_MQ9_Init(); // 初始化MQ-9传感器(ADC和GPIO) printf("ADC Demo Start...\r\n"); while(1) { // 读取并打印气体浓度百分比 printf("可燃气体含量 = %d%%\r\n\n", Get_MQ9_Percentage_value() ); delay_ms(1000); // 每秒打印一次 } }

把代码编译下载到天空星HC32F4A0开发板,打开串口助手(波特率115200),你就能看到类似这样的输出:

ADC Demo Start... 可燃气体含量 = 15% 可燃气体含量 = 16% ...

这时候,你可以尝试向传感器附近呼一口气(含二氧化碳)或者用打火机(注意安全,不要点火!)释放少量丁烷气体,观察百分比数值的变化。同时,你也可以调节模块上的电位器,观察Get_MQ9_DO_value()函数的返回值变化,体验数字报警功能。

整个移植过程就是这样。核心就是理解传感器原理、正确配置ADC、做好数据滤波和换算。代码本身不复杂,但细节决定成败,特别是引脚模式和ADC通道的映射,一定要对照数据手册和代码仔细检查。希望这篇实战教程能帮你顺利把MQ-9用起来。

http://www.jsqmd.com/news/471317/

相关文章:

  • iOS深度定制新纪元:Cowabunga Lite免越狱个性化解决方案
  • SARScape实战:集成GACOS数据优化InSAR大气校正全流程
  • Opencv双边滤波实战:cv2.bilateralFilter在图像去噪与边缘保留中的平衡艺术
  • Ostrakon-VL-8B实战:开发一个微信小程序“AI看图说话”
  • 2026年AI营销服务商选型指南:GEO赛道助力品牌增长 - 行业分析师666
  • [CARLA地图全解析] - 从基础加载到图层切换的实战指南
  • 保姆级教程:手把手教你快速部署Qwen3-0.6B-FP8文本生成模型
  • Vue3 中Provide与Inject的响应式状态管理实践
  • 深度分析江苏靠谱的压力容器钢板厂家,07MnNiMoDR、15CrMo钢板揭秘 - mypinpai
  • wan2.1-vae提示词知识图谱:构建行业术语→风格标签→参数推荐的映射关系
  • VBA Dictionary实战宝典 | 解锁键值对数据处理的6大高效场景
  • 探讨小型家用电梯生产厂,哪家合作案例多更靠谱 - 工业推荐榜
  • LED台灯照度闭环控制系统设计与实现
  • 使用.NET Core封装Lingbot-Depth-Pretrain-ViTL-14模型为Windows服务
  • Cosmos-Reason1-7B实战教程:构建物理常识评测数据集的自动化标注流程
  • AI股票分析师与MySQL数据库联动实战
  • 定制指挥控制台操作台可靠的服务商怎么选 - mypinpai
  • 聊聊长沙ISO环境管理体系认证公司,哪家性价比高 - 工业品牌热点
  • 从模型到应用:基于快马平台构建OpenClaw配置管理与控制仿真系统
  • 使用InstallShield将.inf和.sys驱动文件集成到setup.exe的完整指南
  • 探讨五日游跟团旅行社费用,哪个品牌价格更亲民? - myqiye
  • TIMER-XL:突破长上下文限制的Transformer时序预测新范式
  • GD32 IAP实战:从Keil配置到Boot与App无缝切换
  • 利用Zotero插件实现Word文献引用到LaTeX的自动化转换
  • Qwen3.5-35B-A3B-AWQ-4bit企业落地应用:电商商品图识别、教育题图解析、医疗影像初筛
  • 开源SIEM系统选型指南:五大解决方案深度解析
  • 海康威视WEB插件3D放大功能异常排查与接口修复指南
  • 图形学光栅化技术文档:抗锯齿、频域分析与 Z 缓冲
  • 3种方法解锁专业功能:WeMod-Patcher完全使用指南
  • C语言(03)——从兔子繁殖到算法优化:斐波那契数列的深度剖析与实践