8 I2C总线
I2C(Inter-Integrated Circuit)是一种通用的总线协议。 它是一种只需要两个IO并且支持多主多从的双向两线制总线协议标准。
8.2 使用说明
I2C Master有两种使用方式可以选择:
I2C Slave 则需要使用方法2,以中断方式操作。
8.2.1 方法1(blocking)
8.2.1.1 IO 配置
IO选择,并非所有IO都可以映射成I2C,请查看对应datasheet获取可用IO。
操作模块之前需要打开对应模块的时钟。使用
SYSCTRL_ClearClkGateMulti()
打开时钟。请查看下述代码示例,需要注意的是:SYSCTRL_ITEM_APB_I2C0
对应I2C0,如果使用I2C1需要对应修改。
I2C IO需要配置为默认上拉,芯片内置上拉可以通过
PINCTRL_Pull()
实现(已经包含在PINCTRL_SelI2cIn()
中)。实际应用中建议在外部实现上拉(可以获得更快的响应速度和时钟)。将选定IO映射到I2C模块,两个IO均需要配置为双向(输入+输出)。请参考下述代码实现(
PINCTRL_SelI2cIn()
中包含了输入+输出的配置)。
以下示例可以将指定IO映射成I2C引脚:
#define I2C_SCL GIO_GPIO_10
#define I2C_SDA GIO_GPIO_11
void setup_peripherals_i2c_pin(void)
{
( (1 << SYSCTRL_ITEM_APB_I2C0)
SYSCTRL_ClearClkGateMulti| (1 << SYSCTRL_ITEM_APB_PinCtrl));
(I2C_PORT_0,I2C_SCL,I2C_SDA);
PINCTRL_SelI2cIn}
8.2.1.2 模块配置
参考:
\ING_SDK\sdk\src\BSP\iic.c
包含API:
/** * @brief Init an I2C peripheral * * @param[in] port I2C peripheral ID */ void i2c_init(const i2c_port_t port); /** * @brief Write data to an I2C slave * * @param[in] port I2C peripheral ID * @param[in] addr address of the slave * @param[in] byte_data data to be written * @param[in] length data length * @return 0 if success else non-0 (e.g. time out) */ int i2c_write(const i2c_port_t port, uint8_t addr, const uint8_t *byte_data, int16_t length); /** * @brief Read data from an I2C slave * * @param[in] port I2C peripheral ID * @param[in] addr address of the slave * @param[in] write_data data to be written before reading * @param[in] write_len data length to be written before reading * @param[in] byte_data data to be read * @param[in] length data length to be read * @return 0 if success else non-0 (e.g. time out) */ int i2c_read(const i2c_port_t port, uint8_t addr, const uint8_t *write_data, int16_t write_len, uint8_t *byte_data, int16_t length);
使用方法:
配置IO。
初始化I2C模块:
(I2C_PORT_0); i2c_init
- 写数据:
(I2C_PORT_0, ADDRESS, write_data, DATA_CNT); i2c_write
当读操作完成后API才会返回,为了避免长时间等待ACK等意外情况,使用I2C_HW_TIME_OUT来控制blocking的时间。
- 读数据:
(I2C_PORT_0, ADDRESS, write_data, DATA_CNT, read_data, DATA_CNT); i2c_read
如果write_data不为空,则会首先执行写操作,然后再执行读操作。
8.2.2 方法2(Interrupt)
I2C Slave 以及 I2C Master方法2需要使用Interrupt方式。
8.2.2.1 IO 配置
IO选择,并非所有IO都可以映射成I2C,请查看对应datasheet获取可用IO。
操作模块之前需要打开对应模块的时钟。使用
SYSCTRL_ClearClkGateMulti()
打开时钟。请查看下述代码示例,需要注意的是:SYSCTRL_ITEM_APB_I2C0
对应I2C0,如果使用I2C1需要对应修改。
I2C IO需要配置为默认上拉,芯片内置上拉,可以通过
PINCTRL_Pull()
实现(已经包含在PINCTRL_SelI2cIn()
中)。实际应用中建议在外部实现上拉(可以获得更快的响应速度和时钟)。将选定IO映射到I2C模块,两个IO均需要配置为双向(输入+输出)。请参考下述代码实现(
PINCTRL_SelI2cIn()
中包含了输入+输出的配置)。如果需要使用中断,使用
platform_set_irq_callback()
配置应用中断。
以下示例可以将指定IO映射成I2C引脚,并配置了中断回调函数:
#define I2C_SCL GIO_GPIO_10
#define I2C_SDA GIO_GPIO_11
void setup_peripherals_i2c_pin(void)
{
( (1 << SYSCTRL_ITEM_APB_I2C0)
SYSCTRL_ClearClkGateMulti| (1 << SYSCTRL_ITEM_APB_PinCtrl));
(I2C_PORT_0,I2C_SCL,I2C_SDA);
PINCTRL_SelI2cIn
(PLATFORM_CB_IRQ_I2C0, peripherals_i2c_isr, NULL);
platform_set_irq_callback
}
8.2.2.2 模块初始化
I2C模块初始化需要通过以下API来实现:
通过
I2C_Config()
选择Master/Slave角色,以及I2C地址。使用
I2C_ConfigClkFrequency()
更改时钟配置。根据使用场景打开相应的中断
I2C_IntEnable()
:- I2C_INT_CMPL:该中断的触发代表传输结束。
- I2C_INT_FIFO_FULL:代表RX FIFO中有数据。
- I2C_INT_FIFO_EMPTY:TX FIFO空,需要填充发送数据。
- I2C_INT_ADDR_HIT: 总线上检测到了匹配的地址。
使能I2C模块
I2C_Enable()
。
8.2.2.3 触发传输
调用
I2C_CtrlUpdateDirection()
设置传输方向。- I2C_TRANSACTION_SLAVE2MASTER:Slave发送数据,Master读取数据。
- I2C_TRANSACTION_MASTER2SLAVE:Master发送数据,Slave读取数据。
通过
I2C_CtrlUpdateDataCnt()
设置该次传输的数据大小,最大8个bit(256字节),以字节为单位。使用
I2C_CommandWrite()
触发I2C传输:- I2C_COMMAND_ISSUE_DATA_TRANSACTION: Master有效,触发数据传输。
- I2C_COMMAND_RESPOND_ACK:在接收到的字节后发送一个ACK。
- I2C_COMMAND_RESPOND_NACK:在接收到的字节后发送一个NACK。
- I2C_COMMAND_CLEAR_FIFO:清空FIFO。
- I2C_COMMAND_RESET:reset I2C模块。
8.2.2.4 中断配置
数据的读写需要在中断中进行。
- 在中断触发后,通过
I2C_GetIntState()
来读取中断状态。不同状态需要参考I2C_STATUS_xxx
定义。- I2C_STATUS_FIFO_FULL:读取数据,并通过
I2C_FifoEmpty()
判断FIFO状态。 - I2C_STATUS_FIFO_EMPTY: 填充数据,并通过
I2C_FifoFull()
判断FIFO状态。 - I2C_STATUS_CMPL:一次传输数据结束,判断FIFO中是否有剩余数据并读取。
- I2C_STATUS_ADDRHIT:地址匹配,在该中断中通过
I2C_GetTransactionDir()
判断传输的方向。- I2C_TRANSACTION_MASTER2SLAVE:代表Master发送,Slave则需要读取数据。
- I2C_TRANSACTION_SLAVE2MASTER:代表Slave需要发送,Master读取数据。
- I2C_STATUS_FIFO_FULL:读取数据,并通过
- FIFO相关中断不需要清除标志,其余中断需要通过
I2C_ClearIntState()
来清除标志,避免中断重复触发。
8.2.2.5 编程指南
8.2.2.5.1 场景1:Master只读,Slave只写,不使用DMA
其中I2C配置为Master读操作,Slave收到地址后,将数据返回给Master,CPU操作读写,没有使用DMA。 配置之前需要决定使用的IO,请参考IO 配置。
8.2.2.5.1.1 Master配置
测试数据,每次传输10个字节(FIFO深度是8字节),每个传输单元必须是1字节。
#define DATA_CNT (10)
uint8_t read_data[DATA_CNT] = {0,};
uint8_t read_data_cnt = 0;
初始化I2C模块
此处配置为Master, 7bit地址,并打开了传输结束中断和FIFO FULL中断。
#define ADDRESS (0x71) void setup_peripherals_i2c_module(void) { (APB_I2C0,I2C_ROLE_MASTER,I2C_ADDRESSING_MODE_07BIT,ADDRESS); I2C_Config(APB_I2C0,I2C_CLOCKFREQUENY_STANDARD); I2C_ConfigClkFrequency(APB_I2C0,1); I2C_Enable(APB_I2C0,(1<<I2C_INT_CMPL)|(1 << I2C_INT_FIFO_FULL)); I2C_IntEnable}
Master中断实现
中断中通过I2C_STATUS_FIFO_FULL来读取接收到的数据。当I2C_STATUS_CMPL触发时,当前传输结束,需要判断FIFO中有没有剩余数据。
static uint32_t peripherals_i2c_isr(void *user_data) { uint32_t status = I2C_GetIntState(APB_I2C0); if(status & (1 << I2C_STATUS_FIFO_FULL)) { for(; read_data_cnt < DATA_CNT; read_data_cnt++) { if(I2C_FifoEmpty(APB_I2C0)){ break; } [read_data_cnt] = I2C_DataRead(APB_I2C0); read_data} } if(status & (1 << I2C_STATUS_CMPL)) { for(; read_data_cnt < DATA_CNT; read_data_cnt++) { [read_data_cnt] = I2C_DataRead(APB_I2C0); read_data} (APB_I2C0, (1 << I2C_STATUS_CMPL)); I2C_ClearIntState} return 0; }
Master触发传输
首先需要设置传输方向,此处是I2C_TRANSACTION_SLAVE2MASTER,即代表Slave发送数据,Master读取数据。
void peripheral_i2c_send_data(void) { (APB_I2C0,I2C_TRANSACTION_SLAVE2MASTER); I2C_CtrlUpdateDirection(APB_I2C0, DATA_CNT); I2C_CtrlUpdateDataCnt(APB_I2C0, I2C_COMMAND_ISSUE_DATA_TRANSACTION); I2C_CommandWrite}
使用流程
- 设置IO,setup_peripherals_i2c_pin()。
- 初始化I2C,setup_peripherals_i2c_module()。
- 在需要时候触发I2C读取,peripheral_i2c_send_data()。
- 检查中断状态。
8.2.2.5.1.2 Slave配置
测试数据,每次传输10个字节(fifo深度是8字节),每个传输单元必须是1字节。
#define DATA_CNT (10)
uint8_t write_data[DATA_CNT] = {0,};
uint8_t write_data_cnt = 0;
初始化I2C模块
对于Slave,需要打开I2C_INT_ADDR_HIT,此中断的触发代表收到了匹配的地址。
#define ADDRESS (0x71) void setup_peripherals_i2c_module(void) { (APB_I2C0,I2C_ROLE_SLAVE,I2C_ADDRESSING_MODE_07BIT,ADDRESS); I2C_Config(APB_I2C0,1); I2C_Enable(APB_I2C0,(1<<I2C_INT_ADDR_HIT)|(1<<I2C_INT_CMPL)); I2C_IntEnable}
Slave中断实现以及发送数据
首先需要等待I2C_STATUS_ADDRHIT中断,在该中断中通过
I2C_GetTransactionDir()
判断传输的方向。- I2C_TRANSACTION_MASTER2SLAVE:代表Slave需要读取数据,打开I2C_INT_FIFO_FULL中断。
- I2C_TRANSACTION_SLAVE2MASTER:代表Slave需要发送,打开I2C_INT_FIFO_EMPTY。
如果是Slave写操作,则会触发I2C_STATUS_FIFO_EMPTY中断,此时填写需要发送的数据,直到FIFO满。
等待I2C_STATUS_CMPL中断,该中断的触发代表传输结束,在中断中关闭打开的FIFO中断。
static uint32_t peripherals_i2c_isr(void *user_data) { static uint8_t dir = 2; uint32_t status = I2C_GetIntState(APB_I2C0); if(status & (1 << I2C_STATUS_ADDRHIT)) { = I2C_GetTransactionDir(APB_I2C0); dir if(dir == I2C_TRANSACTION_MASTER2SLAVE) { (APB_I2C0,(1 << I2C_INT_FIFO_FULL)); I2C_IntEnable} else if(dir == I2C_TRANSACTION_SLAVE2MASTER) { (APB_I2C0,(1 << I2C_INT_FIFO_EMPTY)); I2C_IntEnable} (APB_I2C0, (1 << I2C_STATUS_ADDRHIT)); I2C_ClearIntState} if(status & (1 << I2C_STATUS_FIFO_EMPTY)) { for(; write_data_cnt < DATA_CNT; write_data_cnt++) { if(I2C_FifoFull(APB_I2C0)){ break; } (APB_I2C0,write_data[write_data_cnt]); I2C_DataWrite} } if(status & (1 << I2C_STATUS_CMPL)) { (APB_I2C0, (1 << I2C_STATUS_CMPL)); I2C_ClearIntState// prepare for next if(dir == I2C_TRANSACTION_MASTER2SLAVE) { (APB_I2C0,(1 << I2C_INT_FIFO_FULL)); I2C_IntDisable} else if(dir == I2C_TRANSACTION_SLAVE2MASTER) { (APB_I2C0,(1 << I2C_INT_FIFO_EMPTY)); I2C_IntDisable} } return 0; }
使用流程:
- 设置IO,setup_peripherals_i2c_pin()。
- 初始化I2C,setup_peripherals_i2c_module()。
- 检查中断状态,在中断中发送数据,I2C_STATUS_CMPL中断代表传输结束。
8.2.2.5.2 场景2:Master只写,Slave只读,不使用DMA
其中I2C配置为Master写操作,Slave收到地址后,将从Master读取数据,CPU操作读写,没有使用DMA。 配置之前需要决定使用的IO,请参考IO 配置。
8.2.2.5.2.1 Master配置
测试数据,每次传输10个字节(FIFO深度是8字节),每个传输单元必须是1字节。
#define DATA_CNT (10)
uint8_t write_data[DATA_CNT] = {0,};
uint8_t write_data_cnt = 0;
初始化I2C模块
此处配置为Master, 7bit地址,并打开了传输结束中断和FIFO EMPTY中断。 I2C_INT_FIFO_EMPTY中断用来发送数据。
#define ADDRESS (0x71) void setup_peripherals_i2c_module(void) { (APB_I2C0,I2C_ROLE_MASTER,I2C_ADDRESSING_MODE_07BIT,ADDRESS); I2C_Config(APB_I2C0,I2C_CLOCKFREQUENY_STANDARD); I2C_ConfigClkFrequency(APB_I2C0,1); I2C_Enable(APB_I2C0,(1<<I2C_INT_CMPL)|(1 << I2C_INT_FIFO_EMPTY)); I2C_IntEnable}
Master中断实现
- 中断中通过I2C_STATUS_FIFO_EMPTY来发送数据。每次填充FIFO直到FIFO为满,当填充完最后一个数据后,需要关掉I2C_INT_FIFO_EMPTY,否则中断会继续触发。
- 当I2C_STATUS_CMPL触发时,当前传输结束。
static uint32_t peripherals_i2c_isr(void *user_data) { uint32_t status = I2C_GetIntState(APB_I2C0); if(status & (1 << I2C_STATUS_CMPL)) { (APB_I2C0, (1 << I2C_STATUS_CMPL)); I2C_ClearIntState} if(status & (1 << I2C_STATUS_FIFO_EMPTY)) { // push data until fifo is full for(; write_data_cnt < DATA_CNT; write_data_cnt++) { if(I2C_FifoFull(APB_I2C0)){ break; } (APB_I2C0,write_data[write_data_cnt]); I2C_DataWrite} // if its the last, disable empty int if(write_data_cnt == DATA_CNT) { (APB_I2C0,(1 << I2C_INT_FIFO_EMPTY)); I2C_IntDisable} } return 0; }
Master触发传输
首先需要设置传输方向,I2C_TRANSACTION_MASTER2SLAVE,即代表Master发送数据,Slave读取数据。 注意!!!中断发送数据完成后关闭了I2C_INT_FIFO_EMPTY(FIFO空)中断,如果需要开始新一次传输需要打开中断。 打开I2C_INT_FIFO_EMPTY:I2C_IntEnable(APB_I2C0,(1 << I2C_INT_FIFO_EMPTY));
void peripheral_i2c_send_data(void) { (APB_I2C0,I2C_TRANSACTION_MASTER2SLAVE); I2C_CtrlUpdateDirection(APB_I2C0, DATA_CNT); I2C_CtrlUpdateDataCnt(APB_I2C0, I2C_COMMAND_ISSUE_DATA_TRANSACTION); I2C_CommandWrite}
使用流程
- 设置IO,setup_peripherals_i2c_pin()。
- 初始化I2C,setup_peripherals_i2c_module()。
- 在需要时候发送I2C数据,peripheral_i2c_send_data()。
- 检查中断状态。
8.2.2.5.2.2 Slave配置
测试数据,每次传输10个字节(FIFO深度是8字节),每个传输单元必须是1字节。
#define DATA_CNT (10)
uint8_t read_data[DATA_CNT] = {0,};
uint8_t read_data_cnt = 0;
初始化I2C模块
对于Slave,需要打开I2C_INT_ADDR_HIT,此中断的触发代表收到了匹配的地址。
#define ADDRESS (0x71) void setup_peripherals_i2c_module(void) { (APB_I2C0,I2C_ROLE_SLAVE,I2C_ADDRESSING_MODE_07BIT,ADDRESS); I2C_Config(APB_I2C0,1); I2C_Enable(APB_I2C0,(1<<I2C_INT_ADDR_HIT)|(1<<I2C_INT_CMPL)); I2C_IntEnable}
Slave中断实现以及接收数据
首先需要等待I2C_STATUS_ADDRHIT中断,在该中断中通过
I2C_GetTransactionDir()
判断传输的方向。- I2C_TRANSACTION_MASTER2SLAVE:代表Slave需要读取数据,打开I2C_INT_FIFO_FULL中断。
- I2C_TRANSACTION_SLAVE2MASTER:代表Slave需要发送,打开I2C_INT_FIFO_EMPTY。
I2C_STATUS_FIFO_FULL的触发,代表FIFO中有接收到的数据,读取数据,直到FIFO变空。
I2C_STATUS_CMPL代表传输结束,检查FIFO中有没有剩余数据,并且关掉FIFO中断避免再次触发。
static uint32_t peripherals_i2c_isr(void *user_data) { static uint8_t dir = 2; uint32_t status = I2C_GetIntState(APB_I2C0); if(status & (1 << I2C_STATUS_ADDRHIT)) { = I2C_GetTransactionDir(APB_I2C0); dir if(dir == I2C_TRANSACTION_MASTER2SLAVE) { (APB_I2C0,(1 << I2C_INT_FIFO_FULL)); I2C_IntEnable} else if(dir == I2C_TRANSACTION_SLAVE2MASTER) { (APB_I2C0,(1 << I2C_INT_FIFO_EMPTY)); I2C_IntEnable} (APB_I2C0, (1 << I2C_STATUS_ADDRHIT)); I2C_ClearIntState} if(status & (1 << I2C_STATUS_FIFO_FULL)) { for(; read_data_cnt < DATA_CNT; read_data_cnt++) { if(I2C_FifoEmpty(APB_I2C0)){ break; } [read_data_cnt] = I2C_DataRead(APB_I2C0); read_data} } if(status & (1 << I2C_STATUS_CMPL)) { for(;read_data_cnt < DATA_CNT; read_data_cnt++) { [read_data_cnt] = I2C_DataRead(APB_I2C0); read_data} (APB_I2C0, (1 << I2C_STATUS_CMPL)); I2C_ClearIntState // prepare for next if(dir == I2C_TRANSACTION_MASTER2SLAVE) { (APB_I2C0,(1 << I2C_INT_FIFO_FULL)); I2C_IntDisable} else if(dir == I2C_TRANSACTION_SLAVE2MASTER) { (APB_I2C0,(1 << I2C_INT_FIFO_EMPTY)); I2C_IntDisable} } return 0; }
使用流程
- 设置IO,setup_peripherals_i2c_pin()。
- 初始化I2C,setup_peripherals_i2c_module()。
- 检查中断状态,I2C_STATUS_CMPL中断代表传输结束。
8.2.2.5.3 场景3:Master只读,Slave只写,使用DMA
其中I2C配置为Master读操作,Slave收到地址后,将数据返回给Master,DMA操作读写。 配置之前需要决定使用的IO,请参考IO 配置。
8.2.2.5.3.1 Master配置
测试数据,每次传输23个字节(FIFO深度是8字节),每个传输单元必须是1字节。
#define DATA_CNT (23)
uint8_t read_data[DATA_CNT] = {0,};
初始化I2C模块
此处配置为Master, 7bit地址,并打开了传输结束中断,由于使用DMA传输,因此不需要打开FIFO相关中断。其余配置和场景1相同。
#define ADDRESS (0x71) void setup_peripherals_i2c_module(void) { (APB_I2C0,I2C_ROLE_MASTER,I2C_ADDRESSING_MODE_07BIT,ADDRESS); I2C_Config(APB_I2C0,I2C_CLOCKFREQUENY_STANDARD); I2C_ConfigClkFrequency(APB_I2C0,1); I2C_Enable(APB_I2C0,(1<<I2C_INT_CMPL)); I2C_IntEnable}
初始化DMA模块
使用前需要配置DMA模块。
static void setup_peripherals_dma_module(void) { (1 << SYSCTRL_ClkGate_APB_DMA); SYSCTRL_ClearClkGateMulti(1); DMA_Reset(0); DMA_Reset}
Master中断实现
等待I2C_STATUS_CMPL中断的触发,该中断代表传输结束。
static uint32_t peripherals_i2c_isr(void *user_data) { uint32_t status = I2C_GetIntState(APB_I2C0); if(status & (1 << I2C_STATUS_CMPL)) { (APB_I2C0, (1 << I2C_STATUS_CMPL)); I2C_ClearIntState} return 0; }
Master DMA设置
该API实现了I2C0 RXFIFO到DMA的配置,细节请参考DMA文档。
void peripherals_i2c_rxfifo_to_dma(int channel_id, void *dst, int size) { ((aligned (8))); DMA_Descriptor descriptor __attribute__ .Next = (DMA_Descriptor *)0; descriptor(&descriptor,dst,SYSCTRL_DMA_I2C0, DMA_PreparePeripheral2Mem,DMA_ADDRESS_INC,0); size (channel_id, &descriptor); DMA_EnableChannel}
Master触发传输
- 打开I2C模块的DMA功能。
- 设置传输方向I2C_TRANSACTION_SLAVE2MASTER,与场景1相同。
- 设置需要传输的数据大小。
- 配置DMA,并使用I2C_COMMAND_ISSUE_DATA_TRANSACTION触发I2C传输。
void peripheral_i2c_send_data(void) { (APB_I2C0,1); I2C_DmaEnable(APB_I2C0,I2C_TRANSACTION_SLAVE2MASTER); I2C_CtrlUpdateDirection(APB_I2C0, DATA_CNT); I2C_CtrlUpdateDataCnt #define I2C_DMA_RX_CHANNEL (0)//DMA channel 0 (I2C_DMA_RX_CHANNEL, read_data, peripherals_i2c_rxfifo_to_dmasizeof(read_data)); (APB_I2C0, I2C_COMMAND_ISSUE_DATA_TRANSACTION); I2C_CommandWrite}
使用流程
- 设置IO,setup_peripherals_i2c_pin()。
- 初始化I2C,setup_peripherals_i2c_module()。
- 初始化DMA,setup_peripherals_dma_module()。
- 在需要时候触发I2C读取,peripheral_i2c_send_data()。
- 检查中断状态。
8.2.2.5.3.2 Slave配置
测试数据,每次传输23个字节(FIFO深度是8字节),每个传输单元必须是1字节。
#define DATA_CNT (23)
uint8_t write_data[DATA_CNT] = {0,};
初始化I2C模块
对于Slave,需要打开I2C_INT_ADDR_HIT,此中断的触发代表收到了匹配的地址。
#define ADDRESS (0x71) void setup_peripherals_i2c_module(void) { (APB_I2C0,I2C_ROLE_SLAVE,I2C_ADDRESSING_MODE_07BIT,ADDRESS); I2C_Config(APB_I2C0,1); I2C_Enable(APB_I2C0,(1<<I2C_INT_ADDR_HIT)|(1<<I2C_INT_CMPL)); I2C_IntEnable}
初始化DMA模块
使用前需要配置DMA模块。
static void setup_peripherals_dma_module(void) { (1 << SYSCTRL_ClkGate_APB_DMA); SYSCTRL_ClearClkGateMulti(1); DMA_Reset(0); DMA_Reset}
Slave中断实现以及发送数据
- 首先需要等待I2C_STATUS_ADDRHIT中断,在该中断中通过
I2C_GetTransactionDir()
判断传输的方向。- I2C_TRANSACTION_SLAVE2MASTER:代表Slave需要发送,设置DMA发送数据。
- I2C_STATUS_CMPL代表传输结束,关闭I2C DMA功能。
static uint32_t peripherals_i2c_isr(void *user_data) { static uint8_t dir = 2; uint32_t status = I2C_GetIntState(APB_I2C0); if(status & (1 << I2C_STATUS_ADDRHIT)) { = I2C_GetTransactionDir(APB_I2C0); dir if(dir == I2C_TRANSACTION_SLAVE2MASTER) { (); peripherals_i2c_write_data_dma_setup} (APB_I2C0, (1 << I2C_STATUS_ADDRHIT)); I2C_ClearIntState} if(status & (1 << I2C_STATUS_CMPL)) { (APB_I2C0,0); I2C_DmaEnable(APB_I2C0, (1 << I2C_STATUS_CMPL)); I2C_ClearIntState} return 0; }
- 首先需要等待I2C_STATUS_ADDRHIT中断,在该中断中通过
Slave发送数据以及DMA设置
该API实现了DMA传输数据到I2C0 FIFO的配置,细节请参考DMA文档。
void peripherals_i2c_dma_to_txfifo(int channel_id, void *src, int size) { ((aligned (8))); DMA_Descriptor descriptor __attribute__ .Next = (DMA_Descriptor *)0; descriptor(&descriptor,SYSCTRL_DMA_I2C0, DMA_PrepareMem2Peripheral,size,DMA_ADDRESS_INC,0); src (channel_id, &descriptor); DMA_EnableChannel}
设置DMA并打开I2C DMA功能。
void peripherals_i2c_write_data_dma_setup(void) { #define I2C_DMA_TX_CHANNEL (0)//DMA channel 0 (I2C_DMA_TX_CHANNEL, write_data, peripherals_i2c_dma_to_txfifosizeof(write_data)); (APB_I2C0, DATA_CNT); I2C_CtrlUpdateDataCnt(APB_I2C0,1); I2C_DmaEnable}
使用流程
- 设置IO,setup_peripherals_i2c_pin()。
- 初始化I2C,setup_peripherals_i2c_module()。
- 初始化DMA,setup_peripherals_dma_module()。
- 检查中断状态,在中断中设置DMA发送数据,I2C_STATUS_CMPL中断代表传输结束。
8.2.2.5.4 场景4:Master只写,Slave只读,使用DMA
其中I2C配置为Master写操作,Slave收到地址后,读取Master发送的数据,DMA操作读写。 配置之前需要决定使用的IO,请参考IO 配置。
8.2.2.5.4.1 Master配置
测试数据,每次传输23个字节(FIFO深度是8字节),每个传输单元必须是1字节。
#define DATA_CNT (23)
uint8_t write_data[DATA_CNT] = {0,};
初始化I2C模块
请参考场景3中Master配置的
初始化I2C模块
。初始化DMA模块
请参考场景3中Master配置的
初始化DMA模块
。I2C中断实现
请参考场景3中Master配置的
Master中断实现
。I2C Master DMA设置
该API实现了DMA传输数据到I2C0 FIFO的配置,细节请参考DMA文档。
void peripherals_i2c_dma_to_txfifo(int channel_id, void *src, int size) { ((aligned (8))); DMA_Descriptor descriptor __attribute__ .Next = (DMA_Descriptor *)0; descriptor(&descriptor,SYSCTRL_DMA_I2C0, DMA_PrepareMem2Peripheral,size,DMA_ADDRESS_INC,0); src (channel_id, &descriptor); DMA_EnableChannel}
I2C Master触发传输
- 打开I2C DMA 功能。
- 设置传输方向为I2C_TRANSACTION_MASTER2SLAVE,和场景2相同。
- 配置DMA,并使用I2C_COMMAND_ISSUE_DATA_TRANSACTION触发I2C传输。
void peripheral_i2c_send_data(void) { (APB_I2C0,1); I2C_DmaEnable(APB_I2C0,I2C_TRANSACTION_MASTER2SLAVE); I2C_CtrlUpdateDirection(APB_I2C0, DATA_CNT); I2C_CtrlUpdateDataCnt #define I2C_DMA_TX_CHANNEL (0)//DMA channel 0 (I2C_DMA_TX_CHANNEL, write_data, peripherals_i2c_dma_to_txfifosizeof(write_data)); (APB_I2C0, I2C_COMMAND_ISSUE_DATA_TRANSACTION); I2C_CommandWrite }
使用流程
- 设置IO,setup_peripherals_i2c_pin()。
- 初始化I2C,setup_peripherals_i2c_module()。
- 初始化DMA,setup_peripherals_dma_module()。
- 在需要时候触发I2C读取,peripheral_i2c_send_data()。
- 检查中断状态。
8.2.2.5.4.2 Slave配置
测试数据,每次传输23个字节(FIFO深度是8字节),每个传输单元必须是1字节。
#define DATA_CNT (23)
uint8_t read_data[DATA_CNT] = {0,};
初始化I2C模块
请参考场景3中Slave配置的
初始化I2C模块
。初始化DMA模块
请参考场景3中Slave配置的
初始化DMA模块
。Slave中断实现以及接收数据
- 首先需要等待I2C_STATUS_ADDRHIT中断,在该中断中通过
I2C_GetTransactionDir()
判断传输的方向。- I2C_TRANSACTION_MASTER2SLAVE:代表Slave需要接收,设置DMA接收数据。
- I2C_STATUS_CMPL代表传输结束,关闭I2C DMA功能。
static uint32_t peripherals_i2c_isr(void *user_data) { static uint8_t dir = 2; uint32_t status = I2C_GetIntState(APB_I2C0); if(status & (1 << I2C_STATUS_ADDRHIT)) { = I2C_GetTransactionDir(APB_I2C0); dir if(dir == I2C_TRANSACTION_MASTER2SLAVE) { (); peripherals_i2c_read_data_dma_setup} (APB_I2C0, (1 << I2C_STATUS_ADDRHIT)); I2C_ClearIntState} if(status & (1 << I2C_STATUS_CMPL)) { (APB_I2C0,0); I2C_DmaEnable(APB_I2C0, (1 << I2C_STATUS_CMPL)); I2C_ClearIntState} return 0; }
- 首先需要等待I2C_STATUS_ADDRHIT中断,在该中断中通过
Slave 发送数据以及DMA设置
该API实现了I2C0 RXFIFO到DMA的配置,细节请参考DMA文档。
void peripherals_i2c_rxfifo_to_dma(int channel_id, void *dst, int size) { ((aligned (8))); DMA_Descriptor descriptor __attribute__ .Next = (DMA_Descriptor *)0; descriptor(&descriptor,dst,SYSCTRL_DMA_I2C0, DMA_PreparePeripheral2Mem,DMA_ADDRESS_INC,0); size (channel_id, &descriptor); DMA_EnableChannel}
设置DMA并打开I2C DMA功能。
void peripherals_i2c_read_data_dma_setup(void) { #define I2C_DMA_RX_CHANNEL (0)//DMA channel 0 (I2C_DMA_RX_CHANNEL, read_data, peripherals_i2c_rxfifo_to_dmasizeof(read_data)); (APB_I2C0, DATA_CNT); I2C_CtrlUpdateDataCnt(APB_I2C0,1); I2C_DmaEnable}
使用流程
- 设置IO,setup_peripherals_i2c_pin()。
- 初始化I2C,setup_peripherals_i2c_module()。
- 初始化DMA,setup_peripherals_dma_module()。
- 检查中断状态,在中断中设置DMA读取数据,I2C_STATUS_CMPL中断代表传输结束。
8.2.2.5.5 场景5:Master/Slave同时读写
其中I2C操作为,首先执行写操作然后再执行读操作,CPU操作读写,没有使用DMA。 配置之前需要决定使用的IO,请参考IO 配置。
8.2.2.5.5.1 Master配置
测试数据,每次传输8个字节(FIFO深度是8字节),每个传输单元必须是1字节。
#define DATA_CNT (8)
uint8_t write_data[DATA_CNT] = {0,};
uint8_t write_data_cnt = 0;
uint8_t read_data[DATA_CNT] = {0,};
uint8_t read_data_cnt = 0;
初始化I2C模块
- 配置为Master, 7bit地址。
- 打开传输结束中断和ADDR_HIT中断。对于Master来说,如果有Slave响应了该地址,则会有I2C_INT_ADDR_HIT中断。
#define ADDRESS (0x71) void setup_peripherals_i2c_module(void) { (APB_I2C0,I2C_ROLE_MASTER,I2C_ADDRESSING_MODE_07BIT,ADDRESS); I2C_Config(APB_I2C0,I2C_CLOCKFREQUENY_STANDARD); I2C_ConfigClkFrequency(APB_I2C0,1); I2C_Enable(APB_I2C0,(1<<I2C_INT_CMPL)|(1<<I2C_INT_ADDR_HIT)); I2C_IntEnable }
Master中断实现
- I2C_STATUS_ADDRHIT,代表Slave响应了Master发送的地址:
- 如果Master执行写操作,需要打开I2C_INT_FIFO_EMPTY,用来发送数据。
- 如果Master执行读操作,需要打开I2C_INT_FIFO_FULL,用来接收数据。
- 如果Master执行写操作,I2C_STATUS_FIFO_EMPTY中断会出现,此时需要填充待发送的数据。如果数据发送完成,需要关闭I2C_STATUS_FIFO_EMPTY中断,避免重复触发。
- 如果Master执行读操作,I2C_STATUS_FIFO_FULL中断会出现,此时需要读取数据。
- I2C_STATUS_CMPL,代表传输结束:
- 如果Master执行写操作,代表写成功。
- 如果Master执行读操作,需要读取FIFO中的剩余数据。
uint8_t master_write_flag = 0; static uint32_t peripherals_i2c_isr(void *user_data) { uint32_t status = I2C_GetIntState(APB_I2C0); if(status & (1 << I2C_STATUS_ADDRHIT)) { if(master_write_flag) { (APB_I2C0,(1 << I2C_INT_FIFO_FULL)); I2C_IntDisable(APB_I2C0,(1<<I2C_INT_CMPL)|(1 << I2C_INT_FIFO_EMPTY)); I2C_IntEnable} else { (APB_I2C0,(1 << I2C_INT_FIFO_EMPTY)); I2C_IntDisable(APB_I2C0,(1<<I2C_INT_CMPL)|(1 << I2C_INT_FIFO_FULL)); I2C_IntEnable} (APB_I2C0, (1 << I2C_STATUS_ADDRHIT)); I2C_ClearIntState} if(status & (1 << I2C_STATUS_FIFO_EMPTY)) { if(master_write_flag) { // push data until fifo is full for(; write_data_cnt < DATA_CNT; write_data_cnt++) { if(I2C_FifoFull(APB_I2C0)){ break; } (APB_I2C0,write_data[write_data_cnt]); I2C_DataWrite} // if its the last, disable empty int if(write_data_cnt == DATA_CNT) { (APB_I2C0,(1 << I2C_INT_FIFO_EMPTY)); I2C_IntDisable} } } if(status & (1 << I2C_STATUS_FIFO_FULL)) { if(!master_write_flag) { for(; read_data_cnt < DATA_CNT; read_data_cnt++) { if(I2C_FifoEmpty(APB_I2C0)){ break; } [read_data_cnt] = I2C_DataRead(APB_I2C0); read_data} } } // 传输结束中断,代表DATA_CNT个字节接收或者发射完成 if(status & (1 << I2C_STATUS_CMPL)) { if(master_write_flag) { (APB_I2C0, (1 << I2C_STATUS_CMPL)); I2C_ClearIntState} else { for(; read_data_cnt < DATA_CNT; read_data_cnt++) { [read_data_cnt] = I2C_DataRead(APB_I2C0); read_data} (APB_I2C0, (1 << I2C_STATUS_CMPL)); I2C_ClearIntState // prepare for next } } return 0; }
- I2C_STATUS_ADDRHIT,代表Slave响应了Master发送的地址:
Master写传输
首先需要设置传输方向,I2C_TRANSACTION_MASTER2SLAVE,即代表Master发送数据,Slave读取数据。
void peripheral_i2c_write_data(void) { = 1; master_write_flag //write data use fifo empty Interrupt,so disable I2C_INT_FIFO_FULL,enable I2C_INT_FIFO_EMPTY. (APB_I2C0,(1 << I2C_INT_FIFO_FULL)); I2C_IntDisable(APB_I2C0,(1 << I2C_INT_FIFO_EMPTY)); I2C_IntEnable(APB_I2C0,I2C_TRANSACTION_MASTER2SLAVE); I2C_CtrlUpdateDirection(APB_I2C0, DATA_CNT); I2C_CtrlUpdateDataCnt(APB_I2C0, I2C_COMMAND_ISSUE_DATA_TRANSACTION); I2C_CommandWrite}
Master读传输
首先需要设置传输方向,此处是I2C_TRANSACTION_SLAVE2MASTER,即代表Slave发送数据,Master读取数据。
void peripheral_i2c_read_data(void) { = 0; master_write_flag //read data use fifo full Interrupt,so disable I2C_INT_FIFO_EMPTY,enable I2C_INT_FIFO_FULL. (APB_I2C0,(1 << I2C_INT_FIFO_EMPTY)); I2C_IntDisable(APB_I2C0,(1 << I2C_INT_FIFO_FULL)); I2C_IntEnable(APB_I2C0,I2C_TRANSACTION_SLAVE2MASTER); I2C_CtrlUpdateDirection(APB_I2C0, DATA_CNT); I2C_CtrlUpdateDataCnt(APB_I2C0, I2C_COMMAND_ISSUE_DATA_TRANSACTION); I2C_CommandWrite}
使用流程
- 设置IO,setup_peripherals_i2c_pin()。
- 初始化I2C,setup_peripherals_i2c_module()。
- 在需要时候触发I2C写数据,peripheral_i2c_write_data()。
- 当写结束后,可以触发I2C读取,peripheral_i2c_read_data()。
- 检查中断状态。
8.2.2.5.5.2 Slave配置
初始化I2C模块
- 配置为Slave, 7bit地址。
- 打开传输结束中断和ADDR_HIT中断。对于Slave来说,如果有匹配的地址,则会有I2C_INT_ADDR_HIT中断。
#define ADDRESS (0x71) void setup_peripherals_i2c_module(void) { (APB_I2C0,I2C_ROLE_SLAVE,I2C_ADDRESSING_MODE_07BIT,ADDRESS); I2C_Config(APB_I2C0,1); I2C_Enable(APB_I2C0,(1<<I2C_INT_ADDR_HIT)|(1<<I2C_INT_CMPL)); I2C_IntEnable}
Slave中断实现
- I2C_STATUS_ADDRHIT,在该中断中通过
I2C_GetTransactionDir()
判断传输的方向:- I2C_TRANSACTION_MASTER2SLAVE:代表Slave需要读取数据,打开I2C_INT_FIFO_FULL中断。
- I2C_TRANSACTION_SLAVE2MASTER:代表Slave需要发送,打开I2C_INT_FIFO_EMPTY。
- 如果Slave需要发送,I2C_STATUS_FIFO_EMPTY中断会出现,此时需要填充待发送的数据。
- 如果Slave需要读取数据,I2C_STATUS_FIFO_FULL中断会出现,此时需要读取数据。
- I2C_STATUS_CMPL,代表传输结束:
- 如果Slave执行写操作,代表写成功,关闭I2C_STATUS_FIFO_EMPTY中断。
- 如果Slave执行读操作,需要读取FIFO中的剩余数据,关闭I2C_STATUS_FIFO_FULL中断。
static uint32_t peripherals_i2c_isr(void *user_data) { static uint8_t dir = 2; uint32_t status = I2C_GetIntState(APB_I2C0); if(status & (1 << I2C_STATUS_ADDRHIT)) { = I2C_GetTransactionDir(APB_I2C0); dir if(dir == I2C_TRANSACTION_MASTER2SLAVE) { = 1; master_write_flag (APB_I2C0,(1 << I2C_INT_FIFO_FULL)); I2C_IntEnable} else if(dir == I2C_TRANSACTION_SLAVE2MASTER) { = 0; master_write_flag (APB_I2C0,(1 << I2C_INT_FIFO_EMPTY)); I2C_IntEnable} (APB_I2C0, (1 << I2C_STATUS_ADDRHIT)); I2C_ClearIntState } if(status & (1 << I2C_STATUS_FIFO_EMPTY)) { // master read if(!master_write_flag) { // push data until fifo is full for(; write_data_cnt < DATA_CNT; write_data_cnt++) { if(I2C_FifoFull(APB_I2C0)){break;} (APB_I2C0,write_data[write_data_cnt]); I2C_DataWrite} } } if(status & (1 << I2C_STATUS_FIFO_FULL)) { // master write if(master_write_flag) { for(; read_data_cnt < DATA_CNT; read_data_cnt++) { if(I2C_FifoEmpty(APB_I2C0)){break;} [read_data_cnt] = I2C_DataRead(APB_I2C0); read_data} } } if(status & (1 << I2C_STATUS_CMPL)) { if(master_write_flag) { for(;read_data_cnt < DATA_CNT; read_data_cnt++) { [read_data_cnt] = I2C_DataRead(APB_I2C0); read_data} (APB_I2C0, (1 << I2C_STATUS_CMPL)); I2C_ClearIntState} else { (APB_I2C0, (1 << I2C_STATUS_CMPL)); I2C_ClearIntState} if(dir == I2C_TRANSACTION_MASTER2SLAVE) { (APB_I2C0,(1 << I2C_INT_FIFO_FULL)); I2C_IntDisable} else if(dir == I2C_TRANSACTION_SLAVE2MASTER) { (APB_I2C0,(1 << I2C_INT_FIFO_EMPTY)); I2C_IntDisable} } return 0; }
- I2C_STATUS_ADDRHIT,在该中断中通过
使用流程
- 设置IO,setup_peripherals_i2c_pin()。
- 初始化I2C,setup_peripherals_i2c_module()。
- 检查中断状态,在中断中发送数据,I2C_STATUS_CMPL中断代表传输结束。
- 如果是读操作,slave应该在master_write_flag=0之后准备好数据写到FIFO。
8.2.3 时钟配置
I2C时钟配置使用API:
/**
* @brief Set clk frequency for controller.
* @param[in] I2C_BASE base address
* @param[in] option see I2C_ClockFrequenyOptions
*/
void I2C_ConfigClkFrequency(I2C_TypeDef *I2C_BASE, I2C_ClockFrequenyOptions option);
其中option中定义了几个可选项(I2C时钟和系统时钟有关系,以下枚举可能需要根据实际时钟调整):
typedef enum
{
,
I2C_CLOCKFREQUENY_NULL,//up to 100kbit/s
I2C_CLOCKFREQUENY_STANDARD,//up to 400kbit/s
I2C_CLOCKFREQUENY_FASTMODE,//up to 1Mbit/s
I2C_CLOCKFREQUENY_FASTMODE_PLUS
I2C_CLOCKFREQUENY_MANUAL} I2C_ClockFrequenyOptions;
如果选择MANUAL,需要手动配置相关寄存器来生成需要的时钟:
I2C_BASE->TPM
: 乘数因子, 位宽5bit, 所有I2C_BASE->Setup
中的时间参数都会被乘以(TPM+1)。I2C_BASE->Setup
: 使用I2C_ConfigSCLTiming()
配置该寄存器,参数如下:scl_hi
:高电平持续时间,位宽 9bit,默认 0x10。scl_ratio
: 低电平持续时间因子,位宽 1bit,默认 1。hddat
:SCL拉低后SDA的保持时间,位宽 5bit,默认 5。sp
: 可以被过滤的脉冲毛刺宽度,位宽 3bit,默认 1。sudat
: 释放SCL之前的数据建立时间,位宽 5bit,默认 5。
每个参数和时钟的计算关系如下:
高电平持续时间计算:
\(SCL high period = (2\times pclk)+(2+sp+sclhi)\times pclk\times(TPM+1)\)
其中\(pclk\)为I2C模块的系统时钟,默认为24M,实际时钟可以从SYSCTRL_GetClk()
获取。如果\(sp = 1, pclk = 42ns, TPM = 3, sclhi = 150\) ,则:
\(SCL high period = (2\times42)+(2+1+150)\times42\times(3+1) = 25788ns\)
低电平持续时间计算:
\(SCL low period = (2\times pclk)+(2+sp+sclhi\times (sclratio+1))\times pclk\times (TPM+1)\)
如果\(sp = 1, pclk = 42ns, TPM = 3, sclhi = 150, sclratio = 0\),则:
\(SCL low period = (2\times42)+(2+1+150\times1)\times42\times(3 + 1) = 25788ns\)
毛刺抑制宽度: \(spike suppression width = sp\times pclk\times (TPM+1)\)
如果\(sp = 1, pclk = 42ns, TPM = 3\),则:
\(spike suppression width = 1\times42\times(3+1) = 168ns\)
SCL之前的数据建立时间:
\(setup time = (2\times pclk)+(2+sp+sudat)\times pclk\times (TPM+1)\)
如果\(sp = 1, pclk = 42ns, TPM = 3, sudat = 5\),则:
\(setup time = (2\times42)+(2+1+5)\times42\times(3+1) = 1428ns\)
协议对SCL之前的数据建立时间要求为:
- standard mode: 最小250ns。 - fast mode: 最小100ns。 - fast mode plus: 最小50ns。
SCL拉低后SDA的保持时间:
\(hold time = (2\times pclk)+(2+sp+hddat)\times pclk\times (TPM+1)\)
如果\(sp = 1, pclk = 42ns, TPM = 3, hddat = 5\),则:
\(hold time = (2\times 42)+(2+1+5)\times 42\times(3+1) = 1428ns\)
协议对SCL拉低后SDA的保持时间要求为:
- standard mode: 最小300ns。 - fast mode: 最小300ns。 - fast mode plus: 最小0ns。