【ESP32-IDF】高级外设开发2:I2C
ESP32-S3 I2C驱动摘要 ESP32-S3内置两路I2C控制器,采用总线-设备架构,支持主从模式。关键特性包括: 支持标准(100kHz)和快速(400kHz)模式 7/10位地址寻址、SCL时钟拉伸 噪声滤波和中断优先级配置 架构包含: 主机模式:通过START/STOP控制总线,支持多设备通信 从机模式:可响应地址并处理数据收发 典型通信流程: 写操作:主机发送地址+写位,从机ACK后
系列文章目录
持续更新…
前言
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, ®_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 总线、配置设备、读取传感器数据并计算倾角值。示例代码清晰易懂,适合开发者快速上手。
更多推荐



所有评论(0)