系列文章目录

持续更新…



前言

I2C 是最常用的板级外设总线之一:两根线(SCL/SDA)、开漏加上拉、支持多主多从,速度常见 100 kHz(Standard)与 400 kHz(Fast)。从 ESP-IDF v5 开始,I2C 驱动采用总线-设备(bus-device)模型:先创建“主机总线”,再把一个个 I2C 设备挂到这条总线,之后用统一的收发 API 完成寄存器读写、重复起始(Repeated START)等操作。


一、I2C概述

I²C(Inter-Integrated Circuit)是一种常见的两线串行总线,用于在多个设备之间进行短距离通信。在ESP32-S3中,I²C总线提供了与多个外部设备(如传感器、存储器等)进行通信的能力。该总线由两根线构成:SDA(数据线)和 SCL(时钟线)。ESP32-S3 内建两路I²C控制器,可以在多个设备之间进行灵活的多主机和多从机通信。

1.主要特性

I²C协议的特点使其非常适用于嵌入式系统中多设备通信的场景。ESP32-S3的I²C控制器支持以下主要特性:
1.支持主机模式和从机模式:I²C控制器可以配置为主机或从机模式,允许灵活地进行数据通信。
2.支持多主机与多从机通信:I²C总线允许多个主机和从机共享同一条数据总线,增强了系统的扩展性。
3.支持标准模式(100 Kbit/s)和快速模式(400 Kbit/s):I²C协议支持标准模式(100 kHz)和更高速度的快速模式(400 kHz),适应不同的应用需求。
4.支持7位和10位地址寻址:主机可以使用7位或10位地址来寻址从机设备,增加了设备的寻址范围。
5.支持SCL时钟拉伸:从机可以通过拉低SCL时钟线来暂停时钟,从而为处理数据提供足够的时间。
6.噪声滤波:I²C控制器支持对SCL和SDA信号的噪声滤除,通过配置滤波器参数来减少信号干扰,提高通信稳定性。

2.I2C架构及工作原理

ESP32-S3的I2C控制器有主机模式和从机模式两种工作模式。I2C控制器内部由多个模块构成,主要包括:
1.接收和发送存储器(TX/RX RAM):用于存储要发送和接收的数据。
2.命令控制器(CMD_Controller):控制I2C的命令执行,包括START、STOP、WRITE、READ等。
3.SCL时钟控制器(SCL_FSM):负责生成满足I2C协议时序要求的SCL时钟信号。
4.SDA数据控制器(SDA_MAIN_FSM):用于管理数据的传输和接收。
5.串并转换器(DATA_Shifter):用于将串行数据转换为并行数据,或反向转换。
6.SCL和SDA滤波器(SCL_Filter 和 SDA_Filter):用来滤除SCL和SDA线上的噪声,保证信号的质量。
I2C 主机基本架构
在这里插入图片描述
I2C 从机基本架构
在这里插入图片描述
I2C工作原理
I2C总线采用漏极开漏输出模式(open-drain),即SDA和SCL的电平由外部上拉电阻来控制。在I2C总线上,通常只有一个主机来控制数据的传输,而多个从机设备可以共享同一条总线。主机通过发送开始信号来启动通信:当SCL为高电平时,主机将SDA拉低,标志着数据传输的开始。

在数据传输过程中,主机发出9个时钟脉冲,其中前8个脉冲用于传送7位地址和一个读/写位。接着,从机通过在第9个时钟脉冲中将SDA拉低来响应主机的请求。传输的数据位通过SDA线发送,每个字节后会进行应答(ACK/NACK),由从机确认数据是否正确接收。

如果主机既进行写操作又进行读操作,它会在操作之间发送一个重新开始信号(Repeated START),以便在一次通信中切换读写方向或从机设备模式。整个通信过程结束时,主机发送一个停止信号(STOP),结束当前的I2C传输。
I2C 时序图
在这里插入图片描述

3.I2C主机写入从机

流程文字描述
1.空闲总线:SDA/SCL 都由上拉电阻拉高。
2.START:主机在 SCL 为高电平时,将 SDA 从高拉至低,告知所有从机开始传输。
3.发送地址+写标志:主机按 MSB→LSB 串行输出 7 位从机地址和一个写位(0),占用 8 个时钟周期。
4.ACK 检测:第 9 个时钟周期,从机若识别到自己的地址则拉低 SDA,主机检测到低电平即表示 ACK。
5.数据字节写入:主机按需循环输出每个数据字节(8 位),每发完一字节后都需要从机通过 ACK/NACK 响应。
6.STOP:主机在 SCL 高电平时,将 SDA 从低拉至高,结束本次传输并释放总线。

4.I2C主机读取从机

流程文字描述
1.空闲总线:SDA/SCL 同样由上拉电阻拉高。
2.START:主机在 SCL 高电平期间将 SDA 拉低,开始读操作。
3.发送地址+读标志:主机输出 7 位地址和一个读位(1),占 8 个时钟周期。
4.ACK 检测:第 9 个时钟周期,从机拉低 SDA 表示 ACK,主机检测到低电平后继续。
数据读取:
5.前 N−1 字节:主机每接收 8 位数据后拉低 SDA 发出 ACK,告知从机继续发送。
6.最后 1 字节:主机接收后保持 SDA 高电平发出 NACK,告知从机读取结束。 从机在每个 SCL 上升沿前驱动 SDA,完成“写入”操作。
7.STOP:主机在 SCL 高电平期间将 SDA 拉高,结束通信并释放总线。

二、I2C类型定义及相关API

I2C类型定义(基于新驱动)

/**
 * @brief I2C从机设备配置结构体
 * 用于初始化从机时指定硬件参数、地址、功能开关等
 */
typedef struct {
    i2c_port_num_t i2c_port;                 /*!< I2C端口号,-1表示自动选择 */
    gpio_num_t sda_io_num;                   /*!< SDA引脚编号 */
    gpio_num_t scl_io_num;                   /*!< SCL引脚编号 */
    i2c_clock_source_t clk_source;           /*!< 时钟源(如APB时钟、REF_TICK等) */
    uint32_t send_buf_depth;                 /*!< 内部发送环形缓冲区深度,值越大支持越多后台传输 */
    uint16_t slave_addr;                     /*!< 从机地址(7位或10位,取决于addr_bit_len) */
    i2c_addr_bit_len_t addr_bit_len;         /*!< 地址长度(I2C_ADDR_BIT_LEN_7或I2C_ADDR_BIT_LEN_10) */
    int intr_priority;                       /*!< 中断优先级,0表示使用默认优先级(1-3) */
    struct {
#if SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE
        uint32_t stretch_en:1;               /*!< 使能SCL时钟拉伸(从机可暂停通信) */
#endif
#if SOC_I2C_SLAVE_SUPPORT_BROADCAST
        uint32_t broadcast_en:1;             /*!< 使能广播地址响应(0x00地址) */
#endif
#if SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS
        uint32_t access_ram_en:1;            /*!< 允许直接访问I2C内部RAM(非FIFO模式) */
#endif
#if SOC_I2C_SLAVE_SUPPORT_SLAVE_UNMATCH
        uint32_t slave_unmatch_en:1;         /*!< 地址不匹配时触发中断 */
#endif
    } flags;                                 /*!< 功能开关标志(依赖硬件支持) */
} i2c_slave_config_t;

/**
 * @brief I2C从机事件回调结构体
 * 用于注册从机通信中的事件处理函数(运行在ISR上下文)
 */
typedef struct {
#if SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE
    i2c_slave_stretch_callback_t on_stretch_occur;  /*!< 时钟拉伸发生时的回调(如从机处理数据耗时) */
#endif
    i2c_slave_received_callback_t on_recv_done;   /*!< 接收主机数据完成后的回调 */
} i2c_slave_event_callbacks_t;

/**
 * @brief I2C主机总线配置结构体
 * 用于创建主机总线时指定硬件参数、引脚、队列等
 */
typedef struct {
    i2c_port_num_t i2c_port;              /*!< I2C端口号,-1表示自动选择 */
    gpio_num_t sda_io_num;                /*!< SDA引脚编号(内部上拉) */
    gpio_num_t scl_io_num;                /*!< SCL引脚编号(内部上拉) */
    i2c_clock_source_t clk_source;        /*!< 时钟源(同组通道需使用相同时钟) */
    uint8_t glitch_ignore_cnt;            /*!< 噪声滤波阈值(单位:I2C模块时钟周期),典型值7 */
    int intr_priority;                    /*!< 中断优先级,0表示使用默认优先级(1-3) */
    size_t trans_queue_depth;             /*!< 异步传输队列深度(通常为 设备数*每次传输数) */
    struct {
        uint32_t enable_internal_pullup:1;   /*!< 使能内部上拉(高速模式建议外接上拉) */
    } flags;                              /*!< 功能开关标志 */
} i2c_master_bus_config_t;

/**
 * @brief I2C从机设备配置结构体(主机视角)
 * 用于将从机设备挂载到主机总线时指定地址、速率等
 */
typedef struct {
    i2c_addr_bit_len_t dev_addr_length;         /*!< 从机地址长度(7位或10位) */
    uint16_t device_address;                    /*!< 从机地址(不含读写位) */
    uint32_t scl_speed_hz;                      /*!< SCL时钟频率(如100000、400000) */
    uint32_t scl_wait_us;                      /*!< 超时时间(单位:us),0表示使用默认值 */
    struct {
        uint32_t disable_ack_check:1;           /*!< 禁用ACK检查(默认启用,NACK时会终止传输) */
    } flags;                                    /*!< 传输控制标志 */
} i2c_device_config_t;

/**
 * @brief I2C主机事件回调结构体
 * 用于注册主机传输完成后的处理函数(运行在ISR上下文)
 */
typedef struct {
    i2c_master_callback_t on_trans_done;  /*!< 传输完成回调(同步/异步传输均会触发) */
} i2c_master_event_callbacks_t;

I2C相关API(基于新驱动)

/**
 * @brief 初始化I2C从机设备
 * @param[in]  slave_config 从机配置参数(见i2c_slave_config_t)
 * @param[out] ret_handle   输出从机设备句柄(用于后续操作)
 * @return
 *      - ESP_OK: 初始化成功
 *      - ESP_ERR_INVALID_ARG: 无效参数(如引脚非法)
 *      - ESP_ERR_NO_MEM: 内存不足
 */
esp_err_t i2c_new_slave_device(const i2c_slave_config_t *slave_config, i2c_slave_dev_handle_t *ret_handle);

/**
 * @brief 销毁I2C从机设备
 * @param[in] i2c_slave 从机设备句柄(由i2c_new_slave_device创建)
 * @return
 *      - ESP_OK: 销毁成功
 *      - ESP_ERR_INVALID_ARG: 无效句柄
 */
esp_err_t i2c_del_slave_device(i2c_slave_dev_handle_t i2c_slave);

/**
 * @brief 从机接收主机发送的数据(非阻塞)
 * @note 数据接收完成后通过on_recv_done回调通知用户
 * @param[in]  i2c_slave    从机设备句柄
 * @param[out] data         接收缓冲区(需保持有效直到回调触发)
 * @param[in]  buffer_size  缓冲区大小
 * @return
 *      - ESP_OK: 启动接收成功
 *      - ESP_ERR_INVALID_ARG: 参数无效
 *      - ESP_ERR_NOT_SUPPORTED: 非FIFO模式不支持
 */
esp_err_t i2c_slave_receive(i2c_slave_dev_handle_t i2c_slave, uint8_t *data, size_t buffer_size);

/**
 * @brief 从机向主机发送数据(填充到发送缓冲区)
 * @note 硬件FIFO空时,ISR会自动从环形缓冲区取数据发送
 * @param[in] i2c_slave      从机设备句柄
 * @param[in] data           待发送数据(函数返回后可释放)
 * @param[in] size           数据长度(字节)
 * @param[in] xfer_timeout_ms 超时时间(ms,-1表示无限等待)
 * @return
 *      - ESP_OK: 发送成功
 *      - ESP_ERR_TIMEOUT: 超时(设备忙或硬件故障)
 *      - ESP_ERR_NOT_SUPPORTED: 非FIFO模式不支持
 */
esp_err_t i2c_slave_transmit(i2c_slave_dev_handle_t i2c_slave, const uint8_t *data, int size, int xfer_timeout_ms);

/**
 * @brief 注册从机事件回调函数
 * @note 回调运行在ISR上下文,IRAM_SAFE模式下需将回调放入IRAM
 * @param[in] i2c_slave 从机设备句柄
 * @param[in] cbs       回调函数组(见i2c_slave_event_callbacks_t)
 * @param[in] user_data 传给回调的用户数据(需在SRAM中)
 * @return
 *      - ESP_OK: 注册成功
 *      - ESP_ERR_INVALID_ARG: 参数无效
 */
esp_err_t i2c_slave_register_event_callbacks(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_event_callbacks_t *cbs, void *user_data);

#if SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS
/**
 * @brief 读取从机内部RAM(需enable access_ram_en)
 * @param[in]  i2c_slave    从机设备句柄
 * @param[in]  ram_address  RAM偏移地址(不可超过硬件RAM大小)
 * @param[out] data         接收缓冲区
 * @param[in]  receive_size 读取长度
 * @return
 *      - ESP_OK: 读取成功
 *      - ESP_ERR_NOT_SUPPORTED: FIFO模式不支持
 */
esp_err_t i2c_slave_read_ram(i2c_slave_dev_handle_t i2c_slave, uint8_t ram_address, uint8_t *data, size_t receive_size);

/**
 * @brief 写入从机内部RAM(需enable access_ram_en)
 * @param[in] i2c_slave    从机设备句柄
 * @param[in] ram_address  RAM偏移地址
 * @param[in] data         待写入数据
 * @param[in] size         写入长度
 * @return
 *      - ESP_OK: 写入成功
 *      - ESP_ERR_INVALID_SIZE: 长度超过RAM容量
 */
esp_err_t i2c_slave_write_ram(i2c_slave_dev_handle_t i2c_slave, uint8_t ram_address, const uint8_t *data, size_t size);

/**
 * @brief 创建I2C主机总线
 * @param[in]  bus_config  总线配置参数(见i2c_master_bus_config_t)
 * @param[out] ret_bus_handle 输出总线句柄
 * @return
 *      - ESP_OK: 创建成功
 *      - ESP_ERR_NOT_FOUND: 无可用端口
 */
esp_err_t i2c_new_master_bus(const i2c_master_bus_config_t *bus_config, i2c_master_bus_handle_t *ret_bus_handle);

/**
 * @brief 向主机总线添加从机设备
 * @param[in]  bus_handle  主机总线句柄
 * @param[in]  dev_config  从机设备配置(地址、速率等)
 * @param[out] ret_handle  输出从机设备句柄
 * @return
 *      - ESP_OK: 添加成功
 *      - ESP_ERR_NO_MEM: 内存不足
 */
esp_err_t i2c_master_bus_add_device(i2c_master_bus_handle_t bus_handle, const i2c_device_config_t *dev_config, i2c_master_dev_handle_t *ret_handle);

/**
 * @brief 销毁主机总线(会自动移除总线上的所有设备)
 * @param[in] bus_handle 主机总线句柄
 * @return ESP_OK: 销毁成功
 */
esp_err_t i2c_del_master_bus(i2c_master_bus_handle_t bus_handle);

/**
 * @brief 从主机总线移除单个从机设备
 * @param[in] handle 从机设备句柄
 * @return ESP_OK: 移除成功
 */
esp_err_t i2c_master_bus_rm_device(i2c_master_dev_handle_t handle);

/**
 * @brief 主机向从机发送数据
 * @param[in] i2c_dev        从机设备句柄
 * @param[in] write_buffer   发送缓冲区
 * @param[in] write_size     发送长度
 * @param[in] xfer_timeout_ms 超时时间(ms,-1表示无限等待)
 * @return
 *      - ESP_OK: 发送成功
 *      - ESP_ERR_TIMEOUT: 超时(总线忙或硬件故障)
 */
esp_err_t i2c_master_transmit(i2c_master_dev_handle_t i2c_dev, const uint8_t *write_buffer, size_t write_size, int xfer_timeout_ms);

/**
 * @brief 主机向从机发送数据后立即读取数据(支持重复起始信号)
 * @param[in]  i2c_dev        从机设备句柄
 * @param[in]  write_buffer   发送缓冲区(如寄存器地址)
 * @param[in]  write_size     发送长度
 * @param[out] read_buffer    接收缓冲区
 * @param[in]  read_size      接收长度
 * @param[in]  xfer_timeout_ms 超时时间
 * @return ESP_OK: 读写成功
 */
esp_err_t i2c_master_transmit_receive(i2c_master_dev_handle_t i2c_dev, const uint8_t *write_buffer, size_t write_size, uint8_t *read_buffer, size_t read_size, int xfer_timeout_ms);

/**
 * @brief 主机从从机读取数据
 * @param[in]  i2c_dev        从机设备句柄
 * @param[out] read_buffer    接收缓冲区
 * @param[in]  read_size      接收长度
 * @param[in]  xfer_timeout_ms 超时时间
 * @return ESP_OK: 读取成功
 */
esp_err_t i2c_master_receive(i2c_master_dev_handle_t i2c_dev, uint8_t *read_buffer, size_t read_size, int xfer_timeout_ms);

/**
 * @brief 探测总线上的从机设备(发送地址并检查ACK)
 * @param[in] bus_handle     主机总线句柄
 * @param[in] address        待探测的从机地址
 * @param[in] xfer_timeout_ms 超时时间
 * @return
 *      - ESP_OK: 探测到设备(收到ACK)
 *      - ESP_ERR_NOT_FOUND: 未探测到设备(收到NACK)
 */
esp_err_t i2c_master_probe(i2c_master_bus_handle_t bus_handle, uint16_t address, int xfer_timeout_ms);

/**
 * @brief 注册主机传输回调函数
 * @param[in] i2c_dev    从机设备句柄
 * @param[in] cbs        回调函数组(如传输完成回调)
 * @param[in] user_data  用户数据
 * @return ESP_OK: 注册成功
 */
esp_err_t i2c_master_register_event_callbacks(i2c_master_dev_handle_t i2c_dev, const i2c_master_event_callbacks_t *cbs, void *user_data);

/**
 * @brief 重置主机总线(恢复初始状态)
 * @param[in] bus_handle 主机总线句柄
 * @return ESP_OK: 重置成功
 */
esp_err_t i2c_master_bus_reset(i2c_master_bus_handle_t bus_handle);

/**
 * @brief 等待总线上所有未完成的传输结束
 * @param[in] bus_handle  主机总线句柄
 * @param[in] timeout_ms  超时时间(ms,-1表示无限等待)
 * @return ESP_OK: 所有传输完成
 */
esp_err_t i2c_master_bus_wait_all_done(i2c_master_bus_handle_t bus_handle, int timeout_ms);

三、I2C示例程序

ESP32-S3 使用 I2C0 读取 QMI8658 姿态传感器

#include <stdio.h>
#include "esp_err.h"
#include "driver/i2c_master.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <math.h>


#define QMI8658_SENSOR_ADDR 0x6A // 传感器地址
#define I2C_SDA_GPIO GPIO_NUM_1  // SDA引脚
#define I2C_SCL_GPIO GPIO_NUM_2  // SCL引脚

static const char *TAG = "QMI8658";

typedef struct
{
    int16_t accel_x;
    int16_t accel_y;
    int16_t accel_z;
    int16_t gyro_x;
    int16_t gyro_y;
    int16_t gyro_z;
    float AngleX;
    float AngleY;
    float AngleZ;
} qmi8658_data_t;

enum qmi8658_reg
{
    QMI8658_WHO_AM_I,
    QMI8658_REVISION_ID,
    QMI8658_CTRL1,
    QMI8658_CTRL2,
    QMI8658_CTRL3,
    QMI8658_CTRL4,
    QMI8658_CTRL5,
    QMI8658_CTRL6,
    QMI8658_CTRL7,
    QMI8658_CTRL8,
    QMI8658_CTRL9,
    QMI8658_CATL1_L,
    QMI8658_CATL1_H,
    QMI8658_CATL2_L,
    QMI8658_CATL2_H,
    QMI8658_CATL3_L,
    QMI8658_CATL3_H,
    QMI8658_CATL4_L,
    QMI8658_CATL4_H,
    QMI8658_FIFO_WTM_TH,
    QMI8658_FIFO_CTRL,
    QMI8658_FIFO_SMPL_CNT,
    QMI8658_FIFO_STATUS,
    QMI8658_FIFO_DATA,
    QMI8658_I2CM_STATUS = 44,
    QMI8658_STATUSINT,
    QMI8658_STATUS0,
    QMI8658_STATUS1,
    QMI8658_TIMESTAMP_LOW,
    QMI8658_TIMESTAMP_MID,
    QMI8658_TIMESTAMP_HIGH,
    QMI8658_TEMP_L,
    QMI8658_TEMP_H,
    QMI8658_AX_L,
    QMI8658_AX_H,
    QMI8658_AY_L,
    QMI8658_AY_H,
    QMI8658_AZ_L,
    QMI8658_AZ_H,
    QMI8658_GX_L,
    QMI8658_GX_H,
    QMI8658_GY_L,
    QMI8658_GY_H,
    QMI8658_GZ_L,
    QMI8658_GZ_H,
    QMI8658_MX_L,
    QMI8658_MX_H,
    QMI8658_MY_L,
    QMI8658_MY_H,
    QMI8658_MZ_L,
    QMI8658_MZ_H,
    QMI8658_dQW_L = 73,
    QMI8658_dQW_H,
    QMI8658_dQX_L,
    QMI8658_dQX_H,
    QMI8658_dQY_L,
    QMI8658_dQY_H,
    QMI8658_dQZ_L,
    QMI8658_dQZ_H,
    QMI8658_dVX_L,
    QMI8658_dVX_H,
    QMI8658_dVY_L,
    QMI8658_dVY_H,
    QMI8658_dVZ_L,
    QMI8658_dVZ_H,
    QMI8658_AE_REG1,
    QMI8658_AE_REG2,
    QMI8658_RESET = 96
};

esp_err_t qmi8658_register_read(i2c_master_dev_handle_t dev, uint8_t reg_addr, uint8_t *data, size_t len)
{
    return i2c_master_transmit_receive(dev, &reg_addr, 1, data, len, 100); // 超时 100ms
}

esp_err_t qmi8658_register_write_byte(i2c_master_dev_handle_t dev, uint8_t reg_addr, uint8_t value)
{
    uint8_t buf[2] = {reg_addr, value};
    return i2c_master_transmit(dev, buf, sizeof(buf), 100);
}

// 初始化I2C总线和传感器
static esp_err_t qmi8658_init(i2c_master_bus_handle_t *bus, i2c_master_dev_handle_t *dev)
{
    // 配置I2C总线
    i2c_master_bus_config_t bus_config = {
        .i2c_port = 0,                    // 自动选择端口
        .sda_io_num = I2C_SDA_GPIO,        // SDA引脚
        .scl_io_num = I2C_SCL_GPIO,        // SCL引脚
        .clk_source = I2C_CLK_SRC_DEFAULT, // 默认时钟源
        .glitch_ignore_cnt = 7,            // 噪声滤波阈值
        .intr_priority = 0,                // 使用默认中断优先级
        .trans_queue_depth = 0,            // 传输队列深度
        .flags = {
            .enable_internal_pullup = 1 // 使能内部上拉
        }};
    // 创建I2C总线
    esp_err_t ret = i2c_new_master_bus(&bus_config, bus);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "创建I2C总线失败: 0x%x", ret);
        return ret;
    }

    // 添加传感器设备
    i2c_device_config_t dev_config = {
        .dev_addr_length = I2C_ADDR_BIT_LEN_7, // 7位地址
        .device_address = QMI8658_SENSOR_ADDR, // 传感器地址
        .scl_speed_hz = 100000,                // 设备级时钟频率(100kHz)
        .scl_wait_us = 0,                      // 使用默认超时
        .flags = {
            .disable_ack_check = 0 // 启用ACK检查
        }};
    ret = i2c_master_bus_add_device(*bus, &dev_config, dev);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "添加传感器设备失败: 0x%x", ret);
        return ret;
    }

    // 验证传感器连接(读取WHO_AM_I)
    uint8_t id = 0; // 芯片的ID号

    qmi8658_register_read(*dev, QMI8658_WHO_AM_I, &id, 1); // 读芯片的ID号
    while (id != 0x05)                               // 判断读到的ID号是否是0x05
    {
        vTaskDelay(1000 / portTICK_PERIOD_MS);           // 延时1秒
        qmi8658_register_read(*dev, QMI8658_WHO_AM_I, &id, 1); // 读取ID号
    }
    ESP_LOGI(TAG, "QMI8658 OK!"); // 打印信息

    // 配置传感器工作模式
    qmi8658_register_write_byte(*dev, QMI8658_RESET, 0x01); // 复位传感器
    vTaskDelay(10 / portTICK_PERIOD_MS);                    // 延时10ms
    qmi8658_register_write_byte(*dev, QMI8658_CTRL1, 0x40); // CTRL1 设置地址自动增加
    qmi8658_register_write_byte(*dev, QMI8658_CTRL7, 0x03); // CTRL7 允许加速度和陀螺仪
    qmi8658_register_write_byte(*dev, QMI8658_CTRL2, 0x95); // CTRL2 设置ACC 4g 250Hz
    qmi8658_register_write_byte(*dev, QMI8658_CTRL3, 0xd5); // CTRL3 设置GRY 512dps 250Hz

    return ESP_OK; // 添加这行!
}

// 读取传感器数据
void qmi8658_read_data(i2c_master_dev_handle_t dev, qmi8658_data_t *data)
{
    uint8_t status, data_ready = 0;
    int16_t buf[6];

    qmi8658_register_read(dev, QMI8658_STATUS0, &status, 1); // 读状态寄存器
    if (status & 0x03)                                  // 判断加速度和陀螺仪数据是否可读
        data_ready = 1;
    if (data_ready == 1)
    { // 如果数据可读
        data_ready = 0;
        qmi8658_register_read(dev, QMI8658_AX_L, (uint8_t *)buf, 12); // 读加速度和陀螺仪值
        data->accel_x = buf[0];
        data->accel_y = buf[1];
        data->accel_z = buf[2];
        data->gyro_x = buf[3];
        data->gyro_y = buf[4];
        data->gyro_z = buf[5];
    }
}

// 获取XYZ轴的倾角值
void qmi8658_fetch_angleFromAcc(i2c_master_dev_handle_t dev, qmi8658_data_t *p)
{
    float temp;

    qmi8658_read_data(dev, p); // 读取加速度和陀螺仪的寄存器值
    // 根据寄存器值 计算倾角值 并把弧度转换成角度
    temp = (float)p->accel_x / sqrt(((float)p->accel_y * (float)p->accel_y + (float)p->accel_z * (float)p->accel_z));
    p->AngleX = atan(temp) * 57.29578f; // 180/π=57.29578
    temp = (float)p->accel_y / sqrt(((float)p->accel_x * (float)p->accel_x + (float)p->accel_z * (float)p->accel_z));
    p->AngleY = atan(temp) * 57.29578f; // 180/π=57.29578
    temp = sqrt(((float)p->accel_x * (float)p->accel_x + (float)p->accel_y * (float)p->accel_y)) / (float)p->accel_z;
    p->AngleZ = atan(temp) * 57.29578f; // 180/π=57.29578
}

void app_main(void)
{
    i2c_master_bus_handle_t bus = NULL; // I2C总线句柄
    i2c_master_dev_handle_t dev = NULL; // I2C设备句柄
    qmi8658_data_t data; 

    if (qmi8658_init(&bus, &dev) != ESP_OK)
    {
        ESP_LOGE(TAG, "初始化失败,程序退出");
        return;
    }

    while (1)
    {
        qmi8658_fetch_angleFromAcc(dev, &data);
        ESP_LOGI(TAG, "angle_x = %.1f  angle_y = %.1f angle_z = %.1f", data.AngleX, data.AngleY, data.AngleZ);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

总结

本文全面阐述了 ESP32-S3 上 I2C 总线的工作原理与硬件架构,详细介绍了 ESP-IDF v5 引入的总线-设备模型,包括主机总线和从机设备的创建与管理。通过以 QMI8658 姿态传感器为例,展示了如何在 ESP32-S3 上初始化 I2C 总线、配置设备、读取传感器数据并计算倾角值。示例代码清晰易懂,适合开发者快速上手。

Logo

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

更多推荐