Contributed by 爱笑, organized by Ai-Thinker
[Ai-WB2 Intermediate] I2C Communication Interface
I2C (Inter-Integrated Circuit) is a serial communication bus that uses a multi-master/slave architecture to connect low-speed peripheral devices. Each device has a unique address and can act as either a transmitter or a receiver. Every device connected to the bus can have its address set in software, based on a unique address and an always-present master/slave relationship; a master can act as a master transmitter or a master receiver. If two or more masters start transmission at the same time, data corruption is prevented through collision detection and arbitration. The BL602/BL604 contains an I2C controller master that allows flexible configuration of slaveAddr, subAddr, and the transmitted data for easy communication with slave devices. It provides a 2-word-deep FIFO, interrupt support, can be combined with DMA for higher efficiency, and has a flexibly adjustable clock frequency. This article will detail how to use the Ai-WB2 I2C module. 1. I2C Introduction The I2C of the BL602 chip has the following features:
Click to expand full code
**·** 支持主机模式
**·** 支持多主机模式和仲裁功能
**·** 时钟频率可灵活调整
**·** 最高工作频率为40MHZ
**12C时钟设置**
I2C的时钟是由bck(bus clock)而来,可以在bck时钟的基础上做分频处理。 寄存器 I2C_PRD_DATA 可以对数据段的时钟做分频处理。i2c模块将数据发送分为4个阶段,每个阶段在寄存器中用单独一个字节来控制,每个阶段的采样个数是可以设置的,4个采样数共同决定了i2c clock的分频系数。比如现在bck是32M,寄存器I2C_PRD_DATA在不做配置默认情况下的值是0x15151515,那么12C的时钟频率为 32M/(15+1)*4)=500K,同理,寄存器I2C_PRD_START和I2C_PRD_STOP也会分别对起始位和停止位的时钟做分频处理。
**I2C配置项**
**·** 读写标志位
**·** 从设备地址
**·** 从设备寄存器地址
**·** 从设备寄存器地址长度
**·** 数据(发送时,配置发送的数据;接收时,存储接收到的数据)
**·** 数据长度
**·** 使能信号
**1) 读写标志位**I2C Clock Settings The I2C clock is derived from the BCK (bus clock) and can be divided based on the BCK clock. The I2C_PRD_DATA register divides the clock of the data segment. The I2C module divides data transmission into 4 phases, each controlled by a separate byte in the register. The number of samples in each phase is configurable, and the four sample counts together determine the I2C clock division factor. For example, if BCK is 32 MHz and I2C_PRD_DATA is left at its default value of 0x15151515, the I2C clock frequency is 32M/((15+1)*4) = 500 kHz. Similarly, the I2C_PRD_START and I2C_PRD_STOP registers divide the clocks of the start bit and stop bit respectively. I2C Configuration Items· Read/write flag · Slave device address · Slave device register address · Slave device register address length · Data (the data to send when transmitting; the storage for the received data when receiving) · Data length · Enable signal 1) Read/write flag I2C supports two working states: sending and receiving. The PKTDIR register indicates the send or receive state: when set to 0, it indicates the send state; when set to 1, it indicates the receive state. 2) Slave device address Every slave device on the I2C bus has a unique address, usually 7 bits long. Write the slave address to the SLVADDR register. Before sending the slave address, I2C automatically shifts it left by 1 bit and fills the lowest bit with the send/receive direction bit. 3) Slave device register address The slave device register address is the register address at which I2C performs read/write operations on the slave device. Write the slave register address to the I2C_SUB_ADDR register, and set the SAEN register to 1. If the SAEN register is set to 0, the I2C master will skip the slave register address segment when sending. 4) Slave device register address length Write the slave register address length minus 1 to the SABC register. 5) Data The data part represents the data to be sent to the slave device, or the data to be received from the slave device. When I2C sends data, the data needs to be written into the I2C FIFO word by word in sequence, at the register address I2C_FIFO_WDATA. When I2C receives data, the data needs to be read out of the I2C FIFO word by word in sequence, at the register address I2C_FIFO_RDATA. 6) Data length Write the data length minus 1 to the PKTLEN register. 7) Enable signal After configuring the above items, write 1 to the enable signal register MEN, and the I2C transmission process starts automatically. When the read/write flag is set to 0, I2C sends data. The master transmission process is: 1. Start bit 2. (Slave address shifted left by 1 bit + 0) + ACK 3. Slave register address + ACK 4. 1 byte of data + ACK 5. 1 byte of data + ACK 6. Stop bit When the read/write flag is set to 1, I2C receives data. The master transmission process is: 1. Start bit 2. (Slave address shifted left by 1 bit + 0) + ACK 3. Slave register address + ACK 4. Start bit 5. (Slave address shifted left by 1 bit + 1) + ACK 6. 1 byte of data + ACK 7. 1 byte of data + ACK 8. Stop bit FIFO Management The I2C FIFO has a depth of 2 words, and the I2C send and receive paths are split into an RX FIFO and a TX FIFO. The RFICNT register indicates how much data (in words) in the RX FIFO needs to be read. The TFICNT register indicates how much space (in words) remains in the TX FIFO for writing.
Click to expand full code
**I2C FIFO状态:**
**·** RX FIFO underfow:当RX FIFO中的数据被读取完毕或者为空时,继续从RX FIFO中读取数据,寄存器 RFIU 会被置位;
**·** RX FIFO overiow:当I2C接收数据直到RX FIFO的2个word被填满后,在没有读取RX FIFO的情况下,12C再次接收到数据,寄存器 RFIO 会被位;
**·** TX FIFO underiow:当向TX FIFO中填入的数据大小不满足配置的(2C数据长度 PKTLEN,并且已经没有新数据继续填入TXFIFO中时,寄存器TFIU 会被置位;
**·** TX FIFO overfow:当TX FIFO的2个word被填满后,在TXFIFO中的数据没有发出去之前,再次向TXFIFO中填入数据,寄存器 TFIO 会被置位。
**DMA配置流程**I2C can use DMA for data transmission and reception. Set DTEN to 1 to enable DMA send mode; after a channel is allocated for I2C, the DMA transfers data from the memory region to the I2C_FIFO_WDATA register. Set DREN to 1 to enable DMA receive mode; after a channel is allocated for I2C, the DMA transfers data from the I2C_FIFO_RDATA register to the memory region. When the I2C module is used with DMA, the data movement is handled automatically by the DMA, and the CPU does not need to write data into the I2C TX FIFO or read data from the I2C RX FIFO. DMA Send Process 1. Configure the read/write flag to 0 2. Configure the slave device address 3. Configure the slave device register address 4. Configure the slave device register address length 5. Data length 6. Set the enable signal register to 1 7. Configure the DMA transfer size 8. Configure the DMA source address transfer width 9. Configure the DMA destination address transfer width (note: when I2C is used with DMA, the destination address transfer width must be set to 32 bits, aligned to word boundaries) 10. Configure the DMA source address as the memory address storing the data to send 11. Configure the DMA destination address as the I2C TX FIFO address, I2C_FIFO_WDATA 12. Enable DMA DMA Receive Process 1. Configure the read/write flag to 1 2. Configure the slave device address 3. Configure the slave device register address 4. Configure the slave device register address length 5. Data length 6. Set the enable signal register to 1 7. Configure the DMA transfer size 8. Configure the DMA source address transfer width (note: when I2C is used with DMA, the source address transfer width must be set to 32 bits, aligned to word boundaries) 9. Configure the DMA destination address transfer width 10. Configure the DMA source address as the I2C RX FIFO address, I2C_FIFO_RDATA 11. Configure the DMA destination address as the memory address storing the received data 12. Enable DMA Interrupts I2C includes the following interrupts:
Click to expand full code
**·** I2C_TRANS_END_INT:I2C传输结束中断
**·** I2C_TX_FIFO_READY_INT:当I2C TX FIFO有空闲空间可用于填充时,触发中断
**·** I2C_RX_FIFO_READY_INT: 当I2C RX FIFO接收到数据时,触发中断
**·** I2C_NACK_RECV_INT: 当I2C模块检测到NACK状态,触发中断
**·** I2C_ARB_LOST_INT:I2C仲裁丢失中断
**·** I2C_FIFO_ERR_INT:I2C FIFO ERROR中断
**二:I2C驱动API介绍**The HOSAL-layer API of I2C is defined in components/platform/hosal/include/hosal_i2c.h. The commonly used APIs are as follows: · int hosal_i2c_init(hosal_i2c_dev_t *i2c): I2C initialization. Parameter description: · i2c: I2C device instance. It is defined as follows:
Click to expand full code
typedef struct {
uint8_t port; /**< @brief i2c 端口 */
hosal_i2c_config_t config; /**< @brief i2c 配置 */
void *priv; /**< @brief 用户自定义数据 */
} hosal_i2c_dev_t;Among them, hosal_i2c_config_t is the I2C configuration, defined as follows:
Click to expand full code
#define HOSAL_I2C_MODE_MASTER 1 /**< @brief i2c communication is master mode */
#define HOSAL_I2C_MODE_SLAVE 2 /**< @brief i2c communication is slave mode */
typedef struct {
uint32_t address_width; /**< @brief 地址模式: 7 bit or 10 bit */
uint32_t freq; /**< @brief 时钟频率 */
uint8_t scl; /**< @brief i2c clk 引脚 */
uint8_t sda; /**< @brief i2c data 引脚 */
uint8_t mode; /**< @brief master 或 slave 模式 */
} hosal_i2c_config_t;Click to expand full code
**·** 返回值:成功时,返回0;否则,返回非零值。
**·** int hosal_i2c_master_send(hosal_i2c_dev_t *i2c,uint16_t dev_addr, const uint8_t *data,uint16_t size, uint32_ttimeout):Master模式下发送数据。参数说明如下:
**·** i2c:12C设备实例
**·** dev_addr:从机设备地址
**·** data:需要发送的数据
**·** size:发送的数据长度
**·** timeout:通信超时时长
**·** 返回值:成功时,返回0;否则,返回非零值。
**·** int hosal_i2c_master_recv(hosal_i2c_dev_t *i2c, uint16_t dev_addr, uint8 t *data,uint16_t size, uint32_t timeout)Master模式下接收数据。参数说明如下:
**·** i2c:I2C设备实例
**·** dev_addr:从机设备地址
**·** data:接收发送的数据
**·** size:接收的数据长度
**·** timeout:通信超时时长
**·** 返回值:成功时,返回0;否则,返回非零值。
**·** int hosal_i2c_slave_send(hosal_i2c_dev_t *i2c, const uint8_t *data, uint16_t size, uint32_t timeout) : Slave模式下发送数据,参数说明如下:
**·** i2c:I2C设备实例
**·** data:接收发送的数据
**·** size:接收的数据长度
**·** timeout:通信超时时长
**·** 返回值:成功时,返回0,否则,返回非零值。
**·** int hosal_i2c_slave_recy(hosal_i2c_dev_t *i2c, uint8_t *data, uint16_t size, uint32_t timeout):Slave模式下接收数据。参数说明如下:
**·** i2c:I2C设备实例
**·** data:接收发送的数据
**·** size:接收的数据长度
**·** timeout:通信超时时长
**·** 返回值:成功时,返回0;否则,返回非零值。
**·** int hosal_i2c_mem_write(hosal_i2c_dev_t *i2c, uint16_t dev_addr, uint32_t mem_addr,uint16_t mem_addr_size, const wint8_t*data,uint16_ t size,uint32_t timeout):I2C mem写数据。参数说明如下:
**·** i2c:12C设备实例
**·** dev_addr:从机地址
**·** mem_addr:从机内存(寄存器)地址
**·** mem_addr_size:内存(寄存器)地址长度。定义如下:Click to expand full code
#define HOSAL_I2C_MEM_ADDR_SIZE_8BIT 1 /**< @brief i2c memory address size 8bit */
#define HOSAL_I2C_MEM_ADDR_SIZE_16BIT 2 /**< @brief i2c memory address size 16bit */
#define HOSAL_I2C_MEM_ADDR_SIZE_24BIT 3 /**< @brief i2c memory address size 24bit */
#define HOSAL_I2C_MEM_ADDR_SIZE_32BIT 4 /**< @brief i2c memory address size 32bit */Click to expand full code
**·** data:接收发送的数据
**·** size:接收的数据长度
**·** timeout:通信超时时长
**·** 返回值:成功时,返回0;否则,返回非零值。
**·** int hosal_i2c_mem_read(hosal_i2c_dev_t *i2c, uint16_t dev_addr, uint32_t mem_addr,uint16_t mem_addr_size, uint8_t *data.uint16_t size,uint32_t timeout):l2C Mem读数据。参数说明如下:
**·** i2c:12C设备实例
**·** dev_addr:从机地址
**·** mem addr:从机内存(寄存器)地址
**·** mem_addr_size:内存(青存器)地址长度
**·** data:接收发送的数据
**·** size:接收的数据长度
**·** timeout:通信超时时长
**·** 返回值:成功时,返回0;否则,返回非零值。
**·** int hosal_i2c_finalize(hosal_i2c_dev_t *i2c):销毁l2C设备实例,释放相关资源。参数说明如下:
**·** i2c:12C设备实例
**·** 返回值:成功时,返回0;否则,返回非零值。
**三:I2C使用实例**The following demonstrates how to scan I2C device addresses in master mode. The code is as follows:
Click to expand full code
#include <stdio.h>
#include <string.h>
#include <FreeRTOS.h>
#include <task.h>
#include <stdio.h>
#include <stdbool.h>
#include <blog.h>
#include "hosal_i2c.h"
static hosal_i2c_dev_t i2c0;
void i2c_master_init(void) {
int ret = -1;
int i = 0;
i2c0.port = 0;
i2c0.config.freq = 100000; /* only support 305Hz~100000Hz */
i2c0.config.address_width = HOSAL_I2C_ADDRESS_WIDTH_7BIT; /* only support 7bit */
i2c0.config.mode = HOSAL_I2C_MODE_MASTER; /* only support master */
i2c0.config.scl = 4;
i2c0.config.sda = 3;
/* init i2c with the given settings */
ret = hosal_i2c_init(&i2c0);
if (ret != 0) {
hosal_i2c_finalize(&i2c0);
blog_error("hosal i2c init failed!\r\n");
return;
}
uint8_t tx_data[] = {0x01};
uint8_t rx_data[1] = {0x00};
for(uint8_t i = 0;i < 128;i++){
int ret = hosal_i2c_master_send(&i2c0,i,tx_data,1,100);
ret = hosal_i2c_master_recv(&i2c0,i,rx_data,1,100);
if(ret == 0){
printf("0x%x ",i);
}else{
printf(".");
}
if(i != 0 && i % 16 == 0){
printf("\r\n");
}
}
printf("\r\n");
vTaskDelay(500);
ret = hosal_i2c_finalize(&i2c0);
if (ret != 0) {
blog_error("hosal i2c finalize failed!\r\n");
return;
}
}
void main(void) {
printf("start to init i2c master...\r\n");
i2c_master_init();
}
