目录
[PCB图 编辑编辑](#PCB图 编辑编辑)
一、前言
项目成品图片:

哔哩哔哩视频链接:
(资料分享见文末)
二、项目简介
1.功能详解
基于STM32的心率血氧手环(机智云)系统
功能如下:
- 环境采集:自动模式界面实时时间(年月日周时分秒)显示、心率显示、血氧显示、体温显示、步数显示、状态(正常/跌倒)
- 显示功能:环境数据显示在OLED屏幕上
- 模式切换:通过按键可以切换手动模式和自动模式
- 自动模式:自动模式时心率/血氧/体温处于异常范围内或跌倒时蜂鸣器报警
- 手动模式:按键进入手动模式,可控制步数清零、LED的开关
- 阈值调节:阈值设置界面可设置当前时间,设置心率上下限、血氧下限、体温上下限。
- 机智云APP: APP接收心率血氧体温步数和状态(正常/跌倒)信息,可修改所有阈值、可手动清除步数
2.主要器件
- STM32F103C8T6最小系统板
- OLED显示屏(4针IIC协议)
- DS18B20温度传感器
- MAX30102心率血氧传感器
- ADXL345加速度传感器
- ESP8266-01S(WIFI模块)
- 蜂鸣器
- LED灯
三、原理图设计

四、PCB硬件设计
PCB图 

五、程序设计
cpp
#include "stm32f10x.h"
#include "led.h"
#include "beep.h"
#include "usart.h"
#include "delay.h"
#include "oled.h"
#include "key.h"
#include "Modules.h"
#include "adcx.h"
#include "flash.h"
#include "usart2.h"
#include "usart3.h"
#include "TIM2.h"
#include "timer.h"
#include "TIM3.h"
#include "MyRTC.h"
#include "max30102_read.h"
#include "ds18b20.h"
#include "adxl345.h"
#include "myiic.h"
#include "iic.h"
#include "gizwits_product.h"
#include "gizwits_protocol.h"
/****************异方辰电子工作室******************
STM32
* 项目 : STM32心率血氧手环(机智云版)
* 版本 : V2.0
* 日期 : 2026.3.11
* MCU : STM32F103C8T6
* 接口 : 见代码
* IP账号 : 异方辰电子/辰哥单片机设计(同BILIBILI|抖音|快手|小红书|CSDN|公众号|视频号等)
* 作者 : 辰哥
* 工作室 : 异方辰电子工作室
* 授权IP : 辰哥单片机设计、异方辰电子、YFC电子、异方辰系列
* 官方网站 : www.yfcdz.cn
**********************BEGIN***********************/
#define KEY_Long1 11
#define KEY_1 1
#define KEY_2 2
#define KEY_3 3
#define KEY_4 4
#define AUTO_MODE 1 // 自动模式
#define MANUAL_MODE 2 // 手动模式
#define SETTINGS_MODE 3 // 设置模式
#define FLASH_START_ADDR 0x0801f000 // 起始地址
// 外部变量声明
SensorModules sensorData;
SensorThresholdValue Sensorthreshold;
DriveModules driveData;
// 全局变量定义
uint8_t mode = 1; //系统模式 1自动 2手动 3设置
u8 dakai; //串口3使用的传递变量
u8 Flag_dakai; //串口3接收标志位
uint8_t is_secondary_menu = 0; // 0一级菜单,1二级菜单
uint8_t secondary_pos = 1; // 二级菜单光标位置(1-3对应时/分/秒)
uint8_t secondary_type = 0; // 二级菜单类型:0=RTC时间,1=定时开启,2=定时关闭
uint8_t step_num = 0;
// 星期文字数组(1=周一,7=周日)
const uint8_t Week_Chinese[7][2] = {0,1, 0,2, 0,3, 0,4, 0,5, 0,6, 0,7};
extern unsigned char p[16];
// 自动模式缓存变量(修复未定义错误)
static uint16_t last_RTC_Time[6] = {0, 0, 0, 0, 0, 0}; // 自动模式时间缓存
static uint8_t last_week = 0; // 自动模式星期缓存
// 时间设置页面光标缓存(保留原始坐标)
static uint16_t adjust_RTC_Time[6] = {0, 0, 0, 0, 0, 0}; // 时间设置临时缓存(uint16_t无符号)
//static uint8_t adjust_week = 0; // 时间设置星期缓存
// 时间设置全局控制变量
static uint8_t time_adjust_flag = 0; // 时间修改标志(1=进入时间设置,0=退出)
static uint8_t count_t = 1; // 时间设置页面KEY2计数(1-6对应年/月/日/时/分/秒)
// 系统静态变量
uint8_t count_m = 1; // 手动模式按键计数
uint8_t count_s = 1; // 设置模式按键计数
uint8_t last_mode = 0; // 记录上一次的模式
uint8_t auto_page = 1;
int main(void)
{
// 系统初始化
SystemInit();
delay_init(72);
ADCx_Init();
LED_Init();
BEEP_Init();
BEEP_OFF;
USART1_Config();
USART2_Config();
USART3_Config();
Key_Init();
OLED_Init();
OLED_Clear();
MyRTC_Init(); // 初始化RTC
DS18B20_Init(); // DS18B20 温度传感器初始化
IIC_init(); // IIC初始化
adxl345_init(); // ADXL345模块初始化
Init_MAX30102();// MAX30102模块初始化
delay_ms(100);
static uint8_t last_mode = 0;
static uint32_t last_sensor_time = 0;
static uint32_t last_display_time = 0;
static uint32_t last_save_flash_time = 0; // 上次保存FLASH的时间戳
const uint32_t SAVE_FLASH_INTERVAL = 60000; // 保存间隔:60000ms = 1分钟
static uint8_t flash_save_flag = 0;
TIM2_Init(72-1,1000-1);
TIM3_Int_Init(1000-1,72-1);
delay_ms(200);
// 读取FLASH中的阈值和时间
FLASH_ReadThreshold();
// 新增:校验阈值合法性,非法则初始化默认值
if(FLASH_CheckThresholdValid() == 0)
{
FLASH_InitDefaultThreshold(); // 初始化默认阈值
}
// 时间数据合法性校验(原有代码保留,补充注释)
if(adjust_RTC_Time[0] >= 2000 && adjust_RTC_Time[0] <= 2099 && // 年合法
adjust_RTC_Time[1] >= 1 && adjust_RTC_Time[1] <= 12 && // 月合法
adjust_RTC_Time[2] >= 1 && adjust_RTC_Time[2] <= 31 && // 日合法
adjust_RTC_Time[3] <= 23 && adjust_RTC_Time[4] <= 59 && adjust_RTC_Time[5] <= 59)
{
// 将FLASH读取的时间写入RTC工作数组
MyRTC_Time[0] = adjust_RTC_Time[0]; // 年
MyRTC_Time[1] = adjust_RTC_Time[1]; // 月
MyRTC_Time[2] = adjust_RTC_Time[2]; // 日
MyRTC_Time[3] = adjust_RTC_Time[3]; // 时
MyRTC_Time[4] = adjust_RTC_Time[4]; // 分
MyRTC_Time[5] = adjust_RTC_Time[5]; // 秒
MyRTC_SetTime(); // 生效到RTC硬件
}
else
{
// 时间非法时,使用默认时间(2025-01-01 00:00:00)
adjust_RTC_Time[0] = 2026;
adjust_RTC_Time[1] = 3;
adjust_RTC_Time[2] = 12;
adjust_RTC_Time[3] = 12;
adjust_RTC_Time[4] = 12;
adjust_RTC_Time[5] = 12;
MyRTC_Time[0] = 2026;
MyRTC_Time[1] = 3;
MyRTC_Time[2] = 11;
MyRTC_Time[3] = 12;
MyRTC_Time[4] = 45;
MyRTC_Time[5] = 0;
MyRTC_SetTime();
WriteTimeToFlash(); // 将默认时间写入FLASH
}
userInit(); // 机智云用户初始化(数据点初始化等)
gizwitsInit(); // 机智云协议初始化(WiFi配网、数据通信初始化)
gizwitsSetMode(WIFI_AIRLINK_MODE); //默认一键配网
delay_ms(200);
printf("Start \n");
gizwitsHandle((dataPoint_t *)¤tDataPoint);
ScanGizwitsMode();
delay_ms(200);
// 主循环
while (1)
{
gizwitsHandle((dataPoint_t *)¤tDataPoint);
uint32_t current_time = delay_get_tick();
if(current_time - last_save_flash_time >= SAVE_FLASH_INTERVAL)
{
// 改用"标志位+低优先级执行",避免直接在主循环执行FLASH写入
flash_save_flag = 1;
last_save_flash_time = current_time;
}
// 传感器扫描(100ms一次)
if(current_time - last_sensor_time > 100)
{
SensorScan();
last_sensor_time = current_time;
userHandle();
}
if(flash_save_flag == 1)
{
MyRTC_ReadTime();
WriteAllDataToFlash();
flash_save_flag = 0; // 清除标志位
}
// 按键处理(模式切换)
uint8_t current_key_num = KeyNum;
if(current_key_num != 0)
{
switch(mode)
{
case AUTO_MODE:
if(current_key_num == KEY_1)
{
mode = MANUAL_MODE;
count_m = 1;
driveData.LED_Flag = 0;
driveData.BEEP_Flag = 0;
KeyNum = 0;
}
else if(current_key_num == KEY_Long1)
{
mode = SETTINGS_MODE;
count_s = 1;
KeyNum = 0;
}
break;
case MANUAL_MODE:
if(current_key_num == KEY_1)
{
mode = AUTO_MODE;
auto_page = 1; // 强制重置自动模式为第一页
KeyNum = 0;
}
break;
case SETTINGS_MODE:
if(current_key_num == KEY_1)
{
// 若处于时间设置子页面,先退出子页面
if(time_adjust_flag == 1)
{
time_adjust_flag = 0;
count_t = 1; // 重置时间页面计数
// 保存修改后的时间到RTC
MyRTC_Time[0] = adjust_RTC_Time[0];
MyRTC_Time[1] = adjust_RTC_Time[1];
MyRTC_Time[2] = adjust_RTC_Time[2];
MyRTC_Time[3] = adjust_RTC_Time[3];
MyRTC_Time[4] = adjust_RTC_Time[4];
MyRTC_Time[5] = adjust_RTC_Time[5];
MyRTC_SetTime();
WriteTimeToFlash();
// 退出时全屏刷新回到设置模式
OLED_Clear();
OLED_settingsPage1();
SettingsThresholdDisplay1();
OLED_settingsOption(count_s);
OLED_Refresh();
}
else
{
// 保存阈值和时间到FLASH,回到自动模式
WriteThresholdToFlash();
mode = AUTO_MODE;
count_s = 1; // 重置设置模式计数
OLED_Clear();
OLED_autoPage1();
}
KeyNum = 0;
}
break;
}
}
// 模式切换清屏
if(last_mode != mode)
{
OLED_Clear();
last_mode = mode;
switch(mode)
{
case AUTO_MODE:
OLED_autoPage1(); // 绘制固定文字
SensorDataDisplay1(); // 立即绘制数据(心率/血氧/步数)
OLED_ShowRTC_Time(); // 绘制时间并触发刷新
break;
case MANUAL_MODE: OLED_manualPage1(); break;
case SETTINGS_MODE: OLED_settingsPage1(); break;
}
OLED_Refresh();
}
// 模式逻辑处理
switch(mode)
{
// 自动模式
case AUTO_MODE:
{
uint8_t curr_auto_page = SetAuto();
if(curr_auto_page == 1)
{
SensorDataDisplay1();
// 仅时间变化时刷新
if(OLED_ShowRTC_Time()) OLED_Refresh();
}
else
{
SensorDataDisplay2(); // 显示GPS数据+蓝牙发送
}
AutoControl();
Control_Manager();
break;
}
// 手动模式
case MANUAL_MODE:
{
static uint8_t manual_page_initialized = 0;
static uint8_t last_manual_count = 0;
static uint8_t last_LED_Flag = 0;
static uint8_t last_BEEP_Flag = 0;
static uint8_t force_refresh = 0;
if(last_mode != mode)
{
manual_page_initialized = 0;
last_manual_count = 0;
last_LED_Flag = driveData.LED_Flag;
last_BEEP_Flag = driveData.BEEP_Flag;
force_refresh = 1;
count_m = 1;
driveData.LED_Flag = 0;
driveData.BEEP_Flag = 0;
driveData.JSQ_Flag = 0;
driveData.Fan_Flag = 0;
}
uint8_t current_manual_count = SetManual();
uint8_t need_refresh = 0;
if(driveData.LED_Flag != last_LED_Flag || driveData.BEEP_Flag != last_BEEP_Flag)
{
need_refresh = 1;
last_LED_Flag = driveData.LED_Flag;
last_BEEP_Flag = driveData.BEEP_Flag;
}
if(!manual_page_initialized || current_manual_count != last_manual_count || need_refresh || force_refresh)
{
OLED_manualPage1();
OLED_manualOption(current_manual_count);
ManualSettingsDisplay1();
manual_page_initialized = 1;
last_manual_count = current_manual_count;
force_refresh = 0;
OLED_Refresh();
}
if(current_key_num != 0)
{
ManualControl(current_manual_count);
OLED_manualPage1();
OLED_manualOption(current_manual_count);
ManualSettingsDisplay1();
OLED_Refresh();
KeyNum = 0;
}
OLED_manualPage1();
OLED_manualOption(current_manual_count);
ManualSettingsDisplay1();
Control_Manager();
break;
}
// 设置模式
case SETTINGS_MODE:
{
static uint8_t is_threshold_page_inited = 0;
static uint8_t current_settings_page = 1;
uint8_t curr_count_s = SetSelection();
// 按键处理
if(current_key_num != 0)
{
if(time_adjust_flag == 1)
{
// 时间设置页面:先处理KEY2光标切换
uint8_t curr_count_t = SetTimeSelection();
// 处理KEY3/KEY4修改时间值
if(KeyNum == KEY_3 || KeyNum == KEY_4)
{
Adjust_Time_Value(curr_count_t);
KeyNum = 0;
}
}
// 进入时间设置页面(光标在系统时间项,按KEY3)
else if (curr_count_s == 1 && current_key_num == KEY_3)
{
time_adjust_flag = 1;
count_t = 1; // 初始化时间页面计数
// 读取RTC到临时缓存
MyRTC_ReadTime();
for(uint8_t i=0; i<6; i++) adjust_RTC_Time[i] = MyRTC_Time[i];
// 进入时全屏刷新
OLED_ShowTimeAdjustPage(1);
KeyNum = 0;
}
// 处理阈值修改
else if(current_key_num == KEY_3 || current_key_num == KEY_4)
{
ThresholdSettings(curr_count_s);
if (curr_count_s >= 1 && curr_count_s <= 4)
{
SettingsThresholdDisplay1(); // 第一页阈值显示
}
else if (curr_count_s >=5 && curr_count_s <=7)
{
SettingsThresholdDisplay2(); // 第二页阈值显示
}
OLED_Refresh();
KeyNum = 0;
}
}
// 显示逻辑
if(time_adjust_flag == 1)
{
// 时间设置页面:保持光标显示
OLED_TimeAdjustOption(count_t);
}
else
{
if (curr_count_s >= 1 && curr_count_s <= 4)
{
if(current_settings_page != 1)
{
is_threshold_page_inited = 0;
current_settings_page = 1;
}
if (is_threshold_page_inited == 0)
{
OLED_settingsPage1();
is_threshold_page_inited = 1;
}
OLED_settingsOption(curr_count_s);
SettingsThresholdDisplay1();
}
else if(curr_count_s >=5 && curr_count_s <=7)
{
if(current_settings_page != 2)
{
is_threshold_page_inited = 0;
current_settings_page = 2;
}
if (is_threshold_page_inited == 0)
{
OLED_settingsPage2();
is_threshold_page_inited = 1;
}
OLED_settingsOption(curr_count_s);
SettingsThresholdDisplay2();
}
}
break;
}
}
// 兜底刷新(500ms一次,仅非时间修改页面)
if(current_time - last_display_time > 500)
{
if(mode != SETTINGS_MODE || (mode == SETTINGS_MODE && !time_adjust_flag))
{
OLED_Refresh();
}
last_display_time = current_time;
}
userHandle();
}
}
六、实验效果

七、包含内容
