
环境多传感器设备为什么我选树莓派CM4当主控最近花了两周时间从零组装了一台环境多传感器监测设备核心主控用的是树莓派计算模块CM4。它同时挂了温湿度、气压、光照、CO2、TVOC和颗粒物六路传感器7x24小时跑在阳台边上数据通过MQTT上行到本地服务器最后在Grafana上画成实时曲线。这个项目做下来的最大感受是如果只是想“玩玩”买块普通的RPi 4B当然更省事但当你需要把设备做成一个长期运行、体积紧凑、可以复制的监测节点时CM4的优势一下就体现出来了。这篇内容主要给两类朋友看一是想用树莓派做环境监测但还没定方案的新手二是已经在用树莓派4B做原型、部署时却觉得“差点意思”的动手党。我会把整个设备从选型、硬件组装、传感器接线、软件采集到数据上云的关键环节全部过一遍包括我踩过的坑。有些方案是经过实际验证的通用做法有些则是我个人偏好我会在文中标注清楚。1. 为什么选CM4而不是现成的树莓派1.1 CM4和树莓派4B的核心差异很多人的第一反应是环境监测不就是接几个传感器嘛普通树莓派4B上USB口、GPIO都有为啥非要折腾CM4这问题我在选型时候也反复问过自己。CM4本质上就是树莓派4B的核心计算部分把BCM2711处理器、内存和eMMC存储集成到一块只有信用卡大小的模块上但去掉了所有对外接口。它不像4B那样把USB、以太网、HDMI直接做好而是通过底板上的金手指引出信号具体有哪些接口完全由你用的载板决定。这个设计带来的直接好处是设备尺寸可以做得非常小而且接口数量按需配置不需要的东西一个都没有。我用的载板只有巴掌心大小直接把CM4插上去外接一个5V电源口、一个USB转串口调试口、外加一个I2C接口排针就够用了。而普通树莓派4B上那一堆用不上的USB-A口、双HDMI、3.5mm音频口对我来说都是白白浪费的板子面积和成本。如果你做的是多节点批量部署比如办公室放三个、厂房放五个CM4Lite载板的成本优势会更明显。另外还有一个很少有文章提到的点CM4的PCIe通道。虽然这项目里我没用但它在后续扩展时非常值钱可以接NVMe固态或者4G LTE模块。树莓派4B到死都没有PCIe接口这是物理级别的差距。1.2 从开发到部署的决策逻辑选CM4不只是为了小巧真正让我下定决心的是部署稳定性。我之前用树莓派4B做过一个类似的监测原型用的是TF卡系统。TF卡在长时间持续写入的环境下非常容易损坏我那块卡用了一个多月就出现只读故障。CM4提供了eMMC存储选项相当于把系统盘直接焊在模块上没有机械结构、没有触点氧化、抗震动能力更强写寿命和可靠性都比TF卡高一个层级。对于要长期7x24运行、数据持续写入的设备来说这钱花得值。CM4还有一个隐藏优势就是供货形态。因为接口都在载板上载板是随便换的坏了就是一块几十块钱的板子核心计算模块不坏就不用全拆。反观树莓派4B一个USB口损坏或者TF卡座松动整个板子都得换。在设备运维角度这种模块化设计能显著降低故障维修成本。我这次选的是CM4 Lite版本无eMMC因为手上正好有一块闲置的高速TF卡但如果你准备正式部署多台设备我很建议直接上eMMC版本省掉的TF卡故障排查时间远远值回差价。1.3 型号选择建议CM4有多个版本参数差异主要在内存、eMMC和无线模块。我整理的选型建议是这样的配置项选择建议理由内存2GB起步建议4GB环境监测本身内存消耗不大但Grafana Agent或后续加边缘AI模型时需要余量eMMC优先选8GB以上版本系统占4GB左右剩余空间留给日志和本地数据缓冲无线强烈建议带WiFi版本环境监测节点多数位置不方便拉网线WiFi是刚需散热必须加散热片小风扇室外或半封闭机箱内CM4温度经常飙到70度以上我这次用的是4GB内存8GB eMMCWiFi版本。说句实在话哪怕只是在室内用WiFi版本也比有线版本灵活很多后期想换位置不用重新考虑网线长度。2. 传感器选型与整体架构2.1 监测哪些环境参数环境多传感器设备第一步就是确定监测哪些参数。我根据实际需求选了六个温湿度SHT30精度高、长期稳定性好、价格便宜气压BMP280和SHT30同属Bosch系列驱动成熟光照强度BH1750数字输出、量程0-65535lx室内外都够用CO2浓度SCD30把CO2传感器做到了I2C接口红外原理不怕交叉干扰TVOC/空气质量SGP30可以输出TVOC和等效CO2信号PM1.0/2.5/10颗粒物SPS30基于激光散射原理I2C接口这六个模块之间没有任何功能重叠覆盖了空气环境监测的主要维度。根据你的具体场景这套组合可以灵活调整。只关心基础室内环境的话SHT30BMP280就足够了做室内空气质量管理SCD30SGP30SPS30是重点做植物种植环境监测光照和温湿度反而是关键。一个重要的选型原则是优先选数字接口传感器尽量避开模拟输出传感器。模拟传感器比如常见的土壤湿度模块输出的是电压信号需要ADC转换而且电压信号在长线上容易受干扰校准漂移问题也多。数字接口传感器内部自带校准直接把物理量算好通过总线送出来干净利落。2.2 通信接口选型为什么全部走I2C上面六款传感器有一个共同点全部支持I2C通信。这是我特意这么选的。I2C总线只需要两根线SDA数据线、SCL时钟线就可以在一条串行总线上并联多颗设备每个设备通过自己的7位地址区分。这意味着无论挂多少传感器主控GPIO只需要占用两个引脚布线极度简化。相比之下如果每颗传感器都用UART或者SPIGPIO早就被占满了。I2C的另一个好处是接线容错率高。在正确设置上拉电阻的情况下线长1米内都还能稳定工作这对把传感器放在机箱外比如壁挂的场景很重要。UART虽然也能长线传输但一对一连接比较占引脚SPI虽然有更高带宽但4根线加CS片选搞多设备会非常繁琐。I2C也不是没有缺点。它的速度上限一般是400kHz快速模式或1MHz快速模式对传感器数据采集来说完全够用——环境数据本来就是一秒几帧量级根本用不满I2C带宽。如果你以后遇到需要高采样率的传感器比如麦克风阵列再去考虑SPI也不迟。这里要特别提醒一下I2C地址冲突问题。因为所有设备都挂在同一个总线上它们得有不同的地址。BMP280的默认地址是0x76SHT30是0x44BH1750是0x23SCD30是0x61SGP30是0x58SPS30默认也能避开。如果你的传感器和别人的地址撞了通常芯片上会有地址引脚或者寄存器配置位可以改地址实在不行就用TCA9548A这类I2C多路复用器。2.3 供电与电平匹配树莓派GPIO和I2C总线都是3.3V逻辑电平所以选传感器时一定要确定它支持3.3V供电和数据电平。上面列的几款传感器全是3.3V-5V宽压供电、I2C电平3.3V兼容的方案直接挂在树莓派的3.3V电源轨上就行。但这引出了另一个问题多个传感器的总电流。CM4本身的功耗大约在3-6W载板上的3.3V稳压器通常只能提供几百毫安给外部电路六个传感器同时工作时的峰值电流加起来可能超过这个值。我实际测量过这些传感器的电流SHT30约200uABMP280约50uABH1750约200uASGP30约48mA加热阶段SCD30高达约300mA峰值SPS30激光模块也有约60mA。SCD30一个就占了大头。所以我的供电方案是CM4和传感器分开供电。CM4通过载板上的5V DC口供电传感器网络单独从一个5V转3.3V的降压模块取电避免SCD30的峰值电流拖垮系统电压。这在原型阶段看不出来问题但如果长期运行电源噪声和电压跌落会让传感器读数出现随机跳变排查起来要命。3. 硬件组装实操从核心板到传感器网络3.1 烧录eMMC与系统准备如果你的CM4是eMMC版本第一次使用有一个拦路虎把系统烧进eMMC。因为eMMC在CM4模块内部你不能像TF卡那样直接插电脑上写镜像必须先让CM4进入USB烧写模式。具体步骤是这样的把CM4模块上的USB boot拨码开关拨到正确位置不同载板标识不一样通常是USB或MSD模式用USB线把载板上的USB接口连接到电脑电脑上下载树莓派官方的rpiboot工具Windows版直接跑exeLinux/macOS需要编译安装给载板上电CM4会以USB大容量存储设备的形式出现在电脑上用官方Raspberry Pi Imager工具把Raspberry Pi OS Lite镜像写进这个“U盘”注意烧完后要把拨码开关拨回正常启动模式否则下次开机又会进入烧写模式。这个坑我踩过一次折腾了半小时才发现是拨码没拨回来。系统我选的是Raspberry Pi OS Lite 64位版。Lite版没有桌面环境省内存远程连接也干净。对纯采集数据的设备来说桌面是完全多余的东西。3.2 核心板与载板的安装CM4模块靠板底两个JST连接器类似笔记本内存条那种连接方式插到载板上。安装的时候注意方向模块上丝印标了引脚1位置和载板上对应标记对齐后先斜着插到位再轻轻压下固定。这里有两个关键细节散热片必须贴CM4的BCM2711处理器发热不小全速运行时温度轻松上到80度。我用的载板预留了散热片安装孔位配了一块铝制散热片加一个5V小风扇。实测温度从78度跌到50度左右。如果你放在密封机箱里风扇是必须的。天线连接如果你选了WiFi版本CM4模块上有一个U.FL天线座。载板上通常也带一个外接天线座中间那段连接线需要自己扣上去。这个操作很精细U.FL接头是卡扣式的用力按下去听到“咔嗒”一声才算接好。3.3 传感器的接线与布局传感器我全部焊在了一块小的I2C扩展板上板子上留了3.3V、GND、SDA、SCL四根导轨传感器模块像插积木一样插上去再用杜邦线连接。接线顺序明确传感器VCCGNDSDASCLSHT303.3VGNDGPIO2GPIO3BMP2803.3VGNDGPIO2GPIO3BH17503.3VGNDGPIO2GPIO3SCD303.3VGNDGPIO2GPIO3SGP303.3VGNDGPIO2GPIO3SPS303.3VGNDGPIO2GPIO3树莓派上I2C-1总线默认就是GPIO2SDA和GPIO3SCL不用额外配置。所有传感器都挂在同一条SDA/SCL上靠地址区分。还有一个非常容易被忽视的点是I2C总线的上拉电阻。树莓派主板GPIO内部已经有1.8k上拉电阻足以支持少量设备。但当我挂到第五、第六个传感器时总线上的电容负载变大信号上升沿变缓偶尔会出现设备失联。解决方法是:在I2C总线上额外并联一组2.2k欧姆的上拉电阻到3.3V。具体位置尽量靠近总线末端效果会更好。这个操作能显著提升多设备挂载时的稳定性。另外传感器不一定要全放在机箱里。我把SHT30和BMP280用延长线放到了设备外部一个小百叶盒里这样更贴近真实环境空气不受机箱内部CM4发热影响。SCD30和SPS30因为需要更充足的空气流通也在机箱侧面开了进风口。光照传感器BH1750则直接粘在机箱外壁上注意要避开直射灯光。4. 软件栈与数据采集实现4.1 系统基础配置系统烧好后第一次开机建议用有线网络连接路由器或者直接接显示器操作。先执行raspi-config打开几个关键选项设置Interface Options - I2C - Enable启用内核I2C驱动设置Localisation Options把时区改对确保传感器时间戳准确设置SSH远程访问方便以后无头操作设置WiFi连接如果板子在不好插网线的位置配好之后先用i2cdetect -y 1扫描一下总线上有哪些设备。如果一切正常你应该能在输出里看到类似44、58、61、76这样的16进制地址分别对应SHT30、SGP30、SCD30和BMP280。BH1750在23SPS30在69具体看传感器版本。如果地址列表和预期不符优先检查接线、供电然后再考虑模块是否损坏。有一种常见情况是I2C总线被某个地址为UU的设备占用表示该地址被内核驱动占用了不影响使用。4.2 Python驱动与数据采集脚本软件开发我用的Python这是树莓派生态最成熟的语言传感器驱动库几乎都有现成的。我用的库如下smbus2底层I2C读写库所有驱动都基于它Adafruit-BMP280BMP280气压温度读取Adafruit-SHT31SHT30温湿度读取Adafruit-BH1750光照强度读取scd30_i2cSCD30 CO2读取sgp30SGP30 TVOC读取sps30SPS30颗粒物读取官方库在GitHub上可以找到传感器驱动库的基本用法都差不多初始化I2C总线实例化传感器对象然后读取数据。但实际写采集脚本的时候远没有这么简单。我直接放一个精简版的采集脚本作为参考#!/usr/bin/env python3 import time import json import board import busio import adafruit_bmp280 import adafruit_sht31 import adafruit_bh1750 from scd30_i2c import SCD30 from sgp30 import SGP30 # SPS30在部分固件上初始化较慢单独处理 from sps30 import SPS30 # 初始化I2C总线 i2c busio.I2C(board.SCL, board.SDA) # 传感器对象初始化 bmp280 adafruit_bmp280.Adafruit_BMP280_I2C(i2c, address0x76) sht30 adafruit_sht31.SHT31(i2c, address0x44) bh1750 adafruit_bh1750.Adafruit_BH1750(i2c, address0x23) scd30 SCD30(i2c, address0x61) sgp30 SGP30(i2c, address0x58) sps30 SPS30(i2c, address0x69) # SGP30和SPS30需要启动时间 sgp30.start_measurement() time.sleep(5) sps30.start_measurement() time.sleep(5) def read_all(): data {} data[temperature] round(sht30.temperature, 2) data[humidity] round(sht30.relative_humidity, 2) data[pressure] round(bmp280.pressure, 2) data[light] round(bh1750.lux, 2) # SCD30读取可能失败需要重试 if scd30.get_status_register() 0: co2, temp, rh scd30.read_measurement() data[co2] round(co2, 2) else: data[co2] None # SGP30返回TVOC和等效CO2 sgp30_data sgp30.get_air_quality() data[tvoc] sgp30_data[total_voc] data[eco2] sgp30_data[total_eco2] # SPS30返回颗粒物浓度数组 sps30_data sps30.read_measured_values() data[pm1_0] sps30_data[pm1_0] data[pm2_5] sps30_data[pm2_5] data[pm10] sps30_data[pm10] return data if __name__ __main__: while True: try: payload json.dumps(read_all()) print(payload) # 这里后续替换成MQTT发布 except Exception as e: print(f采集异常: {e}) time.sleep(10)这个脚本最核心的两个点是异常处理和滤波。传感器偶尔会返回错误值比如SCD30测量数据还没准备好时读出来是空的SGP30开机前几秒数据也不稳定。虽然我这里用了最简单的打印和异常捕获但正式部署时至少要加一个滑动平均滤波至少把连续5次采集的数据做一次中位数处理能过滤掉大部分偶发漂移值。SPS30和SGP30的启动顺序也很讲究它们都需要在start_measurement()之后等几秒才开始出数据。我试过刚启动就读结果返回的全是垃圾值。等5秒之后数据就正常了。4.3 systemd服务配置采集脚本不能靠人手动跑必须做成系统服务开机自启。这里我强烈推荐用systemd而不是crontab reboot因为systemd有完善的崩溃重启、日志记录和依赖管理。在/etc/systemd/system/env-sensor.service下创建一个服务文件[Unit] DescriptionEnvironmental Sensor Daemon Afternetwork-online.target Wantsnetwork-online.target [Service] Typesimple WorkingDirectory/opt/env-sensor ExecStart/usr/bin/python3 /opt/env-sensor/main.py Restartalways RestartSec10 Userpi Grouppi StandardOutputjournal StandardErrorjournal [Install] WantedBymulti-user.target几个配置参数说明一下Afternetwork-online.target和Wantsnetwork-online.target确保网络就绪后再启动服务避免开机时MQTT连接失败Restartalways让服务崩溃后自动拉起配合RestartSec10防止快速无限重启Userpi指定运行用户避免权限问题StandardOutputjournal把输出写入系统日志用journalctl -u env-sensor -f可以实时查看写完后执行sudo systemctl daemon-reload sudo systemctl enable env-sensor sudo systemctl start env-sensor systemctl status env-sensor这样设备断电重启后采集服务会自动恢复运行。5. 数据上行与可视化5.1 MQTT做数据中枢数据采到之后怎么用刚开始可以在终端打印但一台真正的监测设备必须把数据送出去集中管理。我选了MQTT协议作为数据中枢。MQTT是一个轻量级的发布/订阅消息协议非常适合物联网场景。它的核心概念是Broker消息服务器和Topic主题。传感器设备作为Publisher往Topic发消息下游的存储、告警、可视化程序作为Subscriber订阅Topic两边完全解耦。这意味着你想加一个微信告警机器人只需要再写一个订阅小脚本就行完全不用改采集端。Broker我用的Mosquitto轻量、经典、资源占用低。在服务器上安装sudo apt install mosquitto mosquitto-clients默认配置只允许本机访问如果要从设备端发送到服务器需要配置允许外部监听。在/etc/mosquitto/mosquitto.conf里加一行listener 1883 0.0.0.0生产环境要做用户认证和TLS加密我自己的内网环境做了用户名密码认证allow_anonymous false password_file /etc/mosquitto/passwd生成密码文件sudo mosquitto_passwd -c /etc/mosquitto/passwd envuser然后是设备端的发布。用paho-mqtt库在采集脚本的循环里加上发布逻辑import paho.mqtt.client as mqtt client mqtt.Client() client.username_pw_set(envuser, 你的密码) client.connect(192.168.1.100, 1883, 60) # 每次采集后发布 client.publish(home/env-sensor/data, payload, qos1)QoS等级我选了1保证消息至少送达一次代价是可能重复。对于环境监测这种数据量不大、重复一条也无所谓的场景QoS 1是最合适的。5.2 时序数据库与可视化数据存到普通MySQL不是不行但查询性能、数据压缩和降采样都不如专门的时序数据库。这里我用的InfluxDB 1.8新版2.x改动较大但思路一致它针对时间戳存储做了专门优化。InfluxDB的存储模型很直观一个measurement相当于表tag用于索引field是具体数值time是写入时间。环境监测数据天然适合这种模型home_sensor,devicesensor1 temperature25.3,humidity55.2,co2420 1699999999000000000这样一条数据同时记录了多个field查询的时候按时间范围和时间间隔做聚合非常快。Grafana是前端可视化的标准选择安装之后添加InfluxDB数据源配置好查询语句面板上就能画出温湿度曲线、CO2趋势、颗粒物柱状图。它支持代数运算比如把BMP280的气压转成海平面气压需要温度和湿度修正这些都可以在Grafana的查询表达式里完成。整条数据链路是传感器 - I2C总线 - CM4采集脚本 - MQTT Broker - InfluxDB - Grafana每个环节都是独立组件任何一个挂了都不会导致整个链路失控。比如采集端和Broker之间网络断开那采集脚本本地数据会堆积在内存里等网络恢复之后继续发。如果这个容错还不够可以在采集端加一个SQLite或CSV本地存储作为兜底定时把未发送的数据存入本地文件恢复后再补发。6. 踩坑记录与排查手记6.1 传感器“消失”的排查过程我遇到过的最恼人的问题传感器明明接好了开机时i2cdetect还能看到设备运行一段时间后某些地址突然从总线上消失。排查到最后发现是两个因素叠加第一个因素是SCD30的峰值电流。SCD30上有自动校准加热器启动瞬间电流能到300mA以上这会拉低整个3.3V供电电压。当电压跌到传感器的最低工作电压以下时其它传感器就会瞬间掉线。解决方法是前面提到的独立给传感器网络供电。第二个因素是I2C总线上的电容负载。挂的设备越多总线电容越大信号边沿越缓设备偶尔把高电平误判成低电平导致通信失败。增加上拉电阻后问题明显缓解我之前用的2.2k上拉电阻效果很好如果还不行可以换更小阻值的。排查步骤我整理成了固定套路先用i2cdetect看设备在不在不在就检查电压是否正常、SDA/SCL是否接触良好、有没有设备地址冲突。如果设备在但读数据失败重点检查传感器模块的电源纹波和总线干扰。6.2 数据跳变的三个常见来源环境监测数据的跳变是另一个让人头疼的问题。比如CO2数值三分钟前420ppm突然跳到1000ppm再跳回来。以下是我排查跳变问题的经验优先级采样太频繁SCD30这类传感器本身有响应时间如果5秒读一次数据流上会看到明显的锯齿状波动。我改成10秒采样一次数据平滑很多。另外做一次滑动平均滤波能滤掉高频噪声。电源噪声CM4的开关电源模块纹波如果传导到传感器供电线上高分辨率传感器尤其是SPS30会受到明显影响。我最终用了一个低噪声LDO稳压模块给传感器供电读数立刻稳了不少。气流干扰CO2传感器受风吹影响很大。设备放在阳台边上偶尔一阵风吹过读数就会出现暂时的尖峰。这种不是故障但如果要长期稳定的监测曲线建议给传感器加上防风的遮蔽罩。6.3 长时间运行掉线的处理设备长时间运行最怕掉线。我遇到过以下几种情况WiFi掉线后无法自动重连树莓派的WiFi驱动偶尔会“假死”。解决方法是设置一个定时任务定期ping网关ping不通就重启WiFi接口。更极端的做法是用watchdog模块开启硬件看门狗系统死机后自动重启动。Python进程占用内存越来越多这是典型的长时间运行内存泄漏。排查发现是某个传感器驱动在循环里不断创建新对象。解决方案是显式del临时变量或者定时重启采集进程。日志文件无限增长systemd的journal日志默认是有容量限制的但如果你在采集脚本里开了文件日志一定会把TF卡塞满。务必做好日志轮转或者只保留最近N天的文件。6.4 常见问题速查表症状可能原因解决方案i2cdetect看不到任何设备I2C未启用/接线错误检查config.txt检查SDA/SCL和VCC/GND某个传感器间歇性消失供电不足或上拉电阻不当独立供电检查总线上拉电阻数据出现规律性尖峰电磁干扰或电源纹波缩短长线增加滤波电容开机不启动服务systemd配置错误检查service文件执行daemon-reloadeMMC无法烧写拨码开关位置不对切换USB boot模式重装rpibootWiFi经常断连驱动问题或信号弱重启网络服务或添加外置天线7. 长期运行经验与扩展方向7.1 部署位置与防护设备部署的位置对长期稳定运行影响巨大。我在阳台机身盒子里加了个防水透气的挡板避免阳光直射外壳因为黑色壳体在夏天暴晒后内部温度能高出环境温20度以上这会让传感器测到的温度完全失真。如果设备要在室外或者灰尘大的环境使用建议每三个月拆开清理一次风扇和散热片灰尘堵住散热片会让CM4急剧升温降频进而让采集时间漂移、数据质量变差。7.2 数据备份与固件更新时序数据全部存在一个InfluxDB实例上我还是做了备份方案。InfluxDB支持连续查询可以定期把原始数据降采样到10分钟粒度然后export备份。这个备份频率不需要太高我设置的是每天凌晨3点自动导出前一天数据上传到NAS。设备端计算模块本身不需要频繁更新但如果要更新采集固件最稳妥的方式是OTA式的先把新版本脚本放到服务器上设备端定时检查版本号发现新版本就下载替换并重启服务。我用的是一个简单的Shell脚本加cron实现虽然简陋但足够可靠。7.3 后续扩展思路CM4的PCIe接口我一直留着没用后续如果想直接在这个设备上跑轻量级机器学习模型比如根据环境数据预测室内空气质量趋势可以接一块NVMe固态把模型装在本地而不必每次推理都走网络请求回服务器。另外加一个4G LTE模块就能让这个设备完全脱离WiFi网络跑在户外配合太阳能板供电就能变成真正的野外环境监测站。CM4的功耗控制在5W以内一块20W太阳能板的发电量足够支撑全天运行。这类方案在农业生产和气象站项目里很实用。给我感觉最值得投入的方向是固件端的数据质量标记。原始采集数据里混着设备自检状态、传感器重启次数、信号波动标记这会让后续的数据分析省很多掉头发的功夫。我现在已经在采集脚本里加入了这些状态字段虽然占空间但对长期趋势分析来说非常重要。如果你也打算做这类环境监测设备从第一天开始就把“数据质量”当成一等公民对待别等到积累了几个月数据才发现格式不合理。