1. 系统架构设计思路

做智能家居环境监测系统,我最开始考虑的是如何让各种传感器数据准确又稳定。STM32F103系列单片机是个不错的选择,主频72MHz,Flash容量从64KB到1MB都有,完全够处理多路传感器数据。整个系统架构其实可以分成四层:传感器采集层、主控处理层、通信传输层和应用展示层。

传感器这块我选了DHT11测温湿度,GP2Y1010AU0F测PM2.5,电化学甲醛传感器测甲醛浓度。实际用下来发现,不同传感器的响应时间和数据格式差异很大,比如DHT11是单总线协议,PM2.5传感器输出模拟电压,甲醛传感器需要ADC转换。这时候STM32的多路ADC和GPIO灵活性就派上用场了。

通信方面用了ESP8266 WiFi模块,性价比真的高,一块钱硬币大小,支持STA和AP模式。我选择让STM32通过串口和ESP8266通信,用AT指令配置透传模式,这样就能把数据推到云平台或者手机APP。

显示部分用了LCD1602,虽然像素不高但显示关键数据足够用了。我还加了三个按键用来设置报警阈值,超出阈值时用LED和蜂鸣器报警。整个系统的供电用AMS1117-3.3稳压芯片,实测下来电压波动不超过0.05V,传感器读数很稳定。

2. 硬件选型与电路设计

2.1 STM32最小系统设计

STM32最小系统其实就三部分:电源、晶振和复位。电源电路我用AMS1117-3.3,输入5V输出3.3V,在VCC和GND之间加了100μF电解电容和0.1μF瓷片电容,滤除高频和低频干扰。晶振电路用了8MHz无源晶振,匹配电容22pF,复位电路是10k上拉电阻加0.1μF电容,实测复位信号很干净。

STM32的IO口分配要提前规划好:PA0接甲醛传感器(ADC模式),PA1接PM2.5传感器(ADC模式),PC14接DHT11(推挽输出/上拉输入),PA2/PA3接ESP8266(串口2),PB5-PB8接LCD1602数据线,PC13接报警LED。这样分配外设冲突最少,PCB布线也方便。

2.2 传感器电路细节

DHT11电路最简单,只需要一个4.7k上拉电阻。但要注意的是数据线长度最好别超过20cm,否则容易读取出错。PM2.5传感器GP2Y1010AU0F需要配套电路,我在输出端加了RC滤波(1k电阻和10μF电容),这样ADC采集到的电压更稳定。

甲醛传感器用的是电化学模组,工作电流只有20mA左右,但需要稳定的5V供电。我在模组输出端加了LM358运放做电压跟随,再把信号送到STM32的ADC引脚。实际测试中发现ADC参考电压的稳定性很重要,后来专门用TL431做了个2.5V基准源,读数漂移小了很多。

2.3 ESP8266通信电路

ESP8266-01模块的电路要特别注意电平匹配。模块本身是3.3V供电,但STM32的串口是3.3V电平,直接连接就可以。我在模块的VCC引脚并联了100μF和0.1μF电容,解决瞬间电流不够的问题。CH_PD引脚通过10k电阻上拉到3.3V,GPIO0通过10k电阻下拉到GND,这样模块就能正常启动到AT指令模式。

3. 传感器数据采集实战

3.1 DHT11温湿度采集

DHT11的时序要求很严格,代码写不好容易读不到数据。我参考了官方手册,最终调试成功的驱动代码是这样的:

void DHT11_Start(void)
{
    SET_DHT11_OUT();
    DHT11_DQ_OUT(0);
    delay_ms(18);
    DHT11_DQ_OUT(1);
    delay_us(30);
    SET_DHT11_IN();
}

uint8_t DHT11_ReadByte(void)
{
    uint8_t data = 0;
    for(int i=0; i<8; i++)
    {
        while(DHT11_DQ_IN() == 0);
        delay_us(40);
        data <<= 1;
        if(DHT11_DQ_IN() == 1)
            data |= 1;
        while(DHT11_DQ_IN() == 1);
    }
    return data;
}

实际使用中要加超时判断,否则程序容易卡死。我遇到过一个坑是没等DHT11完成测量就强行读取,后来加了重试机制,连续3次读取失败才报错。

3.2 PM2.5传感器数据处理

GP2Y1010AU0F的输出电压和粉尘浓度不是线性关系,需要查表换算。我实测得到的电压-浓度曲线如下:

电压值 (V)PM2.5浓度 (μg/m³)
0.650
1.2100
1.8200
2.4300
3.0500

在代码里用查表法加线性插值,比直接计算公式更准确:

uint16_t PM25_GetConcentration(float voltage)
{
    float table[5][2] = {{0.6,50}, {1.2,100}, {1.8,200}, {2.4,300}, {3.0,500}};
    
    if(voltage <= table[0][0]) return table[0][1];
    if(voltage >= table[4][0]) return table[4][1];
    
    for(int i=0; i<4; i++)
    {
        if(voltage >= table[i][0] && voltage <= table[i+1][0])
        {
            return table[i][1] + (voltage - table[i][0]) * 
                   (table[i+1][1] - table[i][1]) / 
                   (table[i+1][0] - table[i][0]);
        }
    }
    return 0;
}

3.3 甲醛传感器校准

电化学甲醛传感器需要定期校准,我在代码里设计了两点校准函数。先用零气(纯净空气)校准零点,再用标准气体校准量程:

void HCHO_Calibrate(float zero_voltage, float span_voltage, float span_concentration)
{
    float slope = span_concentration / (span_voltage - zero_voltage);
    eeprom_write(ZERO_OFFSET_ADDR, zero_voltage);
    eeprom_write(SLOPE_ADDR, slope);
}

实际使用时发现温度对读数影响很大,后来又加了温度补偿系数,根据环境温度自动修正读数。

4. 多传感器数据融合算法

单纯用单个传感器数据容易误报,我用了加权融合算法。温湿度用DHT11,PM2.5用GP2Y1010,甲醛用电化学传感器,每个数据都分配一个置信度权重:

typedef struct {
    float temperature;
    float humidity;
    uint16_t pm25;
    float hcho;
    uint8_t temp_confidence;  // 0-100%
    uint8_t humi_confidence;
    uint8_t pm25_confidence;
    uint8_t hcho_confidence;
} EnvData;

EnvData sensor_fusion(EnvData raw_data)
{
    EnvData fused;
    // 温度数据融合
    if(raw_data.temp_confidence > 80) {
        fused.temperature = raw_data.temperature;
    } else {
        fused.temperature = last_good_temp;  // 使用上次有效数据
    }
    
    // PM2.5数据滑动平均滤波
    static uint16_t pm25_buffer[10];
    static uint8_t index = 0;
    pm25_buffer[index] = raw_data.pm25;
    index = (index + 1) % 10;
    
    uint32_t sum = 0;
    for(int i=0; i<10; i++) {
        sum += pm25_buffer[i];
    }
    fused.pm25 = sum / 10;
    
    return fused;
}

还做了异常值剔除,如果当前读数与均值偏差超过30%,就认为是异常数据直接丢弃。实测下来这种融合算法比单纯用原始数据稳定得多,不会因为某个传感器偶尔出错就乱报警。

5. WiFi通信与云平台对接

5.1 ESP8266AT指令配置

ESP8266要用AT指令初始化,我写的配置流程如下:

void ESP8266_Init(void)
{
    uart2_printf("AT+RST\r\n");
    delay_ms(1000);
    uart2_printf("AT+CWMODE=1\r\n");  // STA模式
    delay_ms(200);
    uart2_printf("AT+CWJAP=\"SSID\",\"PASSWORD\"\r\n");
    delay_ms(5000);
    uart2_printf("AT+CIPSTART=\"TCP\",\"api.iot.com\",80\r\n");
    delay_ms(1000);
    uart2_printf("AT+CIPMODE=1\r\n");  // 透传模式
    delay_ms(200);
    uart2_printf("AT+CIPSEND\r\n");
    delay_ms(200);
}

每个AT指令后都要检查响应,超时没反应就重试。我遇到最头疼的是WiFi容易掉线,后来加了心跳包机制,每分钟发个空数据包保持连接。

5.2 数据上传协议设计

为了省流量,我设计了紧凑的二进制协议,一帧数据只要16字节:

#pragma pack(1)
typedef struct {
    uint8_t header;     // 0xAA
    uint16_t temperature;  // 放大10倍
    uint16_t humidity;     // 放大10倍
    uint16_t pm25;
    uint16_t hcho;        // 放大100倍
    uint8_t checksum;
} DataFrame;

上传频率设置成30秒一次,既不会漏掉重要变化,又不会给服务器太大压力。实测下来一天流量不到100KB,完全够用。

5.3 手机APP数据展示

手机端用MQTT协议接收数据,我用Qt写了个跨平台APP,关键代码是这样的:

void MainWindow::onMqttMessage(const QByteArray &message)
{
    DataFrame frame;
    memcpy(&frame, message.constData(), sizeof(frame));
    
    if(frame.header != 0xAA) return;
    
    float temp = frame.temperature / 10.0;
    float humi = frame.humidity / 10.0;
    float hcho = frame.hcho / 100.0;
    
    ui->labelTemp->setText(QString::number(temp, 'f', 1));
    ui->labelHumi->setText(QString::number(humi, 'f', 1));
    ui->labelPM25->setText(QString::number(frame.pm25));
    ui->labelHCHO->setText(QString::number(hcho, 'f', 2));
}

还做了历史数据曲线,用QChart绘制最近24小时的变化趋势,一眼就能看出环境质量变化规律。

6. 系统稳定性优化策略

6.1 电源管理优化

最初版本耗电太大,后来做了这些优化:STM32平时工作在休眠模式,每30秒唤醒一次采集数据;传感器供电用MOS管控制,采样前才上电;LCD1602背光根据环境光照自动调节亮度。优化后整机工作电流从80mA降到15mA,用移动电源能连续工作一周。

6.2 数据异常处理

加了多重数据校验机制:传感器读数范围检查(温度-40~80度,湿度0~100%,PM2.5 0~1000,甲醛0~5ppm);突变值过滤(相邻两次读数变化超过30%需要确认);传感器自检(DHT11的校验和,ADC参考电压检测)。一旦发现异常就自动重启相关模块。

6.3 通信可靠性提升

WiFi通信最容易出问题,我做了三重保障:首先每个数据包带CRC校验,错误就重发;其次建立应答机制,手机APP收到数据后回复ACK;最后还做了离线缓存,网络中断时数据先存Flash,等恢复后再补传。

6.4 长期运行维护

持续运行了三个月后,发现电化学甲醛传感器需要定期校准,后来加了自动校准功能:每周日凌晨3点自动执行零点校准(假设这个时候甲醛浓度最低)。PM2.5传感器光学窗口容易积灰,设计了软件补偿算法,根据使用时间自动增加补偿系数。

整个系统从最初的不稳定到现在的7x24小时可靠运行,踩过了无数坑。最关键是做好错误处理和自动恢复,毕竟智能家居设备没人愿意天天重启维护。现在这个系统已经在我家运行了一年多,除了偶尔停电基本不用管它,真正做到了无感监测。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐