1. 项目介绍
1.1 项目背景
本次设计的方向是智能家居的环境监测方向。
随着人们对室内空气质量的关注度不断提升,环境监测设备在智能家居、健康管理和工业控制等领域扮演着越来越重要的角色。温湿度、大气压力以及挥发性有机化合物(VOC)浓度等参数直接影响着人体的舒适度和健康状况。博世(Bosch)推出的BME690传感器是一款集成度极高的环境监测芯片,能够同时测量温度、湿度、气压和气体电阻值,是构建环境监测系统的理想选择。
本项目基于BME690传感器设计了一套完整的环境参数监测系统,通过I2C接口与带屏十二指神探开发板进行通信,将采集到的环境数据实时显示在LCD屏幕上,并根据多传感器数据融合算法计算空气质量指数(IAQ),为用户提供直观的舒适度参考。项目采用MicroPython进行开发,充分利用了Python语言的易用性和丰富的生态系统,同时结合了嵌入式开发的高效性能,实现了良好的用户体验。
1.2 项目目标
本项目的主要目标是实现以下功能:
- 实时采集环境温度、湿度、大气压力数据
- 实现气体传感器加热控制,读取稳定的气体电阻值
- 基于多传感器数据融合算法计算IAQ指数
- 根据气压计算海拔高度
- 通过LCD屏幕实时显示所有环境参数
- 提供直观的舒适度评估和视觉反馈
1.3 项目特点
本项目具有以下技术特点:
- 多传感器融合:创新性地将温度、湿度、气压、海拔和气体电阻五种参数进行加权融合,计算综合空气质量指数
- Bosch官方算法:严格按照博世官方datasheet和API实现气体加热控制和电阻计算
- 实时显示:采用240x240分辨率的LCD彩屏,提供清晰美观的人机界面
- MicroPython开发:使用MicroPython语言开发,便于快速迭代和调试
- 模块化设计:将传感器驱动、显示驱动和业务逻辑分离,代码结构清晰易维护
2. 硬件介绍
2.1 主控平台——十二指神探开发板

本项目采用十二指神探带屏版开发板作为主控平台。这是一款基于Raspberry Pi RP2040芯片的开发板,配备了一块240x240分辨率的LCD彩色显示屏,以及两个可编程按键和一个拨轮编码器。开发板采用精致设计的白色外壳,不仅外观美观,而且便于板卡的日常使用和展示。
开发板主要特性:
参数 | 规格 |
主控芯片 | Raspberry Pi RP2040 (双核ARM Cortex-M0+, 133MHz) |
显示屏幕 | 1.3英寸IPS LCD,分辨率240x240 |
屏幕驱动 | ST7789 SPI接口 |
存储 | 2MB Flash, 264KB SRAM |
按键 | 2个可编程按键 + 1个拨轮编码器 |
通信接口 | I2C、SPI、UART、GPIO等 |
供电方式 | USB Type-C (5V) 或 3.3V/5V外部供电 |
尺寸 | 紧凑设计,配备外壳 |
开发板的LCD屏幕采用SPI接口与RP2040通信,具有响应速度快、功耗低的优势。两个可编程按键和拨轮编码器可以用于界面切换、参数调整等人机交互操作,极大地丰富了产品的使用场景。
2.2 环境传感器——BME690

BME690是博世Sensortec公司推出的第四代环境传感器,专为移动应用和可穿戴设备设计。该传感器集成了温度、湿度、气压和气体传感器四大功能于一身,是目前市面上集成度最高的环境传感器之一。
BME690传感器主要特性:
参数 | 规格 |
温度测量范围 | -40°C 至 +85°C |
温度精度 | ±1°C |
湿度测量范围 | 0% 至 100% RH |
湿度精度 | ±3% RH |
气压测量范围 | 300 至 1250 hPa |
气压精度 | ±1 hPa |
气体传感器 | 金属氧化物 (MOX) 技术 |
I2C地址 | 0x76 (默认) 或 0x77 |
通信接口 | I2C / SPI |
工作电压 | 1.71V 至 3.6V |
封装尺寸 | 3.0mm x 3.0mm x 0.93mm |
BME690的气体传感器采用金属氧化物(MOX)技术,当加热器加热传感区域时,空气中的挥发性有机化合物会改变传感器的电阻值。通过测量这一电阻变化,可以定性地评估空气质量的变化趋势。需要注意的是,BME690的气体传感器响应特性会随使用时间逐渐稳定,通常需要一段时间的预热才能获得稳定的读数。
2.3 硬件连接

本项目的硬件连接采用I2C通信方式,连接关系如下:
开发板 | BME690传感器 |
GPIO 25 (SCL) | SCL |
GPIO 28 (SDA) | SDA |
3.3V | VCC |
GND | GND |
I2C通信采用400kHz的时钟频率,能够满足实时数据采集的带宽需求。BME690的I2C地址设置为默认的0x76。
3. 方案设计
本项目的设计围绕"精准感知、智能融合、直观呈现"三个核心思想展开。
精准感知是环境监测系统的基础。BME690传感器虽然集成了多种测量功能,但每种测量都有其特定的配置要求和时序要求。特别是气体传感器的测量,需要精确控制加热器的温度和加热时间,才能获得稳定可靠的电阻值。为此,我们严格按照博世官方datasheet和Arduino库的实现方式配置气体测量参数,包括加热电阻值、加热时间和初始加热电流等。
智能融合是本项目的技术亮点。单一传感器的读数往往难以全面反映环境的真实状态,因此我们采用多传感器加权融合算法,综合温度、湿度、气压、海拔和气体电阻五个参数计算IAQ指数。这种方法能够更准确地评估环境质量,避免单一参数波动导致的误判。
直观呈现是提升用户体验的关键。通过240x240分辨率的LCD屏幕,用户可以一目了然地看到所有环境参数和舒适度评估结果。大字体IAQ显示和颜色编码的舒适度条形图,使得数据读取更加直观便捷。
3.3 系统功能框图

3.4 IAQ算法设计
本项目设计的IAQ( Indoor Air Quality)指数计算算法综合考虑了五个环境参数,采用加权融合的方式得出0-500的综合评分。IAQ分数越低表示空气质量越好,具体分级标准如下:
IAQ范围 | 质量等级 | 颜色标识 |
0-50 | 优秀 | 绿色 |
51-100 | 良好 | 浅绿 |
101-150 | 中等 | 黄色 |
151-250 | 差 | 橙色 |
251-500 | 很差 | 红色 |
IAQ计算权重分配:
参数 | 权重 | 评分依据 |
气体电阻 | 45% | 与基线值的偏差程度 |
温度 | 15% | 与舒适基线(25°C)的偏差 |
湿度 | 15% | 最佳范围40-60%的偏离度 |
海拔 | 25% | 与初始海拔的偏差 |
算法特点:
- 自适应基线:首次稳定读数自动设置为基线值,后续读数与基线比较计算偏差
- 异常过滤:过滤掉极端异常值(如气体电阻过小或过大),避免干扰
- 平滑处理:采用滑动窗口平均(10次采样),避免数值剧烈跳动
- 双向评估:气体电阻升高(污染加重)和降低(空气改善)都能正确反映
4. 原理图以及PCB设计

原理图

PCB
5. 软件设计
5.1 开发环境
本项目采用MicroPython进行开发,使用Thonny IDE进行,主要基于以下考虑:
- 快速原型开发:Python语言简洁易用,适合快速验证算法和调试
- 交互式编程:可以通过REPL实时调试,无需频繁编译下载
- 硬件抽象:提供了简洁的GPIO、I2C、SPI等外设接口
- 社区支持:MicroPython社区有丰富的驱动和参考代码
5.2 软件流程图
主程序流程图:

传感器读取流程图:

5.4 关键代码介绍
5.4.1 BME690初始化与校准
BME690的初始化过程是确保测量准确性的基础。初始化包括软件复位、芯片ID校验和校准系数读取三个关键步骤。
def begin(self):
"""初始化传感器"""
if self._addr not in self._i2c.scan():
print("I2C addr 0x76 not found")
return False
# 软复位
self._write_reg(_SOFT_RESET_ADDR, _RESET_CMD)
utime.sleep_ms(10)
# 检查Chip ID
chip_id = self._read_reg(_CHIP_ID_ADDR)[0]
if chip_id != _CHIP_ID:
print("Chip ID mismatch: got 0x%02x expected 0x61" % chip_id)
return False
# 读取校准系数
self._read_calibration()
return True
校准系数的读取是初始化过程中最复杂的部分。BME690内部存储了用于补偿测量的校准参数,这些参数在出厂时已经校准完成。校准数据分两部分存储:0x8A开始的42字节和0xE1开始的14字节。
def _read_calibration(self):
"""读取校准系数"""
coeff1 = self._read_reg(_COEFF1_ADDR, 42)
coeff2 = self._read_reg(_COEFF2_ADDR, 14)
# 温度系数解析
self._calib['par_t1'] = (coeff2[9] << 8) | coeff2[8]
self._calib['par_t2'] = (coeff1[1] << 8) | coeff1[0]
t3 = coeff1[2]
if t3 & 0x80:
t3 = t3 - 256
self._calib['par_t3'] = t3
# 压力系数解析
self._calib['par_p1'] = (coeff1[11] << 8) | coeff1[10]
# ... 更多系数解析
# 气体校准系数
self._calib['par_g1'] = self._read_reg(0xED)[0]
data = self._read_reg(0xEB, 2)
self._calib['par_g2'] = (data[1] << 8) | data[0]
self._calib['par_g3'] = self._read_reg(0xEE)[0]
# 读取加热器范围和初始值
self._calib['res_heat_range'] = (coeff1[39] >> 4) & 0x0F
res_heat_val = coeff1[37]
if res_heat_val & 0x80:
res_heat_val = res_heat_val - 256
self._calib['res_heat_val'] = res_heat_val
5.4.2 强制模式与气体测量配置
BME690的气体测量需要在强制模式(Forced Mode)下进行。配置过程需要严格按照博世官方文档的时序要求进行。
def set_forced_mode(self, os_t=OS_8X, os_p=OS_4X, os_h=OS_2X):
"""配置强制模式"""
# 湿度oversampling
self._write_reg(_CTRL_HUM_REG, os_h)
utime.sleep_ms(10)
# 温度/压力oversampling和模式
ctrl_meas = (os_t << 5) | (os_p << 2) | FORCED_MODE
self._write_reg(_CTRL_MEAS_REG, ctrl_meas)
utime.sleep_ms(10)
# IIR滤波器
self._write_reg(_CONFIG_REG, FILTER_SIZE_3 << 2)
utime.sleep_ms(10)
# 气体测量配置
self._write_reg(_IDAC_HEAT0_REG, 0x7F) # 最大初始加热电流
utime.sleep_ms(10)
res_heat = self._calc_res_heat(320, 25) # 计算加热电阻
self._write_reg(_RES_HEAT0_REG, res_heat)
utime.sleep_ms(10)
self._write_reg(_GAS_WAIT0_REG, 0x96) # 150ms加热时间
utime.sleep_ms(10)
# run_gas=1 (bit5), nb_conv=0
self._write_reg(_CTRL_GAS_1_REG, (1 << 5) | 0x00)
utime.sleep_ms(10)
return True
5.4.3 加热电阻计算
加热电阻值的计算是气体测量配置的关键。博世官方提供了精确的计算公式,需要使用校准系数进行计算。
def _calc_res_heat(self, target_temp, amb_temp):
"""计算加热器寄存器值"""
if target_temp > 400:
target_temp = 400
par_g1 = self._calib['par_g1']
par_g2 = self._calib['par_g2']
par_g3 = self._calib['par_g3']
res_heat_range = self._calib.get('res_heat_range', 1)
res_heat_val = self._calib.get('res_heat_val', 0)
# 博世官方算法
var1 = (((amb_temp * par_g3) // 1000) * 256) & 0xFFFFFFFF
var2 = (par_g1 + 784) * (((((par_g2 + 154009) * target_temp * 5) // 100) + 3276800) // 10)
var3 = var1 + (var2 >> 1)
var4 = var3 // (res_heat_range + 4)
var5 = (131 * res_heat_val) + 65536
heatr_res_x100 = ((var4 // var5) - 250) * 34
res_heat = (heatr_res_x100 + 50) // 100
return max(0, min(127, res_heat))
5.4.4 气体电阻补偿
气体电阻的补偿计算是获取正确气体电阻值的关键步骤。ADC值需要根据gas_range进行转换。
def _compensate_gas(self, adc_gas, gas_range):
"""气体电阻补偿"""
gas_range = int(gas_range) & 0x0F
adc_gas = int(adc_gas)
var1 = 262144 >> gas_range
var2 = adc_gas - 512
var2 = var2 * 3
var2 = 4096 + var2
if var2 > 0:
calc_gas_res = (10000 * var1) // var2
else:
calc_gas_res = 0
gas_res = calc_gas_res * 100
return gas_res
5.4.5 海拔高度计算
海拔高度根据大气压力通过国际标准大气公式计算得出。
def _calc_altitude(self, press_pa, sea_level=101325.0):
"""根据气压计算海拔高度"""
import math
if press_pa <= 0:
return 0
altitude = 44330.0 * (1.0 - math.pow(press_pa / sea_level, 0.1903))
return int(altitude)
5.4.6 IAQ多传感器融合算法
IAQ算法是本项目的核心创新点,通过加权融合多个传感器的数据计算综合空气质量指数。
def _calc_iaq(self, gas_res):
"""IAQ计算 - 多传感器数据融合算法"""
now = utime.ticks_ms()
if hasattr(self, '_last_iaq') and utime.ticks_diff(now, self._last_iaq) < 500:
return self._iaq_score
self._last_iaq = now
# 初始化baseline
if self._gas_baseline == 0 and 10000 < gas_res < 1000000:
self._gas_baseline = gas_res
self._temp_baseline = self._tem_comp
self._hum_baseline = getattr(self, '_last_hum', 500)
self._pres_baseline = getattr(self, '_last_pres', 101325)
self._alt_baseline = getattr(self, '_last_alt', 0)
# 气体分数 (45%)
if self._gas_baseline > 0 and 1000 < gas_res < 2000000:
ratio = self._gas_baseline / gas_res if gas_res > 0 else 0.1
if ratio >= 1:
gas_score = min(100, 50 + (ratio - 1) * 30)
else:
gas_score = max(0, 50 - (1 - ratio) * 50)
else:
gas_score = 50
# 温度分数 (15%)
if hasattr(self, '_temp_baseline'):
temp_diff = abs(self._tem_comp - self._temp_baseline)
temp_score = max(0, 100 - temp_diff * 5)
else:
temp_score = 100
# 湿度分数 (15%)
if hasattr(self, '_last_hum') and self._last_hum > 0:
hum = self._last_hum
if 40 <= hum <= 60:
hum_score = 100
elif hum < 40:
hum_score = max(0, 100 - (40 - hum) * 3)
else:
hum_score = max(0, 100 - (hum - 60) * 3)
else:
hum_score = 50
# 海拔分数 (25%)
alt = getattr(self, '_last_alt', 0)
if hasattr(self, '_alt_baseline'):
alt_diff = abs(alt - self._alt_baseline)
alt_score = max(0, 100 - alt_diff * 0.1)
else:
alt_score = 100
# 加权计算
total = (gas_score * 0.45 + temp_score * 0.15 + hum_score * 0.15 + alt_score * 0.25)
iaq = int(max(0, min(500, total * 5)))
# 平滑处理
self._iaq_samples.append(iaq)
if len(self._iaq_samples) > 10:
self._iaq_samples.pop(0)
self._iaq_score = sum(self._iaq_samples) // len(self._iaq_samples)
return self._iaq_score
6. 功能展示
系统运行效果

系统上电后,首先进行BME690传感器的初始化和校准系数读取。初始化成功后,LCD屏幕顶部显示"Environmental Monitor"标题栏,下方显示舒适度评估区域,中间显示所有环境参数数据。
页面显示效果如下:

串口输出
系统同时通过串口输出详细的环境数据,便于调试和记录:

7. 调试过程与难题解决
气体测量一直返回最大值或零值
问题现象:
adc_gas的值不是1023(最大值)就是0,无法获得有意义的气体电阻读数。
原因分析:
气体测量的配置涉及多个寄存器的协同工作,任何一个配置错误都可能导致测量失败:
- run_gas位未设置:CTRL_GAS_1寄存器的bit5(run_gas)必须置1才能启用气体测量
- 加热器未启用:CTRL_GAS_0寄存器的bit3(hctrl)必须置1才能开启加热器
- 加热时间不足:气体传感器需要足够的加热时间(通常150ms以上)才能达到稳定状态
- 加热温度不合适:目标加热温度需要根据校准系数正确计算
解决方法:
- 确保set_forced_mode()中正确设置CTRL_GAS_1寄存器的run_gas位
- 在每次测量前重新配置气体测量相关寄存器
- 将等待时间从60ms增加到150ms,确保加热完成
- 使用正确的校准系数(res_heat_range和res_heat_val)计算加热电阻
关键配置代码:
# 初始加热电流(加速加热过程)
self._write_reg(_IDAC_HEAT0_REG, 0x7F) # 最大电流
# 目标加热电阻(根据校准系数计算)
res_heat = self._calc_res_heat(320, 25) # 目标温度320°C,环境温度25°C
self._write_reg(_RES_HEAT0_REG, res_heat)
# 加热时间
self._write_reg(_GAS_WAIT0_REG, 0x96) # 约150ms
# 启用气体测量
self._write_reg(_CTRL_GAS_1_REG, (1 << 5) | 0x00) # run_gas=1, nb_conv=0
舒适度评估不准确
问题现象:
IAQ指数波动剧烈,或者与实际环境感受不符。
原因分析:
- 基线设置不合理:首次读数可能处于非稳定状态
- 权重分配不恰当:某些参数权重过高导致结果偏差
- 平滑窗口太小:短期波动影响整体评分
解决方法:
- 实现了更完善的基线初始化逻辑,只在气体电阻在合理范围时设置基线
- 调整了权重分配:气体45%、温度15%、湿度15%、海拔25%
- 使用10次采样的滑动窗口平均,减小波动
8. 心得体会
本次项目中,BME690的datasheet和博世官方C驱动库的代码是我们最重要的参考资料。官方文档详细描述了寄存器配置、时序要求和算法实现,是解决气体测量问题的关键。我们花大量时间阅读和对比datasheet与实际代码的区别,最终找到了正确的配置方法。BME690的气体测量是一个包含加热、等待、测量的时序过程。正确理解这个过程并按照正确的顺序配置寄存器是获得稳定测量的关键。我们最初试图简化这个过程,但最终不得不严格按照官方流程实现。
本次项目是一次难得的嵌入式开发实践。通过从零开始设计并实现一个完整的环境监测系统,我们不仅掌握了BME690传感器的使用方法和MicroPython开发技巧,更重要的是学会了如何阅读和理解官方技术文档,如何系统地调试嵌入式系统,以及如何设计合理的数据融合算法。
虽然项目过程中遇到了不少困难和挫折,特别是气体测量部分的调试花费了大量时间,但每一次问题的解决都让我们对系统有了更深入的理解。最终成功运行的系统是对所有努力的最好回报。