资讯动态

基于Cortex-M0的室内外气象监测仪:I2C传感器与长线通信实践

发布时间:2026/10/5 9:18:06 来源:尧图企业网站定制
做室内外气象监测这种小项目我第一个想到的问题不是“能不能做”而是“用什么平台做才不浪费”。手头这块W55MH32-ADK开发板主控是ARM Cortex-M0内核的单片机板载资源不算豪华但GPIO、ADC、I2C、SPI、UART全都有。拿它做气象站既能把这颗芯片的片内外设用得比较透又不会像用树莓派那样杀鸡用牛刀。这篇内容适合两类人看一类是刚接触Cortex-M0平台、想找一个完整练习项目的嵌入式新手另一类是手头有类似MCU评估板、想快速搭一个能长期跑的室内外温湿度气压监测器的朋友。我会把从传感器选型、长线连接、驱动代码到调试踩坑的全过程都写出来代码可以直接抄但更重要的是理解每一步为什么这么做。1. W55MH32-ADK平台初识与项目设计思路1.1 平台资源盘点这颗MCU到底够不够用W55MH32-ADK这名字里ADK就是Application Development Kit也就是应用开发套件。板子上除了最小系统之外通常会把MCU的引脚通过排针引出另外提供几颗LED、按键有的版本还有电位器和LCD接口。先别急着写代码把资源理清楚是第一步。以我手头这块板子为例MCU跑在几十MHz量级Flash和SRAM的容量具体因批次有差异但跑一个气象监测器完全够了。真正决定方案的是外设I2C控制器、UART、ADC、PWM、定时器这些是关键。你不需要用RTOS不需要外部存储也不需要网络协议栈所以Cortex-M0级别是刚好匹配的。为什么不用ESP32或者树莓派原因有两个。第一这个项目的数据量极小几秒钟读一次传感器换算后刷新屏幕ESP32的Wi-Fi和蓝牙功能在这里完全闲置第二用W55MH32-ADK这类评估板做项目能逼着你直接把寄存器、I2C时序、中断定时器这些底层东西搞明白。等以后换到任何一款ARM单片机这套知识都是通用的。1.2 项目目标与设计约束我的目标非常具体室内显示温度、湿度、气压室外显示温度、湿度所有数据在一块屏幕上同时呈现并且长期稳定运行。室外部分不是用无线模块而是采用有线探头方案把传感器用屏蔽线引到窗外。这样做的原因很简单——无线会引入配对、功耗、干扰等问题对初学者不友好而两线或四线的I2C/单总线屏蔽线方案在十几米范围内完全可靠。设计约束有三点。第一整机只有一个5V或3.3V供电室外传感器必须能与主机同电压工作。第二显示刷新不能阻塞主循环否则按键和后续扩展都会受影响。第三传感器数据要经过滤波和合理性检查不能直接显示原始值否则偶尔的读错误会吓到人。1.3 整体架构一主两从分路采集主机就是W55MH32-ADK。室内传感器走I2C总线我用的是SHT30温湿度传感器加BMP280气压传感器这两颗芯片挂在同一条I2C总线上。室外探头用另一路I2C挂一颗SHT30地址通过引脚配置改到0x45避免和室内SHT300x44冲突。显示用I2C接口的0.96寸OLED屏幕扫描频率低不占用宝贵引脚。于是整个系统就是一条主I2C总线挂室内传感器和屏幕另一条I2C总线单独为室外探头服务。为什么室外要分一条独立总线而不是共用因为室外那根线比较长线缆电容会拖慢信号边沿如果和室内传感器混在一起一旦线上出现振铃一颗器件的数据错误会让整条总线上的设备都受到影响。分总线之后即使室外线缆质量一般也不会影响室内传感器读取。2. 传感器选型与关键参数计算2.1 室内外传感器为什么都选SHT30SHT30是Sensirion公司的数字温湿度传感器I2C接口精度在-20到60摄氏度范围内典型±0.3摄氏度湿度典型±2%RH。最方便的是它出厂做过校准不需要自己标定。更关键的一点是SHT30可以通过改变ADDR引脚电平来切换I2C地址拉低是0x44拉高是0x45。这就解决了“一颗主机读两个相同传感器”的经典问题。室内我还加了一颗BMP280气压传感器。可能有人会问室外探头为什么不也带气压因为气压在几十米范围内几乎不变室外探头测到的气压和室内测量没有本质区别。把BMP280放在室内避开了室外探头长线连接的额外复杂度只要注意不要把传感器贴在窗户缝边或者空调直吹的位置就行。当然也有替代方案。如果你手里只有DHT11/DHT22也能做但DHT类传感器的时序要求比较苛刻长线传输时误码率高而且湿度精度普遍在±2%到±5%之间。SHT30虽然贵几块钱但省掉的时间成本远不止这几块钱。传感器选型省什么都可以别在模拟前端省这是很多项目后来精度翻车的根源。2.2 室外探头的长线设计细节室外SHT30到主机之间我用了大约1.5米的四芯屏蔽双绞线电源、地各用一根SCL和SDA用一对双绞线屏蔽层在主机端单点接地。线缆的线径不小于0.2平方毫米即可太细的线在压降和抗弯折方面都会出问题。屏蔽层单点接地这一点特别重要。如果两端都接地室外探头和主机之间容易形成地环路反而会把共模干扰导入信号线。另外I2C是开漏结构上拉电阻在主机端不能放在室外端。长线带来的电容上升会让SCL和SDA的边沿变缓所以上拉电阻取值要比常规板内连接的4.7k稍大我实测用6.8k比较合适再配合把I2C时钟降到100kHz标准模式信号依然干净。还有一个经常被忽略的点室外探头要做好防潮和防凝露。SHT30本身有覆盖保护膜但引脚焊盘和线缆接头处最好用热缩管加硅胶做密封。否则雨季一到湿度传感器读数会一直卡在99%RH下不来不是传感器坏了是板子进水了。2.3 温湿度气压的换算公式I2C读到的都是16位原始计数不换算成物理单位没有任何意义。SHT30的温度换算公式是T -45 175 × raw / 65535单位是摄氏度。湿度的公式是RH 100 × raw / 65535单位是%RH。这两个公式在SHT30数据手册里写得很清楚raw的合法范围是0到65535。如果你的程序里读到的raw是0或者65535说明要么传感器地址写错要么线缆断了。BMP280要稍微麻烦一点。它的校准参数存储在生产时写入的寄存器里一共有11组系数包括dig_T1/dig_T2等。读取原始温度后要先算中间变量var1、var2再算实际温度气压还要进一步用温度补偿。完整公式比较长我在最后给的代码里会直接写上不需要你背但你要知道“必须先把校准值读出来再做补偿”这件事任何带校准寄存器的压力传感器都类似。精度控制上我做了两个额外处理一是连续采3次数据去掉一个最大值一个最小值再取平均二是把换算结果放到全局变量里等待3次成功更新后才刷新屏幕。读失败时显示上次有效值而不是显示-999或跳变乱码。对用户来说屏幕偶尔卡住一拍远好过不断跳数字。3. 代码实现从驱动到显示3.1 工程目录与驱动分层我建议把工程按下面这种方式分文件别把代码全塞进main.cproject/ ├── main.c // 主循环与调度 ├── sht30.c // SHT30驱动 ├── bmp280.c // BMP280驱动 ├── ssd1306.c // OLED显示驱动 ├── i2c_soft.c // 软件I2C或硬件I2C封装 ├── display.c // 页面排版与刷新 └── config.h // 引脚、地址、速率等宏驱动分层的核心价值是方便替换。比如你手头没有SHT30而是用的AHT20那只要重写sht30.c内部的读写函数接口保持read_temperature_humidity(float *t, float *rh)不变上层代码一行都不用动。反过来如果主控从W55MH32换到别的Cortex-M0也只改i2c_soft.c这一个文件。这种解耦方式在项目变大之后价值极其明显。3.2 SHT30与BMP280的读取函数SHT30每次测量要发一个命令然后等待大约15毫秒再读取6个字节数据温度高字节、温度低字节、CRC校验、湿度高字节、湿度低字节、CRC校验。我用的命令是0x2C 0x06对应高重复性测量功耗高一点但噪声更低。数据手册允许发完命令后不等延时直接开读但实际测试下来还是老老实实延时15ms以上最稳尤其是室外探头那根长线上过早读取容易拿到未更新的旧数据。uint8_t sht30_read(float *temperature, float *humidity) { uint8_t cmd[2] {0x2C, 0x06}; uint8_t buf[6]; uint16_t raw_t, raw_rh; // 发送测量命令 i2c_write_reg(SHT30_ADDR, cmd, 2); // 等待测量完成 delay_ms(20); // 读取6字节数据 if (i2c_read_reg(SHT30_ADDR, NULL, 0, buf, 6) 0) { return 0; } // 检查CRC if (!crc8_check(buf[2], buf)) { return 0; } if (!crc8_check(buf[5], buf 3)) { return 0; } raw_t (buf[0] 8) | buf[1]; raw_rh (buf[3] 8) | buf[4]; *temperature -45.0f 175.0f * raw_t / 65535.0f; *humidity 100.0f * raw_rh / 65535.0f; return 1; }CRC校验是这个驱动里最容易漏掉的部分。SHT30的CRC是多项式0x31的8位CRC初始值0xFF。有不少程序不加CRC也能读到数据但长线传输时偶发一位错误就能让温度显示跳到70度。多写十几行代码把错误帧挡在门外这个投入非常值。BMP280的初始化稍微长一些需要连续读多个校准寄存器。校准参数读出来之后按数据手册的计算流程写就行。这里只提醒一点BMP280的寄存器地址是单字节地址很多I2C封装函数是“先写寄存器地址再读写数据”如果你把寄存器地址和命令数据混在一起传会导致地址错位。我一开始就是接口封装没注意读出来的温度一直在27.1度跳排查了大半天才发现是I2C写操作的寄存器地址被当成了命令的一部分。3.3 OLED显示与刷新策略我用的是0.96寸128x64 OLEDSSD1306控制器I2C接口。屏幕初始化序列直接从常见驱动改过来就行但显示排版和刷新逻辑值得认真设计。我把屏幕分成三个区域顶部显示室内温度和湿度格式如In 26.4C 52%RH中部显示室外温度和湿度格式如Out 18.1C 68%RH底部显示气压格式如Bar 1013.2hPa每一行数据在读取成功后刷新失败则保留旧值。刷新周期我设为2秒一次。为什么不追求1秒一次因为SHT30的最小测量周期是10毫秒但OLED的I2C写入一个全屏图像大约要几十毫秒刷新太频繁会让屏幕出现可见闪烁。而且气象数据本来就不是需要实时反馈的信号2秒完全够用。我实测将刷新频率降到2秒后CPU占用率几乎可以忽略。3.4 编译与下载链路W55MH32-ADK的编译工具链是ARM-GCC或者Keil MDK我习惯用Makefile加arm-none-eabi-gcc。工程里链接脚本和启动文件在官方BSP里都有模板不要自己从头写启动代码。烧录通常有两种方式通过板载Nu-Link仿真器或者通过UART的ISP引导。用Nu-Link的话直接用官方烧录工具把hex下载进去就行用ISP方式需要先把Boot Loader跳线设好然后通过串口工具发送固件。两种方式都支持我建议优先用Nu-Link因为它还能在调试器里单步看I2C时序状态查找问题效率高很多。4. 实测现场与踩坑记录4.1 传感器无应答返回0xFF或0x00这个现象最典型。第一次上电I2C扫描一个设备都找不到读寄存器全是0xFF。先别怀疑芯片坏了按顺序排查供电是否达到3.3V传感器地址是否正确SDA和SCL是否接反SHT30的地址是7位地址0x88包含写位或0x447位地址但I2C从设备地址通常写成不含读写位的0x44。很多新手第一次接触I2C时会把0x44和0x88搞混。我的建议是写一个扫描函数遍历0x03到0x77的所有7位地址打印出有ACK的地址这样五个设备半天能确认完。4.2 室外长线I2C通信不稳定现象是室内数据正常室外数据偶尔读失败尤其在用手触碰线缆的时候。问题出在两处一是线缆电容导致信号边沿过慢二是我最初用的上拉电阻太小开漏输出在1.5米线上形成了振铃。把上拉电阻换到6.8k并将I2C速率降到100kHz之后问题消失。这是非常典型的长线I2C问题可以记住一个经验板内短距离I2C可以用400kHz甚至1MHz一旦线缆超过30厘米优先降到100kHz超过2米还要考虑上拉电阻调整和屏蔽。I2C本身的驱动能力并不强并不是说速率设低就一定可靠线缆质量和屏蔽同样关键。另外上拉电阻不要放在室外探头端。如果放在室外端等于把开漏上拉的长线回路变长噪声会直接从远端耦合进接收端。把上拉放在主机端信号以主机为参考点噪声影响最小。4.3 OLED花屏、数据刷新卡顿OLED花屏多半是I2C速率太高或者电源纹波干扰。SSD1306虽然标称支持400kHz但我在长线短的屏幕上把速率降到200kHz后显示稳定性明显提升。注意屏幕的电源引脚和传感器电源之间最好加一个100nF去耦电容否则OLED内部电荷泵工作时会把电源轨拉出毛刺干扰同一总线上其他器件。刷新卡顿的根源往往不是主控性能而是刷新函数里用了大量延时。SSD1306的驱动不需要在读操作后死等很多移植过来的Python驱动里写了几十毫秒的延时在单片机上就显得特别慢。改成写完后直接返回每2秒在后台刷新一次主循环还能同时处理按键扫描和串口打印。4.4 湿度跳变的真凶传感器自热与环境扰动湿度读数在室内正常但把室外探头拿到阳光下暴晒读数会明显偏低。这不是SHT30坏了是传感器外壳被阳光加热探头周围微环境温度升高导致相对湿度下降。SHT30测的是探头周围空气的湿度而不是整个室外的平均湿度所以室外探头必须放在百叶箱或者至少用外壳遮挡直射阳光和风雨。另外还有一个经验值得分享。SHT30的测量会产生少量自热连续高频率读取会让温度读数偏高0.1到0.3度。气象站这种项目不需要特别高的实时性把测量间隔拉长到2秒以上自热影响就几乎测不出来了。如果你对温度精度有更高要求可以增加一个查表校正项但实际用途中没必要。4.5 长期运行的稳定性验证这个项目放到室外一个雨天的窗台上连续跑了三天期间只有一次通信失败记录且发生在室外探头接口进水之后。把热缩管重新封好后恢复正常。单片机和传感器在正常环境下连续运行一周没有任何问题OLED屏幕没有出现烧屏和残影。关于长期运行的另一个心得是数据滤波与异常值处理。所谓的“滤波”不一定要用复杂的卡尔曼滤波器对气象数据来说中值滤波加合理范围检查就足够。我在采集函数里判断温度在-40到60度、湿度在0到100%RH之外的数据直接丢弃连续丢3次之后再标记该通道异常并显示“--”这样屏幕永远不会出现离谱的跳变。这个项目的代码量不大但涉及了I2C多设备管理、长线通信、传感器补偿、显示刷新、异常处理这几个在嵌入式开发里非常典型的场景。跑通之后你手里的W55MH32-ADK就不再只是点灯板而是一台能实际放在窗台上看天气的小仪器。下一步如果还想玩可以加个微型继电器控制加湿器或者把数据通过串口转发给上位机画曲线都是在这套代码上做增量的事。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑