简介这份资源面向嵌入式开发初学者与气体检测应用开发者围绕MQ137氨气检测模块与STM32的集成方案展开提供从原理到代码落地的完整参考。包内共37个文件以pdf技术文档、c源码、hex固件、uv2工程文件及lst、obj等编译中间文件为主压缩包约2.05MB涵盖模块说明书、气体传感器通用原理图、ADC0809资料以及模拟量与TTL输出两套测试程序便于直接导入Keil环境编译验证。资源重点解决传感器信号采集、浓度换算与接口配置等实际问题读者可据此理解金属氧化物半导体传感器的工作机理掌握ADC采样与GPIO脉冲读取两种实现路径并参考校准与补偿思路搭建氨气监测系统。目前已有3784人学习下载适合需要快速上手气体检测项目的开发者参考借鉴。1. 从一颗传感器到整条链路MQ137 氨气检测到底怎么落地车间里氨气泄漏的报警器响了运维跑过去一看传感器输出飘得离谱换了一颗新的还是老样子。这种场景在气体检测项目里太常见了——问题往往不在传感器本身而在从模拟前端到 ADC 采样再到浓度换算的整条链路上。MQ137 是一颗半导体式氨气传感器敏感材料在接触氨气后电导率发生变化配合负载电阻把电导变化转成电压信号再由 MCU 采集换算成 ppm 浓度。它便宜、驱动简单、响应快适合做工业现场的氨气泄漏预警、养殖场氨气监测、实验室通风联动这类场景。但便宜也意味着它挑电路、挑温湿度、挑标定方法。这份资源把 MQ137 的说明书要点和 STM32 上的实现代码打包在一起对正在做嵌入式气体检测的开发者来说省去了从数据手册里抠参数、从零搭 ADC 采样和标定流程的时间。下面按“先搞懂原理、再动手接线写代码、最后把坑填平”的顺序拆开讲。2. MQ137 的敏感机理与 STM32 采样链路设计2.1 半导体气敏原理与负载电阻选型MQ137 的核心是一层二氧化锡SnO2敏感膜在洁净空气中电导率很低当氨气分子吸附到敏感膜表面时表面氧物种被消耗载流子浓度上升电导率随之增大。这个变化不是线性的而是近似服从幂律关系。传感器模块通常把敏感元件和一颗负载电阻RL串联组成分压电路输出一个随氨气浓度变化的模拟电压。负载电阻的取值直接决定输出电压范围和灵敏度RL 太小输出电压摆幅不够ADC 分辨率浪费RL 太大输出电压容易饱和高浓度区间失去区分度。常见做法是先用 10kΩ 的精密电阻试在目标浓度范围内测一遍输出电压再根据实际摆幅调整到 4.7kΩ 或 20kΩ。这里有个容易被忽略的点MQ137 的加热丝需要 5V 供电加热电流在 150mA 左右如果和 MCU 共用一路 5V 且走线太细加热丝启停时会在电源上产生纹波直接耦合进模拟输出。我一般会在加热丝供电脚旁边并一颗 100μF 电解电容加一颗 0.1μF 陶瓷电容模拟输出到 ADC 之间再串一个 RC 低通截止频率设在 10Hz 左右把高频噪声压下去。2.2 STM32 ADC 多通道采集与加热丝 PWM 控制STM32 这边ADC 用规则通道扫描模式把 MQ137 模拟输出接到某个 ADC 通道同时把加热丝电压监控也接一路方便判断加热丝是否正常工作。采样时间要设够MQ137 输出阻抗在负载电阻量级ADC 采样保持电容需要足够时间充电采样周期设到 71.5 个 ADC 时钟周期以上比较稳妥。加热丝控制用 TIM 输出 PWM占空比接近 100% 但留一点余量避免长时间满占空比导致驱动管发热。下面这段代码是 ADC 多通道 DMA 采集的初始化骨架基于 HAL 库通道号按实际接线改。// ADC1 扫描模式 DMA 循环采集通道0接MQ137模拟输出通道1接加热丝电压监控 static void MX_ADC1_Init(void) { ADC_ChannelConfTypeDef sConfig {0}; hadc1.Instance ADC1; hadc1.Init.ScanConvMode ADC_SCAN_ENABLE; // 多通道扫描 hadc1.Init.ContinuousConvMode ENABLE; // 连续转换 hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_SOFTWARE_START; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 2; // 两个通道 HAL_ADC_Init(hadc1); // 通道0MQ137 模拟输出采样时间拉长以适配高输出阻抗 sConfig.Channel ADC_CHANNEL_0; sConfig.Rank ADC_REGULAR_RANK_1; sConfig.SamplingTime ADC_SAMPLETIME_239CYCLES_5; HAL_ADC_ConfigChannel(hadc1, sConfig); // 通道1加热丝电压监控采样时间可以短一些 sConfig.Channel ADC_CHANNEL_1; sConfig.Rank ADC_REGULAR_RANK_2; sConfig.SamplingTime ADC_SAMPLETIME_71CYCLES_5; HAL_ADC_ConfigChannel(hadc1, sConfig); HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_dma_buf, 2); }这段代码里ScanConvMode打开后ADC 会按 Rank 顺序依次转换两个通道DMA 把结果搬到adc_dma_buf数组adc_dma_buf[0]是 MQ137 输出adc_dma_buf[1]是加热丝电压。采样时间选 239.5 周期是因为 MQ137 模块输出阻抗在 10kΩ 级别STM32 的 ADC 采样保持电容只有几 pF充电时间常数和源阻抗成正比采样时间不够会导致读数偏低且跳变。加热丝 PWM 用 TIM3 通道1频率设在 1kHz 左右占空比 95%避免驱动 MOS 管完全导通时压降不够导致加热丝功率不足。2.3 浓度换算从 ADC 原始值到 ppm 的完整公式拿到 ADC 原始值后先转成电压Vout adc_value * 3.3f / 4095.0f。然后算传感器电阻MQ137 模块的负载电阻 RL 接在输出和 GND 之间传感器电阻 Rs 满足Rs (Vc - Vout) / Vout * RL其中 Vc 是模块供电电压通常是 5V。得到 Rs 后用数据手册里的洁净空气校准值 R0 做归一化ratio Rs / R0。浓度换算用幂函数拟合ppm a * pow(ratio, b)a 和 b 是两个标定系数不同批次传感器会有差异必须实测标定。下面这段换算代码把浮点运算放在 MCU 上跑如果 MCU 没有 FPU建议改成定点或查表。// MQ137 浓度换算ADC值 - 电压 - Rs - ratio - ppm float mq137_calc_ppm(uint16_t adc_val, float rl, float r0, float vc) { float vout adc_val * 3.3f / 4095.0f; // ADC转电压 if (vout 0.01f) return 0.0f; // 防止除零 float rs (vc - vout) / vout * rl; // 传感器电阻 float ratio rs / r0; // 归一化 // 标定系数 a、b 需根据实测数据拟合这里给的是典型值 float ppm 12.5f * powf(ratio, -1.8f); return ppm; }参数说明rl是负载电阻实际阻值用万用表测准r0是洁净空气中的传感器电阻需要在已知洁净空气环境下采集 Rs 并记录vc是模块供电电压实测 5V 不一定准最好用 ADC 另一路测一下。powf的指数 -1.8 是负值因为氨气浓度升高时 Rs 下降ratio 减小ppm 增大。如果发现低浓度段读数偏高多半是 R0 偏小重新在洁净空气中标定一次。3. 标定流程与 STM32 端代码实现细节3.1 两点标定法洁净空气与标准氨气MQ137 出厂不会给每颗传感器单独标定 R0必须自己标。常见做法是两点标定第一点在洁净空气中采集稳定后的 Rs 作为 R0第二点用已知浓度的标准氨气比如 50ppm 或 100ppm采集 Rs 并记录对应的 ppm 值。有了两组数据代入ppm a * ratio^b解出 a 和 b。如果只有一点标准气可以固定 b 为典型值 -1.8反推 a。标定时要注意传感器上电后需要预热MQ137 的加热丝要连续通电 24 小时以上才能进入稳定状态刚上电那几小时读数漂移很大拿这个数据标定等于白标。我一般会让模块连续跑 48 小时每隔一小时记录一次洁净空气下的 Rs等 Rs 变化小于 2% 再开始标定。标准氨气那边流量要控制稳定气室体积别太小否则传感器还没响应完气就散了。3.2 温度补偿与滑动滤波在 MCU 上的实现MQ137 对温度很敏感环境温度每变化 10°CRs 可能漂移 5% 到 10%。如果项目现场温度波动大必须加温度补偿。常见做法是外挂一颗 DS18B20 或 SHT30把温度读进来用分段线性补偿或查表法修正 Rs。补偿系数需要在自己标定的温度点上测没有通用值。滤波方面ADC 原始值用滑动平均窗口长度取 16 或 32既能压噪声又不会让响应太迟钝。下面这段代码把温度补偿和滑动滤波串在一起放在主循环里每 100ms 调一次。#define FILTER_WIN 16 static uint16_t adc_win[FILTER_WIN]; static uint8_t win_idx 0; // 滑动平均滤波 uint16_t mq137_filter(uint16_t new_val) { adc_win[win_idx] new_val; win_idx (win_idx 1) % FILTER_WIN; uint32_t sum 0; for (int i 0; i FILTER_WIN; i) sum adc_win[i]; return (uint16_t)(sum / FILTER_WIN); } // 温度补偿根据实测温度修正 Rs补偿系数需自行标定 float mq137_temp_compensate(float rs, float temp_c) { // 以25°C为基准每偏离1°C按经验系数修正系数需实测 float k 1.0f 0.008f * (temp_c - 25.0f); return rs / k; }FILTER_WIN取 16 是折中窗口再大响应会明显滞后报警场景下滞后比噪声更危险。温度补偿系数 0.008 只是示例实际值要在恒温箱里跑几个温度点拟合出来。如果现场温度变化不超过 ±5°C可以不做补偿但要在说明书里注明工作温度范围。3.3 串口输出与上位机联调调试阶段把 ADC 原始值、Rs、ratio、ppm、温度都通过串口打出来格式用逗号分隔方便上位机画曲线。STM32 的串口用 DMA 发送避免printf阻塞主循环。下面这段是串口格式化输出的片段用snprintf拼字符串再 DMA 发出去。// 串口DMA发送调试数据格式adc,rs,ratio,ppm,temp void mq137_send_debug(uint16_t adc, float rs, float ratio, float ppm, float temp) { char buf[96]; int len snprintf(buf, sizeof(buf), %u,%.1f,%.3f,%.1f,%.1f\r\n, adc, rs, ratio, ppm, temp); HAL_UART_Transmit_DMA(huart1, (uint8_t *)buf, len); }上位机用串口助手或自己写个 Python 脚本把数据存成 CSV用 matplotlib 画时间序列。标定时把标准气通入和撤出的时刻标在曲线上能直观看到响应时间和恢复时间。MQ137 的响应时间通常在 10 秒到 30 秒之间恢复时间更长如果发现响应特别慢检查气路有没有漏气或者传感器是不是老化了。4. 避坑与排查MQ137 实战中翻车的五个典型场景4.1 上电读数一直飘预热时间不够现象模块刚上电串口打出来的 ppm 从几百跳到几千过几分钟又慢慢降下来。原因MQ137 加热丝需要把敏感膜加热到工作温度冷态下电导率完全不对而且敏感膜表面吸附的水汽和杂质需要时间脱附。解决上电后至少预热 24 小时再采信数据标定前预热 48 小时。如果项目要求快速上线可以在固件里加一个预热计时器前 24 小时只输出“预热中”状态不触发报警。4.2 ADC 读数跳变严重电源纹波在作怪现象ADC 原始值在几十个码之间无规律跳动ppm 曲线毛刺很大。原因加热丝 PWM 开关时在 5V 电源上产生纹波通过模拟输出耦合进 ADC或者 ADC 参考电压不稳。解决加热丝供电和模拟供电分开走线加热丝脚并 100μF 0.1μF 电容模拟输出加 RC 低通ADC 参考电压用外部基准或至少加 10μF 钽电容。如果还跳把加热丝 PWM 频率降到 100Hz 以下避开 ADC 采样频段。4.3 标定后低浓度段误差大R0 测错了现象洁净空气下 ppm 显示不为零或者 10ppm 标准气显示 30ppm。原因R0 是在未充分预热的传感器上测的或者洁净空气本身不洁净比如实验室里有氨水挥发。解决标定 R0 前确认环境氨气浓度低于传感器检测下限用风扇吹一下或者放到通风橱里传感器必须预热满 48 小时R0 取多次测量的平均值别用单次值。4.4 高浓度后读数回不来传感器饱和了现象通入 200ppm 氨气后撤掉ppm 读数长时间停在几十 ppm 下不去。原因MQ137 在高浓度下敏感膜表面吸附饱和恢复需要时间如果长期暴露在高浓度还会加速老化。解决避免传感器长时间暴露在超过量程的高浓度氨气中恢复期间可以加热丝断电几秒再上电加速脱附如果经常测高浓度选量程更大的传感器或者加稀释气路。4.5 串口数据对不上浮点打印格式踩坑现象串口打出来的 ppm 全是 0 或者乱码。原因printf浮点支持没开或者snprintf的%f在 Keil 里默认不链接浮点库。解决在 Keil 的 Target 选项里勾选 Use MicroLIB或者在工程设置里打开浮点 printf 支持更稳妥的做法是把浮点转成整数再打印比如ppm * 10转成整数上位机再除以 10。5. 进阶技巧用查表法替代浮点运算与长期稳定性验证STM32F0/F1 这类没有 FPU 的 MCU 上跑powf很吃力一次换算可能几百微秒如果主循环里还有其他任务容易卡顿。我一般会把ppm a * ratio^b这条曲线离散成查表在 ratio 的范围内取 64 个点算出对应的 ppm存成const uint16_t ppm_table[64]运行时用 ratio 算索引再做线性插值。这样一次换算只要几微秒精度损失在 2% 以内对报警场景完全够用。下面这段是查表加插值的实现。// 查表法浓度换算ratio - ppm表长64线性插值 #define TABLE_SIZE 64 static const uint16_t ppm_table[TABLE_SIZE] { /* 标定后填入 */ }; float mq137_lookup_ppm(float ratio) { // ratio 范围假设 0.1 ~ 10.0映射到表索引 float idx_f (ratio - 0.1f) / (10.0f - 0.1f) * (TABLE_SIZE - 1); if (idx_f 0) idx_f 0; if (idx_f TABLE_SIZE - 1) idx_f TABLE_SIZE - 1; uint16_t idx (uint16_t)idx_f; float frac idx_f - idx; if (idx TABLE_SIZE - 1) return ppm_table[TABLE_SIZE - 1]; return ppm_table[idx] * (1 - frac) ppm_table[idx 1] * frac; }表的数据来源就是标定后的幂函数曲线用 Python 或 Excel 生成直接粘进代码。长期稳定性验证方面我习惯在项目现场部署一台参考仪器每隔一周和 MQ137 的读数对比一次记录偏差。如果偏差超过 15%就重新标定 R0。传感器寿命一般在 2 到 3 年但如果在高湿度或高浓度环境下用可能一年就明显漂移。从那以后我每次做气体检测项目都会在固件里留一个“维护模式”长按按键进入自动记录当前洁净空气下的 Rs 并更新 R0不用拆机就能重新标定。希望帮到你。本文还有配套的精品资源点击获取