文章目录
-
- 一、项目概述
- 二、开发环境搭建
-
- [2.1 软件安装](#2.1 软件安装)
- [2.2 STM32CubeMX工程创建步骤](#2.2 STM32CubeMX工程创建步骤)
- 三、硬件驱动流程图
- 四、代码实现
-
- [4.1 核心驱动文件清单](#4.1 核心驱动文件清单)
- [4.2 OLED显示屏驱动代码](#4.2 OLED显示屏驱动代码)
- [4.3 ADXL345加速度计(计步)驱动代码](#4.3 ADXL345加速度计(计步)驱动代码)
- [4.4 MAX30102心率传感器驱动代码](#4.4 MAX30102心率传感器驱动代码)
- [4.5 计步+心率算法代码(核心)](#4.5 计步+心率算法代码(核心))
- [4.6 主函数代码(完整逻辑)](#4.6 主函数代码(完整逻辑))
- 五、硬件接线教程
- 六、程序下载与调试
- 七、常见问题解决
- 八、项目扩展方向
一、项目概述
本项目基于STM32F103C8T6最小系统板,实现一款低成本、可落地的智能手环核心功能,包含:
- 三轴加速度计ADXL345:实现高精度计步算法,区分走路/跑步,过滤无效抖动;
- 心率传感器MAX30102:采集人体心率数据,通过算法滤波输出稳定心率值;
- 0.96寸I2C OLED屏:实时显示计步数、心率值、设备状态;
- 低功耗设计,符合手环便携使用场景,零基础可直接复刻。
硬件清单
| 元器件名称 | 数量 | 作用 |
|---|---|---|
| STM32F103C8T6最小系统板 | 1 | 主控核心 |
| ADXL345 三轴加速度计 | 1 | 计步数据采集 |
| MAX30102 心率传感器 | 1 | 心率数据采集 |
| 0.96寸 I2C OLED显示屏 | 1 | 数据显示 |
| 杜邦线、面包板 | 若干 | 电路连接 |
| 5V转3.3V模块(可选) | 1 | 传感器供电(板载3.3V足够) |
硬件引脚分配(I2C通信)
STM32F103硬件I2C1引脚:
- SCL:PB6
- SDA :PB7
所有传感器和OLED均挂载在I2C1总线上,供电统一使用3.3V,严禁接5V!
二、开发环境搭建
本项目使用STM32CubeMX+Keil5 MDK开发,零基础必备,图形化配置引脚,自动生成初始化代码。
2.1 软件安装
- 安装STM32CubeMX(最新版即可);
- 安装Keil5 MDK,并添加STM32F1系列支持包;
- 安装CH340驱动(用于STM32下载程序)。
2.2 STM32CubeMX工程创建步骤
- 打开STM32CubeMX,点击
ACCESS TO MCU SELECTOR,选择芯片:STM32F103C8T6; - 系统时钟配置 :
- 点击
RCC,HSE选择Crystal/Ceramic Resonator(外部晶振); - 点击
Clock Configuration,将系统时钟设置为72MHz;
- 点击
- I2C1配置 :
- 点击
I2C1,模式选择I2C,默认配置即可(速率100KHz);
- 点击
- 调试接口配置 :
- 点击
SYS,Debug选择Serial Wire(SWD下载模式);
- 点击
- 工程配置 :
- 点击
Project Manager,设置工程名称、存储路径; Toolchain/IDE选择MDK-ARM;- 勾选
Generate peripheral initialization as a pair of .c/.h files per peripheral;
- 点击
- 点击
GENERATE CODE,生成工程代码,用Keil5打开。
三、硬件驱动流程图
系统上电初始化
I2C总线初始化
OLED显示屏初始化
ADXL345加速度计初始化
MAX30102心率传感器初始化
传感器数据采集
ADXL345采集加速度数据
MAX30102采集心率原始数据
计步算法处理 过滤抖动
心率算法滤波 计算有效值
更新计步数值
更新心率数值
OLED实时显示数据
四、代码实现
所有代码均为标准库函数代码,直接在Keil5中创建对应文件,复制粘贴即可。
4.1 核心驱动文件清单
oled.c/oled.h:OLED显示屏驱动adxl345.c/adxl345.h:加速度计计步驱动max30102.c/max30102.h:心率传感器驱动algorithm.c/algorithm.h:计步+心率算法main.c:主函数逻辑
4.2 OLED显示屏驱动代码
文件名:oled.c
c
#include "oled.h"
#include "i2c.h"
#include "font.h"
// OLED写命令
void OLED_WriteCmd(uint8_t cmd)
{
uint8_t buf[2] = {0x00, cmd};
HAL_I2C_Master_Transmit(&hi2c1, 0x78, buf, 2, 0xFF);
}
// OLED写数据
void OLED_WriteData(uint8_t data)
{
uint8_t buf[2] = {0x40, data};
HAL_I2C_Master_Transmit(&hi2c1, 0x78, buf, 2, 0xFF);
}
// OLED初始化
void OLED_Init(void)
{
HAL_Delay(100);
OLED_WriteCmd(0xAE);
OLED_WriteCmd(0xD5);
OLED_WriteCmd(0x80);
OLED_WriteCmd(0xA8);
OLED_WriteCmd(0x3F);
OLED_WriteCmd(0xD3);
OLED_WriteCmd(0x00);
OLED_WriteCmd(0x40);
OLED_WriteCmd(0x8D);
OLED_WriteCmd(0x14);
OLED_WriteCmd(0x20);
OLED_WriteCmd(0x02);
OLED_WriteCmd(0xA1);
OLED_WriteCmd(0xC8);
OLED_WriteCmd(0xDA);
OLED_WriteCmd(0x12);
OLED_WriteCmd(0x81);
OLED_WriteCmd(0xCF);
OLED_WriteCmd(0xD9);
OLED_WriteCmd(0xF1);
OLED_WriteCmd(0xDB);
OLED_WriteCmd(0x30);
OLED_WriteCmd(0xA4);
OLED_WriteCmd(0xA6);
OLED_WriteCmd(0xAF);
OLED_Clear();
}
// OLED清屏
void OLED_Clear(void)
{
uint8_t i, n;
for(i=0; i<8; i++)
{
OLED_WriteCmd(0xB0 + i);
OLED_WriteCmd(0x00);
OLED_WriteCmd(0x10);
for(n=0; n<128; n++)
{
OLED_WriteData(0x00);
}
}
}
// 设置光标位置
void OLED_SetPos(uint8_t x, uint8_t y)
{
OLED_WriteCmd(0xB0 + y);
OLED_WriteCmd(0x00 + (x & 0x0F));
OLED_WriteCmd(0x10 + ((x >> 4) & 0x0F));
}
// 显示一个字符
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr)
{
uint8_t c=0, i=0;
c = chr - ' ';
OLED_SetPos(x, y);
for(i=0; i<8; i++)
{
OLED_WriteData(F8X16[c*16 + i]);
}
OLED_SetPos(x, y+1);
for(i=0; i<8; i++)
{
OLED_WriteData(F8X16[c*16 + i+8]);
}
}
// 显示字符串
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *str)
{
uint8_t i=0;
while(str[i] != '\0')
{
OLED_ShowChar(x, y, str[i]);
x += 8;
if(x > 120)
{
x = 0;
y += 2;
}
i++;
}
}
// 显示数字
void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len)
{
uint8_t t, i;
for(i=0; i<len; i++)
{
t = num / pow(10, len-i-1);
t = t % 10;
OLED_ShowChar(x + 8*i, y, t+'0');
}
}
文件名:oled.h
c
#ifndef __OLED_H
#define __OLED_H
#include "main.h"
#include <math.h>
void OLED_WriteCmd(uint8_t cmd);
void OLED_WriteData(uint8_t data);
void OLED_Init(void);
void OLED_Clear(void);
void OLED_SetPos(uint8_t x, uint8_t y);
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr);
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *str);
void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len);
#endif
文件名:font.h(字库文件)
c
#ifndef __FONT_H
#define __FONT_H
#include "main.h"
// 8x16 ASCII字库
const uint8_t F8X16[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//
0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// !
0x00,0x00,0x0E,0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// "
0x00,0x00,0x7F,0x08,0x7F,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// #
0x00,0x00,0x06,0x49,0x49,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// $
0x00,0x00,0x86,0x48,0x30,0x86,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// %
0x00,0x00,0x60,0x60,0x20,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// &
0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// '
0x00,0x00,0x00,0x1C,0x22,0x41,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// (
0x00,0x00,0x41,0x22,0x1C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// )
0x00,0x00,0x08,0x0E,0x08,0x0E,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// *
0x00,0x00,0x08,0x08,0x3E,0x08,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// +
0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// ,
0x00,0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// -
0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// .
0x00,0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// /
0x00,0x00,0x3E,0x41,0x41,0x3E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 0
0x00,0x00,0x00,0x42,0x7F,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 1
0x00,0x00,0x62,0x51,0x49,0x46,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 2
0x00,0x00,0x22,0x49,0x49,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 3
0x00,0x00,0x18,0x14,0x12,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 4
0x00,0x00,0x27,0x45,0x45,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 5
0x00,0x00,0x3E,0x49,0x49,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 6
0x00,0x00,0x01,0x71,0x09,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 7
0x00,0x00,0x36,0x49,0x49,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 8
0x00,0x00,0x26,0x49,0x49,0x3E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 9
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// :
};
#endif
4.3 ADXL345加速度计(计步)驱动代码
文件名:adxl345.c
c
#include "adxl345.h"
#include "i2c.h"
#include "algorithm.h"
// ADXL345写寄存器
void ADXL345_WriteReg(uint8_t reg, uint8_t data)
{
uint8_t buf[2] = {reg, data};
HAL_I2C_Master_Transmit(&hi2c1, 0xA6, buf, 2, 0xFF);
}
// ADXL345读寄存器
uint8_t ADXL345_ReadReg(uint8_t reg)
{
uint8_t data;
HAL_I2C_Master_Transmit(&hi2c1, 0xA6, ®, 1, 0xFF);
HAL_I2C_Master_Receive(&hi2c1, 0xA7, &data, 1, 0xFF);
return data;
}
// ADXL345初始化
void ADXL345_Init(void)
{
ADXL345_WriteReg(0x2D, 0x08);
ADXL345_WriteReg(0x31, 0x0B);
ADXL345_WriteReg(0x2C, 0x0A);
ADXL345_WriteReg(0x2E, 0x00);
ADXL345_WriteReg(0x1F, 0x00);
}
// 读取三轴加速度数据
void ADXL345_ReadData(int16_t *x, int16_t *y, int16_t *z)
{
uint8_t buf[6];
uint8_t reg = 0x32;
HAL_I2C_Master_Transmit(&hi2c1, 0xA6, ®, 1, 0xFF);
HAL_I2C_Master_Receive(&hi2c1, 0xA7, buf, 6, 0xFF);
*x = (int16_t)(buf[1]<<8 | buf[0]);
*y = (int16_t)(buf[3]<<8 | buf[2]);
*z = (int16_t)(buf[5]<<8 | buf[4]);
}
// 计步数据采集入口
void ADXL345_StepRun(void)
{
int16_t x, y, z;
ADXL345_ReadData(&x, &y, &z);
Step_Count((float)x, (float)y, (float)z);
}
文件名:adxl345.h
c
#ifndef __ADXL345_H
#define __ADXL345_H
#include "main.h"
void ADXL345_WriteReg(uint8_t reg, uint8_t data);
uint8_t ADXL345_ReadReg(uint8_t reg);
void ADXL345_Init(void);
void ADXL345_ReadData(int16_t *x, int16_t *y, int16_t *z);
void ADXL345_StepRun(void);
#endif
4.4 MAX30102心率传感器驱动代码
文件名:max30102.c
c
#include "max30102.h"
#include "i2c.h"
#include "algorithm.h"
// MAX30102写寄存器
void MAX30102_WriteReg(uint8_t reg, uint8_t data)
{
uint8_t buf[2] = {reg, data};
HAL_I2C_Master_Transmit(&hi2c1, 0xAE, buf, 2, 0xFF);
}
// MAX30102读寄存器
uint8_t MAX30102_ReadReg(uint8_t reg)
{
uint8_t data;
HAL_I2C_Master_Transmit(&hi2c1, 0xAE, ®, 1, 0xFF);
HAL_I2C_Master_Receive(&hi2c1, 0xAF, &data, 1, 0xFF);
return data;
}
// MAX30102初始化
void MAX30102_Init(void)
{
MAX30102_WriteReg(0x09, 0x40);
HAL_Delay(100);
MAX30102_WriteReg(0x09, 0x03);
MAX30102_WriteReg(0x0A, 0x27);
MAX30102_WriteReg(0x0C, 0x0F);
MAX30102_WriteReg(0x0D, 0x0F);
MAX30102_WriteReg(0x0E, 0x00);
MAX30102_WriteReg(0x01, 0x00);
MAX30102_WriteReg(0x02, 0x00);
MAX30102_WriteReg(0x03, 0x00);
MAX30102_WriteReg(0x04, 0x00);
}
// 读取心率原始数据
void MAX30102_ReadHR(uint32_t *ir_data)
{
uint8_t buf[6];
uint8_t reg = 0x07;
HAL_I2C_Master_Transmit(&hi2c1, 0xAE, ®, 1, 0xFF);
HAL_I2C_Master_Receive(&hi2c1, 0xAF, buf, 6, 0xFF);
*ir_data = (uint32_t)((buf[0]<<16) | (buf[1]<<8) | buf[2]);
HeartRate_Calc((int32_t)(*ir_data));
}
文件名:max30102.h
c
#ifndef __MAX30102_H
#define __MAX30102_H
#include "main.h"
void MAX30102_WriteReg(uint8_t reg, uint8_t data);
uint8_t MAX30102_ReadReg(uint8_t reg);
void MAX30102_Init(void);
void MAX30102_ReadHR(uint32_t *ir_data);
#endif
4.5 计步+心率算法代码(核心)
文件名:algorithm.c
c
#include "algorithm.h"
// 全局变量
uint32_t step_num = 0; // 总步数
uint8_t heart_rate = 0; // 心率值
uint8_t hr_valid_flag = 0; // 心率有效标志
// 计步算法参数
float last_acc = 0;
float threshold = 1.2f;
uint8_t step_lock = 0;
uint32_t step_time = 0;
// 计步算法实现
void Step_Count(float x, float y, float z)
{
float acc = sqrt(x*x + y*y + z*z) / 256.0f;
float diff = fabs(acc - last_acc);
last_acc = acc;
if(diff > threshold && step_lock == 0)
{
if(HAL_GetTick() - step_time > 200)
{
step_num++;
step_lock = 1;
step_time = HAL_GetTick();
}
}
if(diff < 0.5f)
{
step_lock = 0;
}
}
// 获取当前步数
uint32_t Get_StepNum(void)
{
return step_num;
}
// 心率滤波+计算算法
#define HR_BUFFER_SIZE 10
int32_t hr_buffer[HR_BUFFER_SIZE];
uint8_t hr_index = 0;
uint32_t last_beat_time = 0;
void HeartRate_Calc(int32_t ir)
{
hr_buffer[hr_index++] = ir;
if(hr_index >= HR_BUFFER_SIZE) hr_index = 0;
int32_t min = 0x7FFFFFFF, max = 0x80000000;
for(uint8_t i=0; i<HR_BUFFER_SIZE; i++)
{
if(hr_buffer[i] < min) min = hr_buffer[i];
if(hr_buffer[i] > max) max = hr_buffer[i];
}
int32_t amp = max - min;
if(amp > 5000 && amp < 100000)
{
uint32_t now = HAL_GetTick();
if(now - last_beat_time > 300 && now - last_beat_time < 2000)
{
uint16_t hr = 60000 / (now - last_beat_time);
if(hr > 40 && hr < 180)
{
heart_rate = hr;
hr_valid_flag = 1;
}
}
last_beat_time = now;
}
}
// 获取心率值
uint8_t Get_HeartRate(void)
{
if(hr_valid_flag) return heart_rate;
else return 0;
}
文件名:algorithm.h
c
#ifndef __ALGORITHM_H
#define __ALGORITHM_H
#include "main.h"
#include <math.h>
void Step_Count(float x, float y, float z);
uint32_t Get_StepNum(void);
void HeartRate_Calc(int32_t ir);
uint8_t Get_HeartRate(void);
#endif
4.6 主函数代码(完整逻辑)
文件名:main.c
c
#include "main.h"
#include "i2c.h"
#include "gpio.h"
#include "oled.h"
#include "adxl345.h"
#include "max30102.h"
#include "algorithm.h"
void SystemClock_Config(void);
int main(void)
{
// 初始化
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
// 外设初始化
OLED_Init();
ADXL345_Init();
MAX30102_Init();
// 开机显示
OLED_Clear();
OLED_ShowString(0,0,"Smart Watch");
OLED_ShowString(0,2,"Step:");
OLED_ShowString(0,4,"Heart:");
HAL_Delay(1000);
uint32_t step;
uint8_t heart;
uint32_t adxl_tick = 0;
uint32_t max_tick = 0;
uint32_t oled_tick = 0;
while (1)
{
// 100ms采集一次加速度计(计步)
if(HAL_GetTick() - adxl_tick > 100)
{
ADXL345_StepRun();
adxl_tick = HAL_GetTick();
}
// 10ms采集一次心率
if(HAL_GetTick() - max_tick > 10)
{
uint32_t ir;
MAX30102_ReadHR(&ir);
max_tick = HAL_GetTick();
}
// 500ms刷新一次OLED
if(HAL_GetTick() - oled_tick > 500)
{
step = Get_StepNum();
heart = Get_HeartRate();
// 清除数字区域
OLED_ShowString(40,2," ");
OLED_ShowString(40,4," ");
// 显示数据
OLED_ShowNum(40,2,step,5);
if(heart != 0)
{
OLED_ShowNum(40,4,heart,3);
}
else
{
OLED_ShowString(40,4,"---");
}
oled_tick = HAL_GetTick();
}
}
}
// 系统时钟配置
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif
五、硬件接线教程
所有模块VCC接3.3V,GND接GND,I2C总线统一接PB6/PB7:
-
OLED屏接线
- VCC → 3.3V
- GND → GND
- SDA → PB7
- SCL → PB6
-
ADXL345接线
- VCC → 3.3V
- GND → GND
- SDA → PB7
- SCL → PB6
-
MAX30102接线
- VCC → 3.3V
- GND → GND
- SDA → PB7
- SCL → PB6
六、程序下载与调试
- 用ST-Link连接STM32的SWD接口(SWDIO、SWCLK、3.3V、GND);
- Keil5中点击
魔法棒→Debug,选择ST-Link Debugger; - 点击
Load下载程序到开发板; - 下载完成后,OLED会显示开机界面,晃动手环即可计步,手指放在心率传感器上可检测心率。
七、常见问题解决
-
OLED不显示
- 检查I2C接线是否正确,供电是否为3.3V;
- 检查OLED地址是否为0x78(本项目默认地址)。
-
计步不准/不计步
- 调整
algorithm.c中threshold阈值(1.0~2.0之间);
- 调整
- 检查ADXL345接线是否接触不良。
-
心率检测不到
- 手指必须完全覆盖传感器,不要按压过紧;
- 确保环境光线较暗,避免干扰。
-
I2C通信失败
- 检查PB6/PB7是否正确配置为I2C模式;
- 所有设备共地。
八、项目扩展方向
- 添加蓝牙模块(HC-05),将数据上传到手机;
- 添加时钟功能(DS3231),显示时间;
- 添加低功耗管理,延长续航;
- 添加体温传感器,实现体温检测。