穿戴式生命体征监测仪:STM32 + MAX30102 + GY906 + MSP20 + GPS + 阿里云
这是一套基于 STM32F103C8T6 的穿戴式多参数健康监测终端,能同时采集心率、血氧、血压、体温,并通过 ATGM336H GPS 模块获取定位,最终经 ESP8266 WiFi 以 MQTT 协议上传到阿里云物联网平台。本地 OLED 实时显示,异常时蜂鸣器 + LED 声光报警。适合作为健康监测、老人看护、户外运动等场景的参考方案。
一、项目背景与功能
系统的核心目标是做一款“戴在身上就能知道身体状态”的小型监测设备:
- 心率/血氧:MAX30102 光电传感器,基于 PPG + FFT 算法计算心率和血氧饱和度。
- 体温:GY906(MLX90614)非接触红外测温,测量人体额头/手腕温度。
- 血压:MSP20 压力传感器,通过 ADC 采集并换算为血压值(kPa)。
- GPS 定位:ATGM336H 模块解析 NMEA 语句,获取经纬度。
- 本地显示:0.96 寸 IIC OLED 显示所有体征数据。
- 声光报警:心率/血氧/体温/血压任一超阈值,蜂鸣器 + LED 报警。
- 阈值可调:4 个独立按键设置心率、血氧、体温、血压报警阈值。
- 远程上云:通过 ESP8266 连接 WiFi,以 MQTT 接入阿里云物联网平台,周期性上报健康数据和位置信息。
二、硬件清单
| 元器件 |
数量 |
说明 |
| STM32F103C8T6 最小系统板 |
1 |
主控 MCU,72MHz Cortex-M3 |
| 0.96 寸 OLED 显示屏 |
1 |
IIC 接口,128×64 分辨率 |
| MAX30102 传感器模块 |
1 |
PPG 心率血氧,IIC 接口 |
| GY906(MLX90614)红外测温模块 |
1 |
SMBus/IIC 接口 |
| MSP20 压力传感器 |
1 |
模拟输出,接 PA0 用于血压估算 |
| ATGM336H GPS 模块 |
1 |
串口输出 NMEA 数据 |
| ESP8266 WiFi 模块 |
1 |
串口 AT 指令,连接阿里云 |
| 有源蜂鸣器 |
1 |
PA15 驱动 |
| LED |
1 |
PA12 状态/报警指示灯 |
| 独立按键 |
4 |
PB12~PB15,上拉输入 |
| DC-005 电源接口 |
1 |
5V 输入供电 |
IO 资源分配
三、硬件设计
3.1 原理图

原理图主要分几个区域:
- 主控开发板:STM32F103C8T6 最小系统,引出 USART1/2、ADC、多路 IIC 和 GPIO。
- 电源模块:DC-005 接口 + 330uF/100nF 滤波,5V 转 3.3V 供 MCU 和模块。
- MAX30102 模块:IIC 接 PA6/PA7,INT 中断脚接 PA4(与 WiFi 复位复用,实际代码中 INT 由 MAX30102_INTPin_Read 读取)。
- GY906 模块:IIC 接 PB10/PB11。
- MSP20 压力传感:模拟信号接 PA0。
- ATGM336H GPS:串口接 PA9/PA10。
- ESP8266 WiFi:串口接 PA2/PA3,RST 接 PA4。
- OLED 屏幕:IIC 接 PB0/PB1。
- 蜂鸣器/LED/按键:常规 GPIO 驱动。
四、软件设计
4.1 主循环初始化
main.c 负责初始化所有外设,然后进入主循环采集 GPS、心率和血氧:
int main(void) { delay_init(); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); SPECIAL_PIN_Init();
USART1_Init(9600); Usart2_Init(115200);
Init_adc(); KEY_Init(); LED_Init(); BEEP_Init(); OLED_Init(); OLED_Clear(); page_one();
IIC_GPIO_INIT(); MAX30102_GPIO(); Max30102_reset(); MAX30102_Config();
Mlx96014_Configuration();
WiFi_ResetIO_Init(); MQTT_Buff_Init(); AliIoT_Parameter_Init();
TIM1_Time_Init(99, 7199); TIM4_Init(300, 7200);
while (1) { if (ceshi_status) blood_Loop();
parseGpsBuffer(); printGpsBuffer();
if (Save_Data.longitude[5] == '.') jingdu = ...; else if (Save_Data.longitude[4] == '.') jingdu = ...;
if (Save_Data.latitude[5] == '.') weidu = ...; else if (Save_Data.latitude[4] == '.') weidu = ...;
if (show_mode == 1) { OLED_ShowNum(72, 0, (u8)jingdu, 3, 16); OLED_ShowNum(104, 0, (u16)(jingdu * 100) % 100, 2, 16); OLED_ShowNum(72, 2, (u8)weidu, 3, 16); OLED_ShowNum(104, 2, (u16)(weidu * 100) % 100, 2, 16); } Delay_Ms(500); } }
|
4.2 MAX30102 心率血氧检测
MAX30102 通过 IIC 读取 FIFO 中的红光/红外光数据,然后做 FFT 频谱分析提取脉搏波频率,从而计算心率和血氧:
void blood_data_update(void) { g_fft_index = 0; while (g_fft_index < FFT_N) { while (MAX30102_INTPin_Read() == 0) { max30102_read_fifo(); if (g_fft_index < FFT_N) { s1[g_fft_index].real = fifo_red; s2[g_fft_index].real = fifo_ir; g_fft_index++; } } } }
void blood_data_translate(void) { FFT(s1); FFT(s2);
for (i = 1; i < FFT_N; i++) { ac_red += s1[i].real; ac_ir += s2[i].real; }
s1_max_index = find_max_num_index(s1, 30); Heart_Rate = 60.00 * ((100.0 * s1_max_index) / 512.00); g_blooddata.heart = Heart_Rate;
R = (ac_ir * dc_red) / (ac_red * dc_ir); sp02_num = -45.060 * R * R + 30.354 * R + 94.845; g_blooddata.SpO2 = sp02_num; }
|
心率和血氧结果会分别更新到全局变量 heart_shuju 和 spo2_shuju,供显示和上传使用。
4.3 GY906 体温采集
GY906(MLX90614)通过 SMBus 协议读取人体表面温度,代码中使用软件模拟时序:
void Mlx96014_Configuration(void) { GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_Init(GPIOB, &GPIO_InitStructure); SDA_H; SCL_H; }
people_temp = temp_zong / 50;
|
4.4 MSP20 血压采集
MSP20 是压力传感器,输出模拟电压与压力成比例。代码使用 ADC1 通道 0 + DMA 循环采集:
void Init_adc(void) { GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStructure);
DMA_InitStructure.DMA_BufferSize = 1; DMA_Init(DMA1_Channel1, &DMA_InitStructure);
ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); ADC_SoftwareStartConvCmd(ADC1, ENABLE); }
|
在 TIM1 中断中,把 ADC 值累加后换算为 kPa:
ADC_ConvertedValueLocal = (float)ADC_ConvertedValue[0] / 4096 * 3.3f; msp_zong += ADC_ConvertedValueLocal; if (ADC_count >= 50) { ADC_count = 0; msp20_data = (float)(msp_zong / 165 * 100); msp_zong = 0; }
|
4.5 GPS 经纬度解析
USART1 接收 ATGM336H 的 $GNRMC 语句,解析出 UTC 时间、纬度、经度等信息:
void USART1_IRQHandler(void) { Res = USART_ReceiveData(USART1); if (Res == '$') point1 = 0; USART_RX_BUF[point1++] = Res;
if (USART_RX_BUF[0] == '$' && USART_RX_BUF[4] == 'M' && USART_RX_BUF[5] == 'C') { if (Res == '\n') { memcpy(Save_Data.GPS_Buffer, USART_RX_BUF, point1); Save_Data.isGetData = true; point1 = 0; } } }
|
主循环中调用 parseGpsBuffer() 拆分逗号字段,并把度分格式转换为十进制度:
if (Save_Data.longitude[5] == '.') jingdu = hextoDec(...) * 100 + ... + (...)/60;
|
4.6 阿里云 MQTT 数据上传
ESP8266 通过 AT 指令连接 WiFi 和阿里云 MQTT 服务器,然后使用自定义的 MQTT 协议栈封装数据:
#define PRODUCTKEY "a1AAS8HI34v" #define DEVICENAME "Device" #define DEVICESECRE "16dda89e8b35e1c6a6b0ff42249caf85" #define P_TOPIC_NAME "/sys/a1AAS8HI34v/Device/thing/event/property/post"
|
连接成功后,TIM2 定时器每 10 秒触发一次,交替上报两组数据:
void TIM2_IRQHandler(void) { static u8 i = 0; i++; if (i == 3) i = 1;
if (i == 1) { sprintf(temp, "{\"params\":{\"heart\":%d,\"spo2\":%0.2f,\"temp\":%0.1f,\"msp\":%0.1f}}", heart_shuju, spo2_shuju, people_temp, msp20_data); MQTT_PublishQs0(P_TOPIC_NAME, temp, strlen(temp)); } else if (i == 2) { sprintf(temp, "{\"params\":{\"jing\":%0.2f,\"wei\":%0.2f,\"bao\":%d}}", jingdu, weidu, baojing_status); MQTT_PublishQs0(P_TOPIC_NAME, temp, strlen(temp)); } }
|
TIM4 每 30ms 运行一次 MQTT 状态机,负责 CONNECT、SUBSCRIBE、PING 保活和重连。
4.7 TIM1 中断:按键 + 显示 + 报警
TIM1 每 10ms 进入一次更新中断,完成所有周期性任务:
void TIM1_UP_IRQHandler(void) { if (KEY_count >= 5) { KEY_count = 0; key = KEY_Scan(0); if (key == SET_PRES) { show_mode = (show_mode + 1) % 6; } else if (key == CHOICE_PRES) { if (show_mode != 0) tiaozheng_status = !tiaozheng_status; else ceshi_status = !ceshi_status; } else if (key == ADD_PRES) { if (show_mode == 2) heart_yuzhi += (tiaozheng_status ? 10 : 1); else if (show_mode == 3) spo2_yuzhi += (tiaozheng_status ? 1.0 : 0.1); else if (show_mode == 4) temp_yuzhi += (tiaozheng_status ? 1.0 : 0.1); else if (show_mode == 5) msp20_yuzhi += (tiaozheng_status ? 1.0 : 0.1); } else if (key == REDUCE_PRES) { } }
temp_zong += SMBus_ReadTemp(); if (GPY96_count >= 50) { people_temp = temp_zong / 50; temp_zong = 0; }
ADC_ConvertedValueLocal = (float)ADC_ConvertedValue[0] / 4096 * 3.3f; msp_zong += ADC_ConvertedValueLocal; if (ADC_count >= 50) { msp20_data = msp_zong / 165 * 100; msp_zong = 0; }
if (heart_shuju > heart_yuzhi || spo2_shuju < spo2_yuzhi || people_temp > temp_yuzhi || msp20_data > msp20_yuzhi) baojing_status = 1; else baojing_status = 0;
if (baojing_status) beep = 1, led = 1; else beep = 0, led = 0;
if (MESSAGE_count >= 50) { if (show_mode == 0) { OLED_ShowNum(88, 0, heart_shuju, 3, 16); OLED_ShowNum(72, 2, spo2_shuju, 3, 16); OLED_ShowNum(104, 2, (u32)(spo2_shuju * 100) % 100, 2, 16); OLED_ShowNum(72, 4, people_temp, 3, 16); OLED_ShowNum(104, 4, (int)(people_temp * 10) % 10, 1, 16); OLED_ShowNum(72, 6, msp20_data, 2, 16); OLED_ShowNum(96, 6, (int)(msp20_data * 10) % 10, 1, 16); } } }
|
显示页面设计:
- page_one:首页,显示当前心率、血氧、体温、血压。
- page_two:GPS 页面,显示经度、纬度和报警状态。
- page_three~six:分别设置心率阈值、血氧阈值、体温阈值、血压阈值。
五、实物展示

实物板采用绿色 PCB,中间是 STM32F103C8T6 最小系统板,上方为 OLED 显示屏,左侧为 ESP8266 WiFi 模块和 MAX30102 传感器,下方为 4 个独立按键,右侧为蜂鸣器和电源接口。OLED 首页可看到心率、血氧、体温、血压四项数据。

测试时可以通过手指按压 MAX30102 模拟心率血氧采集,同时用注射器对 MSP20 压力传感器加压,观察血压数值变化。
六、功能特点
| 功能 |
说明 |
| 四体征监测 |
心率、血氧、体温、血压同时采集 |
| GPS 定位 |
ATGM336H 实时获取经纬度 |
| 阿里云上云 |
ESP8266 + MQTT 周期性上报健康/位置数据 |
| 本地显示 |
0.96 寸 OLED 六页菜单 |
| 声光报警 |
超阈值时蜂鸣器 + LED 响应 |
| 阈值可调 |
4 按键设置心率/血氧/体温/血压阈值 |
| FFT 算法 |
MAX30102 采用 FFT 频谱法计算心率血氧 |
| 多 IIC 设备 |
MAX30102、GY906、OLED 分时复用不同 IIC 引脚 |
七、项目总结
这个项目把嵌入式系统常见的几类技术都串起来了:
- 传感器采集:PPG 心率血氧、红外测温、压力传感、GPS NMEA 解析。
- 模拟量采集:MSP20 通过 ADC + DMA 实现连续采样。
- 数字信号处理:FFT 提取脉搏波频谱,计算心率和血氧。
- 多总线协议:三路 IIC/SMBus 分别驱动 MAX30102、GY906、OLED。
- 无线通信:ESP8266 AT 指令 + MQTT 协议栈接入阿里云。
- 人机交互:OLED 菜单 + 独立按键 + 蜂鸣器/LED 报警。
对于想做一个“能穿戴、能显示、能联网”的健康监测类项目的同学,这套方案可以直接复刻参考。
资料下载
完整资料包含:
- STM32 工程源码(Keil 标准库工程)
- 原理图 PDF
- 嘉立创 EDA 工程文件
- 测试视频
- 硬件清单 BOM
资料链接见博客侧边栏头像下方的 资料链接(下载图标)处。