一、MQ-2传感器工作原理
1.1 MQ-2特性参数
| 参数 | 数值 | 说明 |
|---|---|---|
| 检测气体 | 烟雾、液化气、丙烷、氢气、甲烷 | 多种可燃气体 |
| 工作电压 | 5V DC | 加热电压 |
| 预热时间 | ≥48小时 | 达到稳定工作状态 |
| 响应时间 | ≤10秒 | 传感器响应速度 |
| 恢复时间 | ≤30秒 | 传感器恢复时间 |
| 负载电阻 | 10kΩ | 典型值 |
1.2 输出特性
- 模拟输出:电压随气体浓度变化(0.1V-4.5V)
- 数字输出:DOUT引脚输出TTL电平(浓度超过阈值时输出低电平)
- 加热电路:需要5V加热电压,预热后才能稳定工作
二、硬件连接
2.1 STM32F103C8T6与MQ-2连接
MQ-2模块引脚 STM32F103引脚 说明
─────────────────────────────────────────────
VCC 5V 5V电源(加热需要)
GND GND 电源地
AO PA0 ADC1通道0,模拟输出
DO PB0 数字输出(阈值报警)
─────────────────────────────────────────────
2.2 电路设计要点
- 加热电路:MQ-2需要5V加热电压,不能直接接3.3V
- 分压电路:模块内部已有分压电阻,AO输出0-5V
- 电平转换:STM32 ADC输入范围0-3.3V,需要分压或电平转换
三、完整驱动代码实现
3.1 头文件(mq2_sensor.h)
c
#ifndef __MQ2_SENSOR_H
#define __MQ2_SENSOR_H
#include "stm32f10x.h"
// MQ-2配置参数
#define MQ2_ADC_CHANNEL ADC_Channel_0 // PA0 - ADC1通道0
#define MQ2_DIGITAL_PORT GPIOB
#define MQ2_DIGITAL_PIN GPIO_Pin_0
#define MQ2_ANALOG_PORT GPIOA
#define MQ2_ANALOG_PIN GPIO_Pin_0
// 校准参数(根据实际传感器调整)
#define MQ2_CLEAN_AIR_RATIO 9.83 // 洁净空气中的Rs/R0比值
#define MQ2_LOAD_RESISTANCE 10.0 // 负载电阻RL (kΩ)
#define MQ2_VOLTAGE_SUPPLY 5.0 // 电源电压 (V)
// 气体浓度转换系数(根据datasheet调整)
#define MQ2_LPG_COEFF_A 574.25
#define MQ2_LPG_COEFF_B -2.222
#define MQ2_CO_COEFF_A 36974.0
#define MQ2_CO_COEFF_B -3.109
#define MQ2_SMOKE_COEFF_A 3616.1
#define MQ2_SMOKE_COEFF_B -2.675
// 报警阈值
#define MQ2_ALARM_THRESHOLD 1000 // 烟雾浓度报警阈值 (ppm)
// 函数声明
void MQ2_Init(void);
uint16_t MQ2_ReadADC(void);
float MQ2_GetVoltage(void);
float MQ2_GetResistance(void);
float MQ2_GetRatio(void);
float MQ2_GetLPGppm(void);
float MQ2_GetCOppm(void);
float MQ2_GetSmokeppm(void);
uint8_t MQ2_GetDigitalAlarm(void);
void MQ2_CalibrateR0(float clean_air_ratio);
void MQ2_SetAlarmThreshold(uint16_t threshold);
void MQ2_Task(void);
#endif /* __MQ2_SENSOR_H */
3.2 源文件(mq2_sensor.c)
c
#include "mq2_sensor.h"
#include "delay.h"
#include "usart.h"
// 全局变量
static float R0 = 10.0; // 传感器在洁净空气中的电阻值 (kΩ)
static uint16_t alarm_threshold = MQ2_ALARM_THRESHOLD;
/**
* @brief MQ-2传感器初始化
* @param 无
* @retval 无
*/
void MQ2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
RCC_APB2Periph_ADC1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADC时钟分频
// 2. 配置模拟输入引脚 (PA0)
GPIO_InitStructure.GPIO_Pin = MQ2_ANALOG_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(MQ2_ANALOG_PORT, &GPIO_InitStructure);
// 3. 配置数字输出引脚 (PB0)
GPIO_InitStructure.GPIO_Pin = MQ2_DIGITAL_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_Init(MQ2_DIGITAL_PORT, &GPIO_InitStructure);
// 4. ADC参数配置
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 单通道
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; // 1个通道
ADC_Init(ADC1, &ADC_InitStructure);
// 5. 配置ADC通道
ADC_RegularChannelConfig(ADC1, MQ2_ADC_CHANNEL, 1, ADC_SampleTime_239Cycles5);
// 6. 使能ADC
ADC_Cmd(ADC1, ENABLE);
// 7. ADC校准
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
printf("MQ-2传感器初始化完成\r\n");
}
/**
* @brief 读取ADC值
* @param 无
* @retval ADC转换结果 (0-4095)
*/
uint16_t MQ2_ReadADC(void)
{
uint16_t adc_value = 0;
// 启动ADC转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// 等待转换完成
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
// 读取ADC值
adc_value = ADC_GetConversionValue(ADC1);
// 清除标志位
ADC_ClearFlag(ADC1, ADC_FLAG_EOC);
return adc_value;
}
/**
* @brief 获取电压值
* @param 无
* @retval 电压值 (V)
*/
float MQ2_GetVoltage(void)
{
uint16_t adc_value = MQ2_ReadADC();
float voltage = (float)adc_value * 3.3 / 4095.0; // 假设参考电压3.3V
return voltage;
}
/**
* @brief 获取传感器电阻值
* @param 无
* @retval 传感器电阻值 (kΩ)
*/
float MQ2_GetResistance(void)
{
float voltage = MQ2_GetVoltage();
float rs = 0.0;
if(voltage > 0.1 && voltage < 4.9) {
// Rs = (Vc - Vout) / Vout * RL
rs = (MQ2_VOLTAGE_SUPPLY - voltage) / voltage * MQ2_LOAD_RESISTANCE;
}
return rs;
}
/**
* @brief 获取Rs/R0比值
* @param 无
* @retval Rs/R0比值
*/
float MQ2_GetRatio(void)
{
float rs = MQ2_GetResistance();
float ratio = rs / R0;
return ratio;
}
/**
* @brief 获取液化气浓度 (LPG)
* @param 无
* @retval LPG浓度 (ppm)
*/
float MQ2_GetLPGppm(void)
{
float ratio = MQ2_GetRatio();
float ppm = MQ2_LPG_COEFF_A * pow(ratio, MQ2_LPG_COEFF_B);
return ppm;
}
/**
* @brief 获取一氧化碳浓度 (CO)
* @param 无
* @retval CO浓度 (ppm)
*/
float MQ2_GetCOppm(void)
{
float ratio = MQ2_GetRatio();
float ppm = MQ2_CO_COEFF_A * pow(ratio, MQ2_CO_COEFF_B);
return ppm;
}
/**
* @brief 获取烟雾浓度
* @param 无
* @retval 烟雾浓度 (ppm)
*/
float MQ2_GetSmokeppm(void)
{
float ratio = MQ2_GetRatio();
float ppm = MQ2_SMOKE_COEFF_A * pow(ratio, MQ2_SMOKE_COEFF_B);
return ppm;
}
/**
* @brief 读取数字报警信号
* @param 无
* @retval 0: 报警, 1: 正常
*/
uint8_t MQ2_GetDigitalAlarm(void)
{
return GPIO_ReadInputDataBit(MQ2_DIGITAL_PORT, MQ2_DIGITAL_PIN);
}
/**
* @brief 校准R0值(在洁净空气中运行)
* @param clean_air_ratio: 洁净空气中的Rs/R0比值
* @retval 无
*/
void MQ2_CalibrateR0(float clean_air_ratio)
{
float rs_sum = 0.0;
uint8_t samples = 10;
printf("开始校准R0值,请将传感器置于洁净空气中...\r\n");
for(uint8_t i = 0; i < samples; i++) {
rs_sum += MQ2_GetResistance();
Delay_ms(200);
}
float rs_avg = rs_sum / samples;
R0 = rs_avg / clean_air_ratio;
printf("校准完成!R0 = %.2f kΩ\r\n", R0);
}
/**
* @brief 设置报警阈值
* @param threshold: 报警阈值 (ppm)
* @retval 无
*/
void MQ2_SetAlarmThreshold(uint16_t threshold)
{
alarm_threshold = threshold;
}
/**
* @brief MQ-2传感器主任务
* @param 无
* @retval 无
*/
void MQ2_Task(void)
{
static uint8_t calib_flag = 0;
static uint32_t last_time = 0;
// 上电后先预热
if(!calib_flag && (millis() > 30000)) { // 预热30秒
MQ2_CalibrateR0(MQ2_CLEAN_AIR_RATIO);
calib_flag = 1;
}
// 每秒钟读取一次数据
if(millis() - last_time > 1000) {
last_time = millis();
if(calib_flag) {
float voltage = MQ2_GetVoltage();
float resistance = MQ2_GetResistance();
float ratio = MQ2_GetRatio();
float smoke_ppm = MQ2_GetSmokeppm();
uint8_t alarm = MQ2_GetDigitalAlarm();
printf("MQ-2: 电压=%.2fV, 电阻=%.2fkΩ, Rs/R0=%.2f\r\n",
voltage, resistance, ratio);
printf("烟雾浓度: %.1f ppm, 数字报警: %s\r\n",
smoke_ppm, alarm ? "正常" : "报警");
// 检查是否超过报警阈值
if(smoke_ppm > alarm_threshold) {
printf("警告!烟雾浓度超标!\r\n");
// 这里可以添加报警动作,如点亮LED、蜂鸣器等
}
} else {
printf("MQ-2传感器预热中...\r\n");
}
}
}
3.3 主程序示例(main.c)
c
#include "stm32f10x.h"
#include "delay.h"
#include "usart.h"
#include "mq2_sensor.h"
int main(void)
{
// 初始化系统时钟
SystemInit();
// 初始化延时函数
Delay_Init();
// 初始化串口
USART_Init(115200);
printf("STM32 MQ-2烟雾传感器测试\r\n");
// 初始化MQ-2传感器
MQ2_Init();
while(1)
{
// 执行MQ-2传感器任务
MQ2_Task();
// 延时
Delay_ms(10);
}
}
3.4 延时函数(delay.c)
c
#include "delay.h"
static uint8_t fac_us = 0;
static uint16_t fac_ms = 0;
void Delay_Init(void)
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
fac_us = SystemCoreClock / 8000000;
fac_ms = (uint16_t)fac_us * 1000;
}
void Delay_us(uint32_t us)
{
uint32_t temp;
SysTick->LOAD = us * fac_us;
SysTick->VAL = 0x00;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
do {
temp = SysTick->CTRL;
} while((temp & 0x01) && !(temp & (1 << 16)));
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
SysTick->VAL = 0x00;
}
void Delay_ms(uint32_t ms)
{
uint32_t temp;
SysTick->LOAD = ms * fac_ms;
SysTick->VAL = 0x00;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
do {
temp = SysTick->CTRL;
} while((temp & 0x01) && !(temp & (1 << 16)));
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
SysTick->VAL = 0x00;
}
四、关键算法说明
4.1 传感器电阻计算
MQ-2传感器的输出电压与气体浓度关系通过电阻变化体现:
Rs = (Vc - Vout) / Vout * RL
其中:
- Vc:电源电压(5V)
- Vout:传感器输出电压
- RL:负载电阻(10kΩ)
4.2 气体浓度计算
根据MQ-2数据手册,气体浓度与Rs/R0的关系为:
ppm = A * (Rs/R0)^B
不同气体的A、B系数:
| 气体类型 | A系数 | B系数 |
|---|---|---|
| LPG | 574.25 | -2.222 |
| CO | 36974.0 | -3.109 |
| 烟雾 | 3616.1 | -2.675 |
4.3 R0校准方法
-
将传感器置于洁净空气中
-
测量Rs值(多次测量取平均)
-
根据洁净空气中的Rs/R0比值计算R0:
R0 = Rs / Rs/R0_clean_air
参考代码 MQ-2烟雾传感器模块STM32F10x源程序 www.youwenfan.com/contentcsu/56118.html
五、使用注意事项
5.1 预热要求
- 首次使用:需要预热≥48小时才能达到稳定
- 日常使用:上电后预热30秒-2分钟
- 长期存放:再次使用前需要重新预热
5.2 环境因素影响
| 因素 | 影响 | 应对措施 |
|---|---|---|
| 温度 | 高温会降低灵敏度 | 温度补偿算法 |
| 湿度 | 高湿度会影响电阻值 | 湿度补偿或干燥剂 |
| 老化 | 长期使用灵敏度下降 | 定期重新校准R0 |
5.3 常见问题解决
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 读数不稳定 | 预热不充分 | 延长预热时间 |
| 读数偏高 | 传感器污染 | 清洁传感器表面 |
| 读数偏低 | R0值漂移 | 重新校准R0 |
| 无反应 | 加热电路故障 | 检查5V加热电压 |
六、扩展功能建议
6.1 多气体检测
c
// 扩展支持多种气体检测
typedef enum {
GAS_LPG = 0,
GAS_CO,
GAS_SMOKE,
GAS_HYDROGEN,
GAS_PROPANE
} GasType;
float MQ2_GetGasConcentration(GasType gas_type)
{
float ratio = MQ2_GetRatio();
float ppm = 0.0;
switch(gas_type) {
case GAS_LPG:
ppm = 574.25 * pow(ratio, -2.222);
break;
case GAS_CO:
ppm = 36974.0 * pow(ratio, -3.109);
break;
case GAS_SMOKE:
ppm = 3616.1 * pow(ratio, -2.675);
break;
// 其他气体...
}
return ppm;
}
6.2 报警联动控制
c
// 报警联动功能
void MQ2_AlarmHandler(void)
{
float smoke_ppm = MQ2_GetSmokeppm();
if(smoke_ppm > alarm_threshold) {
// 1. 点亮红色报警LED
GPIO_SetBits(GPIOA, GPIO_Pin_1);
// 2. 启动蜂鸣器报警
GPIO_SetBits(GPIOA, GPIO_Pin_2);
// 3. 发送报警短信/邮件(需要GSM/WiFi模块)
SendAlarmMessage(smoke_ppm);
// 4. 关闭燃气阀门(需要继电器控制)
GPIO_ResetBits(GPIOA, GPIO_Pin_3);
} else {
// 恢复正常状态
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
GPIO_ResetBits(GPIOA, GPIO_Pin_2);
GPIO_SetBits(GPIOA, GPIO_Pin_3);
}
}
七、总结
这个STM32F10x MQ-2烟雾传感器驱动程序具有以下特点:
- 完整功能:支持模拟电压读取、数字报警、多种气体浓度计算
- 自动校准:支持R0值自动校准,提高测量精度
- 稳定可靠:包含预热检测、数据滤波等机制
- 易于扩展:模块化设计,方便添加新功能
使用建议:
- 首次使用前务必进行R0校准
- 确保足够的预热时间
- 定期检查传感器状态
- 根据实际环境调整报警阈值
这套驱动可以直接用于智能家居、工业安全、消防报警等场景的烟雾检测应用。