1. 项目概述
作为一名嵌入式系统开发者,我最近完成了一个基于STM32的智能独居老人看护系统项目。这个系统通过多种传感器实时监测老人的健康状况和生活环境,在异常情况下自动报警,同时支持远程监控功能。在实际开发过程中,我遇到了不少技术挑战,也积累了一些值得分享的经验。
这个系统最核心的价值在于它能够全天候监测老人的心率、血氧、活动状态以及居住环境质量,当检测到跌倒、心率异常或环境参数超标时,会立即触发本地报警,同时通过WiFi将数据上传到手机APP,让不在身边的家人也能随时了解老人状况。
2. 系统架构设计
2.1 硬件组成
系统硬件采用模块化设计,主要包含以下核心组件:
-
主控模块:STM32F103C8T6最小系统板,作为整个系统的控制核心。选择这款芯片主要考虑到它丰富的外设接口、适中的处理能力和成熟的生态系统。
-
传感器阵列:
- MAX30102心率血氧传感器(I2C接口)
- MPU6050六轴陀螺仪(I2C接口)
- DS18B20温度传感器(单总线)
- HC-SR04超声波测距模块
- MQ-135空气质量传感器(ADC采集)
-
人机交互模块:
- 0.96寸OLED显示屏(I2C接口)
- 4个轻触按键
- 蜂鸣器报警装置
- LED状态指示灯
-
通信模块:
- ESP8266-01S WiFi模块(串口AT指令)
- 支持连接机智云物联网平台
2.2 软件架构
软件采用分层设计,主要分为以下几个层次:
-
硬件驱动层:为各传感器和外设提供底层驱动,包括:
- 各传感器的初始化函数
- 数据采集接口
- 通信协议实现(I2C、单总线、串口等)
-
业务逻辑层:
- 传感器数据融合处理
- 异常状态检测算法
- 系统状态机管理
- 阈值设置与存储
-
用户界面层:
- OLED显示管理
- 按键事件处理
- 报警提示管理
-
云端通信层:
- 机智云协议处理
- 数据上报与指令接收
- WiFi连接管理
3. 核心功能实现
3.1 多传感器数据采集
系统需要同时采集多种传感器数据,关键在于合理安排采集时序和数据处理流程:
c复制void SensorScan(void)
{
static uint8_t scan_phase = 0;
switch(scan_phase) {
case 0: // 温度采集
temperature = DS18B20_Get_Temp();
sensorData.tempValue = temperature;
break;
case 1: // 心率血氧采集
MAX30102_Read_FIFO();
sensorData.hrValue = getHeartRate();
sensorData.spo2Value = getSPO2();
break;
case 2: // 超声波测距
sensorData.distanceValue = GetUltrasonicDistance();
break;
case 3: // 姿态检测
MPU6050_Get_Data(&AX, &AY, &AZ, &GX, &GY, &GZ);
sensorData.fallDetected = DetectFall(AX, AY, AZ, GX, GY, GZ);
break;
case 4: // 空气质量检测
sensorData.mq135Value = GetMQ135Value();
break;
}
scan_phase = (scan_phase + 1) % 5;
}
注意事项:传感器采集需要注意时序安排,避免多个传感器同时工作导致总线冲突。特别是I2C设备,需要确保每次操作完成后释放总线。
3.2 跌倒检测算法
跌倒检测是系统的关键功能,我们基于MPU6050的加速度和角速度数据实现:
c复制uint8_t DetectFall(int16_t ax, int16_t ay, int16_t az, int16_t gx, int16_t gy, int16_t gz)
{
static uint32_t last_fall_time = 0;
static uint8_t fall_state = 0;
uint32_t current_time = delay_get_tick();
// 计算合加速度
float accel = sqrt(ax*ax + ay*ay + az*az) / 16384.0f;
// 状态机实现跌倒检测
switch(fall_state) {
case 0: // 等待异常加速度
if(accel > 2.5f) { // 超过2.5g认为有剧烈运动
fall_state = 1;
last_fall_time = current_time;
}
break;
case 1: // 检测静止状态
if(current_time - last_fall_time > 500) { // 500ms后检查
if(accel < 0.8f && (abs(az) < 0.3f*16384)) { // 静止且非直立状态
return 1; // 判定为跌倒
}
fall_state = 0;
}
break;
}
return 0;
}
实操心得:跌倒检测算法需要结合实际测试不断调整阈值。我们发现单纯依靠加速度阈值容易产生误报,加入姿态判断后准确率明显提高。
3.3 异常状态判断与报警
系统根据采集到的数据和预设阈值判断是否需要报警:
c复制void AutoControl(void)
{
// 温度异常判断
if(sensorData.tempValue > Sensorthreshold.tempValue) {
driveData.BEEP_Flag = 1;
currentDataPoint.valueTEMPERATURE_ALARM = 1;
}
// 心率异常判断
if(sensorData.hrValue > Sensorthreshold.hrAvgValue ||
sensorData.hrValue < 50) {
driveData.BEEP_Flag = 1;
currentDataPoint.valueHEARTRATE_ALARM = 1;
}
// 血氧异常判断
if(sensorData.spo2Value < Sensorthreshold.sopAvgValue) {
driveData.BEEP_Flag = 1;
currentDataPoint.valueSPO2_ALARM = 1;
}
// 跌倒检测
if(sensorData.fallDetected) {
driveData.BEEP_Flag = 1;
currentDataPoint.valueFALL_ALARM = 1;
}
// 距离过近判断(防撞)
if(sensorData.distanceValue < Sensorthreshold.distanceValue &&
sensorData.distanceValue > 2) {
driveData.BEEP_Flag = 1;
currentDataPoint.valueDISTANCE_ALARM = 1;
}
// 空气质量判断
if(sensorData.mq135Value > Sensorthreshold.mq135Value) {
driveData.BEEP_Flag = 1;
currentDataPoint.valueAIR_ALARM = 1;
}
}
4. 人机交互设计
4.1 多模式界面管理
系统提供三种操作模式,通过按键切换:
- 自动模式:默认模式,显示传感器数据和系统状态
- 手动模式:允许手动控制灯光和蜂鸣器
- 设置模式:调整各项报警阈值
模式切换状态机实现:
c复制uint8_t SetAuto(void)
{
if(KeyNum == KEY_2) {
auto_page = (auto_page == 1) ? 2 : 1;
KeyNum = 0;
OLED_Clear();
}
return auto_page;
}
uint8_t SetManual(void)
{
if(KeyNum == KEY_2) {
count_m = (count_m % 2) + 1; // 在1和2之间切换
KeyNum = 0;
}
return count_m;
}
uint8_t SetSelection(void)
{
if(KeyNum == KEY_2) {
count_s = (count_s % 5) + 1; // 在1-5之间循环
KeyNum = 0;
}
return count_s;
}
4.2 OLED显示优化
为了在有限的OLED屏幕上高效显示信息,我们采用分页设计:
c复制void SensorDataDisplay1(void)
{
OLED_ShowString(0, 0, "Temp:", 8);
OLED_ShowNum(40, 0, sensorData.tempValue, 2, 8);
OLED_ShowString(60, 0, "C", 8);
OLED_ShowString(0, 2, "HR:", 8);
OLED_ShowNum(24, 2, sensorData.hrValue, 3, 8);
OLED_ShowString(54, 2, "bpm", 8);
OLED_ShowString(0, 4, "SpO2:", 8);
OLED_ShowNum(40, 4, sensorData.spo2Value, 3, 8);
OLED_ShowString(70, 4, "%", 8);
OLED_ShowString(0, 6, "Distance:", 8);
OLED_ShowNum(64, 6, sensorData.distanceValue, 2, 8);
OLED_ShowString(88, 6, "cm", 8);
}
void SensorDataDisplay2(void)
{
OLED_ShowString(0, 0, "Fall:", 8);
OLED_ShowString(40, 0, sensorData.fallDetected ? "YES" : "NO ", 8);
OLED_ShowString(0, 2, "Air Q:", 8);
OLED_ShowNum(48, 2, sensorData.mq135Value, 3, 8);
// 显示WiFi连接状态
OLED_ShowString(0, 4, "WiFi:", 8);
if(gizwitsGetCurrentMode() == WIFI_CON_ROUTER) {
OLED_ShowString(40, 4, "Connected", 8);
} else {
OLED_ShowString(40, 4, "Disconnected", 8);
}
// 显示系统运行时间
uint32_t run_time = delay_get_tick() / 1000;
OLED_ShowString(0, 6, "Time:", 8);
OLED_ShowNum(40, 6, run_time/60, 2, 8); // 分钟
OLED_ShowString(56, 6, "m", 8);
OLED_ShowNum(72, 6, run_time%60, 2, 8); // 秒
OLED_ShowString(88, 6, "s", 8);
}
显示优化技巧:OLED刷新时采用局部刷新策略,只更新变化的数据区域,可以显著降低闪烁感并提高响应速度。
5. 物联网通信实现
5.1 机智云平台接入
系统通过ESP8266模块接入机智云平台,关键实现步骤:
-
设备注册与配置:
- 在机智云开发者中心创建产品
- 定义数据点(对应各传感器数据和控制指令)
- 生成产品密钥(Product Key)和产品密钥(Product Secret)
-
协议移植:
- 将机智云提供的协议库(Gizwits Protocol)移植到STM32工程
- 实现必要的回调函数处理云端指令
-
数据上报处理:
c复制void userHandle(void)
{
currentDataPoint.valueTEMPERATURE = sensorData.tempValue;
currentDataPoint.valueHEARTRATE = sensorData.hrValue;
currentDataPoint.valueSPO2 = sensorData.spo2Value;
currentDataPoint.valueDISTANCE = sensorData.distanceValue;
currentDataPoint.valueAIR_QUALITY = sensorData.mq135Value;
// 上报数据到云端
gizwitsHandle((dataPoint_t *)¤tDataPoint);
}
// 处理云端下发的指令
void gizwitsEventProcess(eventInfo_t *info)
{
if(info->event == EVENT_LED_ONOFF) {
driveData.LED_Flag = info->value.LED_ONOFF;
}
if(info->event == EVENT_BEEP_ONOFF) {
driveData.BEEP_Flag = info->value.BEEP_ONOFF;
}
}
5.2 WiFi配网实现
系统支持两种配网方式:
-
AirLink一键配网:
- 手机APP发送包含SSID和密码的广播包
- ESP8266进入混杂模式捕获这些包并连接路由器
-
SoftAP模式:
- ESP8266自身作为热点
- 手机连接热点后通过网页配置网络
关键代码实现:
c复制void ScanGizwitsMode(void)
{
if(KeyNum == KEY_3) { // 长按KEY3进入AirLink模式
gizwitsSetMode(WIFI_AIRLINK_MODE);
OLED_ShowString(0, 6, "AirLink Mode", 8);
KeyNum = 0;
}
else if(KeyNum == KEY_4) { // 长按KEY4进入SoftAP模式
gizwitsSetMode(WIFI_SOFTAP_MODE);
OLED_ShowString(0, 6, "SoftAP Mode", 8);
KeyNum = 0;
}
}
通信优化经验:在实际测试中发现,ESP8266的固件版本对连接稳定性影响很大。推荐使用AT固件版本1.6.2或更高,并适当增加重试机制和超时处理。
6. 系统优化与调试
6.1 低功耗设计
虽然本项目主要使用市电供电,但考虑到可能的电池备份需求,我们做了以下优化:
-
传感器采样频率调整:
- 心率血氧传感器:默认1Hz,异常时提高到2Hz
- 温度传感器:每5秒采样一次
- 姿态传感器:采用中断唤醒方式
-
显示背光控制:
- 无操作30秒后降低OLED亮度
- 2分钟后关闭背光,按键唤醒
-
通信模块功耗管理:
- WiFi模块在无数据传输时进入省电模式
- 数据上报间隔动态调整(正常状态5分钟一次,异常状态30秒一次)
6.2 系统稳定性增强
- 看门狗应用:
- 启用STM32独立看门狗(IWDG),超时时间1秒
- 关键任务流程中定期喂狗
c复制void IWDG_Init(void)
{
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
IWDG_SetPrescaler(IWDG_Prescaler_32); // 32分频
IWDG_SetReload(0xFFF); // 约1秒超时
IWDG_ReloadCounter();
IWDG_Enable();
}
void Task_Scheduler(void)
{
static uint32_t last_feed_time = 0;
if(delay_get_tick() - last_feed_time > 500) {
IWDG_ReloadCounter();
last_feed_time = delay_get_tick();
}
// 其他任务调度...
}
- 异常恢复机制:
- 传感器通信失败自动重试(最多3次)
- WiFi连接断开自动重连
- 关键参数自动校验与恢复
6.3 调试技巧分享
在开发过程中,以下几个调试方法特别有用:
- 串口日志分级:
- 定义不同级别的日志(DEBUG、INFO、WARN、ERROR)
- 通过宏控制输出级别,便于问题定位
c复制#define LOG_LEVEL_DEBUG 0
#define LOG_LEVEL_INFO 1
#define LOG_LEVEL_WARN 2
#define LOG_LEVEL_ERROR 3
#ifndef CURRENT_LOG_LEVEL
#define CURRENT_LOG_LEVEL LOG_LEVEL_DEBUG
#endif
#define LOG(level, fmt, ...) \
do { \
if(level >= CURRENT_LOG_LEVEL) { \
printf("[%s] " fmt "\n", \
level == 0 ? "DEBUG" : \
level == 1 ? "INFO" : \
level == 2 ? "WARN" : "ERROR", \
##__VA_ARGS__); \
} \
} while(0)
-
传感器数据可视化:
- 通过串口将传感器数据输出到PC
- 使用Python脚本绘制实时曲线,便于分析数据变化规律
-
边界条件测试:
- 特别测试各种极端情况(如传感器断开、WiFi信号弱、快速按键操作等)
- 验证系统在各种异常条件下的表现
7. 常见问题与解决方案
在实际开发和部署过程中,我们遇到了不少典型问题,以下是其中几个具有代表性的案例:
7.1 MAX30102数据不稳定
现象:心率血氧数据偶尔出现跳变,特别是在老人移动时。
排查过程:
- 检查硬件连接,确认I2C上拉电阻合适(4.7kΩ)
- 测量电源电压,发现运动时电压有轻微波动
- 分析原始光电容积图(PPG)数据,发现运动伪影严重
解决方案:
- 在电源端增加100μF电解电容稳压
- 优化佩戴方式,确保传感器与皮肤良好接触
- 在软件中加入运动状态检测和滤波算法:
c复制float FilterHeartRate(float raw_hr)
{
static float hr_buffer[5] = {0};
static uint8_t index = 0;
static uint8_t buffer_full = 0;
// 更新缓冲区
hr_buffer[index] = raw_hr;
index = (index + 1) % 5;
if(index == 0) buffer_full = 1;
// 中值滤波
if(buffer_full) {
float temp[5];
memcpy(temp, hr_buffer, sizeof(temp));
// 冒泡排序
for(int i=0; i<4; i++) {
for(int j=0; j<4-i; j++) {
if(temp[j] > temp[j+1]) {
float tmp = temp[j];
temp[j] = temp[j+1];
temp[j+1] = tmp;
}
}
}
return temp[2]; // 返回中值
}
return raw_hr;
}
7.2 WiFi频繁断开
现象:在部分环境中,WiFi连接不稳定,频繁断开重连。
排查过程:
- 测试不同位置信号强度(RSSI)
- 检查路由器设置,发现启用了802.11n only模式
- 分析ESP8266固件版本,较旧版本存在兼容性问题
解决方案:
- 升级ESP8266固件到最新版本
- 调整路由器设置为802.11b/g/n混合模式
- 在代码中增加重连机制:
c复制void WiFi_Check(void)
{
static uint32_t last_check_time = 0;
static uint8_t retry_count = 0;
if(delay_get_tick() - last_check_time > 30000) { // 每30秒检查一次
if(gizwitsGetCurrentMode() != WIFI_CON_ROUTER) {
retry_count++;
if(retry_count > 3) {
// 多次重连失败,尝试重新初始化
gizwitsInit();
retry_count = 0;
} else {
gizwitsSetMode(WIFI_AIRLINK_MODE);
}
} else {
retry_count = 0;
}
last_check_time = delay_get_tick();
}
}
7.3 误报警问题
现象:系统偶尔在没有实际异常时触发报警。
排查过程:
- 分析报警触发时的传感器数据
- 发现主要是短时干扰导致的数据跳变
- 检查阈值判断逻辑,发现是立即触发没有延时确认
解决方案:
- 对关键参数增加延时确认机制(如连续3次超阈值才报警)
- 引入滞后比较(Hysteresis)避免临界值抖动:
c复制uint8_t CheckThresholdWithHysteresis(float value, float threshold, float hysteresis)
{
static uint8_t last_state = 0;
if(value > threshold + (last_state ? 0 : hysteresis)) {
last_state = 1;
return 1;
} else if(value < threshold - (last_state ? hysteresis : 0)) {
last_state = 0;
return 0;
}
return last_state;
}
8. 项目扩展与改进方向
目前系统已经实现了基本功能,但还有不少可以改进和扩展的地方:
8.1 硬件改进
- 增加备用电源:添加锂电池和充电管理电路,确保市电中断时系统仍能工作
- 模块化设计:将传感器设计为可插拔模块,便于维护和升级
- 外壳优化:设计更美观实用的外壳,改善散热和电磁兼容性
8.2 软件功能增强
- 行为模式学习:通过机器学习算法学习老人的日常活动模式,提高异常检测准确率
- 语音交互:增加语音提示和简单语音控制功能
- 多设备组网:支持多个监测节点组网,覆盖更大活动范围
8.3 云端功能扩展
- 数据分析报表:提供长期趋势分析和健康报告
- 多用户共享:支持家庭成员共同查看数据
- 紧急联系人联动:异常情况下自动通知预设联系人
在实际部署中,我们还发现老人对技术的接受程度各不相同。下一步计划优化用户界面,提供更简单直观的操作方式,比如大字体显示、更明显的报警提示等。同时也在考虑增加一键呼叫功能,让老人在需要帮助时能快速联系到家人。