ESP32-S3学习笔记<4>:I2C的应用
ESP32-S3学习笔记<4>:UART的应用
- 1. 头文件包含
- 2. I2C的配置
- 2.1 i2c_num 的选择
- 2.2 i2c_conf 的设定
- 2.2.1 mode/设置模式
- 2.2.2 sda_io_num、scl_io_num/设置SDA和SCL的GPIO
- 2.2.3 sda_pullup_en、scl_pullup_en/设置SDA和SCL的上拉使能
- 2.2.4 clk_speed/设置I2C速率
- 2.2.5 clk_flags/标记位
- 3. 驱动的安装
- 4. 读或写
- 4.1 创建命令序列/链
- 4.2 添加start/start-repeated命令
- 4.3 添加单个字节写入
- 4.4 添加多个字节写入
- 4.5 读取单个字节数据
- 4.6 读取多个字节数据
- 4.7 添加stop命令
- 4.8 执行命令序列
- 4.9 删除命令序列
- 5. 示例
1. 头文件包含
#include "driver/i2c.h"
#include "driver/gpio.h"
2. I2C的配置
使用如下函数配置I2C外设:
esp_err_t i2c_param_config(i2c_port_t i2c_num, const i2c_config_t *i2c_conf);
各个参数的含义:
2.1 i2c_num 的选择
参数 i2c_num 用于指定要配置的I2C外设。ESP32-S3有2个I2C外设。选择 I2C_NUM_0 或 I2C_NUM_1。
typedef enum {I2C_NUM_0 = 0, /*!< I2C port 0 */
#if SOC_HP_I2C_NUM >= 2I2C_NUM_1, /*!< I2C port 1 */
#endif /* SOC_HP_I2C_NUM >= 2 */
#if SOC_LP_I2C_NUM >= 1LP_I2C_NUM_0, /*< LP_I2C port 0 */
#endif /* SOC_LP_I2C_NUM >= 1 */I2C_NUM_MAX, /*!< I2C port max */
} i2c_port_t;
2.2 i2c_conf 的设定
第二个参数 i2c_conf 用于配置I2C的工作参数。结构体 i2c_config_t 的定义为:
typedef struct {i2c_mode_t mode; /*!< I2C mode */int sda_io_num; /*!< GPIO number for I2C sda signal */int scl_io_num; /*!< GPIO number for I2C scl signal */bool sda_pullup_en; /*!< Internal GPIO pull mode for I2C sda signal*/bool scl_pullup_en; /*!< Internal GPIO pull mode for I2C scl signal*/union {struct {uint32_t clk_speed; /*!< I2C clock frequency for master mode, (no higher than 1MHz for now) */} master; /*!< I2C master config */
#if SOC_I2C_SUPPORT_SLAVEstruct {uint8_t addr_10bit_en; /*!< I2C 10bit address mode enable for slave mode */uint16_t slave_addr; /*!< I2C address for slave mode */uint32_t maximum_speed; /*!< I2C expected clock speed from SCL. */} slave; /*!< I2C slave config */
#endif // SOC_I2C_SUPPORT_SLAVE};uint32_t clk_flags; /*!< Bitwise of ``I2C_SCLK_SRC_FLAG_**FOR_DFS**`` for clk source choice*/
} i2c_config_t;typedef void *i2c_cmd_handle_t; /*!< I2C command handle */
2.2.1 mode/设置模式
参数 mode 用于设置I2C的模式。一般是使用 I2C_MODE_MASTER 。
typedef enum{
#if SOC_I2C_SUPPORT_SLAVEI2C_MODE_SLAVE = 0, /*!< I2C slave mode */
#endifI2C_MODE_MASTER, /*!< I2C master mode */I2C_MODE_MAX,
} i2c_mode_t;
2.2.2 sda_io_num、scl_io_num/设置SDA和SCL的GPIO
2.2.3 sda_pullup_en、scl_pullup_en/设置SDA和SCL的上拉使能
硬件设计上,I2C的两个信号线一般在远端使用电阻上拉。这里就不需要使能了。如果外面没有上拉电阻,这里也可以使能内部上拉以补救。
2.2.4 clk_speed/设置I2C速率
如设置为100000,则表示100Kbps的I2C速率。
2.2.5 clk_flags/标记位
设置为 I2C_SCLK_SRC_FLAG_FOR_NOMAL 即可。
3. 驱动的安装
使用如下函数安装I2C驱动:
esp_err_t i2c_driver_install(i2c_port_t i2c_num, i2c_mode_t mode, size_t slv_rx_buf_len, size_t slv_tx_buf_len, int intr_alloc_flags);
- i2c_num:指定I2C外设,I2C_NUM_0 或者 I2C_NUM_1 。
- mode:指定I2C模式,I2C_MODE_MASTER。
- slv_rx_buf_len、slv_tx_buf_len:用于指定slave模式时的接收和发送缓存大小。对于master模式,设置为0即可。
- intr_alloc_flags:设定中断优先级之类的标记。定义在 esp_intr_alloc.h 文件中。
这一步完成之后,I2C就可以准备读写其他设备了。
4. 读或写
ESP32-S3的I2C驱动,使用一种链接的方式来实现读写。程序首先要创建一个链接,然后将I2C访问过程中的每一个步骤,按类型不同,使用不同的函数压入到链中,最后在命令驱动按照链去执行每一个步骤。使用起来还是很方便的。
4.1 创建命令序列/链
使用如下命令创建一个命令序列。返回值要保留,后面压入命令和执行命令都要用到。
i2c_cmd_handle_t i2c_cmd_link_create(void);
4.2 添加start/start-repeated命令
I2C访问的开始,不论是读还是写,都是以一个start条件开始的。因此读写序列的第一个命令,一定是添加一个start命令。
另外I2C读取数据的时候,在总线地址和目标地址发送完成后,需要重新发送一个start(start-repeated)命令。
这两种情况都可以通过如下函数添加。
esp_err_t i2c_master_start(i2c_cmd_handle_t cmd_handle);
其中,第一个参数 cmd_handle,即是由函数 i2c_cmd_link_create 创建返回的句柄。
4.3 添加单个字节写入
I2C主设备,需要向从设备写入总线地址、寄存器地址、或者单个字节的数据时,都可以添加单个字节写入命令。通过如下函数实现该功能:
esp_err_t i2c_master_write_byte(i2c_cmd_handle_t cmd_handle, uint8_t data, bool ack_en);
其中,第二个参数 data 是要写入的数据。第三个参数 ack_en ,用于设定是否需要检查从机的ACK。正常应该是需要的。
4.4 添加多个字节写入
也可以一次添加多个数据写入,例如写入EEPROM数据等。通过如下函数来实现该功能:
esp_err_t i2c_master_write(i2c_cmd_handle_t cmd_handle, const uint8_t *data, size_t data_len, bool ack_en);
第二个参数 data ,指向一个保存多个数据的缓存。data_len 用于指定写入数据量。
4.5 读取单个字节数据
使用如下函数向命令序列中添加单个字节读取命令。
esp_err_t i2c_master_read_byte(i2c_cmd_handle_t cmd_handle, uint8_t *data, i2c_ack_type_t ack);
这个命令和单个字节写入有点类似,但是第三个参数,即是否需要ACK不同。其定义为:
typedef enum {I2C_MASTER_ACK = 0x0, /*!< I2C ack for each byte read */I2C_MASTER_NACK = 0x1, /*!< I2C nack for each byte read */I2C_MASTER_LAST_NACK = 0x2, /*!< I2C nack for the last byte*/I2C_MASTER_ACK_MAX,
} i2c_ack_type_t;
如何设置,需要根据应用而定。它控制读取数据时,每个字节接收完成后,是否需要向从机发送ACK响应。一般来说,连续读取一串数据,都需要发送ACK,但是最后一个数据读完后应该发送NACK。
4.6 读取多个字节数据
使用如下函数向命令序列中添加多个字节的读取命令。
esp_err_t i2c_master_read(i2c_cmd_handle_t cmd_handle, uint8_t *data, size_t data_len, i2c_ack_type_t ack);
这种情况下,最后一个参数ack,一般应设置为 I2C_MASTER_LAST_NACK ,以指示最后一个字节接收完成后,不发送ACK。除非这条读命令添加后,还需要再添加读取命令。
4.7 添加stop命令
使用如下函数添加stop命令,以结束I2C传输:
esp_err_t i2c_master_stop(i2c_cmd_handle_t cmd_handle);
4.8 执行命令序列
使用如下命令执行已经构建好的命令序列:
esp_err_t i2c_master_cmd_begin(i2c_port_t i2c_num, i2c_cmd_handle_t cmd_handle, TickType_t ticks_to_wait);
ticks_to_wait 用于设置超时时间。以FreeRTOS的systick跳动为单位。
4.9 删除命令序列
序列执行完成后,需要删除命令序列。因为命令序列是动态分配的。
void i2c_cmd_link_delete(i2c_cmd_handle_t cmd_handle);
5. 示例
以下示例,先向24C02 EEPROM写入一些字节,然后读取这些数据并打印。
test_i2c.h文件:
#define TEST_I2C_GPIO_SCL (GPIO_NUM_42)
#define TEST_I2C_GPIO_SDA (GPIO_NUM_41)void TEST_I2C_I2CConfig(void) ;
void TEST_I2C_eeprom_wr_test(void) ;
void TEST_I2C_eeprom_rd_test(void) ;
test_i2c.c文件:
#include "driver/i2c.h"#include "test_i2c.h"void TEST_I2C_I2CConfig(void)
{esp_err_t iErrCode ;i2c_config_t stI2CConfigInfo ;stI2CConfigInfo.mode = I2C_MODE_MASTER ;stI2CConfigInfo.sda_io_num = TEST_I2C_GPIO_SDA ;stI2CConfigInfo.scl_io_num = TEST_I2C_GPIO_SCL ;stI2CConfigInfo.sda_pullup_en = true ;stI2CConfigInfo.scl_pullup_en = true ;stI2CConfigInfo.master.clk_speed = 100000 ;stI2CConfigInfo.clk_flags = I2C_SCLK_SRC_FLAG_FOR_NOMAL ;iErrCode = i2c_param_config(I2C_NUM_0, &stI2CConfigInfo) ;if(ESP_OK != iErrCode){printf("i2c_param_config error. Return value is %d.\n", iErrCode) ;}iErrCode = i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0) ;if(ESP_OK != iErrCode){printf("i2c_driver_install error. Return value : %d.\n ", iErrCode) ;}return ;
}void TEST_I2C_eeprom_wr_test(void)
{esp_err_t iErrCode ;i2c_cmd_handle_t pvCmd ;unsigned char aucSendBuf[8] = {0xAA,0x11,0xBB,0x22,0xCC,0x33,0xDD,0x44} ;/* 创建I2C命令序列 */pvCmd = i2c_cmd_link_create() ;/* 添加I2C起始命令 */i2c_master_start(pvCmd) ;/* 添加I2C单字节发送:设备总线地址 */i2c_master_write_byte(pvCmd, 0xA0, true) ;/* 添加I2C单字节发送:寄存器地址 */i2c_master_write_byte(pvCmd, 0x10, true) ;/* 添加I2C多字节发送:数据 */i2c_master_write(pvCmd, aucSendBuf, sizeof(aucSendBuf), true) ;/* 添加I2C停止命令 */i2c_master_stop(pvCmd) ;/* 启动命令序列,并等待完成 */iErrCode = i2c_master_cmd_begin(I2C_NUM_0, pvCmd, 500) ;if(ESP_OK != iErrCode){printf("i2c_master_cmd_begin error. Return value : %d. \n ", iErrCode) ;while(1) ;}/* 命令执行完成,删除序列 */i2c_cmd_link_delete(pvCmd) ;printf("24C02 Write done.\n") ;return ;
}void TEST_I2C_eeprom_rd_test(void)
{esp_err_t iErrCode ;i2c_cmd_handle_t pvCmd ;unsigned char aucRecvBuf[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} ;/* 创建I2C命令序列 */pvCmd = i2c_cmd_link_create() ;/* 添加I2C起始命令 */i2c_master_start(pvCmd) ;/* 添加I2C单字节发送:设备总线地址(写入) */i2c_master_write_byte(pvCmd, 0xA0, true) ;/* 添加I2C单字节发送:寄存器地址 */i2c_master_write_byte(pvCmd, 0x10, true) ;/* 添加I2C重复起始命令 */i2c_master_start(pvCmd) ;/* 添加I2C单字节发送:设备总线地址(读出) */i2c_master_write_byte(pvCmd, 0xA1, true) ;/* 添加I2C多字节接收:数据 */i2c_master_read(pvCmd, aucRecvBuf, sizeof(aucRecvBuf), I2C_MASTER_LAST_NACK) ;/* 添加I2C停止命令 */i2c_master_stop(pvCmd) ;/* 启动命令序列,并等待完成 */iErrCode = i2c_master_cmd_begin(I2C_NUM_0, pvCmd, 1000) ;if(ESP_OK != iErrCode){printf("i2c_master_cmd_begin failed. Return value : %d. \n", iErrCode) ;while(1) ;}/* 命令执行完成,删除序列 */i2c_cmd_link_delete(pvCmd) ;printf("24C02 read done. Value is %02x %02x %02x %02x %02x %02x %02x %02x.\n", aucRecvBuf[0], aucRecvBuf[1], aucRecvBuf[2], aucRecvBuf[3], aucRecvBuf[4], aucRecvBuf[5], aucRecvBuf[6], aucRecvBuf[7]) ;return ;
}
main.c文件:
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"#include "test_i2c.h"void app_main(void)
{TEST_I2C_I2CConfig() ;TEST_I2C_eeprom_wr_test() ;vTaskDelay(50) ;TEST_I2C_eeprom_rd_test() ;while (true){vTaskDelay(50) ;}
}