概览
IIC总线,有两条线构成 。利用开漏结构构成的总线,低 开漏,高 上拉。 SDA数据线,用于在总线上传输数据,时钟高数据有效。数据线上拉电阻拉高,IC通信口默认高阻态。 SCL时钟线,时钟信号。驱动移位寄存器,提供数据传输的节拍。时钟默认上拉电阻拉高。IC口默认高阻态。 通信速率与要求 通信过程
START:数据线默认高电平,在SCL为高的状态下拉低数据线打破默认状态。表示有数据即将发送,总线各设备做好准备。处于休眠中的设备会在这个时刻唤醒。 P-STOP:在SCL为高的状态下数据线恢复静默高电平。表示本次通信结束。总线数据线恢复到静默状态。 数据传输最先传输最高有效位 SLAVE ADDRESS:从机地址,表示接下来的数据归属,总线上N个设备,该数据发送给谁。这个设备被动接收数据。 R/W:信号是读还是写 A:回复确认信号有效为 低 (非静默状态) 过程:有设备需要使用总线发送数据。 1.设备输出SCL,SCL总线开始。 2.设备发送START bit,此时CLK保持高有效 3.CLK 拉低 开始第一个时钟 4.在时钟节拍下,SDA数据按照MSB 依次发送。 5.发送器每发送一个字节后,在第9个时钟脉冲期间释放数据线。发送完成后,主机等待从机ACK–A。
几种模式下的参数要求
术语
总线仲裁。
多个主机发起通信需求 ,最先出现1的设备被淘汰,因为静默电平位1
可以理解为SCL被拉低时,相当于SCL接地,那么其他设备无法把SCL拉高。 同理数据,SDA一旦被拉低,发出的高信号无法传达至总线,所以信息传递失败,故仲裁出局。
主设备发送数据 0 时,直接拉低SDA数据线通过上图的方式。所以这个时候其他设备发出高,SDA数据线上的电平任然是0.
读写时序
主机发起通信,写数据 1.起始 ,SDA和SCL都为高的状态下。SDA由高变低打破静默状态 这是传输数据的一个开始 2.主机需要把传输的数据(地址信息),放到数据寄存器中。然后在硬件的主导下按照SCL的节拍依次把移位寄存器的内容发送到SDA总线上。从机需要在SCL高电平期间读取SDA信号。在 硬件的主导下按照SCL的节拍依次读取电平装入移位寄存器。 主机需要给 数据寄存器装填数据。硬件检查数据装填完成后,开始发送数据。 3.从机在硬件主导下开始计数,在第八个时钟下降沿处,准备做应答ACK。主机这个时候需要读取ACK消息,所以此时主机要释放SDA线(浮空),这时从机的 ACK (低)输出到SDA上,主机在SCL高电平上读取ACK 这个过程不需要CPU的 参与所以,硬件完全主导。 4.下个时刻主机开始传输第二字节的数据,那么需要 CPU或者DMA的参与把 数据装入数据寄存器中。装载完成后开始数据 传输。 读时序 同理 深刻的理解通信时序后,对 硬件主导的I2C机制理解会轻松许多 。
STM32的I2C
STM32 I2C结构
I2C核心为数据移位寄存器,按照SCLK移入或者发出数据。用于数据缓存的数据寄存器。 默认工作在从机模式下,当 接收到起始位后,移位 寄存器接收SDA线上的数据,接收7位后与 自己的地址进行比较。 头或地址不匹配:接口会忽略它并等待下一个起始位。 地址匹配:接口会依次: ● 发出应答脉冲(如果 ACK 位置 1) ● ADDR 位会由硬件置 1 并在 ITEVFEN 位置 1 时生成一个中断。 ● 如果 ENDUAL=1,则软件必须读取 DUALF 位状态来核对哪些从地址进行了应答。 很长的一段, 上一个章节分析的很详细。为什么要设置EV阶段 1。等待上一个状态的结束 2. 下一个阶段的准备 I2C GPIO初始化
void I2C_GPIO_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource8,GPIO_AF_I2C1);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource9,GPIO_AF_I2C1);
}
重点 .GPIO_OType = GPIO_OType_OD; 开漏输出 开漏输出就是不输出电压,控制输出低电平时引脚接地,控制输出高电平时引脚既不输出高电平,也不输出低电平,为高阻态。 SDA,SCK 默认拉高,所以必须使用开漏模式。
void I2C_cfgInit(void)
{
I2C_InitTypeDef iic_init_struct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
iic_init_struct.I2C_Ack = I2C_Ack_Enable;
iic_init_struct.I2C_AcknowledgedAddress =I2C_AcknowledgedAddress_7bit;
iic_init_struct.I2C_ClockSpeed = 100000;
iic_init_struct.I2C_DutyCycle = I2C_DutyCycle_2;
iic_init_struct.I2C_Mode =I2C_Mode_I2C;
iic_init_struct.I2C_OwnAddress1 = 0X0A;
I2C_Init(I2C1, &iic_init_struct);
I2C_Cmd(I2C1, ENABLE);
}
总线的初始化非常常规,没有特别的地方。
数据发送 数据的发送 比较复杂
1.S位发送起始位 EV5事件 SB=1(SB?) SB Start Bit 也就是说发送其实位后,需要读取SB 后再把地址写入数据寄存器 1.发送起始位,SB自动1.读取SR1 后,切入数据到数据寄存器,SB 清零。 所以需要等待SB为1. 2.写入地址数据到 DR 数据寄存器 3.从机应答ACK(硬件主导) 4。EV6,EV8 EV6发送数据后接收到应答,ADDR=1 表示地址成功发送。 EV8 数据寄存器空,表示数据发送完成 5.写入数据Data1 6.从机应答(硬件)EV8确定数据发送完成
7.EV8_2停止 8.P停止位
库函数版本没有对,I2C全过程进行封装,需要用户一步一步来调用
uint32_t I2C_EE_PageWrite(u8* pBuffer, u8 WriteAddr, u8 NumByteToWrite)
{
I2CTimeout = I2CT_LONG_TIMEOUT;
while(I2C_GetFlagStatus(EEPROM_I2C, I2C_FLAG_BUSY))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(4);
}
I2C_GenerateSTART(EEPROM_I2C, ENABLE);
I2CTimeout = I2CT_FLAG_TIMEOUT;
while(!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(5);
}
I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter);
I2CTimeout = I2CT_FLAG_TIMEOUT;
while(!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(6);
}
I2C_SendData(EEPROM_I2C, WriteAddr);
I2CTimeout = I2CT_FLAG_TIMEOUT;
while(! I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(7);
}
while(NumByteToWrite--)
{
I2C_SendData(EEPROM_I2C, *pBuffer);
pBuffer++;
I2CTimeout = I2CT_FLAG_TIMEOUT;
while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(8);
}
}
I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
return 1;
}
所谓事件就是发送过程中硬件的输入条件,硬件执行通信协议需要按照时序和逻辑来发送数据。这些数据需要通信模块以外的部分来提供 所以产生事件。 库函数事件
stm32f4xx_i2c.h 中定义好事件对应的位 也就是Evx 使用 库函数 检测到对应事件的发生。
ErrorStatus I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
使用 while等待
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
}
通过这个结构,可以判断通信的步骤是否完成,以便进行下一部动作。
S -->等待EV5->发送地址->ACK->等待EV6,EV8->发送数据->EV8->ACK 。。。。。
主接收器的传输序列图 大同小异
主要部分就是 S ,地址,Data ,Wait(EVx) 所以只需要4个 API
S
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState)
地址
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction)
Data
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data)
Wait(EVx)
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
}
|