ESP32学习笔记_Peripherals(2)——i2C

I2C

简介(from AI):
这篇博客以ESP32的I2C通信为核心,深入讲解了I2C协议基础知识、ESP32硬件支持及其API使用方法,适合嵌入式开发者快速上手I2C编程。文章逻辑清晰,从理论到实践层层展开,涵盖了I2C通信原理、总线特性、上拉电阻的选择等基础内容,并结合ESP-IDF的API对I2C主从模式的安装、卸载、数据读写进行了详尽说明。通过表格和代码示例,详细展示了API参数和返回值的用法,代码部分以ADXL345加速度传感器为案例,提供了完整的初始化和数据读取流程,注释详尽,便于实操应用。博文不仅技术内容全面,还注重实际工程需求,如线程安全、功耗优化等问题的讨论,贴合嵌入式开发场景。

前言:本文档是本人在依照乐鑫科技编写的ESP32 API文档进行学习时所做的学习笔记,可能存在疏漏和错误,如有发现,敬请指正。

参考资料:
I2C 接口 - ESP32 - — ESP-IDF 编程指南 latest 文档


I2C(Inter-Integrated Circuit)是一种串行同步半双工通信协议,I2C总线上可以同时挂载多个主机和从机。I2C 使用两条双向开漏线:串行数据线 (SDA) 和串行时钟线 (SCL),通过电阻上拉。通常,I2C 从机设备具有 7 位地址或 10 位地址

总线是连接计算机或嵌入式系统中各个组件之间的通信通道,用于在设备之间传输数据、地址和控制信号。它是一种共享资源,可以同时连接多个设备,按一定的协议进行数据交换。根据功能和用途,总线可以分为 数据总线(传输数据)、地址总线(指定存储位置)和 控制总线(传递控制信号)。常见的总线类型包括并行总线(如 PCI)和串行总线(如 I²C、SPI、UART),它们广泛用于芯片间通信、外设控制和数据传输

SCL(串行时钟线) 由主设备生成时钟信号
SDA(串行数据线) 用于传输数据,支持双向通信

通信角色

  • 支持一个主设备(Master)控制多个从设备(Slave)
  • 也支持多个主设备,但需要处理总线仲裁

总线仲裁是指在多主设备的通信系统中,当多个主设备同时尝试控制总线时,通过某种机制决定哪一个设备可以优先使用总线的过程

特性主设备(Master)从设备(Slave)
角色发起通信并控制总线响应主设备并传输数据
时钟生成生成 SCL 信号使用主设备的时钟
通信发起发送起始条件,选择从设备被动等待主设备选择
地址管理指定从设备的地址每个从设备有唯一地址
多设备支持支持多个主设备(多主模式)支持多个从设备

通信过程

  • 主设备启动通信: 主设备拉低 SDA,并开始时钟信号(SCL)
  • 发送从设备地址: 主设备发送 7 位或 10 位地址,并附加读/写位
  • 从设备应答: 目标从设备检测到地址匹配后,发送 ACK(低电平)
  • 数据传输
    • 主设备发送或接收数据字节
    • 主/从设备在每个字节后发送 ACK
  • 停止通信: 主设备释放 SDA 和 SCL,总线恢复空闲状态

在 I2C协议中,地址是用来标识从机设备的唯一标识符。当主机(Master)与从机(Slave)通信时,需要通过这个地址选择要与之通信的从机设备

ESP32 有 2 个 I2C 控制器(也被称为端口),负责处理 I2C 总线上的通信。单个 I2C 控制器既可以是主机也可以是从机
ESP32 支持 I2C 标准模式 (Sm) 和快速模式 (Fm),可分别达到 100 kHz 和 400 kHz(主机模式的 SCL 时钟频率不应大于 400 kHz)

SCL 的频率受上拉电阻及导线电容的影响。因此,强烈建议选择适当的上拉电阻,确保频率准确。推荐的上拉电阻值通常在 1 kΩ 到 10 kΩ 之间
SCL 的频率越高,上拉电阻应该越小(但不能小于 1 kΩ)。较大的电阻会降低电流,增加时钟切换时间并降低频率。通常推荐 2 kΩ 到 5 kΩ 左右的电阻,也可根据电流需求进行一定调整

上拉电阻是一种连接在信号线和电源正极之间的电阻,用于将信号线默认保持在逻辑高电平状态,防止信号悬空导致不稳定。它广泛应用于数字电路和通信总线(如 I²C),起到稳定信号、电平匹配和设定默认逻辑状态的作用。阻值通常在 1 kΩ 至 10 kΩ 之间,选择时需权衡功耗和信号响应速度
当有低电平输入时,信号线会从高电平被拉低到低电平,直到低电平输入解除,信号线才会恢复到高电平。上拉电阻在这个过程中不会干扰低电平输入的有效性,只在信号线空闲时提供默认高电平状态

I2C 驱动程序提供以下服务:

  • 资源分配 包括如何使用正确的配置来分配 I2C 总线,以及如何在完成工作后回收资源
  • I2C 主机控制器 包括 I2C 主机控制器的行为,介绍了数据发送、数据接收和数据的双向传输
  • I2C 从机控制器包括 I2C 从机控制器的行为,涉及数据发送和数据接收
  • 电源管理 描述了不同时钟源对功耗的影响
  • IRAM 安全 描述了如何在 cache 被禁用时正常运行 I2C 中断
  • 线程安全 列出了驱动程序中线程安全的 API
  • Kconfig 选项 列出了支持的 Kconfig 选项,这些选项可以对驱动程序产生不同影响

在嵌入式开发中,IRAM 安全(IRAM-Safe) 通常是指某些函数或代码段被设计为在 IRAM(Internal RAM, 内部 RAM) 中运行,以确保在系统关键时刻(如中断或低功耗模式)仍然可用
线程安全(Thread-Safe) 是指某些代码或函数在多线程环境下能够正确运行,不会因并发访问共享资源而导致数据错误或异常行为。在 I²C 通信中,线程安全是非常重要的,尤其是在多任务操作系统(如 FreeRTOS)中,多个线程可能同时尝试访问 I²C 总线
Kconfig选项 是一种用于配置嵌入式系统(例如 Linux 内核、Zephyr、ESP-IDF 等)的配置机制。它允许开发者通过层级化的菜单选项为系统功能和模块进行配置。Kconfig 文件定义了这些配置选项的规则、依赖关系和默认值

Headers that need to be included in the I2C application

  • i2c_master.h 用于使用主机模式的新驱动程序的应用
  • i2c_slave.h 用于使从机模式的新驱动程序的应用

上述头文件中包含的公共头文件

  • i2c_types_legacy.h仅在旧驱动程序中使用的旧公共类型
  • i2c_types.h提供公共类型的头文件

Install/Uninstall master bus and device

Install I2C master bus and device

I2C 主机总线是基于总线-设备模型设计的,因此需要分别使用 i2c_master_bus_config_ti2c_device_config_t 来分配 I2C 主机总线实例和 I2C 设备实例

i2c_new_master_bus()

分配和初始化 I2C 总线

esp_err_t i2c_new_master_bus(const i2c_master_bus_config_t *bus_config,
       i2c_master_bus_handle_t *ret_bus_handle)

参数
bus_config I2C 主机总线配置
ret_bus_handle I2C 总线句柄(传入地址,由函数写入)
bus_config 定义

// 示例
i2c_master_bus_config_t i2c_mst_config = {
    .clk_source = I2C_CLK_SRC_DEFAULT,
    .i2c_port = TEST_I2C_PORT,
    .scl_io_num = I2C_MASTER_SCL_IO,
    .sda_io_num = I2C_MASTER_SDA_IO,
    .glitch_ignore_cnt = 7,
    .flags.enable_internal_pullup = true,
};

clk_source I2C主总线的时钟源
i2c_port I2C端口号,-1 表示自动选择(不包括低功耗I2C实例)
scl_io_num I2C SCL信号的GPIO编号,内部支持上拉
sda_io_num I2C SDA信号的GPIO编号,内部支持上拉
glitch_ignore_cnt 设定线上的干扰周期过滤值,小于该值的干扰将被忽略,典型值为7(单位:I2C模块时钟周期)
flags.enable_internal_pullup 启用内部上拉(内部上拉强度不足以支持高频传输,推荐使用外部合适的上拉电阻)
intr_priority I2C中断优先级,若设置为0,则驱动程序使用默认优先级(通常为1、2或3)
trans_queue_depth 内部传输队列深度。值越大,支持的后台挂起传输越多,仅适用于异步传输,建议设置为 max_device_num per_transaction
allow_pd 配置驱动程序是否允许系统在睡眠模式下关闭外设电源。在进入睡眠之前,系统将备份 I2C 寄存器上下文,当系统退出睡眠模式时,这些上下文将被恢复。关闭外设可以节省更多功耗,但代价是消耗更多内存来保存寄存器上下文。你需要在功耗和内存消耗之间做权衡。此配置选项依赖于特定的硬件功能,如果在不支持的芯片上启用它,你将看到类似 not able to power down in light sleep 的错误消息

端口宏定义

#define I2C_NUM_0  (0)  // I2C 端口 0
#define I2C_NUM_1  (1)  // I2C 端口 1

时钟配置

i2c_clock_source_t::I2C_CLK_SRC_DEFAULT 默认的 I2C 时钟源
i2c_clock_source_t::I2C_CLK_SRC_APB 以 APB 时钟作为 I2C 时钟源

I2C_CLK_SRC_DEFAULT 是一种由硬件或驱动自动选择的默认时钟源模式,其具体时钟来源因平台而异,通常为内部模块的时钟。该模式适合对时钟精度和性能要求不高的应用场景,开发者无需关注具体的时钟配置,适用于低速或普通速率的通信(如 100kHz 或 400kHz)。这种模式具有灵活性,并能在大多数情况下提供足够的性能,同时优化功耗
I2C_CLK_SRC_APB 模式明确使用 APB(高级外设总线)时钟作为 I2C 的时钟源,提供高频率和稳定的时钟信号,通常与 CPU 的主频相关。这种模式适合对通信速率和时钟精度有较高要求的应用,例如高速 I2C(1MHz 或更高)或需要稳定传输的场景。尽管功耗相对较高,但它在高性能和时间敏感的任务中表现优异

返回值
ESP_OK I2C 主机总线初始化成功
ESP_ERR_INVALID_ARG I2C 总线初始化失败,因为参数无效
ESP_ERR_NO_MEM 由于内存不足,创建 I2C 总线失败
ESP_ERR_NOT_FOUND 没有可用的 I2C 端口

i2c_master_bus_add_device()

分配 I2C 设备实例,并将设备挂载到主机总线上

分成bus和device能够更好的管理总线。比如一个总线上挂在的设备有不同的地址和传输速率,在这种框架下只要对device操作就可以了。如果没有device的话,要频繁去改变bus的参数,很麻烦

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)

参数
bus_handle I2C 总线句柄
dev_config 设备配置
ret_handle 设备句柄(传入地址,由函数写入)
dev_config 定义

// 示例
i2c_device_config_t dev_cfg = {
    .dev_addr_length = I2C_ADDR_BIT_LEN_7,
    .device_address = 0x58,
    .scl_speed_hz = 100000,
};

dev_addr_length 从设备的地址长度
device_address I2C 设备原始地址
scl_speed_hz SCL 线路频率
scl_wait_us 超时时间(单位:微秒),该值不能过小,否则可能无法正确处理时钟拉伸/干扰,设为0表示使用默认值(默认值为 0,可不自己设置)
disable_ack_check 禁用ACK检查。如果设置为 false,表示启用ACK检查,当检测到NACK时,传输将停止并返回错误(默认值为 false
返回值
ESP_OK 成功创建 I2C 主设备
ESP_ERR_INVALID_ARG I2C 总线初始化失败,因为参数无效
ESP_ERR_NO_MEM 由于内存不足,创建 I2C 总线失败

i2c_master_get_bus_handle()

该函数用于获取指定 I2C 端口号对应的主机总线句柄。前提是该端口的句柄已经初始化,函数仅返回已存在的句柄,且该句柄不能并发使用(适用于另一个模块中不方便获取该句柄时)

esp_err_t i2c_master_get_bus_handle(i2c_port_num_t port_num,
         i2c_master_bus_handle_t *ret_handle)

参数
port_num I2C端口号,用于指定需要获取句柄的I2C端口
ret_handle 指向一个变量的指针,用于存储检索到的句柄(传入地址,由函数写入)
返回值
ESP_OK 成功,句柄已成功检索
ESP_ERR_INVALID_ARG 参数无效,例如提供了非法的端口号
ESP_ERR_INVALID_STATE 状态无效,例如I2C端口尚未初始化

Uninstall I2C master bus and device

如果不再需要之前安装的 I2C 总线或设备,调用 i2c_master_bus_rm_device()i2c_del_master_bus() 来回收资源,以释放底层硬件

i2c_master_bus_rm_device()

从 I2C 主总线上移除指定的设备

esp_err_t i2c_master_bus_rm_device(i2c_master_dev_handle_t handle)

参数
handle I2C设备句柄,需要删除的目标设备句柄
返回值
ESP_OK 设备成功被删除

i2c_del_master_bus()

反初始化 I2C 主总线,并删除对应的总线句柄

esp_err_t i2c_del_master_bus(i2c_master_bus_handle_t bus_handle)

参数
bus_handle I2C 总线句柄,需要删除的目标总线句柄
返回值
ESP_OK 总线成功被删除
其它 表示某些模块删除失败

Install I2C slave device

i2c_new_slave_device()

初始化一个 I2C 从设备,并返回对应的设备句柄

esp_err_t i2c_new_slave_device(const i2c_slave_config_t *slave_config,
          i2c_slave_dev_handle_t *ret_handle)

参数
slave_config 设备配置
ret_handle 设备句柄(传入地址,由函数写入)
slave_config 定义

// 示例
i2c_slave_config_t i2c_slv_config = {
    .i2c_port = I2C_SLAVE_NUM,
    .clk_source = I2C_CLK_SRC_DEFAULT,
    .scl_io_num = I2C_SLAVE_SCL_IO,
    .sda_io_num = I2C_SLAVE_SDA_IO,
    .slave_addr = ESP_SLAVE_ADDR,
    .send_buf_depth = 100,
    .receive_buf_depth = 100,
};

i2c_port 端口号,-1 表示自动选择端口
clk_source 总线的时钟源
slc_io_num 总线 SCL 信号的 GPIO 引脚编号
sda_io_num 总线 SDA 信号的 GPIO 引脚编号
slave_addr 从设备地址
send_buf_depth 从设备地址的位长度(通常为7位或10位)
receive_buf_depth 内部接收环形缓冲区的深度
这个参数有点奇怪,示例里面给了,但是文档中 i2c_slave_config_t 的解析里面和 “i2c_slave.h” 的定义又没有,从https://github.com/espressif/esp-idf/blob/master/components/esp_driver_i2c/include/driver/i2c_slave.h#L146 可以看出是文档疏漏
addr_bit_len 从设备地址的位长度(通常为7位或10位)
intr_priority 内部传输环形缓冲区的深度,值越大可支持更多后台挂起的传输
allow_pd 如果设置为 1,在进入/退出睡眠模式前后,驱动会备份/恢复I2C寄存器,从而允许系统关闭I2C的电源域以节省功耗,但会增加RAM消耗
返回值
ESP_OK 成功创建 I2C 从设备
ESP_ERR_INVALID_ARG 参数无效
ESP_ERR_NO_MEM 由于内存不足,创建从设备失败

Uninstall I2C slave device

如果不再需要之前安装的 I2C 总线,建议调用 i2c_del_slave_device() 来回收资源,以释放底层硬件

i2c_del_slave_device()

反初始化 I2C 从设备并释放对应的资源

esp_err_t i2c_del_slave_device(i2c_slave_dev_handle_t i2c_slave)

参数
i2c_slave I2C 设备句柄,需要删除的目标设备句柄
返回值
ESP_OK 设备成功被删除
ESP_ERR_INVALID_ARG 参数无效,例如传入的设备句柄为空或无效

I2C Master Controller

通过调用 i2c_new_master_bus() 安装好 I2C 主机控制器驱动程序后,ESP32 就可以与其他 I2C 设备进行通信了

I2C Master Write

在成功安装 I2C 主机总线之后,可以通过调用 i2c_master_transmit() 来向从机设备写入数据,通信过程如下

Master : START | Device address W     Data     STOP
Slave  :                          ACK      ACK

i2c_master_transmit()
在 I2C 总线上执行一次写入事务

  • 如果未注册回调函数,函数将阻塞直到传输完成或超时
  • 如果注册了回调函数,则事务为异步,函数立即返回,传输完成信息通过回调函数获取
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)

参数
i2c_devi2c_master_bus_add_device 创建的 I2C 主设备句柄
write_buffer 待发送的数据缓冲区
write_size write_buffer 的大小(字节数)
xfer_timeout_ms 输入参数,等待超时时间(单位:毫秒),-1 表示永不超时

返回值
ESP_OK 成功,数据已通过 I2C 总线发送
ESP_ERR_INVALID_ARG 参数无效,例如设备句柄为空、缓冲区无效或缓冲区大小为 0
ESP_ERR_TIMEOUT 超时,可能是总线忙或硬件故障,传输时间超过 xfer_timeout_ms

I2C Master Read

在成功安装 I2C 主机总线后,可以通过调用 i2c_master_receive() 从从机设备读取数据
通信过程如下

Master : START | Device address W          ACK          NACK STOP
Slave  :                          ACK Data     ACK Data

i2c_master_receive()
在 I2C 总线上执行一次读取,从从设备接收数据

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)

参数
i2c_dev I2C 主设备句柄
read_buffer 用于存储从 I2C 总线接收到的数据
read_size 读取缓冲区的大小
xfer_timeout_ms 输入参数,设置等待超时时间(单位:毫秒),-1 表示无限等待直到操作完成。

返回值
ESP_OK 成功
ESP_ERR_INVALID_ARG 参数无效
ESP_ERR_TIMEOUT 超时

I2C Master Write and Read Permalink to this headline

从一些 I2C 设备中读取数据之前需要进行写入配置(如 ADXL345),可通过 i2c_master_transmit_receive() 接口进行配置
通信过程如下

Master : START | Device address W          Data    START   Device address R      NACK STOP
Slave  :                          ACK Data     ACK                          Data

i2c_master_transmit_receive()
在 I2C 总线上执行一次 写入-读取(Write-Read)

  • 主设备先向从设备发送数据(写操作),然后从从设备接收数据(读操作)
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)

参数
i2c_dev I2C 主设备句柄
write_buffer 待发送的数据缓冲区
write_size 写缓冲区的大小(字节数)
read_buffer 用于存储从设备返回的数据
read_size 读缓冲区的大小(字节数)
xfer_timeout_ms 输入参数,设置等待超时时间(单位:毫秒),-1 表示无限等待直到操作完成

返回值
ESP_OK 成功
ESP_ERR_INVALID_ARG 参数无效
ESP_ERR_TIMEOUT 超时

Example Code:ESP32 I2C Communication with ADXL345 Accelerometer

/*
* Author: Lamonce
* Description: i2c communication test
* Date: 2024-12-5
* esp-idf Version: 5.3.1
*/

#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
#include "driver/i2c_master.h"
#include "esp_log.h"

#define ADXL345_I2C_ADDRESS 0x53  // ADXL345 地址
#define I2C_MASTER_SDA_IO 21      // SDA 引脚
#define I2C_MASTER_SCL_IO 22      // SCL 引脚
#define I2C_MASTER_FREQ_HZ 400000 // 通信速率
#define GRAVITY_ACCELERATION 32 / 8196.0 // 重力加速度

i2c_master_bus_handle_t i2c_master_bus_handle; // I2C 总线句柄
i2c_master_dev_handle_t i2c_master_ADXL345_handle; // ADXL345 设备句柄

// 在此处添加函数声明
void i2cMasterInit(void); // I2C 总线初始化
void i2cADXL345Init(void); // ADXL345 设备初始化
void ADXL345ReadData(void); // 读取 ADXL345 数据

void app_main(void)
{
    vTaskDelay(5000 / portTICK_PERIOD_MS); // 延时 5 秒,方便观察LOG
    i2cMasterInit(); // I2C 总线初始化
    i2cADXL345Init(); // ADXL345 设备初始化

    for (int i = 0; i < 10; i++)
    {
        ADXL345ReadData();
        vTaskDelay(3000 / portTICK_PERIOD_MS);
    }

    i2c_del_master_bus(i2c_master_bus_handle); // 删除 I2C 总线
    ESP_LOGI("I2C", "I2C master has been deleted.");
}

void i2cMasterInit(void)
{
    i2c_master_bus_config_t i2c_master_config = {
        .clk_source = I2C_CLK_SRC_DEFAULT, // 使用默认时钟源
        .i2c_port = I2C_NUM_0, // I2C 端口
        .sda_io_num = I2C_MASTER_SDA_IO, // SDA 引脚
        .scl_io_num = I2C_MASTER_SCL_IO, // SCL 引脚
        .glitch_ignore_cnt = 7, // 设置主机总线的毛刺周期
        .flags.enable_internal_pullup = true, // 启用内部上拉
    };

    ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_master_config, &i2c_master_bus_handle)); // 创建 I2C 总线
    ESP_LOGI("I2C", "I2C master has been initialized.");
}

void i2cADXL345Init(void)
{
    i2c_device_config_t i2c_device_config = {
        .dev_addr_length = I2C_ADDR_BIT_LEN_7, // 设备地址长度
        .device_address = ADXL345_I2C_ADDRESS, // 设备地址
        .scl_speed_hz = I2C_MASTER_FREQ_HZ, // 通信速率
    };

    ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_master_bus_handle, &i2c_device_config, &i2c_master_ADXL345_handle)); // 添加设备,注意传参顺序
    ESP_LOGI("I2C", "ADXL345 has been added to the bus.");

    uint8_t adxl345_config_data[] = {
        0x31, 0x0f, // 设置数据左对齐
        0x2c, 0x08, // 设置速率
        0x2d, 0x08, // 设置电源模式
        0x2e, 0x80, // 使能 DATA_READY 中断
        0x1e, 0x00, // x偏移量
        0x1f, 0x00, // y偏移量
        0x20, 0x00, // z偏移量
    };

    for (int i = 0; i < sizeof(adxl345_config_data); i += 2) // 写入配置数据
    {
        ESP_ERROR_CHECK(i2c_master_transmit(i2c_master_ADXL345_handle, &adxl345_config_data[i], 2, -1));
    }
    vTaskDelay(100 / portTICK_PERIOD_MS); // 延时 100ms,等待配置生效

    uint8_t init_check = 0;
    uint8_t reg_address = 0x00;

    ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_master_ADXL345_handle, &reg_address, 1, &init_check, 1, -1)); // 读取初始化检查寄存器
    ESP_LOGI("ADXL345", "init_check: %x", init_check);
    if (init_check == 0xE5) // 读出的数据为0XE5,表示正确
    {
        ESP_LOGI("ADXL345", "ADXL345 has been initialized successfully.");
    }
    else
    {
        ESP_LOGE("ADXL345", "ADXL345 initialization failed.");
    }
}

void ADXL345ReadData(void)
{
    uint8_t reg_address = 0x32;
    uint8_t adxl345_data[6];
    ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_master_ADXL345_handle, &reg_address, 1, adxl345_data, sizeof(adxl345_data), -1)); // 读取数据
    // ADXL345 数据为补码,需要转换为原码
    for (int i = 0; i < sizeof(adxl345_data); i++) // 将补码转换为原码
    {
        adxl345_data[i] = ~adxl345_data[i];
        adxl345_data[i] += 1;
    }
    int16_t x_data = (adxl345_data[1] << 8) | adxl345_data[0];
    int16_t y_data = (adxl345_data[3] << 8) | adxl345_data[2];
    int16_t z_data = (adxl345_data[5] << 8) | adxl345_data[4];

    // 格式化数据
    x_data >>= 3;
    y_data >>= 3;
    z_data >>= 3;
    if (x_data > 4096)
    {
        x_data -= 8192;
    }
    if (y_data > 4096)
    {
        y_data -= 8192;
    }
    if (z_data > 4096)
    {
        z_data -= 8192;
    }

    // 转换为重力加速度
    double x_data_g = x_data * GRAVITY_ACCELERATION;
    double y_data_g = y_data * GRAVITY_ACCELERATION;
    double z_data_g = z_data * GRAVITY_ACCELERATION;
    ESP_LOGI("ADXL345", "x_data: %fg, y_data: %fg, z_data: %fg", x_data_g, y_data_g, z_data_g);
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值