一、所选主题和项目介绍
1.1 项目背景
室内环境质量直接影响人们的健康和工作效率。CO2浓度过高会导致注意力下降、疲劳感增加;光照强度影响视觉舒适度和生物节律;温湿度则关系到人体的热舒适感受。传统的环境监测设备通常是独立的,数据无法远程查看和历史追溯。
本项目采用M5Stack CoreS3作为传感器采集终端,通过WiFi MQTT协议将DLight光照传感器和SCD40 CO2/温湿度传感器的实时数据上报至树莓派MQTT Broker,数据持久化存储在SQLite数据库中,并通过Flask Web仪表盘实现数据的实时可视化展示。
1.2 项目目标
- 光照采集:使用DLight (BH1750) 传感器采集环境光照值
- CO2/温湿度采集:使用SCD40传感器采集CO2浓度、温度、湿度
- PAHUB I2C多路复用:通过PAHUB切换I2C通道管理多个传感器
- MQTT数据上报:CoreS3通过WiFi MQTT将传感器数据上报树莓派
- SQLite数据持久化:树莓派端接收MQTT数据并写入SQLite数据库
- Web可视化仪表盘:Flask + Chart.js 实时曲线图展示环境数据
- CRC数据校验:SCD40数据读取实现CRC8校验,确保数据完整性
1.3 创新点
- 多传感器融合:集成光照(DLight)、CO2(SCD40)、温湿度多种传感器于一体
- PAHUB I2C多路复用:通过PAHUB解决多I2C设备地址冲突,实现单总线多设备管理
- Web可视化仪表盘:Flask + Chart.js实时数据曲线图,支持1h/6h/24h/7d时间范围切换
- SQLite数据持久化:树莓派端自动存储传感器历史数据,支持趋势分析
- CRC数据校验:SCD40传感器数据读取实现CRC8校验,确保数据完整性
- 传感器自动降级:动态检测UIFlow Unit库可用性,自动切换裸I2C通信
二、硬件介绍
2.1 主控设备
M5Stack CoreS3 — 基于ESP32-S3的高性能物联网控制器,配备2.0寸320x240彩色IPS LCD显示屏,支持WiFi/BLE无线通信,提供Grove扩展接口。
参数 | 规格 |
|---|---|
主控芯片 | ESP32-S3,双核240MHz |
Flash | 16MB |
PSRAM | 8MB |
显示屏 | 2.0寸 320x240 IPS LCD (ST7789) |
无线 | 2.4GHz WiFi + BLE 5.0 |
接口 | Grove x2 (Port A/B), USB-C, UART, I2C |
内置传感器 | MPU6886(6轴IMU), BMM150(磁力计), SPM1423(麦克风) |
电池 | 内置500mAh锂电池 |
2.2 传感器模块
DLight (BH1750) 光照传感器 — 基于I2C接口的数字光照传感器,可直接输出光照强度值(lux)。
参数 | 规格 |
|---|---|
通信接口 | I2C (地址: 0x23) |
测量范围 | 0 ~ 65535 lux |
分辨率 | 高分辨率模式: 1 lux |
工作电压 | 2.4V ~ 3.6V |
工作电流 | 典型 120μA |
CO2 SCD40 传感器 — 基于光声传感技术的NDIR CO2传感器,集成温度和湿度测量功能。
参数 | 规格 |
|---|---|
通信接口 | I2C (地址: 0x62) |
CO2测量范围 | 0 ~ 40000 ppm |
CO2精度 | ±(30 ppm + 3%) |
温度精度 | ±0.5°C |
湿度精度 | ±3% RH |
工作电压 | 1.7V ~ 3.6V |
预热时间 | ~30秒 |
PAHUB (U040-B) I2C多路复用器 — 通过I2C控制的多通道复用器,地址0x70,支持8路I2C通道切换,用于解决多I2C设备地址冲突。
参数 | 规格 |
|---|---|
通信接口 | I2C (地址: 0x70) |
通道数 | 8路 (CH0-CH7) |
工作电压 | 3.3V |
切换方式 | 写寄存器: |
2.3 硬件连接

CoreS3 Port A (I2C0) 连接PAHUB:
CoreS3引脚 | PAHUB | 功能 |
|---|---|---|
GPIO1 (SCL) | SCL | I2C时钟线 |
GPIO2 (SDA) | SDA | I2C数据线 |
3.3V | VCC | 电源正极 |
GND | GND | 电源地 |
PAHUB通道分配:
PAHUB通道 | 外设 | I2C地址 | 功能 |
|---|---|---|---|
CH0 | SCD40 | 0x62 | CO2浓度、温度、湿度 |
CH5 | DLight (BH1750) | 0x23 | 环境光照强度 |
三、方案框图和项目设计思路
3.1 系统架构
本项目采用CoreS3作为传感器采集终端 + 树莓派作为数据中继和可视化服务器的架构设计:
3.2 数据协议
MQTT数据格式(WiFi网络):
光照数据主题: fastbond4/{project_id}/light
{
"lux": 1250,
"level": "normal",
"timestamp": 1700000000
}
CO2/温湿度数据主题: fastbond4/{project_id}/co2
{
"co2": 780,
"temperature": 25.3,
"humidity": 58.7,
"timestamp": 1700000000
}
状态数据主题: fastbond4/{project_id}/status
{
"co2": 780,
"temp": 25.3,
"hum": 58.7,
"lux": 1250,
"timestamp": 1700000000
}
3.3 设备角色
设备 | 角色 | 通信方式 | 传感器 |
|---|---|---|---|
CoreS3 | 传感器采集终端 | WiFi MQTT | DLight (BH1750) + SCD40 (CO2/TH) |
树莓派3B+ | MQTT Broker + Web服务器 | WiFi | Mosquitto + SQLite + Flask |
3.4 传感器数据分级策略
光照强度分级(用于CoreS3显示界面指示):
等级 | 范围 | 颜色 |
|---|---|---|
Dark | < 100 lux | 红色 |
Dim | 100 ~ 500 lux | 黄色 |
Normal | 500 ~ 2000 lux | 绿色 |
Bright | ≥ 2000 lux | 白色 |
CO2浓度分级(用于CoreS3显示界面指示):
等级 | 范围 | 颜色 |
|---|---|---|
正常 | < 800 ppm | 绿色 |
偏高 | 800 ~ 1200 ppm | 黄色 |
超标 | ≥ 1200 ppm | 红色 |
四、原理图和PCB展示及介绍
4.1 原理图展示

原理图
4.2 PCB布局展示

PCB
4.3 设计过程
通过KiCad完成了电路的原理图绘制和PCB版图设计。
4.4 硬件连接原理图

本项目使用M5Stack官方硬件,硬件连接采用I2C总线通过PAHUB多路复用器管理多个传感器,后期使用自制PCB实现设备连接。
4.5 PAHUB I2C多路复用
DLight(BH1750, 地址0x23)和SCD40(地址0x62)挂载在同一I2C总线,通过PAHUB(U040-B, 地址0x70)进行通道切换:
def select_pahub_channel(ch):
"""
选择PAHUB通道,切换I2C子设备
Args:
ch: 通道号 (0-7)
"""
if has_pahub:
i2c.writeto(ADDR_PAHUB, bytes([1 << ch]))
time.sleep_ms(10)
def release_pahub():
"""释放PAHUB,恢复I2C总线"""
if has_pahub:
i2c.writeto(ADDR_PAHUB, bytes([0]))
4.6 I2C总线扫描
系统启动时自动扫描I2C总线,检测PAHUB和传感器设备:
def setup():
"""初始化硬件: 扫描I2C设备,检测PAHUB可用性"""
i2c = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000)
devices = i2c.scan()
has_pahub = ADDR_PAHUB in devices
print("[I2C] Devices:", [hex(d) for d in devices], "PAHUB:", has_pahub)
# 动态检测UIFlow Unit库可用性
DLightUnit_cls = getattr(unit, 'DLightUnit', None)
CO2Unit_cls = getattr(unit, 'CO2Unit', None)
五、软件流程和关键代码介绍
5.1 主程序结构
项目包含以下核心代码文件:
文件 | 说明 | 核心功能 |
|---|---|---|
| CoreS3多传感器 MQTT发送端 | DLight/SCD40 I2C读取 → WiFi MQTT上报 → LCD显示 |
| 树莓派 Flask Web仪表盘 | SQLite读取 → Chart.js 实时曲线图 |
5.2 CoreS3 MQTT环境监测发送端
"""
@file cores3_mqtt_sender.py
@brief CoreS3 多传感器数据 MQTT 发送端
@details 使用M5Stack CoreS3 + PAHUB + DLight(BH1750) + CO2(SCD40)
通过WiFi MQTT上传光照/CO2/温湿度数据到树莓派
硬件连接:
CoreS3 HY Port A (I2C0): SCL=GPIO1, SDA=GPIO2 -> PAHUB
MQTT主题:
fastbond4/{project_id}/light - 光照数据
fastbond4/{project_id}/co2 - CO2/温湿度数据
"""
MQTT_CONFIG = {
"broker_host": "192.168.2.62",
"broker_port": 1883,
"project_id": "env_monitor",
"publish_interval": 10,
"keepalive": 60
}
ADDR_PAHUB = 0x70
ADDR_DLIGHT = 0x23
ADDR_SCD40 = 0x62
CH_CO2 = 0
CH_DLIGHT = 5
5.2.1 WiFi连接
def connect_wifi():
"""等待UIFlow2 WiFi连接,最多等待20秒"""
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
timeout = MQTT_CONFIG.get("wifi_timeout", 20)
start = time.ticks_ms()
while not wlan.isconnected():
if time.ticks_ms() - start > timeout * 1000:
break
time.sleep_ms(500)
if wlan.isconnected():
print("[WiFi] Connected: {}".format(wlan.ifconfig()[0]))
wifi_ok = True
return True
return False
5.2.2 MQTT连接
def connect_mqtt():
"""连接MQTT Broker,发布在线状态"""
global mqtt_client, mqtt_connected
if not wifi_ok:
mqtt_connected = False
return False
try:
if mqtt_client is not None:
try:
mqtt_client.disconnect()
except Exception:
pass
import ubinascii
client_id = "cores3_{}".format(
ubinascii.hexlify(wlan.config('mac')[-3:]).decode())
mqtt_client = MQTTClient(
client_id, MQTT_CONFIG["broker_host"],
port=MQTT_CONFIG["broker_port"],
keepalive=MQTT_CONFIG["keepalive"])
mqtt_client.connect()
mqtt_connected = True
mqtt_client.publish(
"fastbond4/{}/status".format(MQTT_CONFIG["project_id"]),
json.dumps({"co2": 0, "temp": 0, "hum": 0,
"lux": 0, "status": "online"}))
return True
except Exception as e:
mqtt_connected = False
return False
5.2.3 DLight光照传感器读取
def read_light():
"""通过PAHUB通道5读取DLight光照值 (BH1750)"""
global last_light
try:
select_pahub_channel(CH_DLIGHT)
if dlight is not None:
lux = dlight.get_lux()
last_light = int(lux)
else:
last_light = read_dlight_fallback()
release_pahub()
return last_light
except Exception as e:
release_pahub()
return last_light
5.2.4 BH1750裸I2C备用读取
当UIFlow DLightUnit库不可用时,通过裸I2C协议读取BH1750:
def read_dlight_fallback():
"""通过裸I2C协议读取BH1750光照值"""
i2c.writeto(ADDR_DLIGHT, bytes([BH1750_CONT_H_RES]))
time.sleep_ms(180)
data = i2c.readfrom(ADDR_DLIGHT, 2)
raw = (data[0] << 8) | data[1]
lux = raw / 1.2
return int(lux)
5.2.5 SCD40 CO2/温湿度传感器读取
def read_co2():
"""通过PAHUB通道0读取SCD40 CO2/温湿度数据"""
global last_co2, last_temp, last_hum
try:
select_pahub_channel(CH_CO2)
if co2 is not None:
if not co2_warmed:
co2.stop_periodic_measurement()
time.sleep_ms(100)
co2.set_start_periodic_measurement()
co2_warmed = True
release_pahub()
return last_co2, last_temp, last_hum
if co2.is_data_ready():
last_co2 = co2.co2
last_temp = co2.temperature
last_hum = co2.humidity
else:
last_co2, last_temp, last_hum = read_scd40_fallback()
release_pahub()
return last_co2, last_temp, last_hum
except Exception as e:
release_pahub()
return last_co2, last_temp, last_hum
5.2.6 SCD40 CRC8校验
def crc8_scd40(data):
"""SCD40 CRC8校验算法,多项式0x31 (x^8 + x^5 + x^4 + 1)"""
crc = 0xFF
for b in data:
crc ^= b
for _ in range(8):
if crc & 0x80:
crc = (crc << 1) ^ 0x31
else:
crc = crc << 1
crc &= 0xFF
return crc
def scd40_read_with_crc(num_words):
"""
读取SCD40数据并验证CRC8校验
SCD40每个数据字后跟1字节CRC校验
"""
data = i2c.readfrom(ADDR_SCD40, num_words * 3)
results = []
for i in range(num_words):
hi = data[i * 3]
lo = data[i * 3 + 1]
crc = data[i * 3 + 2]
if crc8_scd40(bytes([hi, lo])) != crc:
raise ValueError("SCD40 CRC mismatch at word {}".format(i))
results.append((hi << 8) | lo)
return results
def read_scd40_fallback():
"""通过裸I2C协议读取SCD40,含就绪检测和CRC校验"""
global co2_warmed, last_co2_read_time
now = time.ticks_ms()
if not co2_warmed:
scd40_send_cmd(SCD40_CMD_STOP)
time.sleep_ms(10)
scd40_send_cmd(SCD40_CMD_START)
co2_warmed = True
last_co2_read_time = now
return last_co2, last_temp, last_hum
if now - last_co2_read_time < 1000:
return last_co2, last_temp, last_hum
scd40_send_cmd(SCD40_CMD_READY)
time.sleep_ms(10)
i2c.writeto(ADDR_SCD40, b'\x00')
ready_data = i2c.readfrom(ADDR_SCD40, 3)
if len(ready_data) >= 2:
ready_val = (ready_data[0] << 8) | ready_data[1]
if ready_val == 0:
scd40_send_cmd(SCD40_CMD_READ)
time.sleep_ms(10)
vals = scd40_read_with_crc(3)
last_co2 = vals[0]
last_temp = -45 + 175.0 * vals[1] / 65535.0
last_hum = 100.0 * vals[2] / 65535.0
last_co2_read_time = now
return last_co2, last_temp, last_hum
5.2.7 MQTT数据发布
def publish_data(light_val, co2_val, temp, hum):
"""通过MQTT发布传感器数据到树莓派"""
global mqtt_client, mqtt_connected, publish_count
if not mqtt_connected:
return
level_name, _ = get_light_level(light_val)
ts = int(time.time())
try:
# 发布光照数据
mqtt_client.publish(
"fastbond4/{}/light".format(MQTT_CONFIG["project_id"]),
json.dumps({"lux": light_val, "level": level_name,
"timestamp": ts}))
# 发布CO2/温湿度数据
mqtt_client.publish(
"fastbond4/{}/co2".format(MQTT_CONFIG["project_id"]),
json.dumps({"co2": co2_val, "temperature": round(temp, 1),
"humidity": round(hum, 1), "timestamp": ts}))
# 发布综合状态
mqtt_client.publish(
"fastbond4/{}/status".format(MQTT_CONFIG["project_id"]),
json.dumps({"co2": co2_val, "temp": round(temp, 1),
"hum": round(hum, 1), "lux": light_val,
"timestamp": ts}))
publish_count += 1
M5.Speaker.tone(1500, 50)
except Exception as e:
mqtt_connected = False
5.2.8 主循环
def loop():
"""主循环: 读取传感器 → MQTT发布 → LCD显示"""
global last_publish_time, mqtt_connected, wifi_ok
M5.update()
now = time.ticks_ms()
# 读取所有传感器
light_val = read_light()
co2_val, temp, hum = read_co2()
level, level_color = get_light_level(light_val)
draw_screen(light_val, level, level_color, co2_val, temp, hum,
publish_count, mqtt_connected)
# 每10秒发布一次
if now - last_publish_time >= MQTT_CONFIG["publish_interval"] * 1000:
if not wifi_ok:
connect_wifi()
if wifi_ok and not mqtt_connected:
connect_mqtt()
if mqtt_connected:
publish_data(light_val, co2_val, temp, hum)
last_publish_time = now
time.sleep_ms(200)
5.2.9 增量屏幕显示
def draw_screen(light_val, level_name, level_color, co2_val,
temp, hum, count, mqtt_ok):
"""增量更新屏幕显示,避免全屏清空导致闪烁"""
label_light.setText("Light: {} lux".format(light_val))
label_level.setText(level_name)
label_level.setColor(level_color)
# 仅擦除条状图区域,而非全屏
M5.Lcd.fillRect(10, 58, 300, 10, COLOR_BLACK)
bar_w = min(int(light_val / 10000.0 * 300), 300)
if bar_w > 0:
M5.Lcd.fillRect(10, 58, bar_w, 10, level_color)
M5.Lcd.drawRect(10, 58, 300, 10, COLOR_CYAN)
co2_color = COLOR_GREEN if co2_val < 800 \
else (COLOR_YELLOW if co2_val < 1200 else COLOR_RED)
label_co2.setText("CO2: {} ppm".format(co2_val))
label_co2.setColor(co2_color)
label_temphum.setText("T: {:.1f}C H: {:.1f}%".format(temp, hum))
label_pub_count.setText("Published: {}".format(count))
mqtt_clr = COLOR_GREEN if mqtt_ok else COLOR_RED
label_mqtt.setText("MQTT: {}".format("OK" if mqtt_ok else "DOWN"))
label_mqtt.setColor(mqtt_clr)
CoreS3显示界面(320x240 LCD):
5.3 树莓派 Flask Web可视化仪表盘
5.3.1 SQLite数据库查询
def query_sensor_data(sensor_type, range_hours):
"""从SQLite查询指定时间范围的传感器历史数据
Args:
sensor_type: 传感器类型 (temperature/humidity/light/co2)
range_hours: 查询时间范围(小时)
Returns:
dict: {"labels": [时间戳列表], "values": [数值列表]}
"""
conn = get_db_connection()
if not conn:
return None
try:
cursor = conn.cursor()
time_limit = (datetime.now() - timedelta(hours=range_hours))\
.strftime("%Y-%m-%d %H:%M:%S")
cursor.execute(
"SELECT timestamp, value_real FROM sensor_log "
"WHERE sensor_type=? AND timestamp>=? "
"ORDER BY timestamp ASC",
(sensor_type, time_limit))
rows = cursor.fetchall()
conn.close()
if not rows:
return None
return {"labels": [r["timestamp"] for r in rows],
"values": [r["value_real"] for r in rows]}
except Exception:
conn.close()
return None
5.3.2 最新传感器值获取
def get_latest_values():
"""获取各传感器最新值
Returns:
dict: {sensor_type: value}
"""
conn = get_db_connection()
if not conn:
return {"temperature": None, "humidity": None,
"light": None, "co2": None}
result = {"temperature": None, "humidity": None,
"light": None, "co2": None}
try:
cursor = conn.cursor()
for stype in ["temperature", "humidity", "light", "co2"]:
cursor.execute(
"SELECT value_real FROM sensor_log "
"WHERE sensor_type=? ORDER BY timestamp DESC LIMIT 1",
(stype,))
row = cursor.fetchone()
if row:
result[stype] = row["value_real"]
conn.close()
except Exception:
conn.close()
return result
5.3.3 RESTful API
@app.route("/api/data")
def api_data():
"""API: 返回传感器当前值和历史曲线数据
请求参数:
range: 1h | 6h | 24h | 7d (默认: 1h)
返回:
JSON: {now: {...}, charts: {...}}
"""
range_map = {"1h": 1, "6h": 6, "24h": 24, "7d": 168}
hours = range_map.get(request.args.get("range", "1h"), 1)
charts = {}
for stype in ["temperature", "humidity", "light", "co2"]:
data = query_sensor_data(stype, hours)
if data:
charts[stype] = data
return jsonify({
"now": get_latest_values(),
"charts": charts
})
5.3.4 SQLite数据库表结构
CREATE TABLE IF NOT EXISTS sensor_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL,
sensor_type TEXT NOT NULL,
value_real REAL,
value_int INTEGER,
str_value TEXT,
level_text TEXT,
raw_json TEXT
);
5.3.5 Web界面技术栈
技术 | 用途 |
|---|---|
Flask | Python Web框架,提供HTTP服务和RESTful API |
Chart.js 4.4.0 | 前端图表库,支持实时曲线图、多时间范围切换 |
SQLite | 嵌入式数据库,存储传感器时序数据 |
响应式CSS | Grid/Flexbox布局,支持桌面和移动端访问 |
六、硬件功能展示图及说明
6.1 显示界面
6.1.1 CoreS3 MQTT环境监测界面 (320x240)



6.1.2 Flask Web仪表盘界面

Flask Web仪表盘界面:

6.2 系统运行日志
树莓派端Web服务器启动日志:
==================================================
树莓派环境监测Web服务器
监听: 0.0.0.0:5000
数据库: env_data.db
==================================================
请访问: http://<树莓派IP>:5000
==================================================
SQLite数据库存储状态(查询结果):
总记录数: 5760
各类型分布:
temperature: 1440
humidity: 1440
light: 1440
co2: 1440
最近5条:
[TIMESTAMP] temperature: 25.3 C
[TIMESTAMP] humidity: 58.7 %
[TIMESTAMP] light: 1250 lux
[TIMESTAMP] co2: 780 ppm
七、设计中遇到的难题和解决方法
7.1 I2C多设备地址冲突
问题描述:DLight(BH1750, 地址0x23)和SCD40(地址0x62)需要同时挂载在同一I2C总线。
解决方案:
- 使用PAHUB(U040-B, I2C 0x70)多路复用器
- 读写前切换对应通道,完成后释放总线
def select_pahub_channel(ch):
"""通过PAHUB切换I2C通道,解决多设备地址冲突"""
if has_pahub:
i2c.writeto(ADDR_PAHUB, bytes([1 << ch]))
time.sleep_ms(10)
7.2 SCD40数据就绪检测与CRC校验
问题描述:SCD40传感器需要等待数据就绪后才能读取,且数据包含CRC校验,读取时序不当会导致数据错误。
解决方案:
- 查询数据就绪寄存器后再读取
- 实现CRC8校验算法验证数据完整性
- 读取失败时返回上次缓存值
def scd40_read_with_crc(num_words):
"""读取SCD40数据并验证CRC8校验"""
data = i2c.readfrom(ADDR_SCD40, num_words * 3)
results = []
for i in range(num_words):
hi = data[i * 3]
lo = data[i * 3 + 1]
crc = data[i * 3 + 2]
if crc8_scd40(bytes([hi, lo])) != crc:
raise ValueError("SCD40 CRC mismatch at word {}".format(i))
results.append((hi << 8) | lo)
return results
7.3 MQTT连接稳定性
问题描述:CoreS3通过WiFi连接MQTT Broker,网络波动可能导致连接断开,影响数据上报。
解决方案:
- 每次上报前检查WiFi和MQTT连接状态
- 断线时自动重连
- 使用keepalive机制维持长连接
def loop():
"""主循环中检查连接状态并自动恢复"""
if now - last_publish_time >= MQTT_CONFIG["publish_interval"] * 1000:
if not wifi_ok:
connect_wifi() # WiFi自动重连
if wifi_ok and not mqtt_connected:
connect_mqtt() # MQTT自动重连
if mqtt_connected:
publish_data(light_val, co2_val, temp, hum)
7.4 SQLite数据写入与Web查询并发
问题描述:MQTT数据订阅写入线程和Flask Web查询线程同时访问SQLite数据库,可能导致写入冲突。
解决方案:
- 每个数据库操作创建独立连接,用后即关闭
- 使用短连接模式,避免长连接资源占用
def get_db_connection():
"""每次查询创建独立数据库连接,避免并发冲突"""
if not os.path.exists(DB_PATH):
return None
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
7.5 传感器I2C备用读取方案
问题描述:部分M5Stack固件版本可能不包含DLightUnit或CO2Unit的UIFlow库,导致传感器初始化失败。
解决方案:
- 优先使用UIFlow官方Unit类
- 如果Unit类不可用,自动降级为裸I2C通信
- 通过fallback标志位记录当前工作模式
def setup():
"""自动检测Unit库可用性,动态选择读取方案"""
DLightUnit_cls = getattr(unit, 'DLightUnit', None)
CO2Unit_cls = getattr(unit, 'CO2Unit', None)
if DLightUnit_cls is not None:
dlight = DLightUnit_cls(i2c)
else:
using_fallback_dlight = True
# 裸I2C读取BH1750
if CO2Unit_cls is not None:
co2 = CO2Unit_cls(i2c)
else:
using_fallback_co2 = True
# 裸I2C读取SCD40
7.6 屏幕显示闪烁
问题描述:CoreS3 LCD屏幕每200ms全屏清空重绘,导致显示闪烁。
解决方案:
- Label对象预创建于
setup(),运行中仅用setText()增量更新 - 条状图区域局部擦除,避免全屏重绘
- 仅在初始化时调用一次
fillScreen
def draw_screen(light_val, level_name, level_color, co2_val, temp, hum, count, mqtt_ok):
"""增量更新屏幕显示,避免全屏清空导致闪烁"""
label_light.setText("Light: {} lux".format(light_val))
label_level.setText(level_name)
label_level.setColor(level_color)
# 仅擦除条状图区域,而非全屏
M5.Lcd.fillRect(10, 58, 300, 10, COLOR_BLACK)
bar_w = min(int(light_val / 10000.0 * 300), 300)
if bar_w > 0:
M5.Lcd.fillRect(10, 58, bar_w, 10, level_color)
M5.Lcd.drawRect(10, 58, 300, 10, COLOR_CYAN)
八、心得体会
8.1 项目收获
- 物联网全栈开发:从传感器数据采集(I2C/GPIO)、无线传输(MQTT)、数据存储(SQLite)到前端可视化(Chart.js)的完整链路实践
- 多传感器管理:掌握了通过PAHUB I2C多路复用器管理多个I2C设备的技巧,解决了地址冲突问题
- 嵌入式开发:深入理解M5Stack CoreS3的UIFlow2开发环境、I2C通信协议和异常处理机制
- 数据可视化:掌握Chart.js实时数据曲线图的前端开发技术,实现多时间范围切换
- SCD40传感器协议:理解光声传感技术原理,掌握CRC8校验算法实现
8.2 技术亮点
- 多传感器融合:单I2C总线通过PAHUB管理DLight和SCD40两个传感器
- 自动传感器降级:动态检测Unit库可用性,自动切换裸I2C通信
- CRC数据校验:SCD40数据读取实现CRC8校验,确保数据完整性
- Web实时仪表盘:Flask + Chart.js实现响应式实时数据可视化,支持多时间范围切换
- 增量屏幕刷新:Label预创建 + setText增量更新,消除LCD显示闪烁
- 稳定MQTT连接:自动重连机制+keepalive,保证数据上报可靠性
8.3 改进建议
- Web界面增强:增加历史数据导出(CSV/Excel)、报警阈值配置、数据统计功能
- 多设备支持:扩展支持更多MQTT传感器终端,实现多节点数据融合
- 数据加密:MQTT启用TLS/SSL加密传输,提高数据安全性
- 告警推送:CO2浓度超标或温度异常时,通过MQTT推送告警消息
- OTA升级:支持通过MQTT远程固件升级
- 移动端适配:优化Web界面在手机浏览器上的显示效果
- 断网缓存:CoreS3端实现WiFi断线时本地缓存数据,恢复后批量上报
- 低功耗模式:实现CoreS3深度休眠,延长电池续航
创意方向关联
本项目的技术创新为以下创意方向提供了新的思路:
1. 人工智能在嵌入式系统中的应用
本项目的传感器数据采集和处理技术为AI应用提供了基础:
- 异常检测:CO2/温度/光照数据的时序分析可用于室内环境异常预警
- 预测分析:基于历史数据预测室内环境变化趋势
- 智能调控:结合AI算法自动控制通风、照明等设备
2. 楼宇自动化
环境监测是楼宇自动化的核心组件:
- 室内空气质量监测:实时监测CO2浓度,指导新风系统运行
- 光照管理:根据环境光照自动调节窗帘和照明
- 能耗优化:基于环境数据优化空调和照明能耗
3. 无线 / WiFi-7
项目基于MQTT物联网标准协议:
- MQTT物联网标准协议:易于扩展到5G/WiFi-7物联网终端
- 多终端组网:支持多个CoreS3终端同时上报,实现多节点环境监测网络
技术迁移价值
本项目开发的技术方案可迁移到:
- 智能农业:大棚环境监测(温度、湿度、光照、CO2)
- 仓储管理:仓库环境监控
- 数据中心:机房环境监测
- 智能家居:室内环境质量监测与控制
致谢
感谢 DigiKey 和 电子森林 提供的FastBond4活动支持,本次活动链接:https://www.eetree.cn/page/digikey-fastbond
