由 爱笑 贡献,Ai-Thinker 进行整理
【Ai-WB2中级篇】I2C通信接口
I2C (linter-intergrated Circuit)是一种串行通讯总线,使用多主从架构,用来连接低速外围装置。 每个器件都有一个唯一的地址识别,并且都可以作为一个发送器或接收器。每个连接到总线的器件都可以通过唯一的地址和一直存在的主、从机关系用软件设置地址,主机可以作为主机发送器或主机接收器。如果有两个或多个主机同时初始化,数据传输可以通过冲突检测和仲裁防止数据被破坏。 BL602/BL604包含一个12C控制器主机,可灵活配置slaveAddr、subAddr以及传输数据,方便与从设备通信,提供2个word深度的fif0,提供中断功能,可搭配DMA使用提高效率,可灵活调整时钟频率。 本文将详细介绍如何使用Ai-WB2的I2C模块。 一:I2C介绍 BL602芯片的I2C有如下特性:
📜 点击展开完整代码
**·** 支持主机模式
**·** 支持多主机模式和仲裁功能
**·** 时钟频率可灵活调整
**·** 最高工作频率为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支持发送和接收两种工作状态,寄存器PKTDIR表示发送或者接收状态,设置为0时,表示发送状态,设置为1时,表示接收状态。 2) 从设备地址 每个对接I2C的从设备,都会有唯一设别地址,通常该地址是7位长度,将从设备地址写入寄存器 SLVADDR,I2C在将从设备地址发送出去之前,会自动左移1位,并在最低位补上发送接收方向位。 3) 从设备寄存器地址 从设备青存器地址表示I2C需要对从设备某个寄存器做读写操作的寄存器地址,将从设备寄存器地址写入寄存器 I2C_SUB_ADDR,同时需要将寄存器 SAEN 置1。如果将寄存器 SAEN 置0,那么!2C主机发送时会跳过从设备寄存器地址段。 4) 从设备寄存器地址长度 将从设备寄存器地址长度减1再写入青存器 SABC。 5) 数据 数据部分表示需要发送到从设备的数据,或者需要从从设备接收到的数据。 当I2C发送数据时,需要将数据依次以word为单位写入I2CFIFO,发送数据写FIFO的寄存器地址 I2C_FIFO_WDATA。当I2C接收数据时,需要依次以word为单位从12C_FIFO中将数据读出来,接收数据读FIFO的寄存器地址I2C_FIFO_RDATA。 6) 数据长度 将数据长度减1再写入寄存器PKTLEN。 7) 使能信号 将以上几项配置完成后,再将使能信号寄存器 MEN 写1,就自动启动I2C发送流程了。 当读写标志位配置为0时,12C发送数据,主机发送流程: 1. 起始位 2. (从设备地址左移1位+0)+ ACK 3. 从设备寄存器地址 +ACK 4. 1字节数据+ ACK 5. 1字节数据+ACK 6. 停止位 当读写标志位配置为1时,I2C接收数据,主机发送流程: 1. 起始位 2. (从设备地址左移1位+0)+ ACK 3. 从设备寄存器地址 +ACK 4. 起始位 5. (从设备地址左移1位+1)+ ACK 6. 1字节数据+ ACK 7. 1字节数据+ ACK 8. 停止位 FIFO管理 I2C FIFO深度为2个word,I2C发送和接收可分为RX FIFO和TX FIFO。寄存器 RFICNT 表示RX FIFO中有多少数据(单位word)需要读取。 寄存器 TFICNT 表示TX FIFO中剩余多少空间(单位Word)可供写入。
📜 点击展开完整代码
**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可以使用DMA进行数据的发送和接收。将 DTEN 置1,则开启DMA发送模式,为I2C分配好通道后,DMA会将数据从存储区传输型12C_FIFO_WDATA寄存器中。将 DREN 置1.则开启DMA接收模式,为I2C分配好通道后,DMA会将 I2C_FIFO_RDATA寄存器中的传输到存储区中。I2C模块搭配使用DMA时,数据部分将由DMA自动完成搬运,不需要CPU再将数据写入I2CTXFIFO或者从I2C RXFIFO中读取数据。 DMA发送流程 1. 配置读写标志位为0 2. 配置从设备地址 3. 配置从设备寄存器地址 4. 配置从设备寄存器地址长度 5. 数据长度 6. 使能信号寄存器置1 7. 配置DMA transfer size 8. 配置DMA源地址transfer width 9. 配置DMA目的地址transfer width(需要注意!2C搭配DMA使用时,目的地址transfer width需要设置为32bits,以word对齐使用) 10. 配置DMA源地址为存储发送数据的内存地址 11. 配置DMA目的地址为12CTXFIFO地址,I2C_FIFO_WDATA 12. 使能DMA DMA接收流程 1. 配置读写标志位为1 2. 配置从设备地址 3. 配置从设备寄存器地址 4. 配置从设备寄存器地址长度 5. 数据长度 6. 使能信号寄存器置1 7. 配置DMA transfer size 8. 配置DMA源地址transfer width(需要注意)2C搭配DMA使用时,源地址transfer width需要设置为32bits,以word对齐使用) 9. 配置DMA目的地址transfer width 10. 配置DMA源地址为12C RXFIFO地址,I2C_FIFO_RDATA 11. 配置DMA目的地址为存储接收数据的内存地址 12. 使能DMA 中断 I2C包括如下几种中断:
📜 点击展开完整代码
**·** 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介绍**I2C的HOSAL层APl在 components/platform/hosal/include/hosal_i2c.h 中定义,常用的AP如下: · int hosal_i2c_init(hosal_i2c_dev_t *i2c):12C初始化。参数说明如下: · i2c:12C设备实例。其定义如下:
📜 点击展开完整代码
typedef struct {
uint8_t port; /**< @brief i2c 端口 */
hosal_i2c_config_t config; /**< @brief i2c 配置 */
void *priv; /**< @brief 用户自定义数据 */
} hosal_i2c_dev_t;其中,hosal_i2c_config_t为l2C配置,定义如下:
📜 点击展开完整代码
#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;📜 点击展开完整代码
**·** 返回值:成功时,返回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:内存(寄存器)地址长度。定义如下:📜 点击展开完整代码
#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 */📜 点击展开完整代码
**·** 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使用实例**下面将演示在主机模式下,如何对I2C的设备地址进行扫描。代码如下:
📜 点击展开完整代码
#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();
}
