基于esp32的IIC通讯
本文源码地址在:http://download.csdn.net/download/noticeable/9962029
IIC 通讯应该是当代比较常用的几种通讯方式之一,其无需特殊的IO接口,连线方式少,只有两条串行总线(SCL,SDA),用来完成数据传输。
本文重点测试相关的在esp32实现IIC通讯的完成,不涉及实际的传感器,在后面会有相应的传感器与esp32连接实现功能的文章,这里重点关注相关的IO配置及功能实现等,以此来学习相关API接口的配置方法。
本文源码可以分为以下几个部分:
PART1:
定义相关参数
1 #define DATA_LENGTH 512 /*!<Data buffer length for test buffer*/ 2 #define RW_TEST_LENGTH 5 /*!<Data length for r/w test, any value from 0-DATA_LENGTH*/ 3 4 #define I2C_SLAVE_SCL_IO 26 /*!<gpio number for i2c slave clock */ 5 #define I2C_SLAVE_SDA_IO 25 /*!<gpio number for i2c slave data */ 6 #define I2C_SLAVE_NUM I2C_NUM_0 /*!<I2C port number for slave dev */ 7 #define I2C_SLAVE_TX_BUF_LEN (2*DATA_LENGTH) /*!<I2C slave tx buffer size */ 8 #define I2C_SLAVE_RX_BUF_LEN (2*DATA_LENGTH) /*!<I2C slave rx buffer size */ 9 10 #define I2C_MASTER_SCL_IO 19 /*!< gpio number for I2C master clock */ 11 #define I2C_MASTER_SDA_IO 18 /*!< gpio number for I2C master data */ 12 #define I2C_MASTER_NUM I2C_NUM_1 /*!< I2C port number for master dev */ 13 #define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master do not need buffer */ 14 #define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master do not need buffer */ 15 #define I2C_MASTER_FREQ_HZ 100000 /*!< I2C master clock frequency */ 16 17 #define ESP_SLAVE_ADDR 0x28 /*!< ESP32 slave address, you can set any 7bit value */ 18 #define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ 19 #define READ_BIT I2C_MASTER_READ /*!< I2C master read */ 20 #define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ 21 #define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ 22 #define ACK_VAL 0x0 /*!< I2C ack value */ 23 #define NACK_VAL 0x1 /*!< I2C nack value */
这里主要定义了相关引脚分批,从设备地址,ACK返回数据及iic总线时钟频率、传输位长度等。
PART2:
i2c总线主机初始化
void i2c_master_init() { int i2c_master_port = I2C_MASTER_NUM; i2c_config_t conf; conf.mode = I2C_MODE_MASTER; conf.sda_io_num = I2C_MASTER_SDA_IO; conf.sda_pullup_en = GPIO_PULLUP_ENABLE; conf.scl_io_num = I2C_MASTER_SCL_IO; conf.scl_pullup_en = GPIO_PULLUP_ENABLE; conf.master.clk_speed = I2C_MASTER_FREQ_HZ; i2c_param_config(i2c_master_port, &conf); i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0); }
i2c_master_init 函数主要是用来对i2c总线的引脚和功能进行配置,
conf.mode用来配置接口在i2c总线中的作用 可选模式为I2C_MODE_SLAVE II2C_MODE_MASTER,这里接口作为主机使用
conf.sda_io_num 、conf.scl_io_num 配置I2C总线的数据总线引脚和I2C的时钟总线引脚
sda_pullup_en、scl_pullup_en 是否启动内部上拉电阻,这里是数据和时钟总线,需要使用上拉电阻。
conf.master.clk_speed 设定时钟频率,这里设置为标准模式100K
最后调用i2c_param_config函数对参数进行初始化,到这里,一个I2C的引脚配置就完成了。
对接口配置完后,需要调用i2c_driver_install对I2C总线的驱动进行安装,主要是对数据发送缓存进行内存分配。
PART3:
i2c总线从机初始化
1 void i2c_slave_init() 2 { 3 int i2c_slave_port = I2C_SLAVE_NUM; 4 i2c_config_t conf_slave; 5 conf_slave.sda_io_num = I2C_SLAVE_SDA_IO; 6 conf_slave.sda_pullup_en = GPIO_PULLUP_ENABLE; 7 conf_slave.scl_io_num = I2C_SLAVE_SCL_IO; 8 conf_slave.scl_pullup_en = GPIO_PULLUP_ENABLE; 9 conf_slave.mode = I2C_MODE_SLAVE; 10 conf_slave.slave.addr_10bit_en = 0; 11 conf_slave.slave.slave_addr = ESP_SLAVE_ADDR; 12 i2c_param_config(i2c_slave_port, &conf_slave); 13 i2c_driver_install(i2c_slave_port, conf_slave.mode, I2C_SLAVE_RX_BUF_LEN, I2C_SLAVE_TX_BUF_LEN, 0); 14 }
i2c_slave_init 函数与 i2c_master_init 函数的区别不大,主要的是其多了从机地址和是否使能I2C 10bit地址模式。
PART4:
i2c总线主机向从机写数据函数
1 esp_err_t i2c_master_write_slave(i2c_port_t i2c_num, uint8_t* data_wr, size_t size) 2 { 3 i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 4 i2c_master_start(cmd); 5 i2c_master_write_byte(cmd, ( ESP_SLAVE_ADDR << 1 ) | WRITE_BIT, ACK_CHECK_EN); 6 i2c_master_write(cmd, data_wr, size, ACK_CHECK_EN); 7 i2c_master_stop(cmd); 8 esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_RATE_MS); 9 i2c_cmd_link_delete(cmd); 10 return ret; 11 }
主机向从机写地址函数主要是完成I2C协议的写数据操作。
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 作用主要是创建和初始化I2C命令链接,这点是需要注意的(在构建I2C命令链接之前,我们需要调用i2c_cmd_link_create()来创建命令链接。发送命令后,我们需要调用i2c_cmd_link_delete()来释放并返回资源。)
i2c_master_start(cmd); I2C主机的队列命令产生启动信号。
i2c_master_write_byte(cmd, (ESP_SLAVE_ADDR << 1 ) | WRITE_BIT, ACK_CHECK_EN); I2C主机的队列命令将一个字节写入I2C总线。
i2c_master_write(cmd, data_wr, size, ACK_CHECK_EN); I2C主机的队列命令将缓冲区写入I2C总线。
i2c_master_stop(cmd); 产生I2C停止信号。
esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_RATE_MS); I2C主机发送排队命令。此功能将触发发送所有排队的命令。(需要注意的是:只能在I2C主模式下调用此功能)
i2c_cmd_link_delete(cmd); 调用i2c_cmd_link_delete()来释放并返回资源。
PART5:
主机读取从机数据函数
1 esp_err_t i2c_master_read_slave(i2c_port_t i2c_num, uint8_t* data_rd, size_t size) 2 { 3 if (size == 0) { 4 return ESP_OK; 5 } 6 i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 7 i2c_master_start(cmd); 8 i2c_master_write_byte(cmd, ( ESP_SLAVE_ADDR << 1 ) | READ_BIT, ACK_CHECK_EN); 9 if (size > 1) { 10 i2c_master_read(cmd, data_rd, size - 1, ACK_VAL); 11 } 12 i2c_master_read_byte(cmd, data_rd + size - 1, NACK_VAL); 13 i2c_master_stop(cmd); 14 esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_RATE_MS); 15 i2c_cmd_link_delete(cmd); 16 return ret; 17 }
此函数主要完成I2C协议所需的读操作。
2~5行表示如果从机没数据发送过来则为空。
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd); 与写操作同理,调用i2c_cmd_link_create()来创建命令链接;并产生启动信号。
i2c_master_write_byte(cmd, ( ESP_SLAVE_ADDR << 1 ) | READ_BIT, ACK_CHECK_EN); I2C主机的队列命令将一个读指令写入I2C总线。
9~11表示如果返回的数据大于1字节(即返回数据不止为ACK),则调用i2c_master_read对从机数据进行读取。
i2c_master_read_byte(cmd, data_rd + size - 1, NACK_VAL); 调用 i2c_master_read_byte函数对i2c总线进行字节读取。
esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_RATE_MS); I2C主机发送排队命令。此功能将触发接收所有排队的命令。(需要注意的是:只能在I2C主模式下调用此功能)
i2c_cmd_link_delete(cmd); 调用i2c_cmd_link_delete()来释放并返回资源。
从part4和part5函数可以看出主机的数据写出和数据读入都是依靠队列的形式写出来的。
PART6:
将缓存区的数据读区显示
1 void disp_buf(uint8_t* buf, int len) 2 { 3 int i; 4 for (i = 0; i < len; i++) { 5 printf("%02x ", buf[i]); 6 if (( i + 1 ) % 16 == 0) { 7 printf("\n"); 8 } 9 } 10 printf("\n"); 11 }
这部分即将缓存取的数据依次读取显示即可。
PART7:
app_main函数的编写
1 void app_main() 2 { 3 i2c_slave_init(); 4 i2c_master_init(); 5 6 int i = 0; 7 int ret; 8 uint8_t* data = (uint8_t*) malloc(DATA_LENGTH); 9 uint8_t* data_wr = (uint8_t*) malloc(DATA_LENGTH); 10 uint8_t* data_rd = (uint8_t*) malloc(DATA_LENGTH); 11 //--------------------------------------------------- 12 for (i = 0; i < DATA_LENGTH; i++) { 13 data[i] = i; 14 } 15 size_t d_size = i2c_slave_write_buffer(I2C_SLAVE_NUM, data, RW_TEST_LENGTH, 1000 / portTICK_RATE_MS); 16 if (d_size == 0) { 17 printf("i2c slave tx buffer full\n"); 18 ret = i2c_master_read_slave(I2C_MASTER_NUM, data_rd, DATA_LENGTH); 19 } else { 20 ret = i2c_master_read_slave(I2C_MASTER_NUM, data_rd, RW_TEST_LENGTH); 21 } 22 printf("*******************\n"); 23 printf("TASK MASTER READ FROM SLAVE\n"); 24 printf("*******************\n"); 25 printf("====TASK Slave buffer data ====\n"); 26 disp_buf(data, d_size); 27 if (ret == ESP_OK) { 28 printf("====TASK Master read ====\n"); 29 disp_buf(data_rd, d_size); 30 } else { 31 printf("Master read slave error, IO not connected...\n"); 32 } 33 vTaskDelay(2000 / portTICK_RATE_MS); 34 //--------------------------------------------------- 35 int size; 36 for (i = 0; i < DATA_LENGTH; i++) { 37 data_wr[i] = i + 10; 38 } 39 40 ret = i2c_master_write_slave( I2C_MASTER_NUM, data_wr, RW_TEST_LENGTH); 41 if (ret == ESP_OK) { 42 size = i2c_slave_read_buffer( I2C_SLAVE_NUM, data, RW_TEST_LENGTH, 1000 / portTICK_RATE_MS); 43 } 44 printf("*******************\n"); 45 printf("TASK MASTER WRITE TO SLAVE\n"); 46 printf("*******************\n"); 47 printf("----TASK Master write ----\n"); 48 disp_buf(data_wr, RW_TEST_LENGTH); 49 if (ret == ESP_OK) { 50 printf("----TASK Slave read: [%d] bytes ----\n", size); 51 disp_buf(data, size); 52 } else { 53 printf("TASK Master write slave error, IO not connected....\n"); 54 } 55 vTaskDelay(2000 / portTICK_RATE_MS); 56 }
app_main的任务是调用初始化函数对i2c引脚的、初始化缓存器分配,并打印出数据和整个模拟I2C总线的过程信息。
实验现象:
连接esp32的io18->io25,连接io19->io26,打开minicom,按下复位键,可以看到如下数据打印信息 ,说明I2C总线通讯成功。