欢迎光临
我们一直在努力

ESP32-S3健康监测手环开发实战(一)

ESP32-S3健康监测手环开发实战(一):项目架构设计与双I2C总线实现

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

1. 项目概述

ESP32-S3引脚图

1.1 功能需求分析

本项目实现一款健康监测智能手环,主要功能包括:

功能模块传感器实现能力
跌倒检测 MPU6050 三轴加速度+陀螺仪,状态机检测
心率血氧 MAX30102 PPG光电检测,实时测量
体温监测 DS18B20 单总线温度传感器
数据显示 SSD1306 128×32 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)
    未经允许不得转载:171主机测评 » ESP32-S3健康监测手环开发实战(一)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址