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

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

1. 项目概述

ESP32-S3引脚图

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, "所有传感器初始化完成,开始监测...");
    // ... 进入主循环
}

初始化顺序设计原则:

  1. 显示设备优先:可以显示启动进度和错误信息
  2. 核心设备其次:跌倒检测是核心功能,必须成功
  3. 辅助设备最后:心率血氧等功能失败不影响主功能

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智能手表项目的整体架构:

  1. 双I2C总线设计:I2C_NUM_0(400kHz)用于MPU6050,I2C_NUM_1(100kHz)用于MAX30102
  2. 组件化架构:驱动层和应用层分离,便于维护和复用
  3. 分层采样策略:跌倒检测50Hz,生理数据1Hz,显示2Hz
  4. 错误处理:核心模块失败退出,辅助模块失败降级运行

下篇预告

在下一篇文章中,我们将深入讲解MPU6050跌倒检测算法的实现,包括:

  • 互补滤波姿态解算原理
  • 三阶段跌倒检测状态机设计
  • 阈值参数的选取与调优

系列文章导航:

  • 本篇:项目架构设计与双I2C总线实现
  • 第二篇:MPU6050跌倒检测算法与状态机设计
  • 第三篇:MAX30102心率血氧算法与PPG信号处理
  • 第四篇:软件I2C实现与OLED显示优化

开发环境:ESP-IDF 5.3.2 + VS Code
硬件平台:ESP32-S3

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐