最近在做一个社区关怀项目,需要为独居老人设计一套低成本、易部署的居家监护方案。核心需求是能实时监测老人的活动状态,并在异常时及时通知家人。经过一番选型,最终决定基于STM32微控制器来搭建这套系统,因为它兼具了性能、功耗和丰富的生态支持。本文将完整分享从硬件选型、软件设计到云端联调的全过程,手把手教你打造一个功能完整的“基于STM32的独居老人监护系统”。无论你是嵌入式新手想练手,还是正在寻找实际项目方案的开发者,都能从中获得可直接复用的代码和配置。
1. 系统背景与核心设计思路
随着社会老龄化加剧,独居老人的安全与健康监护成为一个日益突出的社会问题。传统的定期上门探望或电话问候存在时间盲区,无法做到24小时不间断的守护。因此,利用物联网技术构建智能监护系统,实现非侵入式的实时监测与预警,具有重要的现实意义。
本系统的核心设计目标是:低成本、低功耗、易用、可靠。我们选择STM32F103C8T6(俗称“蓝桥杯”或“最小系统板”)作为主控,它价格低廉、资源丰富,足以胜任本项目的需求。系统通过多种传感器采集环境与人体活动数据,经由WiFi模块上传至云端平台(如机智云),家属可通过手机APP远程查看状态并接收报警信息。
系统核心功能模块包括:
- 环境监测:使用DHT11温湿度传感器监测室内环境。
- 活动感知:使用HC-SR501人体红外传感器(PIR)监测老人是否在活动区域出现。
- 紧急呼叫:设置一个物理按键,供老人在紧急情况下主动触发报警。
- 本地显示:使用0.96寸OLED显示屏(SSD1306驱动)实时显示传感器数据、系统状态和时间。
- 无线通信:使用ESP8266 WiFi模块(AT指令固件)连接家庭路由器,将数据上传至云端。
- 云端与远程通知:数据上传至机智云IoT平台,平台可配置报警规则,并通过APP推送或短信通知家属。
整个系统的数据流如下图所示(概念流程):
传感器数据(DHT11, PIR) -> STM32 (采集、处理、逻辑判断) -> UART -> ESP8266 (WiFi传输) -> 路由器 -> 机智云服务器 -> 家属手机APP OLED显示 <--| |--> 云端报警规则触发 -> 推送通知 紧急按键 --|2. 硬件准备与开发环境搭建
2.1 硬件清单与连接
以下是本项目所需的核心硬件组件及简要说明:
| 组件 | 型号/说明 | 关键接口 | 备注 |
|---|---|---|---|
| 主控MCU | STM32F103C8T6 最小系统板 | GPIO, USART, I2C | 核心处理器,也可用其他F1系列型号 |
| WiFi模块 | ESP-01S (ESP8266) | UART (TX/RX) | 负责网络通信,需烧录AT固件 |
| 温湿度传感器 | DHT11 | 单总线 (GPIO) | 测量室内温湿度 |
| 人体红外传感器 | HC-SR501 | GPIO | 检测人体移动,输出高低电平 |
| 显示模块 | 0.96寸 OLED (I2C接口, SSD1306) | I2C (SCL, SDA) | 用于本地信息显示 |
| 紧急按键 | 轻触开关 | GPIO (外部中断) | 用于老人主动报警 |
| 其他 | 杜邦线、面包板、USB转TTL、电阻等 | - | 用于连接与调试 |
核心连接示意图(基于STM32F103C8T6):
- ESP8266 (ESP-01S):
VCC-> 3.3V (注意:必须接3.3V!)GND-> GNDTX-> STM32的PA3(USART2_RX)RX-> STM32的PA2(USART2_TX)CH_PD-> 3.3VGPIO0-> 悬空或接高电平(运行模式)
- OLED (I2C):
VCC-> 3.3VGND-> GNDSCL-> STM32的PB6(I2C1_SCL)SDA-> STM32的PB7(I2C1_SDA)
- DHT11:
VCC-> 3.3VGND-> GNDDATA-> STM32的PA1(GPIO)
- HC-SR501:
VCC-> 5V (该模块通常需要5V供电)GND-> GNDOUT-> STM32的PA0(GPIO)
- 紧急按键:
- 一端接GND,另一端接STM32的
PA8(GPIO),并在PA8与3.3V之间接一个10K上拉电阻。
- 一端接GND,另一端接STM32的
2.2 软件开发环境
IDE/工具链:
- Keil uVision5 (MDK-ARM): 主流的STM32开发IDE。确保已安装对应STM32F1系列的Device Family Pack。
- STM32CubeMX:强烈推荐使用。用于图形化配置引脚、时钟、外设,并生成初始化代码框架,能极大提高开发效率,避免底层配置错误。
- 串口调试助手: 如XCOM、SSCOM,用于调试ESP8266的AT指令和查看系统打印日志。
- 机智云开发者中心: 用于创建产品、定义数据点、获取设备通信密钥。
固件库:
- HAL库: 本项目使用STM32CubeMX生成的HAL库代码。HAL库抽象程度高,可移植性好,适合快速开发。
- OLED/传感器驱动: 需要准备或编写SSD1306 OLED的I2C驱动、DHT11的单总线驱动。
版本说明:
- STM32CubeMX版本: V6.x 均可。
- Keil uVision5版本: V5.3x 及以上。
- HAL库版本: 随CubeMX安装即可,本文不依赖特定小版本。
- ESP8266 AT固件版本: 建议使用安信可官方发布的AT固件(如
AT version:1.7.0.0及以上),稳定性较好。
3. 核心模块驱动与原理
在开始整合系统前,我们需要先搞定各个传感器和模块的驱动。这是整个项目的基石。
3.1 OLED显示模块驱动 (SSD1306, I2C)
0.96寸OLED屏是信息展示的窗口,我们通过I2C协议驱动它。首先需要编写基本的画点、画线、显示字符和字符串的函数。
核心驱动文件oled.c/oled.h要点:
// oled.h 部分定义 #define OLED_I2C_ADDRESS 0x78 // SSD1306的I2C地址,通常是0x78或0x7A #define OLED_WIDTH 128 #define OLED_HEIGHT 64 void OLED_Init(void); // 初始化OLED void OLED_Clear(void); // 清屏 void OLED_ShowChar(uint8_t x, uint8_t y, char chr); // 在指定坐标显示一个字符 void OLED_ShowString(uint8_t x, uint8_t y, char *str); // 显示字符串 void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len); // 显示数字 void OLED_Refresh(void); // 更新显存到屏幕初始化流程:
- 配置STM32的I2C1(PB6, PB7)为标准模式,速度100kHz或400kHz。
- 向OLED发送一系列初始化命令序列(可在商家例程中找到),设置对比度、扫描方式、开启显示等。
- 在STM32内部维护一个
OLED_GRAM[128][8]的显存数组,所有绘图操作先修改这个数组。 - 调用
OLED_Refresh()时,将整个显存通过I2C一次性发送给OLED。
在main.c中的简单使用示例:
OLED_Init(); OLED_Clear(); OLED_ShowString(0, 0, "Temp:"); OLED_ShowNum(40, 0, 25, 2); OLED_ShowString(60, 0, "C"); OLED_Refresh();3.2 DHT11温湿度传感器驱动
DHT11是一款单总线数字温湿度传感器,通信时序要求严格。
驱动关键点:
- 单总线协议: 同一根数据线用于双向通信。STM32需要先拉低总线至少18ms(起始信号),然后释放并等待DHT11响应。
- 数据格式: DHT11一次传输40位数据(8bit湿度整数+8bit湿度小数+8bit温度整数+8bit温度小数+8bit校验和)。小数部分通常为0。
- 时序读取: DHT11通过高低电平的持续时间来表示‘0’(26~28us高电平)和‘1’(70us高电平)。需要使用微秒级延时函数和精准的时序判断。
// dht11.c 中的数据读取函数框架 uint8_t DHT11_Read_Data(uint8_t *temp, uint8_t *humi) { uint8_t buf[5]; uint8_t i, j; // 1. 主机拉低至少18ms DHT11_IO_OUT(); DHT11_DQ_OUT(0); delay_ms(20); DHT11_DQ_OUT(1); delay_us(30); // 2. 切换为输入模式,等待DHT11响应 DHT11_IO_IN(); // ... 检查响应信号 ... // 3. 读取40位数据 for(j=0; j<5; j++) { for(i=0; i<8; i++) { // ... 等待低电平结束 ... delay_us(40); // 延时40us后检测电平 if(DHT11_DQ_IN() == 1) { buf[j] |= (1 << (7-i)); while(DHT11_DQ_IN() == 1); // 等待高电平结束 } } } // 4. 校验和检查 if(buf[4] == (buf[0]+buf[1]+buf[2]+buf[3])) { *humi = buf[0]; *temp = buf[2]; return 1; // 成功 } return 0; // 失败 }3.3 HC-SR501人体红外传感器
这个模块使用非常简单,它是一个数字输出传感器。当检测到人体移动时,输出高电平(或可设置),否则输出低电平。我们只需要配置一个GPIO口为输入模式,周期性读取其电平状态即可。
// 初始化 GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉,默认高电平,触发时模块输出低电平则改为PULLDOWN HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 读取状态 uint8_t pir_state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0); if(pir_state == GPIO_PIN_SET) { // 检测到活动 OLED_ShowString(0, 3, "Motion: Yes "); } else { // 无活动 OLED_ShowString(0, 3, "Motion: No "); }3.4 ESP8266 WiFi模块通信 (AT指令)
ESP8266通过串口与STM32通信,使用AT指令集控制。这是连接云端的关键。
基础AT指令流程:
- 测试通信:
AT-> 回复OK - 设置模式:
AT+CWMODE=1-> 设置为Station模式(连接路由器) - 连接WiFi:
AT+CWJAP="你的WiFi名","你的WiFi密码"-> 回复WIFI CONNECTED和WIFI GOT IP - 连接机智云服务器:
AT+CIPSTART="TCP","m2m.gizwits.com",80(或AT+CIPSTART="SSL","m2m.gizwits.com",8883用于加密) - 发送数据:
AT+CIPSEND=<长度>-> 收到>后,发送具体的协议数据。
我们需要在STM32上实现一个稳定的AT指令发送与解析函数。关键点在于处理串口接收中断,使用状态机解析OK、ERROR以及IPD数据。
// esp8266.c 中的发送指令并等待回复函数示例 ESP8266_StatusTypeDef ESP8266_SendCmdAndWaitResp(uint8_t *cmd, uint8_t *expect_resp, uint32_t timeout) { USART_ClearRxBuffer(); // 清空接收缓冲区 USART_SendString(cmd); // 发送AT指令 return USART_WaitForString(expect_resp, timeout); // 等待指定字符串,超时返回错误 } // 在主循环或初始化中调用 void WiFi_Init(void) { delay_ms(1000); // 上电后等待模块稳定 if(ESP8266_SendCmdAndWaitResp("AT\r\n", "OK", 1000) != ESP8266_OK) { OLED_ShowString(0, 6, "WiFi: AT Fail"); return; } OLED_ShowString(0, 6, "WiFi: AT OK "); // ... 后续连接WiFi和服务器 }4. 系统整合与业务逻辑实现
当所有模块驱动调试通过后,就可以开始编写主程序业务逻辑了。
4.1 使用STM32CubeMX生成工程框架
- 打开STM32CubeMX,选择MCU型号
STM32F103C8Tx。 - 配置时钟: 在
RCC中,将HSE设置为Crystal/Ceramic Resonator。在Clock Configuration标签页,将系统时钟源设置为HSE,并将主频设置为72MHz(STM32F103的最高频率)。 - 配置GPIO:
- PA1: 输出模式(初始化时),用于控制DHT11总线。
- PA0: 输入模式,用于读取HC-SR501。
- PA8: 外部中断模式,下降沿触发(按键按下接地),用于紧急呼叫。
- 配置I2C1: 模式选择
I2C,保持默认设置。 - 配置USART2: 异步模式,波特率115200(与ESP8266默认一致)。
- 配置SYS: Debug选择
Serial Wire(如果要用ST-Link调试)。 - 生成代码: 设置好工程路径、名称、选择MDK-ARM,然后生成代码。
4.2 主程序逻辑设计
主程序采用前后台(超级循环)架构,在main函数的while(1)循环中周期性执行数据采集、显示和上报任务。紧急按键采用外部中断实现即时响应。
main.c核心逻辑结构:
// 全局变量定义 uint8_t temperature, humidity; uint8_t motion_detected; uint32_t last_upload_time = 0; uint32_t last_motion_time = 0; #define UPLOAD_INTERVAL 10000 // 数据上传间隔10秒 #define MOTION_TIMEOUT 300000 // 无活动超时时间5分钟 (300秒) int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); MX_USART2_UART_Init(); // 初始化各模块 OLED_Init(); DHT11_Init(); ESP8266_Init(); // 内部包含WiFi连接和服务器连接 OLED_Clear(); OLED_ShowString(0, 0, "Elder Guardian"); OLED_ShowString(0, 2, "Initializing..."); OLED_Refresh(); // 等待网络连接成功 while(WiFi_Status != WIFI_CONNECTED) { HAL_Delay(500); } OLED_ShowString(0, 2, "WiFi Connected "); OLED_Refresh(); HAL_Delay(1000); OLED_Clear(); while (1) { // 1. 读取传感器数据 (每2秒一次) static uint32_t last_sensor_time = 0; if(HAL_GetTick() - last_sensor_time > 2000) { last_sensor_time = HAL_GetTick(); if(DHT11_Read_Data(&temperature, &humidity)) { // 更新OLED显示 OLED_ShowString(0, 0, "Temp:"); OLED_ShowNum(40, 0, temperature, 2); OLED_ShowString(60, 0, "C Hum:"); OLED_ShowNum(90, 0, humidity, 2); OLED_ShowString(114, 0, "%"); } motion_detected = HAL_GPIO_ReadPin(PIR_GPIO_Port, PIR_Pin); if(motion_detected) { last_motion_time = HAL_GetTick(); // 更新最后活动时间 OLED_ShowString(0, 2, "Motion: Yes "); } else { OLED_ShowString(0, 2, "Motion: No "); } OLED_Refresh(); } // 2. 检查无活动超时报警 (逻辑判断) if((HAL_GetTick() - last_motion_time > MOTION_TIMEOUT) && (last_motion_time != 0)) { // 触发无活动报警 Send_Alert_To_Cloud("NO_MOTION_TIMEOUT"); last_motion_time = HAL_GetTick(); // 重置,避免连续报警 OLED_ShowString(0, 4, "Alert:No Motion!"); OLED_Refresh(); } // 3. 定时上传数据到云端 if(HAL_GetTick() - last_upload_time > UPLOAD_INTERVAL) { last_upload_time = HAL_GetTick(); Upload_Sensor_Data_To_Cloud(temperature, humidity, motion_detected); OLED_ShowString(0, 6, "Data Uploaded "); OLED_Refresh(); } // 4. 处理ESP8266接收到的数据 (在串口中断中填充缓冲区,这里解析) ESP8266_DataProcess(); HAL_Delay(100); // 主循环延时,降低CPU占用 } } // 紧急按键中断服务函数 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin == KEY_Pin) { // 消抖处理 HAL_Delay(50); if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET) { // 发送紧急报警到云端 Send_Alert_To_Cloud("EMERGENCY_BUTTON"); OLED_ShowString(0, 4, "Alert:EMERGENCY!"); OLED_Refresh(); } } }4.3 机智云接入与数据通信
机智云提供了完整的IoT接入方案。我们需要在开发者中心创建产品,定义数据点,然后按照其通信协议封装数据。
创建产品与数据点:
- 登录机智云开发者中心,创建新产品,如“独居老人监护器”。
- 定义数据点(Data Points):
temperature(温度): 读写类型,数值范围0-50。humidity(湿度): 读写类型,数值范围20-90。motion(活动): 读写类型,布尔值(0/1)。alert(报警): 只写类型,枚举值(0:正常,1:无活动超时,2:紧急按钮)。
- 记录产品的
Product Key和Product Secret,以及每个数据点的标识名。
设备端协议对接:
- 机智云设备接入协议相对固定。我们需要根据产品的
Product Key和设备MAC地址生成一个PK + MAC的校验码,作为设备唯一标识(did)。 - 通信数据需要按照机智云的标准格式进行封装,通常包括包头、数据长度、命令、序列号、数据内容、校验和等。
- 机智云设备接入协议相对固定。我们需要根据产品的
示例数据上报函数(简化逻辑):
// 根据机智云文档实现的数据打包函数 void Gizwits_PacketSensorData(uint8_t *buf, uint8_t temp, uint8_t humi, uint8_t motion) { // 这里是伪代码,实际需按机智云《设备接入协议文档》拼接字节 buf[0] = 0xFF; // 包头 buf[1] = 0xFF; buf[2] = 0x03; // 命令: 数据上报 buf[3] = 0x00; // 数据长度高字节 buf[4] = 0x06; // 数据长度低字节 (假设6个字节数据) buf[5] = temp; // 温度数据 buf[6] = humi; // 湿度数据 buf[7] = motion; // 活动状态 // ... 计算校验和 buf[8] } void Upload_Sensor_Data_To_Cloud(uint8_t temp, uint8_t humi, uint8_t motion) { uint8_t send_buf[64]; uint16_t len; // 1. 按照协议打包传感器数据 len = Gizwits_PacketSensorData(send_buf, temp, humi, motion); // 2. 通过ESP8266发送 ESP8266_SendData(send_buf, len); }报警上传函数类似,只是命令字和数据内容不同。
- APP端查看:
- 使用机智云提供的“机智云”APP或自定义开发APP,扫描设备二维码或手动添加设备,即可实时查看传感器数据和报警信息。
5. 常见问题与调试技巧
在开发过程中,你可能会遇到以下问题:
| 问题现象 | 可能原因 | 排查思路与解决方案 |
|---|---|---|
| OLED屏幕不亮或乱码 | 1. I2C地址错误。 2. I2C引脚接反或接触不良。 3. 初始化序列错误。 4. 电源电压不足。 | 1. 用逻辑分析仪或I2C扫描程序确认设备地址(0x78或0x7A)。 2. 检查SCL/SDA连接,确认上拉电阻(通常OLED模块已集成)。 3. 核对初始化命令,特别是对比度、显示开关命令。 4. 确保供电为3.3V且稳定。 |
| DHT11读取总是失败 | 1. 时序不精确。 2. 总线未加上拉电阻。 3. 传感器损坏或接触不良。 4. 延时函数不准确。 | 1.最重要:使用示波器或逻辑分析仪查看DATA线时序,确保起始信号、等待响应、位读取的时间满足手册要求。STM32的delay_us函数在72MHz下需要精细校准。2. 在DATA线和VCC之间接一个4.7K-10K的上拉电阻。 3. 更换传感器测试。 4. 使用系统滴答定时器( HAL_Delay)实现微秒延时,或使用定时器精确延时。 |
| ESP8266无响应或AT指令失败 | 1. 波特率不匹配。 2. 供电不足。 3. 接线错误(TX/RX反接)。 4. 模块未进入AT模式(GPIO0需拉高)。 5. 固件问题。 | 1. 确认STM32串口与ESP8266波特率一致(通常115200)。先用USB转TTL连接电脑,用串口助手测试AT指令。 2.单独给ESP8266供电,或确保你的3.3V电源能提供至少500mA电流。这是最常见的问题! 3. 检查TX接RX,RX接TX。 4. 确保ESP-01S的 GPIO0引脚在上电时处于高电平(悬空或接3.3V)。5. 尝试重新烧录稳定的AT固件。 |
| 能连WiFi但连不上机智云服务器 | 1. 网络问题(路由器限制)。 2. 产品密钥(PK/PS)错误。 3. 协议封装错误。 4. 服务器地址或端口错误。 | 1. 尝试用ESP8266连接其他网络服务(如AT+CIPSTART="TCP","www.baidu.com",80)测试外网连通性。2. 仔细核对开发者中心的产品信息。 3.使用网络调试工具(如网络调试助手)模拟设备发送数据包,与协议文档逐字节比对,这是定位协议问题最高效的方法。 4. 确认使用的是正确的服务器地址和端口( m2m.gizwits.com, 80/8883)。 |
| 系统运行一段时间后死机 | 1. 堆栈溢出。 2. 中断处理不当。 3. 看门狗未喂狗。 4. 电源不稳定。 | 1. 在Keil中调整启动文件的堆栈大小。检查是否有大型局部数组。 2. 确保中断服务函数(如串口接收中断)执行时间尽可能短,避免在中断内调用 HAL_Delay等阻塞函数。3. 如果开启了独立看门狗(IWDG),需定期喂狗。 4. 检查电源模块,尤其是当所有外设同时工作时电压是否被拉低。 |
通用调试建议:
- 分模块调试: 务必先让每个模块(OLED、DHT11、ESP8266)单独工作,再整合。
- 善用串口打印: 在关键步骤(如初始化成功、数据读取、指令发送)后,通过STM32的另一个串口(或重定向
printf)打印日志,是嵌入式调试的利器。 - 逻辑分析仪是神器: 对于I2C、单总线、UART通信问题,一个几十块的逻辑分析仪能直观显示波形和时序,事半功倍。
6. 优化扩展与最佳实践
一个基础系统完成后,可以考虑以下优化和扩展,使其更稳定、更实用:
低功耗设计:
- 对于电池供电场景,可以使用STM32的休眠模式。在无活动超时后,让STM32进入
STOP模式,仅保留RTC和外部中断(如PIR或按键)唤醒。ESP8266也可以仅在需要上传数据时上电。 - 调整传感器采样频率,非必要时不采集。
- 对于电池供电场景,可以使用STM32的休眠模式。在无活动超时后,让STM32进入
数据可靠性:
- 增加重发机制: 云端未回复ACK或网络异常时,将数据包存入缓冲区,定时重发,最多重试3-5次。
- 本地存储: 加入SPI Flash或EEPROM,在网络中断时临时存储报警和重要数据,网络恢复后补传。
- 数据滤波: 对DHT11的读数进行滑动平均滤波,避免显示值跳动剧烈。
功能扩展:
- 增加更多传感器: 如MQ-2烟雾传感器、蜂鸣器声光报警、DS1302时钟模块为OLED提供准确时间。
- 本地语音提示: 加入SYN6288语音模块,在报警时用语音提醒老人(如果听力尚可)。
- 电池电量监测: 使用ADC检测电池电压,电量低时上报云端提醒充电或更换电池。
- OTA远程升级: 利用STM32的IAP功能和机智云平台,实现固件远程升级,修复bug或增加功能无需上门。
代码结构优化:
- 使用RTOS: 如果功能复杂,可以考虑移植FreeRTOS,将传感器采集、网络通信、显示刷新等任务拆分,提高系统响应性和可维护性。
- 模块化编程: 将每个外设的驱动、业务逻辑、网络协议分层,方便代码复用和阅读。
- 配置文件: 将WiFi SSID/密码、服务器地址、报警阈值等配置信息存储在Flash的固定区域,甚至可以通过串口或蓝牙在配网时修改。
生产注意事项:
- 静电防护: 所有IO口,尤其是外接传感器的引脚,建议串联电阻或增加TVS管进行保护。
- 电源去耦: 在MCU和每个主要芯片的电源引脚附近放置0.1uF和10uF的电容,滤除高频和低频噪声。
- 外壳与安全: 设计3D打印或购买合适的外壳,避免电路板裸露。确保紧急按键大而醒目,方便老人操作。
通过以上步骤,一个功能完备、稳定可靠的独居老人监护系统就搭建完成了。它不仅是一个STM32的学习项目,更是一个能解决实际问题的物联网应用。你可以在此基础上继续深化,比如设计更美观的PCB、开发更友好的手机APP界面,甚至尝试接入其他IoT平台。