【mcuclub】MS1100 VOC气体传感器在智能家居空气质量监测中的应用
1. 从零开始:认识你家的“隐形杀手”与守护神MS1100
最近几年,新家装修、购置新家具后,大家最关心的是什么?没错,就是甲醛、苯这些看不见摸不着,却又实实在在危害家人健康的“隐形杀手”。我身边就有朋友,新房晾了半年,一进去还是觉得刺鼻,心里总是不踏实。传统的解决办法要么是买昂贵的专业检测仪(用几次就闲置),要么是凭感觉开窗通风,效果难以量化。
其实,随着智能家居的普及,我们完全可以用很低的成本,自己动手搭建一个24小时在线的空气质量监测站。今天要跟大家深入聊的,就是我在多个智能家居项目中反复使用过的一款“神器”——MS1100 VOC气体传感器。你可以把它理解为你家里的“电子鼻”,专门负责嗅探空气中的有害挥发性有机物(VOC),尤其是甲醛和苯。
MS1100本质上是一个半导体式气体传感器。它的核心是一个微小的陶瓷管,表面涂有特殊的金属氧化物材料。当它接触到目标气体分子时,材料的电阻会发生变化。这个变化非常细微,但传感器内部的电路能把它放大,并转换成我们可以轻松读取的电压信号。简单来说,空气越“脏”,它输出的电压信号就越高(或数字信号跳变),原理非常直观。
我之所以特别喜欢在智能家居项目里推荐MS1100,主要是因为它完美契合了DIY和产品化的几个核心需求:价格极其亲民(一个模块通常就二三十元)、接口超级简单(就4根线:电源、地、模拟输出、数字输出)、功耗超低(工作电流小于50微安,电池供电都能扛很久)。你不需要成为电子专家,只要会用单片机读个电压,就能把数据拿到手。
2. 硬件连接:5分钟搞定MS1100与单片机的“牵手”
拿到MS1100模块(常见的是CJMCU-1100这种集成调理电路的小板子),翻到背面,你会看到四个排针,标注非常清晰:VCC、GND、AO、DO。接下来,就是让它和你的大脑(单片机)说上话。这里我以最经典的STM32系列单片机为例,给大家拆解两种最常用的连接和读取方式。
第一种是“傻瓜式”的数字接口(DO)读取。 这个最适合新手快速实现报警功能。DO引脚内部连接了一个比较器,它会将AO的模拟电压和一个可调阈值(通过模块上的蓝色电位器调节)进行比较。当气体浓度超过你设定的阈值时,DO引脚会从高电平变成低电平(具体看模块设计,有些是反的)。你只需要把DO脚接到单片机的任何一个普通GPIO口上,设置为输入模式,然后不断去读取这个引脚的状态就行了。
// 示例:读取MS1100的DO引脚状态(假设低电平表示浓度超标)
if(HAL_GPIO_ReadPin(MS1100_DO_GPIO_Port, MS1100_DO_Pin) == GPIO_PIN_RESET)
{
// 触发报警!打开蜂鸣器,点亮红色LED
Buzzer_On();
LED_Red_On();
printf("警告!VOC浓度超标!\r\n");
}
else
{
// 浓度正常
Buzzer_Off();
LED_Green_On();
}
这种方式简单粗暴,但缺点是你只知道“超标”和“正常”两种状态,不知道具体浓度有多高,也无法记录浓度变化趋势。
第二种是“专业式”的模拟接口(AO)读取。 这才是发挥MS1100全部威力的方式。AO引脚会输出一个连续的电压信号,通常在0到VCC之间(比如5V供电时,输出0-5V)。这个电压值随着气体浓度的升高而升高。我们需要用单片机的ADC(模数转换器) 功能,把这个模拟电压转换成数字值。STM32的ADC分辨率通常是12位,也就是把参考电压(比如3.3V)分成4096份,这样我们就能得到一个0到4095之间的精细数值,从而反推出具体的浓度趋势。
接线也很简单:MS1100的AO脚接到单片机具有ADC功能的引脚上,比如STM32F103的PA0、PA1等。在代码里,你需要初始化ADC,然后定时去采样这个引脚上的电压。这里有个关键细节:MS1100需要预热! 刚上电时,内部的陶瓷加热器需要工作3到5分钟,输出才能稳定。所以你的程序上电后最好先延迟几分钟再开始正式读数。
// 示例:使用STM32 HAL库读取MS1100的AO模拟值(12位ADC)
uint16_t MS1100_Read_AO_Value(void)
{
uint16_t adc_value = 0;
HAL_ADC_Start(&hadc1); // 启动ADC转换
if(HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) // 等待转换完成
{
adc_value = HAL_ADC_GetValue(&hadc1); // 获取转换结果
}
HAL_ADC_Stop(&hadc1);
return adc_value; // 返回0-4095之间的数字
}
// 在主循环中,我们可以将ADC值转换为电压,并进一步处理
void main_loop(void)
{
uint16_t raw_adc = MS1100_Read_AO_Value();
float voltage = (raw_adc / 4095.0) * 3.3; // 假设ADC参考电压为3.3V
printf("ADC原始值:%d, 对应电压:%.2f V\r\n", raw_adc, voltage);
// 这里可以添加你的浓度判断逻辑
if(voltage > 2.0) // 假设电压大于2V认为浓度偏高
{
// 触发中级警报
}
HAL_Delay(2000); // 每2秒采样一次
}
2.1 电路设计与抗干扰要点
在实际焊接和布线时,有几点容易踩坑的经验分享给大家。首先,电源一定要干净。MS1100虽然功耗小,但对电源纹波比较敏感。如果和电机、继电器等大电流器件共用电源,最好给MS1100的VCC加上一个磁珠和10uF、0.1uF的电容组合进行滤波,避免干扰导致读数跳动。
其次,模拟信号线要短。AO那根线是微弱的模拟信号,尽量让它远离单片机的晶振、数字开关信号线等噪声源。如果实在避不开,可以用屏蔽线或者把AO线用地线包裹起来。模块上的那个蓝色可调电阻,我建议你先不要动。出厂时一般设置在一个合理的默认阈值。等你整个系统调通了,想改变数字输出的触发灵敏度时,再用小螺丝刀慢慢微调。
最后是校准思路。MS1100是一个定性半定量的传感器,官方一般不会提供“XX电压等于XX ppm浓度”的精确公式。我们常用的方法是“相对值校准”。在已知空气良好的环境下(比如室外通风处),让传感器稳定运行,记录下此时的ADC值作为“基线值”。然后,在需要监测的空间里,传感器读数相对于这个基线值的变化量,就代表了空气的污染程度。变化量越大,说明VOC浓度越高。这个方法在实践中非常有效且易于实现。
3. 软件驱动:编写稳健的MS1100数据采集程序
硬件连好了,接下来就是让单片机“学会”如何与MS1100优雅地对话。写驱动代码,稳定性和准确性是第一位的。我们不能只读一次ADC就当作最终结果,那样随机误差会很大。下面我分享一个经过实战检验的驱动代码框架,包含初始化和数据采集两部分。
首先,我们需要一个稳健的初始化函数。这个函数不仅要配置好ADC通道,还要考虑到传感器的预热需求。
// bsp_ms1100.h 头文件部分关键定义
#ifndef BSP_MS1100_H
#define BSP_MS1100_H
#include "main.h" // 包含你的HAL库或标准库头文件
// 定义硬件连接引脚
#define MS1100_AO_PIN GPIO_PIN_0
#define MS1100_AO_GPIO_PORT GPIOA
#define MS1100_DO_PIN GPIO_PIN_1
#define MS1100_DO_GPIO_PORT GPIOA
// 定义ADC句柄(需在main.c中外部声明)
extern ADC_HandleTypeDef hadc1;
// 函数声明
void MS1100_Init(void);
uint16_t MS1100_Get_AO_Value(void);
uint8_t MS1100_Get_DO_Status(void);
void MS1100_Preheat(uint32_t time_ms); // 预热函数
#endif
接着是源文件的具体实现。这里我特别加入了软件滤波。直接读取的ADC值可能会因为电源噪声或环境干扰而有少量跳动。常用的方法是连续采样多次,然后取平均值,或者去掉最大最小值后再取平均(中位值平均滤波)。
// bsp_ms1100.c 源文件实现
#include "bsp_ms1100.h"
// MS1100初始化函数
void MS1100_Init(void)
{
// 1. 初始化GPIO,DO引脚为输入模式
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = MS1100_DO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL; // 模块内部一般有上拉,这里可不拉
HAL_GPIO_Init(MS1100_DO_GPIO_PORT, &GPIO_InitStruct);
// 2. 初始化ADC(假设已在CubeMX中配置好hadc1,这里仅启动)
HAL_ADC_Start(&hadc1);
// 注意:实际项目中,ADC的通道、采样时间等应在CubeMX里图形化配置好,更稳妥。
printf("MS1100硬件初始化完成。\r\n");
}
// 传感器预热函数
void MS1100_Preheat(uint32_t time_ms)
{
printf("MS1100预热中,请等待 %ld 毫秒...\r\n", time_ms);
HAL_Delay(time_ms); // 阻塞延时,简单演示。实际产品建议用非阻塞式定时器。
printf("MS1100预热完成,可以开始测量。\r\n");
}
// 获取AO模拟值(带软件滤波)
uint16_t MS1100_Get_AO_Value(void)
{
#define SAMPLE_TIMES 10 // 采样次数,可调整
uint32_t adc_sum = 0;
uint16_t adc_value[SAMPLE_TIMES];
uint16_t temp;
int i, j;
// 采集一组样本
for(i = 0; i < SAMPLE_TIMES; i++)
{
HAL_ADC_Start(&hadc1);
if(HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK)
{
adc_value[i] = HAL_ADC_GetValue(&hadc1);
}
HAL_ADC_Stop(&hadc1);
HAL_Delay(5); // 每次采样间隔5ms,避免过于密集
}
// 简单冒泡排序,为了后续可扩展为去掉头尾的滤波
for(i = 0; i < SAMPLE_TIMES-1; i++)
{
for(j = i+1; j < SAMPLE_TIMES; j++)
{
if(adc_value[i] > adc_value[j])
{
temp = adc_value[i];
adc_value[i] = adc_value[j];
adc_value[j] = temp;
}
}
}
// 求平均值(这里采用全部样本平均,你也可以去掉最高最低各一个再平均)
for(i = 0; i < SAMPLE_TIMES; i++)
{
adc_sum += adc_value[i];
}
return (uint16_t)(adc_sum / SAMPLE_TIMES);
}
// 获取DO数字状态
uint8_t MS1100_Get_DO_Status(void)
{
// 假设模块设计为:浓度正常时DO输出高电平,超标时输出低电平
if(HAL_GPIO_ReadPin(MS1100_DO_GPIO_PORT, MS1100_DO_PIN) == GPIO_PIN_RESET)
{
return 1; // 返回1表示浓度超标
}
else
{
return 0; // 返回0表示浓度正常
}
}
3.1 数据平滑与阈值判断算法
有了稳定的ADC读数,我们还需要在应用层做一些处理,让显示和报警更“人性化”,避免数值频繁跳动吓到用户。这里可以引入一个一阶滞后滤波(或称为指数加权平均),它计算量小,效果却很好。
// 在应用层,比如 main.c 中
float filtered_adc_value = 0.0;
float filter_factor = 0.2; // 滤波系数,越小越平滑,但响应越慢。0.1~0.3之间常用。
void update_MS1100_data(void)
{
uint16_t raw_adc = MS1100_Get_AO_Value(); // 调用驱动函数获取原始值
// 一阶滞后滤波
filtered_adc_value = filter_factor * raw_adc + (1 - filter_factor) * filtered_adc_value;
// 将滤波后的ADC值转换为浓度等级
uint8_t air_quality_level = evaluate_air_quality(filtered_adc_value);
// 根据等级做出不同反应
switch(air_quality_level)
{
case 0: // 优
set_led_color(GREEN);
break;
case 1: // 良
set_led_color(BLUE);
break;
case 2: // 轻度污染
set_led_color(YELLOW);
fan_set_speed(50); // 开启风扇,低速
break;
case 3: // 重度污染
set_led_color(RED);
fan_set_speed(100); // 风扇全速
buzzer_alarm_intermittent(); // 蜂鸣器间歇报警
break;
}
}
// 一个简单的评估函数示例(阈值需要根据实际测试校准)
uint8_t evaluate_air_quality(float adc_val)
{
if(adc_val < 800) return 0; // 优
else if(adc_val < 1500) return 1; // 良
else if(adc_val < 2500) return 2; // 轻度污染
else return 3; // 重度污染
}
通过这样的软件处理,最终呈现给用户的将是一个变化平滑、反应合理的空气质量指示,大大提升了使用体验。
4. 智能家居实战:打造联动报警与空气净化系统
单独一个检测仪显示数字,价值有限。智能家居的精髓在于“联动”。当MS1100检测到污染时,它应该能自动指挥其他设备工作,形成一个闭环的空气质量管理系统。下面我设计两个典型的实战场景,并给出实现思路。
场景一:智能通风联动系统。 这个系统特别适合新装修的房间或厨房。它的逻辑是:当MS1100检测到VOC浓度持续超标(比如过滤后的ADC值连续3次超过阈值),自动打开智能排风扇或窗户电机(通过继电器控制),同时通过Wi-Fi模块向手机App推送一条告警信息:“客厅甲醛浓度升高,已自动开启通风”。
// 伪代码逻辑示例
void smart_ventilation_task(void)
{
static int超标计数 = 0;
float current_level = get_filtered_ms1100_level();
if(current_level > THRESHOLD_POLLUTED)
{
超标计数++;
if(超标计数 >= 3) // 连续3次超标,避免误报
{
relay_control(FAN_RELAY, ON); // 打开风扇继电器
send_wifi_notification(“VOC超标,风扇已开启”);
超标计数 = 0; // 重置计数器
}
}
else
{
超标计数 = 0; // 浓度恢复正常,重置计数器
// 可以添加条件:如果风扇是自动打开的,当浓度低于安全阈值一段时间后,再自动关闭风扇。
if(is_fan_auto_opened())
{
relay_control(FAN_RELAY, OFF);
send_wifi_notification(“空气已达标,风扇关闭”);
}
}
}
场景二:空气净化器自动调速系统。 很多家用空气净化器有手动档位。我们可以用MS1100+单片机做一个外挂的智能调速控制器。系统实时监测空气质量,并动态调整净化器的风速档位(通过模拟红外遥控信号或者直接控制净化器的电机输入)。空气质量优时,净化器低速静音运行;检测到污染则自动加速,全力净化。
这个系统的硬件核心是MS1100和一颗红外发射管(模仿遥控器)。单片机根据MS1100的数据,决定发射哪一组红外编码信号(对应净化器的不同档位)。软件上需要实现红外NEC编码的发射函数,并建立一个“浓度-档位”的映射表。
// 红外控制净化器档位示例
typedef enum {
FAN_SPEED_AUTO = 0,
FAN_SPEED_LOW,
FAN_SPEED_MID,
FAN_SPEED_HIGH
} fan_speed_t;
fan_speed_t map_air_quality_to_speed(float adc_value)
{
if(adc_value < 1000) return FAN_SPEED_LOW;
else if(adc_value < 2000) return FAN_SPEED_MID;
else return FAN_SPEED_HIGH;
}
void control_air_purifier(void)
{
float adc_val = get_filtered_ms1100_level();
fan_speed_t target_speed = map_air_quality_to_speed(adc_val);
static fan_speed_t current_speed = FAN_SPEED_LOW;
if(target_speed != current_speed)
{
// 发送对应的红外遥控码
switch(target_speed)
{
case FAN_SPEED_LOW:
send_ir_code(IR_CODE_LOW_SPEED);
break;
case FAN_SPEED_MID:
send_ir_code(IR_CODE_MID_SPEED);
break;
case FAN_SPEED_HIGH:
send_ir_code(IR_CODE_HIGH_SPEED);
break;
}
current_speed = target_speed;
printf("空气质量变化,净化器档位已调整为:%d\r\n", target_speed);
}
}
4.1 物联网集成与数据可视化
如果想让你的空气质量监测站再上一个台阶,物联网集成是必经之路。你可以选用ESP8266或ESP32这类自带Wi-Fi的MCU,或者通过串口让STM32连接一个Wi-Fi模块。将MS1100采集的数据,连同温湿度传感器(如DHT11)的数据一起,定期上传到云端平台,比如ThingsBoard、阿里云IoT或Home Assistant。
在Home Assistant里,你可以轻松创建出一个美观的仪表盘,实时显示家里各个房间的VOC浓度曲线图。更进一步,可以设置自动化规则:当书房传感器数值超过阈值时,自动打开书房的空气净化器并发送通知到你的手机。我自己的工作室就部署了这样一套系统,通过历史数据曲线,能清晰地看到每天开窗通风后浓度下降的过程,以及打印机工作时VOC的小幅上升,非常有意思。
5. 避坑指南与性能优化:来自实战的经验之谈
玩了这么多年传感器,MS1100虽然简单可靠,但也有一些坑是新手容易掉的。这里我把常见的几个问题和优化技巧列出来,希望能帮你节省大量调试时间。
第一个大坑:忽视温湿度影响。 半导体气体传感器的一个固有特性就是其灵敏度受环境温湿度影响。夏天和冬天,潮湿天和干燥天,同样的气体浓度,MS1100输出的电压可能会有差异。对于要求不高的定性报警,可以忽略。但如果想做相对精确的长期趋势监测,有条件的可以增加一个温湿度传感器(如SHT30),在软件里根据温湿度对ADC读数进行一个简单的补偿。网上可以找到一些补偿算法的研究,或者你可以自己在不同温湿度下测试,建立一个简单的查找表。
第二个坑:长期漂移与定期校准。 所有半导体气体传感器都存在长期漂移问题,即使用几个月后,基线输出可能会慢慢变化。这就是为什么我强调要用“相对值”而不是绝对值来判断。一个实用的维护方法是:定期自动校准基线。你可以让系统在每天凌晨(假设此时家里没人,空气最好)自动记录一次ADC值,并更新为新的“清洁空气基线”。或者增加一个手动校准按钮,在确认空气良好时按下,让单片机记录当前值作为新基线。
第三个技巧:优化功耗以实现电池供电。 MS1100本身功耗极低,但单片机如果一直全速运行就比较耗电。如果你想做一个便携式或无线粘贴式的检测仪,必须优化功耗。方法是让单片机绝大部分时间处于深度睡眠模式,只用内部RTC定时,比如每5分钟唤醒一次。唤醒后,快速给MS1100上电,等待几十秒预热(可用定时器精确控制),然后读取ADC值,判断是否需要报警。如果正常,将数据记录到EEPROM或通过无线发送出去,然后再次进入深度睡眠。这样,用一块小容量锂电池也能工作好几个月。
第四个建议:多传感器融合提高可靠性。 单一传感器有时会受干扰(比如酒精、香水等也会导致VOC读数升高)。在重要的场合,可以考虑多传感器融合。例如,用MS1100监测总VOC趋势,同时用一个更专一的甲醛传感器(如电化学式)进行交叉验证。或者结合二氧化碳传感器(如SGP30)的数据,综合判断室内空气状况(高CO2且高VOC,基本可以确定是人员密集、通风不良)。这样做出的联动控制决策会更加准确和可靠。
在我自己的项目中,我把一个MS1100模块放在3D打印机柜里,用来监测打印过程中可能释放的微量气体,并联动柜内的小风扇。另一个放在家里的储物间,监测是否有物品霉变产生的异味。它的低成本和高可靠性,使得这种“泛在感知”成为可能。希望这篇文章能帮你打开思路,不仅仅是学会使用一个模块,更是掌握一种用智能化手段解决身边实际问题的能力。
更多推荐


所有评论(0)