基于嵌入式Linux与传感器技术的空气质量监测系统实战开发

发布时间:2026/7/29 7:30:29
基于嵌入式Linux与传感器技术的空气质量监测系统实战开发 1. 项目缘起从一块“过时”的开发板说起几年前英特尔推出了一款名为 Edison 的微型计算模块它集成了双核 Atom 处理器、1GB 内存、Wi-Fi 和蓝牙尺寸却只有一张邮票大小。当时它被寄予厚望是物联网IoT和创客领域的明星产品。然而随着技术迭代Edison 逐渐淡出了主流视野官方支持也慢慢减少。很多人可能觉得现在再去折腾它是不是有点“考古”的味道我手头恰好有这么一块“吃灰”已久的 Edison 开发板与其让它继续沉睡不如用它来做点有意思又有实际意义的事情。于是一个想法诞生了用它来搭建一个空气质量监测站。这个想法并非空穴来风。首先空气质量是关乎每个人健康的重要环境指标PM2.5、VOCs挥发性有机物、温湿度等数据的实时监测对于家庭、办公室甚至小型工作坊都很有价值。其次Edison 虽然“老”但其 x86 架构、完整的 Linux 系统Yocto Linux和相对丰富的接口如 I2C、UART、GPIO让它依然具备强大的项目承载能力。最后这个过程本身就是一个绝佳的嵌入式 Linux 开发实战案例涉及系统定制、传感器驱动、数据采集、网络通信和轻量级服务部署每一步都充满了挑战和学习的乐趣。所以这不仅仅是一个监测项目更是一次对经典硬件潜力的深度挖掘和一次完整的嵌入式开发旅程。2. 硬件选型与核心传感器解析搭建空气质量监测站传感器的选择是核心。我们需要监测的典型参数包括颗粒物浓度PM2.5/PM10、有害气体如甲醛、TVOC、温度、湿度以及大气压力。针对 Edison 的接口特性和项目复杂度我选择了以下组合方案。2.1 颗粒物传感器攀藤 PMS5003PM2.5 监测是空气质量的核心。我选择了攀藤Plantower的 PMS5003 激光粉尘传感器。这是一款在创客和开源项目中经久不衰的型号其稳定性和性价比得到了广泛验证。工作原理它采用激光散射原理。传感器内部有一个激光二极管发射激光束当空气中的颗粒物通过检测区域时会散射激光。散射光被光电探测器接收并转换为电信号经过算法处理即可计算出不同粒径如 PM1.0 PM2.5 PM10的颗粒物数量浓度并最终转换为质量浓度μg/m³。接口与通信PMS5003 使用 UART串口通信这对于 Edison 来说非常友好。Edison 板载了多个 UART 接口我们只需要连接 RX、TX、GND 和 VCC5V四根线即可。其数据协议是公开的每一条数据帧包含起始字符、帧长度、各浓度数据及校验和解析起来相对简单。选型理由相比一些更初级的基于红外原理的传感器如 GP2Y1010AU0F激光传感器的精度和可靠性要高得多。而相比一些工业级或更昂贵的型号PMS5003 在满足民用级精度要求的同时保持了较低的成本和功耗非常适合本项目。2.2 气体与温湿度传感器博世 BME680为了监测挥发性有机化合物VOC、室内空气质量指数IAQ、温度、湿度和气压我选择了博世 Sensortec 的 BME680。这是一款高度集成的环境传感器。核心功能温度与湿度高精度测量为空气质量数据提供环境补偿参考。气压可用于粗略海拔估算或天气趋势分析。气体传感这是其核心特色。它内置了一个金属氧化物MOX气体传感器主要对 VOCs 敏感如乙醇、甲醛、一氧化碳等。通过复杂的算法芯片能输出一个“室内空气质量指数”IAQ范围通常为 0-500数值越低越好以及等效的二氧化碳当量eCO2和 VOC 当量。接口与通信BME680 使用 I2C 或 SPI 接口。我选择了 I2C因为它接线更简单只需 SDA、SCL、VCC、GND且 Edison 的 I2C 驱动成熟。其寄存器配置和数据读取有标准的库支持。选型理由BME680 将四种传感器合而为一极大简化了硬件设计和空间占用。其气体传感器虽不能区分具体气体种类但提供的 IAQ 指数对于评估整体空气污染水平非常有价值。它的功耗也经过优化适合长期运行。2.3 主控与连接英特尔 Edison 开发板Edison 在本项目中扮演着“大脑”和“网关”的角色。计算能力双核 Atom 处理器足以流畅运行一个定制的 Linux 系统、驱动多个传感器、运行数据采集程序甚至托管一个简单的 Web 服务器。操作系统官方提供的 Yocto Linux 镜像是一个高度可定化的嵌入式 Linux 发行版。我们可以根据需要裁剪系统移除不必要的服务让系统更轻量、启动更快。连接性板载 Wi-Fi 和蓝牙是项目的关键。Wi-Fi 用于将采集到的数据上传到云端或本地服务器也可以直接提供 Web 访问界面。蓝牙则可以用于近距离配置或调试。扩展接口通过底部的 70 针扩展接口我们可以方便地连接 UART用于 PMS5003和 I2C用于 BME680等外设。硬件连接清单组件接口类型连接到 Edison 引脚备注攀藤 PMS5003UARTRX - J17-1 (GPIO 130), TX - J17-3 (GPIO 131), VCC - 5V, GND - GND需在系统中配置 UART 设备博世 BME680I2CSDA - J17-5 (I2C-6 SDA), SCL - J17-7 (I2C-6 SCL), VCC - 3.3V, GND - GNDEdison 有多个 I2C 总线需确认设备号电源DC 5V/2A通过 Micro USB 或扩展板供电确保电源稳定传感器同时工作时电流可能超过1A注意Edison 的引脚功能需要通过sysfs或设备树Device Tree进行配置才能将某个 GPIO 复用为 UART 功能。这是使用 Edison 进行嵌入式开发的第一道坎。3. 软件环境搭建与系统定制拿到硬件只是第一步让 Edison 的 Linux 系统“认识”并驱动我们的传感器需要一番配置。官方提供的默认镜像功能齐全但略显臃肿我们可以进行精简。3.1 刷写与定制 Yocto Linux 镜像获取基础镜像从英特尔开源技术中心OSTC的仓库下载针对 Edison 的最新 Yocto 镜像。使用flashall.sh脚本通过 USB OTG 口将系统刷写到 Edison 上。首次启动与配置刷写完成后通过串口使用 FTDI USB 转串口模块连接 Edison 的 J3 接口登录系统。默认用户名为root无密码。首先配置 Wi-Fi使用configure_edison --setup命令交互式地设置网络、主机名和密码。系统精简登录后移除一些不需要的包以节省空间和内存。例如如果不需要蓝牙功能可以移除相关的服务和库。opkg remove bluez5-dev bluez5-noinst-tools pulseaudio-module-bluetooth同样可以移除一些不用的开发工具或文档。3.2 配置传感器硬件接口这是最关键的一步需要在内核层面正确配置引脚功能。配置 UART 给 PMS5003Edison 的 GPIO 130 和 131 默认可能不是 UART 功能。我们需要手动配置。登录 Edison查看当前引脚复用情况cat /sys/kernel/debug/gpio。将 GPIO 130 和 131 配置为 UART 模式。这可以通过写sysfs接口实现。创建一个脚本setup_uart.sh#!/bin/bash # 将 GPIO130 和 GPIO131 从 GPIO 模式释放并设置为 UART 模式 echo 130 /sys/class/gpio/unexport 2/dev/null echo 131 /sys/class/gpio/unexport 2/dev/null # 具体设置命令可能因内核版本而异有时需要操作 /sys/devices/platform/... 下的文件 # 更可靠的方法是使用设备树覆盖Device Tree Overlay但这需要重新编译内核或模块。 # 一个常见的方法是使用 MRAA 库英特尔IoT库来配置。实际上对于 Edison更推荐使用MRAA库或UPM库它们封装了底层的引脚操作来简化这个过程。我们可以先安装 MRAAopkg update opkg install mraa然后编写一个 Python 脚本利用 MRAA 初始化 UART。import mraa import serial # 使用 mraa 确认引脚但实际通信使用 pyserial # 首先确保系统已识别出 tty 设备如 /dev/ttyMFD1 uart serial.Serial(/dev/ttyMFD1, baudrate9600, timeout2)踩坑点Edison 的 UART 设备名可能是/dev/ttyMFD1/dev/ttyMFD2等具体哪个对应我们连接的物理引脚需要查阅 Edison 的引脚映射表或通过试验确定。dmesg | grep tty命令在连接传感器后查看内核信息有助于识别。配置 I2C 给 BME680I2C 的配置通常比 UART 简单。加载 I2C 驱动模块通常已内置modprobe i2c-dev。安装 I2C 工具opkg install i2c-tools。扫描 I2C 总线查看 BME680 是否被识别i2cdetect -y -r 6假设使用 I2C-6 总线。如果看到地址0x76或0x77BME680 的默认地址说明连接成功。安装 Python 的 smbus 库以便操作opkg install python-smbus。3.3 安装必要的软件包我们需要 Python 环境以及相关的传感器库。opkg update opkg install python3 python3-pip python3-setuptools pip3 install pyserial # 用于UART通信 pip3 install bme680 # 官方的BME680 Python库或者使用Adafruit的库 # 对于PMS5003可以安装现成的库如 pms5003 pip3 install pms5003如果现成的 PMS5003 库不兼容我们就需要根据其数据手册手动编写串口读取和解析的代码这其实是一个很好的学习过程。4. 数据采集程序的核心实现有了硬件和基础环境接下来就是编写核心的数据采集程序。这个程序需要完成以下任务初始化传感器、定时读取数据、处理数据如单位转换、简单滤波、将数据打包并发送出去。我将采用 Python 来实现因为它开发效率高库丰富。4.1 传感器驱动与数据读取PMS5003 数据读取 PMS5003 会主动以大约每秒一次的频率通过串口发送数据帧。我们的程序需要监听串口读取完整的数据帧并进行校验。import serial import struct import time class PMS5003Reader: def __init__(self, port/dev/ttyMFD1, baudrate9600): self.ser serial.Serial(port, baudratebaudrate, timeout2) # 数据帧起始字节 self.START_BYTES b\x42\x4d def _read_frame(self): 读取并解析一帧数据 # 寻找起始字节 while True: start1 self.ser.read(1) if start1 self.START_BYTES[0:1]: start2 self.ser.read(1) if start2 self.START_BYTES[1:2]: break # 读取帧长度高字节低字节 frame_len_bytes self.ser.read(2) frame_len struct.unpack(H, frame_len_bytes)[0] # 大端序 # 读取数据部分和校验和 raw_data self.ser.read(frame_len) # 包含数据校验和 # 校验此处省略校验和计算代码 # 解析数据 # 数据格式PM1.0标准(2字节), PM2.5标准(2字节), PM10标准(2字节), ... data struct.unpack(H*(frame_len-2), raw_data[:-2]) pm1_0_std, pm2_5_std, pm10_std data[0], data[1], data[2] # ... 解析其他数据 return {pm1_0: pm1_0_std, pm2_5: pm2_5_std, pm10: pm10_std} def read_data(self): 获取一次有效的传感器数据 return self._read_frame()经验之谈串口读取时一定要处理好帧同步和校验。如果校验失败应该丢弃这一帧数据重新同步。此外PMS5003 在刚上电或风扇启动时前几十秒的数据可能不稳定程序最好能忽略这段时间的数据或做一个简单的启动延迟。BME680 数据读取 使用bme680库会简单很多。import bme680 import time class BME680Reader: def __init__(self, i2c_addr0x77, i2c_bus6): self.sensor bme680.BME680(i2c_addr, i2c_bus) # 配置传感器参数 self.sensor.set_humidity_oversample(bme680.OS_2X) self.sensor.set_pressure_oversample(bme680.OS_4X) self.sensor.set_temperature_oversample(bme680.OS_8X) self.sensor.set_filter(bme680.FILTER_SIZE_3) self.sensor.set_gas_status(bme680.ENABLE_GAS_MEAS) self.sensor.set_gas_heater_temperature(320) # 摄氏度 self.sensor.set_gas_heater_duration(150) # 毫秒 self.sensor.select_gas_heater_profile(0) # 气体传感器需要预热和稳定 self._burn_in_time 300 # 预热300秒 self._start_time time.time() def read_data(self): 获取一次传感器数据气体读数需要稳定周期 if self.sensor.get_sensor_data(): data { temperature: self.sensor.data.temperature, pressure: self.sensor.data.pressure, humidity: self.sensor.data.humidity, } # 处理气体读数 if self.sensor.data.heat_stable: data[gas_resistance] self.sensor.data.gas_resistance # 可以在这里根据温湿度补偿计算 IAQ、eCO2 等库可能提供或需自己实现 # 例如使用一个简单的算法将 gas_resistance 和 humidity 转换为 IAQ 分数 iaq_score self._calculate_iaq(data[gas_resistance], data[humidity]) data[iaq] iaq_score else: data[gas_resistance] None data[iaq] None return data return None def _calculate_iaq(self, gas_resistance, humidity): 一个简化的 IAQ 计算示例非博世官方算法 if gas_resistance is None: return None # 这是一个非常粗略的示例真实算法复杂且依赖校准 # 假设 gas_resistance 越高空气质量越好 base_score (gas_resistance / 10000.0) # 归一化 # 湿度补偿高湿可能影响MOX传感器 humidity_factor 1.0 (humidity - 40.0) * 0.01 # 示例系数 iaq max(0, min(500, (500 - base_score * 50 * humidity_factor))) return int(iaq)注意BME680 的气体传感器MOX需要一段“预热”时间burn-in period才能输出稳定的读数通常需要几分钟。在此期间heat_stable标志为 False气体电阻值会剧烈变化不应采用。官方库和算法通常包含了复杂的补偿和计算上述_calculate_iaq函数仅为示意生产环境应使用更可靠的算法或直接依赖传感器芯片内部计算如果支持。4.2 主循环与数据整合主程序将两个传感器读取器结合起来以固定的时间间隔例如每 5 秒采集一次数据并整合成一个 JSON 对象。import json import time from datetime import datetime def main_loop(): pm_reader PMS5003Reader() bme_reader BME680Reader() # 等待BME680预热 print(等待BME680气体传感器预热...) time.sleep(bme_reader._burn_in_time) while True: try: # 读取数据 pm_data pm_reader.read_data() bme_data bme_reader.read_data() # 整合数据 sensor_data { timestamp: datetime.utcnow().isoformat() Z, pm1_0: pm_data.get(pm1_0), pm2_5: pm_data.get(pm2_5), pm10: pm_data.get(pm10), temperature: bme_data.get(temperature), humidity: bme_data.get(humidity), pressure: bme_data.get(pressure), iaq: bme_data.get(iaq), gas_resistance: bme_data.get(gas_resistance), } # 打印到控制台调试用 print(json.dumps(sensor_data, indent2)) # 调用数据发送函数 send_data_to_backend(sensor_data) time.sleep(5) # 采集间隔 except KeyboardInterrupt: print(程序退出。) break except Exception as e: print(f数据采集出错{e}) time.sleep(10) # 出错后等待稍长时间再试5. 数据上云与本地服务部署策略采集到的数据需要被存储、展示和分析。这里有两种主流思路一是将数据发送到云端物联网平台二是在 Edison 本地部署轻量级服务实现数据存储和可视化。5.1 方案一数据上云以 MQTT 云平台为例这是更通用和可扩展的方案。Edison 作为 MQTT 客户端将数据发布到 MQTT 代理Broker云端服务器订阅这些主题并处理数据。安装 MQTT 客户端库pip3 install paho-mqtt编写 MQTT 发布函数import paho.mqtt.client as mqtt import json MQTT_BROKER your.broker.address # 例如mqtt.eclipseprojects.io公共测试 MQTT_PORT 1883 MQTT_TOPIC edison/air_quality/data CLIENT_ID fedison-airmon-{os.uname()[1]} client mqtt.Client(client_idCLIENT_ID) def on_connect(client, userdata, flags, rc): if rc 0: print(连接到 MQTT Broker 成功) else: print(f连接失败返回码 {rc}) client.on_connect on_connect client.connect(MQTT_BROKER, MQTT_PORT, 60) client.loop_start() # 启动网络循环线程 def send_data_to_backend(data): payload json.dumps(data) result client.publish(MQTT_TOPIC, payload, qos1) if result.rc mqtt.MQTT_ERR_SUCCESS: print(数据发布成功) else: print(f数据发布失败: {result})云端处理你可以使用像Node-RED部署在树莓派或云服务器上、ThingsBoard、阿里云物联网平台或AWS IoT Core等平台来接收 MQTT 数据进行存储、可视化创建仪表盘和设置报警规则。5.2 方案二本地部署轻量级服务如果希望数据完全在本地网络处理可以在 Edison 上运行一个轻量级的 Web 服务器和数据库。本地数据库使用SQLite是最简单的选择。opkg install sqlite3然后在 Python 中使用sqlite3库创建表并插入数据。Web 服务器与 API使用Flask或Bottle这样的微型 Python Web 框架创建一个简单的 REST API 来接收和提供数据。from flask import Flask, jsonify, request, render_template import sqlite3 import json app Flask(__name__) DATABASE /home/root/air_quality.db def init_db(): conn sqlite3.connect(DATABASE) c conn.cursor() c.execute(CREATE TABLE IF NOT EXISTS sensor_data (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT, pm2_5 REAL, temperature REAL, humidity REAL, iaq INTEGER)) conn.commit() conn.close() app.route(/api/data, methods[POST]) def receive_data(): data request.json # 将数据存入 SQLite conn sqlite3.connect(DATABASE) c conn.cursor() c.execute(INSERT INTO sensor_data (timestamp, pm2_5, temperature, humidity, iaq) VALUES (?, ?, ?, ?, ?), (data[timestamp], data[pm2_5], data[temperature], data[humidity], data.get(iaq))) conn.commit() conn.close() return jsonify({status: success}), 201 app.route(/api/latest) def get_latest(): conn sqlite3.connect(DATABASE) c conn.cursor() c.execute(SELECT * FROM sensor_data ORDER BY timestamp DESC LIMIT 1) row c.fetchone() conn.close() if row: # 转换为字典返回 pass return jsonify(row) app.route(/) def index(): return render_template(index.html) # 一个简单的图表页面 if __name__ __main__: init_db() app.run(host0.0.0.0, port5000, debugFalse)前端可视化在index.html中可以使用Chart.js或ECharts等 JavaScript 图表库通过 AJAX 定期从/api/latest或/api/history获取数据动态更新图表。5.3 方案对比与选择方案优点缺点适用场景MQTT 云平台扩展性强可连接多设备云端计算和存储能力强有成熟的可视化和告警方案数据可远程访问。依赖互联网可能涉及服务费用部分平台数据隐私性取决于平台。多设备集中管理、需要复杂分析、远程监控、与其它智能家居系统集成。本地服务数据完全本地隐私性好不依赖外网延迟低。Edison 计算和存储资源有限开发和维护全套服务DB后端前端工作量较大难以实现远程访问需内网穿透。对数据隐私要求极高、网络环境不稳定或不可用、作为单一独立监测站使用。对于本项目我推荐混合方案在 Edison 本地运行数据采集和 MQTT 发布客户端在家庭局域网内的另一台性能稍强的设备如树莓派、旧笔记本或 NAS上部署 MQTT Broker如 Mosquitto、Node-RED 和 InfluxDB/Grafana。这样既利用了 Edison 的传感能力又将数据存储和展示的重任交给了更适合的设备平衡了性能、隐私和复杂度。6. 系统优化与长期运行考量要让这个监测站稳定地 7x24 小时运行还需要解决一些实际问题。6.1 电源管理与看门狗Edison 和传感器需要稳定的电源。一个可靠的 5V/2A 电源适配器是基础。对于长期运行有两个关键点软件看门狗编写一个简单的看门狗脚本定期检查主采集程序是否在运行。如果程序崩溃看门狗负责重启它。可以用systemd服务来实现这一点这是更规范的方法。硬件看门狗Edison 模块本身支持硬件看门狗定时器。我们需要在内核中启用它并编写一个用户空间程序定期“喂狗”。如果系统完全挂起硬件看门狗会在超时后强制重启整个系统。这需要修改内核配置并编写相应的守护进程。6.2 数据存储与日志即使数据发送到云端本地也应保留一份最近的日志用于故障排查。循环日志使用 Python 的logging模块将程序运行日志和采集到的原始数据可选写入文件。配置RotatingFileHandler防止日志文件无限增大占满存储空间。简易本地缓存在发送数据到云端前可以先写入一个本地 SQLite 数据库或简单的 CSV 文件。如果网络暂时中断数据不会丢失待网络恢复后可以进行断点续传需要更复杂的逻辑。6.3 传感器校准与维护传感器尤其是气体传感器需要定期维护才能保证数据准确性。PMS5003激光传感器窗口可能会积灰影响精度。需要定期用棉签和气吹清洁进气口和激光腔如果设计允许。它的风扇也有寿命长时间运行后噪音可能变大。BME680MOX 气体传感器会随时间老化其灵敏度会变化。博世建议每 12 个月进行一次“基线校准”。具体做法是将传感器置于已知的良好空气环境如室外空气中连续运行 48 小时让其重新建立“干净空气”的基准电阻值。一些高级的驱动库如bme680库的某些分支支持保存和加载基线值的功能。数据漂移处理在软件层面可以对采集到的数据进行滑动平均滤波以平滑短时的噪声波动。对于长期监测可以设定一个“参考值”修正机制例如在已知空气质量良好的时段如深夜通风后手动记录一组数据作为临时基准进行偏移补偿。6.4 将程序封装为系统服务为了让采集程序在 Edison 启动时自动运行并在崩溃后自动重启最好将其配置为systemd服务。创建一个服务文件/etc/systemd/system/airquality.service[Unit] DescriptionAir Quality Monitoring Service Afternetwork.target [Service] Typesimple Userroot WorkingDirectory/home/root/air_quality ExecStart/usr/bin/python3 /home/root/air_quality/main.py Restarton-failure RestartSec10s StandardOutputjournal StandardErrorjournal [Install] WantedBymulti-user.target启用并启动服务systemctl daemon-reload systemctl enable airquality.service systemctl start airquality.service查看服务状态和日志systemctl status airquality.servicejournalctl -u airquality.service -f。通过以上步骤一个基于英特尔 Edison 的、功能相对完善的空气质量监测站就搭建完成了。它不仅输出了有价值的环境数据更让我们重温了嵌入式 Linux 开发的完整流程从硬件接口调试、驱动层配置到应用层逻辑编写再到系统服务部署和运维考量。虽然 Edison 已不是最新硬件但通过这样的项目其价值得以延续而我们获得的实战经验对于理解当代物联网设备的开发逻辑依然具有很高的参考价值。