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();
- }
复制代码 |
|