1. 项目概述:基于STM32的智能空气质量监测系统
作为一名嵌入式开发工程师,我最近完成了一个基于STM32F103C8T6的智能空气质量监测系统项目。这个系统能够实时监测环境中的温湿度、一氧化碳浓度、甲醛含量和PM2.5颗粒物浓度,并通过OLED显示屏本地显示数据,同时还能通过WiFi将数据上传至机智云平台,实现远程监控。当检测到空气质量异常时,系统会自动触发声光报警并启动通风设备。
这个项目的核心价值在于将多种传感器集成到一个紧凑的系统中,实现了环境参数的全面监测。相比市面上单一的温湿度计或空气质量检测仪,我们的系统具有以下优势:
- 多参数综合监测:同时检测5种关键环境指标
- 本地+远程双显示:既有OLED本地显示,又支持手机APP远程查看
- 智能联动控制:当检测到有害气体超标时自动启动通风和报警
- 可定制阈值:用户可以根据不同环境需求调整报警阈值
2. 系统硬件设计与选型
2.1 主控芯片选择与电路设计
我们选择了STM32F103C8T6作为主控芯片,这是一款基于ARM Cortex-M3内核的32位微控制器,具有以下特点:
- 72MHz主频,性能足够处理多传感器数据
- 64KB Flash和20KB SRAM,满足程序存储和运行需求
- 丰富的外设接口:USART、SPI、I2C、ADC等
- 低功耗特性,适合长时间运行的监测设备
主控电路设计要点:
- 电源部分:采用AMS1117-3.3V稳压芯片,将5V输入转换为3.3V供MCU使用
- 复位电路:10kΩ上拉电阻+0.1μF电容构成硬件复位
- 时钟电路:8MHz晶振+两个22pF负载电容
- 调试接口:SWD四线调试接口(SWDIO、SWCLK、GND、VCC)
注意:STM32的VDDA和VSSA引脚必须连接,即使不使用ADC功能,这是很多初学者容易忽略的地方
2.2 传感器选型与接口设计
2.2.1 DHT11温湿度传感器
- 数字信号输出,单总线接口
- 测量范围:湿度20-90%RH,温度0-50℃
- 精度:湿度±5%RH,温度±2℃
- 接口电路:数据线接4.7kΩ上拉电阻
2.2.2 MQ-7一氧化碳传感器
- 模拟电压输出,需要ADC采集
- 检测范围:20-2000ppm
- 预热时间:约1分钟
- 接口电路:输出端接10kΩ负载电阻
2.2.3 三合一VOC传感器
- 检测甲醛、苯、TVOC等有害气体
- 模拟电压输出,需要ADC采集
- 检测范围:0-5ppm(甲醛)
- 接口电路:输出端接10kΩ分压电阻
2.2.4 PM2.5粉尘传感器
- 采用激光散射原理
- 串口输出,直接输出μg/m³浓度值
- 检测范围:0-1000μg/m³
- 接口电路:TX接MCU的USART RX引脚
2.3 外围模块设计
2.3.1 OLED显示模块
- 0.96寸128x64分辨率
- I2C接口,节省IO资源
- 显示内容:实时数据、报警状态、系统模式
2.3.2 ESP8266 WiFi模块
- 串口AT指令控制
- 支持STA/AP模式
- 连接机智云平台实现远程监控
2.3.3 报警与执行机构
- 有源蜂鸣器:GPIO直接驱动,需加三极管放大
- LED指示灯:双色LED,红绿指示不同状态
- 继电器模块:控制通风风扇,注意加续流二极管
3. 系统软件架构与实现
3.1 主程序流程设计
系统软件采用模块化设计,主程序流程如下:
c复制int main(void)
{
// 硬件初始化
SystemInit();
Peripheral_Init(); // 外设初始化
Sensor_Init(); // 传感器初始化
Gizwits_Init(); // 机智云初始化
// 主循环
while(1) {
Sensor_Update(); // 更新传感器数据
Display_Update(); // 更新显示
Alarm_Check(); // 报警检查
Gizwits_Handle(); // 处理云平台通信
Mode_Handler(); // 处理系统模式切换
}
}
3.2 传感器数据采集实现
3.2.1 DHT11温湿度采集
DHT11采用单总线协议,时序要求严格:
c复制void DHT11_ReadData(uint8_t *temp, uint8_t *humi)
{
// 主机拉低总线18ms
DHT11_IO_OUT();
DHT11_DQ_OUT(0);
delay_ms(18);
// 主机释放总线,等待传感器响应
DHT11_DQ_OUT(1);
delay_us(30);
DHT11_IO_IN();
// 等待传感器拉低80us
while(DHT11_DQ_IN()==1);
while(DHT11_DQ_IN()==0); // 等待80us低电平结束
// 开始接收40位数据
for(int i=0; i<5; i++) {
data[i] = 0;
for(int j=0; j<8; j++) {
while(DHT11_DQ_IN()==0); // 等待50us低电平
delay_us(30);
if(DHT11_DQ_IN()==1)
data[i] |= (1<<(7-j));
while(DHT11_DQ_IN()==1);
}
}
// 校验数据
if(data[4] == (data[0]+data[1]+data[2]+data[3])) {
*humi = data[0];
*temp = data[2];
}
}
3.2.2 MQ-7气体传感器采集
MQ-7输出模拟电压,需要通过ADC采集:
c复制#define MQ7_ADC_CHANNEL ADC_Channel_0
uint16_t MQ7_GetValue(void)
{
ADC_RegularChannelConfig(ADC1, MQ7_ADC_CHANNEL, 1, ADC_SampleTime_239Cycles5);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
return ADC_GetConversionValue(ADC1);
}
3.3 机智云平台接入实现
3.3.1 数据点定义
在机智云平台创建产品时,需要定义以下数据点:
| 数据点名称 | 标识名 | 数据类型 | 读写类型 | 范围/枚举 |
|---|---|---|---|---|
| 温度 | temp | 数值 | 只读 | -40~80℃ |
| 湿度 | humi | 数值 | 只读 | 0~100%RH |
| 一氧化碳 | co | 数值 | 只读 | 0~2000ppm |
| 甲醛 | ch2o | 数值 | 只读 | 0~5ppm |
| PM2.5 | pm25 | 数值 | 只读 | 0~999μg/m³ |
| 风扇控制 | fan | 布尔 | 可写 | 开/关 |
3.3.2 数据上报处理
c复制void gizwitsHandle(dataPoint_t *dataPoint)
{
// 填充数据点
dataPoint->valueTemp = (int32_t)(sensorData.tempValue * 10);
dataPoint->valueHumi = (int32_t)(sensorData.humiValue * 10);
dataPoint->valueCO = sensorData.COValue;
dataPoint->valueCH2O = (int32_t)(sensorData.CH2OValue * 1000);
dataPoint->valuePM25 = sensorData.PM25Value;
// 上报数据
gizwitsPassthroughData(dataPoint);
}
3.3.3 命令接收处理
c复制void gizwitsEventProcess(eventInfo_t *info)
{
if(info->event == EVENT_fan) {
if(info->value.fan) {
FAN_On(); // 开启风扇
} else {
FAN_Off(); // 关闭风扇
}
}
}
4. 系统功能实现细节
4.1 多模式工作逻辑
系统支持三种工作模式,通过按键切换:
-
自动模式:
- 自动采集所有传感器数据
- 自动判断是否超过阈值
- 超标时自动启动报警和风扇
- OLED显示实时数据
-
手动模式:
- 用户可以手动控制风扇开关
- 报警功能保持启用
- 用于测试或特殊情况
-
设置模式:
- 可以调整各参数的报警阈值
- 阈值保存在Flash中,断电不丢失
- 提供默认值恢复功能
模式切换状态机实现:
c复制void Mode_Handler(void)
{
static uint8_t last_mode = 0;
if(KeyNum == KEY_MODE) {
KeyNum = 0;
last_mode = mode;
mode = (mode + 1) % 3;
OLED_Clear();
}
switch(mode) {
case AUTO_MODE:
Auto_Mode_Handler();
break;
case MANUAL_MODE:
Manual_Mode_Handler();
break;
case SETTINGS_MODE:
Settings_Mode_Handler();
break;
}
}
4.2 阈值设置与存储
阈值设置功能允许用户根据实际需求调整报警阈值,所有阈值保存在STM32的Flash中:
c复制#define FLASH_START_ADDR 0x0801F000 // Flash最后一页起始地址
void Save_Thresholds(void)
{
FLASH_Unlock();
FLASH_ErasePage(FLASH_START_ADDR);
// 存储温度阈值
FLASH_ProgramHalfWord(FLASH_START_ADDR, Sensorthreshold.tempValue);
// 存储湿度阈值
FLASH_ProgramHalfWord(FLASH_START_ADDR+2, Sensorthreshold.humiValue);
// 存储CO阈值
FLASH_ProgramHalfWord(FLASH_START_ADDR+4, Sensorthreshold.COValue);
// 存储甲醛阈值(float转换为两个uint16)
uint32_t ch2o_raw = *(uint32_t*)&Sensorthreshold.CH2OValue;
FLASH_ProgramHalfWord(FLASH_START_ADDR+6, (uint16_t)(ch2o_raw & 0xFFFF));
FLASH_ProgramHalfWord(FLASH_START_ADDR+8, (uint16_t)((ch2o_raw >> 16) & 0xFFFF));
// 存储PM2.5阈值
FLASH_ProgramHalfWord(FLASH_START_ADDR+10, Sensorthreshold.PM25Value);
FLASH_Lock();
}
4.3 报警逻辑实现
报警系统采用多级判断,避免误报:
c复制void Alarm_Check(void)
{
// 温度报警检查
if(sensorData.tempValue > Sensorthreshold.tempValue) {
Set_Alarm(TEMP_ALARM);
}
// 湿度报警检查
if(sensorData.humiValue > Sensorthreshold.humiValue) {
Set_Alarm(HUMI_ALARM);
}
// CO报警检查
if(sensorData.COValue > Sensorthreshold.COValue) {
Set_Alarm(CO_ALARM);
}
// 甲醛报警检查
if(sensorData.CH2OValue > Sensorthreshold.CH2OValue) {
Set_Alarm(CH2O_ALARM);
}
// PM2.5报警检查
if(sensorData.PM25Value > Sensorthreshold.PM25Value) {
Set_Alarm(PM25_ALARM);
}
// 综合判断是否启动风扇
if(Get_Alarm_Status() != NO_ALARM) {
if(mode == AUTO_MODE) {
FAN_On();
}
BEEP_On();
} else {
if(mode == AUTO_MODE) {
FAN_Off();
}
BEEP_Off();
}
}
5. 系统调试与优化
5.1 传感器校准与数据滤波
传感器数据通常存在噪声和偏差,需要进行校准和滤波:
5.1.1 MQ-7传感器校准
MQ-7在不同浓度下的输出电压需要校准:
c复制// MQ-7校准曲线:y = a * x^b
#define MQ7_A 100.0
#define MQ7_B -0.8
float MQ7_ConvertToPPM(uint16_t adc_value)
{
float voltage = adc_value * 3.3 / 4095.0;
float rs_ro = (5.0 - voltage) / voltage; // 计算传感器电阻比
// 使用幂函数拟合曲线
float ppm = MQ7_A * pow(rs_ro, MQ7_B);
return ppm;
}
5.1.2 数据滑动平均滤波
c复制#define FILTER_SIZE 5
typedef struct {
float buffer[FILTER_SIZE];
uint8_t index;
} Filter_t;
float Filter_AddValue(Filter_t *filter, float new_value)
{
filter->buffer[filter->index] = new_value;
filter->index = (filter->index + 1) % FILTER_SIZE;
float sum = 0;
for(int i=0; i<FILTER_SIZE; i++) {
sum += filter->buffer[i];
}
return sum / FILTER_SIZE;
}
5.2 功耗优化策略
为延长电池供电时的使用时间,系统采用了以下功耗优化措施:
-
传感器分时供电:
- 非连续监测的传感器(如MQ-7)通过MOSFET控制供电
- 仅在采样时通电,其他时间断电
-
动态频率调整:
- 正常运行时使用72MHz主频
- 空闲时切换到低功耗模式
-
显示背光控制:
- OLED背光自动调节,根据环境光强改变亮度
- 无操作30秒后降低亮度,60秒后关闭背光
低功耗模式实现:
c复制void Enter_LowPower_Mode(void)
{
// 关闭外设时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, DISABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, DISABLE);
// 配置唤醒源(按键或定时器)
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 进入STOP模式
PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
// 唤醒后恢复系统时钟
SystemInit();
}
5.3 常见问题与解决方案
在实际开发过程中,我们遇到了以下几个典型问题:
-
ESP8266连接不稳定:
- 问题现象:WiFi模块偶尔会断开连接
- 解决方案:
- 增加电源滤波电容(100μF电解+0.1μF陶瓷)
- 添加AT指令重试机制
- 实现心跳包保持连接
-
传感器数据跳变:
- 问题现象:某些传感器数据偶尔会出现异常跳变
- 解决方案:
- 增加软件滤波算法(滑动平均+中值滤波)
- 设置合理的数据有效范围,超限数据丢弃
- 对传感器供电增加LC滤波
-
Flash写入失败:
- 问题现象:阈值保存到Flash时偶尔失败
- 解决方案:
- 在写入前确保Flash已解锁
- 写入前先擦除整个扇区
- 添加写入验证机制
- 对关键数据添加CRC校验
-
OLED显示残影:
- 问题现象:快速刷新时屏幕会出现残影
- 解决方案:
- 降低刷新频率至30Hz
- 实现局部刷新而非全屏刷新
- 在切换页面时增加清屏延迟
6. 项目扩展与改进方向
目前的系统已经实现了基本功能,但仍有改进空间:
6.1 硬件改进
-
传感器升级:
- 更换更精确的SHT30温湿度传感器
- 使用电化学甲醛传感器替代现有的半导体式
- 增加CO2传感器监测室内通风状况
-
低功耗设计:
- 改用STM32L系列低功耗MCU
- 设计锂电池供电电路
- 增加太阳能充电功能
-
外壳设计:
- 3D打印专用外壳
- 优化传感器进气结构
- 增加安装挂孔
6.2 软件功能扩展
-
数据记录与分析:
- 增加SD卡存储历史数据
- 实现24小时变化曲线显示
- 添加数据导出功能
-
智能联动:
- 与智能家居系统对接
- 超标时自动打开空气净化器
- 实现多设备组网监测
-
用户界面增强:
- 增加多语言支持
- 设计更直观的图标系统
- 添加声控交互功能
6.3 云端功能强化
-
数据可视化:
- 在机智云平台创建数据看板
- 生成日报/周报统计
- 设置自定义预警规则
-
多用户共享:
- 实现家庭账号共享
- 设置不同权限级别
- 增加留言提醒功能
-
第三方对接:
- 对接微信小程序
- 实现数据分享到社交平台
- 与健康APP数据互通
在实际开发中,我发现STM32的生态系统非常完善,有丰富的库函数和开发工具支持。同时,机智云平台为物联网项目提供了快速开发的解决方案,大大缩短了开发周期。这个项目从硬件设计到软件实现,再到云端对接,涵盖了嵌入式开发的完整流程,是一个非常好的学习案例。