1. 所选主题和项目介绍
1.1 项目简介
本次设计选择以多传感器环境监测站为主题,这个是任务是任务2下的入门级题目1:多传感器环境监测站,基于 Analog Devices MAX32655 微控制器,整合 Bosch BME688 四合一环境传感器和 SSD1306 OLED 显示屏,实现对温度、湿度、气压和空气质量(IAQ)的实时采集、本地显示与超限报警。整套系统通过 I2C 总线挂载传感器和显示屏,主循环周期 250 ms,结构简洁、成本低、可扩展性强,适用于智能家居、办公环境监测、小型机房等多种室内场景。
1.2 项目目标
本项目设定了以下具体目标:
(1)多参数实时采集
通过单条 I2C2 总线同时采集温度、湿度、气压和气体电阻四项环境参数,采样周期 250 ms(4 Hz),覆盖 BME688 SEQUENTIAL 模式下三个 heater profile 的完整循环。
(2)OLED 本地实时显示
通过 SSD1306 0.96 寸 OLED(128×64)显示所有传感器数据、IAQ 估算指数与报警状态,刷新采用按行局部更新策略,避免全屏刷新造成的视觉闪烁。
(3)空气质量估算
基于开源 G6EJD 算法将 BME688 原始气体电阻转换为 0-500 的 IAQ 索引,结合湿度补偿输出六级分类(Excellent / Good / Moderate / Poor / Severe / Hazardous),实现无外部库的纯 C 空气质量估算。
(4)超限报警
当任意环境参数超出设定阈值(温度上下限、湿度上下限、IAQ 高限、气体电阻低限)时,通过板载 GPIO 驱动红色 LED 常亮、绿色 LED 持续闪烁指示运行状态;UART 实时打印报警触发/解除事件。
(5)串口调试输出
通过 LPUART0(IBRO 时钟源,0% 波特率误差)以 115200 8N1 输出每帧传感器数据、报警状态切换和周期诊断信息,便于上位机记录与现场调试。
2. 硬件介绍
2.1 主控板 - MAX32655

本系统采用 Analog Devices MAX32655 微控制器作为核心处理器。MAX32655 是一款面向 IoT 边缘节点的 32-bit 低功耗 Cortex-M4 处理器,主频 100 MHz,内置 FPU、1 MB Flash、256 KB SRAM,集成丰富的通信外设(UART、SPI、I²C、I²S、CAN、USB 等),同时具备低功耗模式与安全性扩展,非常适合传感器数据汇聚节点。
2.2 环境传感器 - Bosch BME688

BME688 是 Bosch Sensortec 推出的四合一环境传感器,集成了:
- 高精度 温度传感器(精度 ±0.5°C @ 0-65°C)
- 高精度 湿度传感器(精度 ±3 %RH @ 20-80 %RH)
- 高精度 气压传感器(精度 ±0.6 hPa,范围 300-1100 hPa)
- VOC 气体传感器(MOX 原理,对挥发有机物和还原性气体敏感)
- 内置加热器和 3 个 heater profile,支持 SEQUENTIAL / PARALLEL 多模式扫描
BME688 主要参数:
参数 | 规格 |
接口 | I²C(最快 3.4 MHz)/ SPI(最快 10 MHz) |
I²C 地址 | 0x77(SDO=VDDIO)或 0x76(SDO=GND) |
工作电压 | 1.71 V - 3.6 V(VDD),3.3 V(VDDIO) |
温度范围 | -40 °C ~ +85 °C |
湿度范围 | 0 %RH ~ 100 %RH |
气压范围 | 300 hPa ~ 1100 hPa |
气体响应 | 对 VOC、酒精、CO、NH₃ 等还原性气体敏感 |
封装 | LGA-8,3.0 × 3.0 × 0.93 mm |
本设计采用 SEQUENTIAL 模式,3 个 heater profile 分别为 100°C / 200°C / 320°C,每个 150 ms 加热 + 250 ms 间隔,循环一次约 1.2 s,可同时获得气体电阻的多温度响应曲线,比单点测量更能反映 VOC 种类差异。
2.3 OLED 显示模块 - SSD1306

本系统采用基于 SSD1306 驱动芯片的 0.96 寸 OLED 显示屏作为人机交互界面。该屏幕具有 128×64 像素分辨率、单色(白色)显示、I2C 接口、低功耗(关闭像素电流 < 10 µA)等优点。
OLED 的 GDDRAM 采用页面(Page)组织方式:128 列 × 8 页,每页 8 行像素。本设计通过批量写(multi-byte write)将多字节数据合并到一次 I2C 事务中传输,相比单字节写入降低 I2C 开销约 5-12 倍。
3. 方案框图和项目设计思路
3.1 系统总体架构
本环境监测站采用"传感器-主控-输出"的经典三层架构。系统由以下几个主要部分组成:
3.2 总线共享设计思路
(1)单一 I2C2 总线挂载
BME688 和 SSD1306 OLED 共用 I2C2 总线(P0.30 SCL / P0.31 SDA),总线速率统一配置为 100 kHz(标准模式)。两条总线访问相互独立,不会因为 OLED 写屏阻塞 BME688 采集。
(2)MSDK I2C 外设抽象
直接调用 MSDK 的 MXC_I2C_MasterTransaction 接口,将 I²C 事务封装为静态请求结构体 mxc_i2c_req_t 单次复用,避免每次申请栈空间。E_BUSY 自动重试机制借鉴 Env-Sys 项目中验证过的模式(Shutdown + Init + SetFrequency 后重试)。
(3)BME688 平台无关驱动
复用 Bosch 官方 BME68x 纯 C 驱动(BSD-3-Clause 许可),通过 bme68x_dev.read / write / delay_us 三个回调函数对接 MSDK I2C2。该驱动与平台完全解耦,未来可无缝迁移到其他 MCU。
3.3 硬件设计思路
I2C 总线设计
信号 | 引脚 | 备注 |
SCL | P0.30 | I2C2 时钟,ALT1 功能 |
SDA | P0.31 | I2C2 数据,ALT1 功能 |
上拉 | 4.7 kΩ → 3.3 V | 总线空闲为高电平 |
UART 输出设计
使用 LPUART0(UART3 模块)作为调试日志输出:
- TX 引脚 P2.7(ALT2 功能)
- 时钟源:IBRO 7.3728 MHz(内部低功耗振荡器)
- 波特率 115200 8N1,分频比 64,误差 0%(远优于 APB 100 MHz 的 1.7% 误差)
报警 LED 设计
LED | 引脚 | 指示语义 |
绿色 | P0.18 | 每 500 ms 翻转 - 系统运行心跳 |
红色 | P0.19 | 常亮 = 报警;熄灭 = 正常 |
LED 直接由 GPIO 驱动,无外部三极管,简化电路。
3.4 软件设计思路
(1)分层架构
┌─────────────────────────────────────────┐
│ 应用层(主循环) │
│ - 传感器读取 + 报警判断 + OLED 渲染 │
├─────────────────────────────────────────┤
│ 算法层 │
│ - IAQ 估算(开源 G6EJD) │
│ - 报警状态机(去抖 + 滞回) │
├─────────────────────────────────────────┤
│ 驱动适配层 │
│ - BME68x I2C 读写回调 │
│ - OLED 批量写 + 局部刷新 │
├─────────────────────────────────────────┤
│ 硬件抽象层(MSDK) │
│ - MXC_I2C / MXC_UART / MXC_GPIO │
└─────────────────────────────────────────┘
(2)主循环时序
250 ms ┌─ bme688_app_fetch ──────────────┐
│ I2C 读 BME688 (连续 8 字节×3) │
├─ iaq_push ───────────────────────┤
│ EMA 基线更新 + IAQ 计算 │
├─ alarm_poll ─────────────────────┤
│ 3 样本去抖 + 双阈值滞回判定 │
├─ render_one ─────────────────────┤
│ 8 行 OLED 局部刷新 │
├─ LED 控制 ────────────────────────┤
│ 绿灯翻转 + 红灯按报警状态驱动 │
└─ delay_ms(250) ───────────────────┘
(3)增量刷新:OLED 只在数据变化时按行(page-by-page)刷新,避免 1 KB 全屏写入造成的 1.5 秒视觉闪烁。
(4)滞回防抖:报警触发阈值与解除阈值之间保留 2 单位的滞回宽度(温度 °C / 湿度 %),结合 3 样本连续确认,杜绝传感器噪声引发的 LED 抖动。
(5)开发工具-CodeFusion Studio
具体配置如下:
引脚配置,LED的输出控制:

I2C2的配置,控制器模式:

LPUART配置:

4. 软件设计
4.1 软件架构
本系统软件采用分层架构设计,从底层到高层包括:
┌─────────────────────────────────────────┐
│ 应用层(main 主循环) │
│ - 传感器调度 + 报警处理 + UI 渲染 │
├─────────────────────────────────────────┤
│ 算法层 │
│ - IAQ 估算(开源 G6EJD) │
│ - 报警状态机(6 路条件 + 滞回) │
├─────────────────────────────────────────┤
│ 驱动适配层 │
│ - bme688_app(MSDK I2C2 适配) │
│ - OLED(批量写 + 局部刷新) │
├─────────────────────────────────────────┤
│ 硬件抽象层(MSDK) │
│ - MXC_I2C / MXC_UART / MXC_GPIO / SysTick│
└─────────────────────────────────────────┘
4.2 主程序流程图

4.3 关键代码分析
BME688 I2C2 适配层(bme688_app.c)
static BME68X_INTF_RET_TYPE bme688_write(uint8_t reg_addr,
const uint8_t *data,
uint32_t length,
void *intf_ptr)
{
uint8_t buf[BME68X_LEN_INTERLEAVE_BUFF]; /* 32 bytes */
if (length + 1U > sizeof(buf)) return BME68X_E_COM_FAIL;
buf[0] = reg_addr;
for (uint32_t i = 0; i < length; i++) buf[1U + i] = data[i];
return (i2c_write(buf, length + 1U) == 0)
? BME68X_INTF_RET_SUCCESS : BME68X_E_COM_FAIL;
}
static BME68X_INTF_RET_TYPE bme688_read(uint8_t reg_addr,
uint8_t *data,
uint32_t length,
void *intf_ptr)
{
return (i2c_read(reg_addr, data, length) == 0)
? BME68X_INTF_RET_SUCCESS : BME68X_E_COM_FAIL;
}
I2C 事务封装(bme688_app.c)
static int i2c_xfer(uint8_t *tx, uint32_t tx_len,
uint8_t *rx, uint32_t rx_len)
{
g_req.i2c = MXC_I2C2;
g_req.addr = g_addr; /* 探测到的 0x77 或 0x76 */
g_req.tx_buf = tx;
g_req.tx_len = tx_len;
g_req.rx_buf = rx;
g_req.rx_len = rx_len;
g_req.restart = 0;
g_req.callback = NULL;
int rc = MXC_I2C_MasterTransaction(&g_req);
if (rc == E_BUSY) { /* MSDK 已知问题:连续读写偶发 */
(void)MXC_I2C_Shutdown(MXC_I2C2);
(void)MXC_I2C_Init(MXC_I2C2, 1, 0U);
(void)MXC_I2C_SetFrequency(MXC_I2C2, 100000U);
(void)MXC_I2C_SetClockStretching(MXC_I2C2, 1);
rc = MXC_I2C_MasterTransaction(&g_req);
}
return rc;
}
IAQ 估算(iaq.c)
static uint16_t compute_iaq_index(float g_ohm, float h_pct)
{
float gas_ratio = (g_gas_reference > 0.0f)
? g_ohm / g_gas_reference : 1.0f;
float gas_contrib = clampf(1.0f - gas_ratio, 0.0f, 1.0f);
float hum_dev = my_fabsf(h_pct - IAQ_HUM_CENTER_PCT) / 60.0f;
float hum_contrib = clampf(hum_dev, 0.0f, 1.0f);
float iaq_percent = 0.75f * gas_contrib + 0.25f * hum_contrib;
float iaq_f = iaq_percent * (float)IAQ_MAX;
if (iaq_f > IAQ_MAX) iaq_f = (float)IAQ_MAX;
return (uint16_t)(iaq_f + 0.5f);
}
报警状态机(alarm.c)
static const struct {
float trig; /* 触发阈值 */
float rel; /* 解除阈值 - 滞回 */
} thresholds[NUM_FLAGS] = {
/* T_HIGH */ {32.0f, 30.0f},
/* T_LOW */ {10.0f, 12.0f},
/* H_LOW */ {30.0f, 33.0f},
/* H_HIGH */ {65.0f, 62.0f},
/* IAQ */ {150.0f, 130.0f},
/* GAS */ {5.0f, 8.0f},
};
#define DEBOUNCE_IN 3U /* 连续 3 样本触发 */
#define DEBOUNCE_OUT 3U /* 连续 3 样本解除 */
OLED 局部刷新(oled.c)
static int oled_write_multi(uint8_t ctrl_byte, const uint8_t *data,
uint32_t len)
{
/* 一次 I2C 事务写 len 个 GDDRAM 字节 */
static uint8_t buf[129];
buf[0] = ctrl_byte;
for (uint32_t i = 0; i < len; i++) buf[1u + i] = data[i];
s_req.tx_buf = buf;
s_req.tx_len = len + 1u;
/* ... MXC_I2C_MasterTransaction + E_BUSY retry ... */
}
void OLED_FillRegion(uint8_t x, uint8_t y, uint8_t len)
{
oled_cmd3_then_erase(x, y, len); /* 1 事务清空 len 字节 */
}
4.4 数据流时序
5. 硬件功能展示
整体硬件连接

OLED 显示布局
本系统 OLED 采用 6×8 小字体(OLED_FONT_6x8 = 12),每行 21 字符 × 8 像素高,共 8 行:
UART 输出格式

报警阈值表
条件 | 触发阈值 | 解除阈值 | 含义 |
T_HIGH | > 32 °C | < 30 °C | 室内过热 |
T_LOW | < 10 °C | > 12 °C | 过冷 |
H_LOW | < 30 %RH | > 33 %RH | 干燥 |
H_HIGH | > 65 %RH | < 62 %RH | 潮湿 |
IAQ_HIGH | > 150 | < 130 | 空气质量 Moderate 或更差 |
GAS_LOW | < 5 kΩ | > 8 kΩ | 极强还原性气体(污染) |
6. 设计中遇到的难题和解决方法
串口发送乱码问题
问题描述:
实际运行时,PC 端串口助手一直收到星号 *(ASCII 0x2A = 00101010),而非预期的诊断字符串。
这个问题到现在还没有解决,只能换另一个串口进行打印了。
7. 心得体会
本次设计成功实现了一个基于 MAX32655 + BME688 的多传感器环境监测站。从最初的硬件选型、软件架构设计,到 I2C 总线适配、OLED 显示优化、报警算法实现,整个过程让我对嵌入式系统开发有了更深入的理解和实践经验。体验了CFS的配置,建议功能可以在升级,SDK关联上CFS的配置文件。