ESP32-S3健康监测手环开发实战(一)
·
ESP32-S3健康监测手环开发实战(一):项目架构设计与双I2C总线实现
本系列文章将带你从零开始,基于ESP-IDF 5.3.2框架开发一款健康监测智能手环。本篇作为入门篇,重点讲解项目的整体架构设计和ESP32-S3双I2C总线的配置方法。
1. 项目概述

1.1 功能需求分析
本项目实现一款健康监测智能手环,主要功能包括:
| 功能模块 | 传感器 | 实现能力 |
|---|---|---|
| 跌倒检测 | MPU6050 | 三轴加速度+陀螺仪,状态机检测 |
| 心率血氧 | MAX30102 | PPG光电检测,实时测量 |
| 体温监测 | DS18B20 | 单总线温度传感器 |
| 数据显示 | SSD1306 | 128x32 OLED屏幕 |
1.2 硬件选型与引脚分配
ESP32-S3拥有丰富的外设资源,本项目的引脚分配如下:
┌─────────────────────────────────────────────────────────────┐
│ ESP32-S3 引脚分配 │
├─────────────────────────────────────────────────────────────┤
│ I2C_NUM_0 (400kHz) - MPU6050 加速度计/陀螺仪 │
│ ├─ GPIO8 - SDA │
│ └─ GPIO9 - SCL │
├─────────────────────────────────────────────────────────────┤
│ I2C_NUM_1 (100kHz) - MAX30102 心率血氧传感器 │
│ ├─ GPIO5 - SDA │
│ ├─ GPIO15 - SCL │
│ └─ GPIO3 - INT (中断引脚) │
├─────────────────────────────────────────────────────────────┤
│ 软件I2C (100kHz) - SSD1306 OLED显示屏 │
│ ├─ GPIO10 - SDA │
│ └─ GPIO11 - SCL │
├─────────────────────────────────────────────────────────────┤
│ OneWire - DS18B20 温度传感器 │
│ └─ GPIO18 - DATA │
└─────────────────────────────────────────────────────────────┘
1.3 为什么需要双I2C总线?
ESP32-S3内置两路独立的硬件I2C控制器(I2C_NUM_0和I2C_NUM_1),本项目采用双总线设计,主要基于以下考虑:
1. 设备频率兼容性
- MPU6050支持最高400kHz的快速模式I2C
- MAX30102建议使用100kHz标准模式以保证数据稳定性
2. 总线隔离避免干扰
- MPU6050需要高频率采样(50Hz)进行跌倒检测
- MAX30102使用中断驱动方式采集PPG数据
- 隔离两个设备可以避免互相影响
3. 简化软件设计
- 无需考虑I2C总线仲裁和地址冲突
- 每个传感器独立管理,代码更清晰
2. ESP-IDF项目结构设计
2.1 目录结构
本项目采用ESP-IDF标准的组件化架构:
Smart_Watch/
├── CMakeLists.txt # 项目根CMake配置
├── sdkconfig # ESP-IDF配置文件
├── main/ # 主程序目录
│ ├── CMakeLists.txt # main组件配置
│ ├── main.c # 程序入口
│ └── app/ # 应用层代码
│ ├── app_mpu6050.c/h # 跌倒检测应用
│ ├── app_max30102.c/h # 心率血氧应用
│ ├── app_ds18b20.c/h # 温度测量应用
│ └── app_ssd1306_oled.c/h# OLED显示应用
├── components/ # 自定义组件(驱动层)
│ ├── mpu6050/ # MPU6050 I2C驱动
│ ├── max30102/ # MAX30102 I2C驱动
│ └── ssd1306_oled/ # OLED驱动(含软件I2C)
└── managed_components/ # ESP-IDF官方组件
├── espressif__ds18b20/ # DS18B20驱动
└── espressif__onewire_bus/ # OneWire总线驱动
2.2 分层架构设计
项目采用三层架构,职责清晰:
┌─────────────────────────────────────────────────────────┐
│ 应用层 (main/app/) │
│ app_mpu6050.c app_max30102.c app_ds18b20.c app_oled.c │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 业务逻辑:跌倒检测算法、心率计算、界面更新 │ │
│ └─────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ 驱动层 (components/) │
│ mpu6050/ max30102/ ssd1306_oled/ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 硬件抽象:寄存器操作、I2C通信、数据格式转换 │ │
│ └─────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ ESP-IDF HAL层 │
│ i2c_driver gpio_driver onewire_bus │
└─────────────────────────────────────────────────────────┘
2.3 CMakeLists.txt配置
项目根目录 CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(main)
main组件 CMakeLists.txt:
idf_component_register(
SRCS "main.c"
"app/app_mpu6050.c"
"app/app_ds18b20.c"
"app/app_max30102.c"
"app/app_ssd1306_oled.c"
INCLUDE_DIRS "."
"app"
REQUIRES mpu6050 espressif__ds18b20 espressif__onewire_bus max30102 ssd1306_oled
)
关键配置说明:
SRCS:列出所有源文件INCLUDE_DIRS:头文件搜索路径REQUIRES:声明依赖的组件
3. 硬件I2C驱动配置
3.1 I2C_NUM_0 配置(MPU6050)
MPU6050使用I2C_NUM_0端口,配置400kHz快速模式:
// app_mpu6050.h 中的硬件配置定义
#define APP_I2C_PORT I2C_NUM_0
#define APP_I2C_SDA_PIN 8 // GPIO8
#define APP_I2C_SCL_PIN 9 // GPIO9
#define APP_I2C_FREQ_HZ 400000 // 400kHz
I2C总线初始化代码(app_mpu6050.c):
static esp_err_t i2c_bus_init(void)
{
i2c_config_t conf = {
.mode = I2C_MODE_MASTER, // 主机模式
.sda_io_num = APP_I2C_SDA_PIN, // SDA引脚
.scl_io_num = APP_I2C_SCL_PIN, // SCL引脚
.sda_pullup_en = GPIO_PULLUP_ENABLE, // 启用内部上拉
.scl_pullup_en = GPIO_PULLUP_ENABLE, // 启用内部上拉
.master.clk_speed = APP_I2C_FREQ_HZ, // 时钟频率
};
// 配置I2C参数
esp_err_t ret = i2c_param_config(APP_I2C_PORT, &conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "I2C 参数配置失败: %s", esp_err_to_name(ret));
return ret;
}
// 安装I2C驱动
ret = i2c_driver_install(APP_I2C_PORT, conf.mode, 0, 0, 0);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "I2C 驱动安装失败: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "I2C 总线初始化成功 (SDA=%d, SCL=%d, %dHz)",
APP_I2C_SDA_PIN, APP_I2C_SCL_PIN, APP_I2C_FREQ_HZ);
return ESP_OK;
}
3.2 I2C_NUM_1 配置(MAX30102)
MAX30102使用独立的I2C_NUM_1端口,配置100kHz标准模式:
// app_max30102.h 中的硬件配置定义
#define APP_MAX30102_I2C_PORT I2C_NUM_1 // 独立I2C端口
#define APP_MAX30102_I2C_SDA_PIN GPIO_NUM_5 // SDA引脚
#define APP_MAX30102_I2C_SCL_PIN GPIO_NUM_15 // SCL引脚
#define APP_MAX30102_I2C_FREQ_HZ 100000 // 100kHz
#define APP_MAX30102_INT_PIN GPIO_NUM_3 // 中断引脚
I2C配置代码与I2C_NUM_0类似,但使用不同的端口和引脚。
3.3 配置参数详解
| 参数 | 说明 | 推荐值 |
|---|---|---|
mode |
I2C工作模式 | I2C_MODE_MASTER(主机) |
sda_io_num |
SDA数据线GPIO | 根据硬件设计选择 |
scl_io_num |
SCL时钟线GPIO | 根据硬件设计选择 |
sda_pullup_en |
SDA内部上拉 | 一般启用,外部有上拉可禁用 |
scl_pullup_en |
SCL内部上拉 | 一般启用,外部有上拉可禁用 |
master.clk_speed |
时钟频率(Hz) | 100000~400000 |
注意事项:
- ESP32-S3的GPIO均可用作I2C引脚
- 建议使用外部4.7kΩ上拉电阻,内部上拉作为备用
- 400kHz快速模式要求更严格的信号质量
4. 主程序框架设计
4.1 初始化顺序
传感器初始化需要按照依赖关系有序进行:
void app_main(void)
{
ESP_LOGI(TAG, "Smart Watch 启动");
// 1. 初始化OLED(优先初始化,用于显示启动状态)
esp_err_t ret = app_oled_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "OLED 初始化失败");
// 不退出,继续运行其他功能
}
// 2. 初始化MPU6050(核心传感器,失败则退出)
ret = app_mpu6050_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "MPU6050 初始化失败,程序退出");
return;
}
// 3. 初始化DS18B20
ret = app_ds18b20_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "DS18B20 初始化失败,程序退出");
return;
}
// 4. 初始化MAX30102(独立I2C_NUM_1端口)
ret = app_max30102_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "MAX30102 初始化失败");
// 不退出,继续运行其他功能
}
ESP_LOGI(TAG, "所有传感器初始化完成,开始监测...");
// ... 进入主循环
}
初始化顺序设计原则:
- 显示设备优先:可以显示启动进度和错误信息
- 核心设备其次:跌倒检测是核心功能,必须成功
- 辅助设备最后:心率血氧等功能失败不影响主功能
4.2 主循环与采样策略
主循环采用分层采样策略,平衡实时性和资源消耗:
#define SAMPLE_PERIOD_MS 20 // 主循环周期 20ms (50Hz)
while (1) {
// [每20ms] 读取MPU6050进行跌倒检测
ret = app_mpu6050_read(&sensor_data);
if (ret == ESP_OK) {
if (app_mpu6050_detect_fall(&sensor_data)) {
// 检测到跌倒,显示警报
app_oled_show_fall_alert();
vTaskDelay(pdMS_TO_TICKS(5000)); // 警报显示5秒
app_mpu6050_reset_fall_state();
app_oled_clear_fall_alert();
}
}
// [每1秒] 读取温度和心率(50次循环)
if (++temp_read_counter >= 50) {
temp_read_counter = 0;
app_ds18b20_read_temperature(&temperature);
app_max30102_read(&hr_data);
}
// [每500ms] 更新显示(25次循环)
if (++display_update_counter >= 25) {
display_update_counter = 0;
app_oled_update(current_hr, current_spo2, temperature, false);
}
vTaskDelay(pdMS_TO_TICKS(SAMPLE_PERIOD_MS));
}
采样策略总结:
| 任务 | 采样周期 | 频率 | 说明 |
|---|---|---|---|
| MPU6050读取 | 20ms | 50Hz | 跌倒检测需要高频采样 |
| 温度/心率读取 | 1000ms | 1Hz | 生理数据变化慢 |
| OLED显示更新 | 500ms | 2Hz | 平衡刷新率和功耗 |
5. 错误处理策略
5.1 初始化错误处理
// 核心模块失败:退出程序
if (app_mpu6050_init() != ESP_OK) {
ESP_LOGE(TAG, "MPU6050 初始化失败,程序退出");
return;
}
// 辅助模块失败:记录日志,继续运行
if (app_max30102_init() != ESP_OK) {
ESP_LOGE(TAG, "MAX30102 初始化失败");
// 继续运行,心率功能不可用
}
5.2 运行时错误处理
ret = app_mpu6050_read(&sensor_data);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "传感器读取失败: %s", esp_err_to_name(ret));
// 跳过本次处理,等待下次采样
continue;
}
6. 总结
本篇介绍了ESP32-S3智能手表项目的整体架构:
- 双I2C总线设计:I2C_NUM_0(400kHz)用于MPU6050,I2C_NUM_1(100kHz)用于MAX30102
- 组件化架构:驱动层和应用层分离,便于维护和复用
- 分层采样策略:跌倒检测50Hz,生理数据1Hz,显示2Hz
- 错误处理:核心模块失败退出,辅助模块失败降级运行
下篇预告
在下一篇文章中,我们将深入讲解MPU6050跌倒检测算法的实现,包括:
- 互补滤波姿态解算原理
- 三阶段跌倒检测状态机设计
- 阈值参数的选取与调优
系列文章导航:
- 本篇:项目架构设计与双I2C总线实现
- 第二篇:MPU6050跌倒检测算法与状态机设计
- 第三篇:MAX30102心率血氧算法与PPG信号处理
- 第四篇:软件I2C实现与OLED显示优化
开发环境:ESP-IDF 5.3.2 + VS Code
硬件平台:ESP32-S3
更多推荐
所有评论(0)