☰
Arduino UNO Q 多传感器环境感知节点:SENSE v0.1 实战
2026/10/7 7:44:50 网站建设 项目流程

1. 从一块开发板说起:SENSE v0.1 到底想解决什么问题

1.1 这个项目是什么,适合谁看

SENSE v0.1 是我最近折腾的一个小型环境感知节点项目,核心思路很简单:用一块 Arduino UNO Q 做主控,挂上 SH1107 OLED 显示屏、LSM6DSO 六轴惯性传感器、SHT40 温湿度传感器和 SGP40 空气质量传感器,做成一个能实时显示环境数据的桌面小设备。名字叫 SENSE,取的就是"感知"的意思,v0.1 说明它还在很早期的阶段,功能不算复杂,但该有的骨架都搭起来了。

这个项目适合几类人参考:一是刚接触 Arduino 生态、想找一个"传感器种类够多但不至于劝退"的练手项目的人;二是手里已经攒了几块传感器模块、想找个理由把它们串起来用的人;三是对环境监测、桌面小摆件感兴趣,想自己做一个能看温湿度、空气质量的小工具的人。如果你完全没碰过 Arduino,也不用慌,我会把每个环节的坑都讲清楚,照着做基本能跑通。

需要提前说明的是,SENSE v0.1 不是一个追求极致精度或工业级可靠性的项目,它更像是一个"能跑起来、能看数据、能继续往上加功能"的原型。我个人的定位是把它当成一个可扩展的感知平台,后面想加什么传感器、想接什么显示逻辑,都可以在这个基础上改。

1.2 为什么选这几个器件

选型这件事,我踩过不少坑,最后定下来的这套组合,背后是有取舍的。

主控选 Arduino UNO Q,主要是看中它在经典 UNO 形态上做了升级,引脚布局熟悉,社区资料多,同时性能比老款 UNO 强不少,跑多个 I2C 传感器加一块 OLED 刷新完全够用。如果你手里是别的 UNO 兼容板,大部分逻辑也能直接搬过去,只要注意 I2C 引脚定义和电压电平就行。

显示屏用 SH1107 OLED,而不是更常见的 SSD1306,原因很实际:SH1107 常见于 1.12 寸到 1.3 寸的小屏,分辨率有 128x128 这种方形规格,显示多行数据时排版更舒服。SSD1306 大多是 128x64,行数少,塞温湿度加空气质量会显得挤。SH1107 的驱动库虽然没 SSD1306 那么泛滥,但 Adafruit 和 U8g2 都支持,实际用下来问题不大。

传感器三件套的分工是这样的:LSM6DSO 负责加速度和角速度,能感知设备的姿态和晃动;SHT40 负责温湿度,精度和稳定性在同类里口碑很好;SGP40 负责挥发性有机物(VOC)相关的空气质量指数,适合做趋势判断。这三个都是 I2C 接口,接线简单,地址也不冲突,这是能挂在一起的前提。

提示:选传感器时,I2C 地址冲突是最容易被忽略的坑。SHT40 默认地址是 0x44,SGP40 是 0x59,LSM6DSO 常见是 0x6A 或 0x6B,SH1107 一般是 0x3C 或 0x3D。动手前先用 I2C 扫描程序确认一遍,能省掉大量"为什么读不到数据"的排查时间。

2. 硬件连接与地址规划:把线接对是成功的一半

2.1 供电与电平的注意事项

Arduino UNO Q 的 I2C 引脚通常工作在 3.3V 或 5V 逻辑,具体要看板子版本。SHT40、SGP40、LSM6DSO 这几款传感器基本都是 3.3V 供电和 3.3V 逻辑,SH1107 OLED 模块有的支持 3.3V 到 5V 宽压,有的只支持 3.3V。这里有个关键点:如果主控 I2C 是 5V 逻辑,而传感器是 3.3V 逻辑,直接连可能损坏传感器,或者通信不稳定。

我的做法是统一走 3.3V。UNO Q 上一般有 3.3V 输出引脚,把四个模块的 VCC 都接到 3.3V,GND 共地,SDA 和 SCL 分别并联到主控的 I2C 引脚。这样电平一致,最省心。如果某个 OLED 模块只支持 5V 供电但逻辑是 3.3V,那就要单独处理供电,别偷懒。

实际接线时,我建议用面包板先搭一遍,确认能通信再考虑焊接到洞洞板或做 PCB。I2C 总线上的上拉电阻也值得留意:很多模块自带上拉,如果多个模块都带上拉,并联后阻值会变小,可能导致总线负载过重。一般 4.7k 到 10k 是常见值,模块自带的话通常不用额外加,但如果通信不稳,可以检查一下总上拉阻值。

2.2 I2C 地址扫描与冲突排查

接线完成后,第一件事不是写业务代码,而是跑一个 I2C 扫描程序,把所有在线设备的地址打印出来。这一步能直接告诉你哪些模块被识别到了、地址是多少。

#include <Wire.h> void setup() { Wire.begin(); Serial.begin(115200); while (!Serial); Serial.println("I2C Scanner starting..."); } void loop() { byte error, address; int nDevices = 0; Serial.println("Scanning..."); for (address = 1; address < 127; address++) { Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { Serial.print("Device found at 0x"); if (address < 16) Serial.print("0"); Serial.println(address, HEX); nDevices++; } } if (nDevices == 0) Serial.println("No I2C devices found"); delay(5000); }

跑完这个程序,你应该能看到类似 0x3C(OLED)、0x44(SHT40)、0x59(SGP40)、0x6A(LSM6DSO)这样的地址列表。如果某个设备没出现,先检查接线,再检查供电,最后检查模块本身是否损坏。我遇到过好几次是杜邦线接触不良,换根线就好了,别一上来就怀疑代码。

注意:SHT40 的地址是固定的 0x44,不能通过引脚改。如果你同时要用两个 SHT40,就需要 I2C 多路复用器,比如 TCA9548A。SGP40 地址固定 0x59,同理。LSM6DSO 可以通过 SA0 引脚在 0x6A 和 0x6B 之间切换,这个设计在需要多传感器时很有用。

2.3 各模块的初始化顺序

初始化顺序在 v0.1 里我做了固定安排:先初始化 Wire 库,再初始化 OLED,然后依次初始化三个传感器。原因是 OLED 初始化最快,先点亮屏幕能给后面的调试一个视觉反馈;传感器初始化如果失败,可以在屏幕上直接显示错误信息,比只看串口方便。

每个传感器的初始化都要检查返回值。比如 SHT40 的 begin 函数如果返回 false,说明通信失败,这时候不要继续往下跑,直接在屏幕上显示"SHT40 FAIL",然后进入一个安全的等待循环。这样你一眼就能看出是哪个模块出了问题,而不是面对一堆乱码数据发呆。

3. 传感器数据读取:每个器件的脾气都不一样

3.1 SHT40 温湿度读取的细节

SHT40 用起来很舒服,但有几个细节不注意就会读到错误数据。它支持几种测量模式,区别在于精度和功耗。v0.1 里我用的是高精度模式,命令是 0xFD,测量时间大约 8.3 毫秒。发送命令后要等足够时间再读数据,读太早会拿到旧值或者无效值。

数据格式是 6 个字节:温度高字节、温度低字节、温度 CRC、湿度高字节、湿度低字节、湿度 CRC。转换公式是:

  • 温度 = -45 + 175 * (raw_temp / 65535)
  • 湿度 = -6 + 125 * (raw_hum / 65535)

湿度公式里的 -6 和 125 是官方给的修正系数,直接套用就行。CRC 校验建议做,虽然大多数时候数据没问题,但一旦总线受干扰,没有校验就可能把错误数据当成真实值显示出来。

#include <Wire.h> #include <Adafruit_SHT4x.h> Adafruit_SHT4x sht4 = Adafruit_SHT4x(); void setupSHT40() { if (!sht4.begin()) { Serial.println("SHT40 not found"); return; } sht4.setPrecision(SHT4X_HIGH_PRECISION); sht4.setHeater(SHT4X_NO_HEATER); } void readSHT40() { sensors_event_t humidity, temp; sht4.getEvent(&humidity, &temp); Serial.print("Temp: "); Serial.print(temp.temperature); Serial.println(" C"); Serial.print("Hum: "); Serial.print(humidity.relative_humidity); Serial.println(" %"); }

加热器功能在潮湿环境下有用,但会显著增加功耗和温度读数偏差,v0.1 里默认关闭。如果你发现湿度长期接近 100%,可以考虑短时开启加热器驱潮,但别一直开着。

3.2 SGP40 空气质量数据的理解

SGP40 输出的是一个原始信号,需要经过算法转换成 VOC 指数。它不像某些传感器直接给你一个"空气质量等级",而是给你一个 0 到 500 左右的指数,数值越低越好。这个指数是相对值,不是绝对浓度,所以更适合看趋势而不是看单点数值。

SGP40 的读取需要先发送湿度补偿值,因为它的测量结果受环境湿度影响。SHT40 读到的温湿度正好可以用来做补偿,这也是我把这两个传感器放在一起的原因之一。补偿值的转换有固定公式,把温度和湿度转成对应的 ticks 再传给 SGP40。

#include <Wire.h> #include <Adafruit_SGP40.h> Adafruit_SGP40 sgp; void setupSGP40() { if (!sgp.begin()) { Serial.println("SGP40 not found"); return; } } void readSGP40(float temp, float hum) { uint16_t voc = sgp.measureVocIndex(temp, hum); Serial.print("VOC Index: "); Serial.println(voc); }

SGP40 有个"预热"特性:刚上电时读数会偏高,需要运行一段时间才稳定。我一般让它跑 5 到 10 分钟再看数据。另外,它对面部酒精、香水、烹饪油烟都很敏感,放在厨房附近数值会飙升,这是正常现象,不是故障。

3.3 LSM6DSO 姿态数据的处理

LSM6DSO 是六轴传感器,输出三轴加速度和三轴角速度。v0.1 里我主要用它做两件事:一是检测设备是否被移动或倾斜,二是给后续可能的姿态计算留接口。

加速度数据的单位是 g,静止时某个轴应该接近 1g(取决于摆放方向)。角速度单位是度每秒,静止时应该接近 0。如果静止时读数漂移很大,可能是量程设置不合适,或者传感器需要校准。

#include <Wire.h> #include <Adafruit_LSM6DSO.h> Adafruit_LSM6DSO lsm; void setupLSM6DSO() { if (!lsm.begin_I2C()) { Serial.println("LSM6DSO not found"); return; } lsm.setAccelRange(LSM6DS_ACCEL_RANGE_2_G); lsm.setGyroRange(LSM6DS_GYRO_RANGE_250_DPS); lsm.setAccelDataRate(LSM6DS_ACCEL_RATE_104_HZ); lsm.setGyroDataRate(LSM6DS_GYRO_RATE_104_HZ); } void readLSM6DSO() { sensors_event_t accel, gyro, temp; lsm.getEvent(&accel, &gyro, &temp); Serial.print("Accel X: "); Serial.print(accel.acceleration.x); Serial.print(" Y: "); Serial.print(accel.acceleration.y); Serial.print(" Z: "); Serial.println(accel.acceleration.z); }

量程选择上,2G 和 250dps 适合桌面级应用,灵敏度高。如果你要做剧烈运动的检测,可以调到 4G 或 8G,但分辨率会下降。数据速率 104Hz 对显示用途来说绰绰有余,再高只会增加总线负担。

4. SH1107 显示布局:小屏幕上怎么塞下所有信息

4.1 屏幕初始化与坐标系

SH1107 的初始化比 SSD1306 稍微麻烦一点,因为它的显存寻址方式和分辨率规格更多样。我用的是 U8g2 库,它对 SH1107 的支持比较完善,能自动处理不同分辨率的偏移问题。

#include <U8g2lib.h> #include <Wire.h> U8G2_SH1107_128X128_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); void setupOLED() { u8g2.begin(); u8g2.setContrast(128); u8g2.clearBuffer(); u8g2.setFont(u8g2_font_6x10_tf); u8g2.drawStr(0, 12, "SENSE v0.1"); u8g2.sendBuffer(); }

这里有个坑:不同厂家的 SH1107 模块,显存列偏移可能不一样。有的模块需要设置 0x00 偏移,有的需要 0x02。如果显示内容整体偏移了两列,或者右边有花屏,大概率是这个问题。U8g2 的构造参数里可以指定偏移,具体值要查你手上模块的规格书,或者试出来。

4.2 多传感器数据的排版策略

128x128 的屏幕,用 6x10 的字体,一行能放大约 21 个字符,纵向能放 12 行左右。我的排版是这样的:

  • 第一行:标题 "SENSE v0.1"
  • 第二到三行:温度、湿度
  • 第四到五行:VOC 指数和状态
  • 第六到七行:加速度 X/Y/Z
  • 第八到九行:角速度 X/Y/Z
  • 底部:运行时间或状态提示

这样安排的好处是信息分区清晰,一眼能看出哪块数据异常。如果屏幕更小,比如 64x128,就要考虑分页显示,用按键切换页面。v0.1 里我没做按键,因为屏幕够用,后面加按键的话可以扩展成多页。

刷新策略上,不要每次循环都全屏重绘,那样会闪。我的做法是只更新数值变化的区域,用u8g2.clearBuffer()配合局部绘制,或者用双缓冲机制。U8g2 本身支持缓冲,sendBuffer()才真正推送到屏幕,所以绘制过程不会闪。

4.3 数值格式化与单位显示

传感器读出来的是浮点数,直接显示会带一长串小数,既占地方又不好看。我的处理是:温度保留一位小数,湿度保留整数,VOC 显示整数,加速度保留两位小数。用dtostrf或者snprintf格式化后再绘制。

char buf[16]; snprintf(buf, sizeof(buf), "T: %.1f C", temp); u8g2.drawStr(0, 24, buf); snprintf(buf, sizeof(buf), "H: %.0f %%", hum); u8g2.drawStr(0, 36, buf);

单位符号要注意字体支持。U8g2 的某些字体不包含摄氏度符号,显示出来会是乱码。我一般直接用 "C" 代替 "°C",省事又不会出错。百分号在大多数字体里都有,问题不大。

5. 常见问题与排查技巧实录

5.1 传感器读不到数据的排查顺序

这是最高频的问题,我整理了一个排查顺序,按这个走基本能定位:

现象可能原因排查方法
I2C 扫描无设备接线错误或供电不足检查 VCC/GND/SDA/SCL,测电压
扫描到地址但读数为 0初始化失败或模式未设置检查 begin 返回值,确认测量模式
数据偶尔跳变总线干扰或上拉不当缩短线长,检查上拉电阻
某个传感器一直失败模块损坏或地址冲突单独测试该模块,换模块验证
OLED 显示花屏显存偏移或分辨率不匹配调整 U8g2 构造参数

我遇到最多的是供电问题。四个模块同时挂在 3.3V 上,如果主控的 3.3V 输出电流不够,就会出现某些模块时好时坏。这时候可以给传感器单独供电,或者换一个电流能力更强的稳压模块。

5.2 SGP40 数值异常的判断

SGP40 的数值异常通常有三种表现:一直很高、一直很低、剧烈跳动。

一直很高,最常见的原因是刚上电没预热,或者附近有酒精、香水、清洁剂。等十分钟再看,如果还是高,检查一下是不是放在了密闭空间里,VOC 积累会导致读数偏高。

一直很低,可能是传感器没真正开始测量,或者湿度补偿值传错了。确认measureVocIndex的调用参数是当前温湿度,而不是固定值。

剧烈跳动,多半是电源噪声或者 I2C 干扰。可以在 SGP40 的 VCC 和 GND 之间加一个 100nF 电容,能明显改善。

5.3 OLED 显示不全或偏移的处理

SH1107 的显示问题我踩过两次。一次是内容整体右移两列,查了半天发现是模块的显存偏移设置不对,在 U8g2 初始化时指定偏移量后解决。另一次是屏幕下半部分不显示,原因是分辨率参数写错了,实际是 128x128,我按 128x64 初始化,自然只显示一半。

提示:拿到一块不熟悉的 OLED 模块,先确认三件事:驱动芯片型号(SH1107 还是 SSD1306)、分辨率(128x64 还是 128x128)、I2C 地址(0x3C 还是 0x3D)。这三个信息对了,显示基本不会出大问题。

5.4 整体运行不稳定的系统级排查

如果单个模块都正常,但合在一起跑就出问题,那多半是系统级的原因。我总结了几条经验:

  • 电源要够:四个模块加主控,峰值电流可能超过 200mA,USB 供电有时会不够,建议用独立电源。
  • 线要短:I2C 总线越长越容易受干扰,尽量控制在 20cm 以内,超过就要考虑降低速率或加缓冲。
  • 循环要合理:不要在一个 loop 里把所有传感器都读一遍然后立刻刷新屏幕,可以分时读取,比如每 100ms 读一个传感器,轮流来,减轻总线压力。
  • 看门狗要用:长时间运行的程序,建议开启看门狗,防止某个传感器卡死导致整个系统无响应。

6. 后续可以怎么扩展

SENSE v0.1 只是个起点,我在做的过程中已经想好了几个扩展方向。一是加一个 SD 卡模块,把数据记录下来,方便回看趋势;二是加一个蜂鸣器或 LED,当 VOC 指数超过阈值时报警;三是把数据通过串口或无线模块传到电脑上,做更复杂的可视化。

硬件上,I2C 总线还能再挂几个传感器,比如气压计、光照传感器,只要地址不冲突就行。软件上,可以把显示逻辑做成多页,用按键切换,这样即使屏幕小也能展示更多信息。

我个人在实际操作中的体会是,这类多传感器项目,最难的不是写代码,而是把硬件调稳。代码可以慢慢改,但接线、供电、地址这些基础问题如果没处理好,后面会一直返工。所以我的建议是,每加一个模块就先单独测通,再合到一起,别一次性全接上然后指望它一次跑通。踩过的坑告诉我,分步验证是最省时间的做法。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询