I2C 通讯协议(Inter-Integrated Circuit),I2C是一种通信协议!!和USART串口和SPI、SDIO等等都是通信协议。由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、 CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。
类似的,学习通信协议通常分为物理层和协议层来学习:
一.物理层
1.I2C常用的连接方式,百试不厌
特点主要有:
(1)支持多设备的总线
(2)一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
(3)每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
(4)总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
注意::I2C协议中,0的优先级高于3.3v,当不发送数据时都为高阻态。想发送逻辑1时,先查看是否为总线是否为3.3v,若为3.3v则发送,若为0v,表面有其他设备在发送逻辑0。
(5)标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式
下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
高速模式目前用FPGA实现
二.协议层
I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。
1.基本读写过程
2.起始和停止信号
当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。当 SCL 是高电平时 SDA线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。
3.数据有效性
I2C 使用 SDA 信号线来传输数据,使用 SCL 信号线进行数据同步。 SDA
数据线在 SCL 的每个时钟周期传输一位数据。
传输时, SCL 为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL为低电平时, SDA 的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据
做好准备。每次数据传输都以字节为单位,每次传输的字节数不受限制。
4.地址及数据方向
I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,也就是第8位。
7位地址 000 1011
8位读 0001 0111
8位写 0001 0110
8位=7位<<1|读/写
5.响应
响应包括“应答(ACK)”和“非应答(NACK)”两种信号。
作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。
当发送端发送完毕后,将SDA控制权交给接收端,若 接收端SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。
三.I2C特性
有分软件模式(控制每个时刻的引脚状态,比较繁琐,一般不使用)
硬件模式(STM32的专门片上外设I2C,复杂I2C通信协议)
四.I2C的外设介绍
1.F407的通信引脚
都是根据GPIO引脚的复用功能:
2.时钟控制逻辑
SCL 线的时钟信号,由 I2C 接口根据时钟控制寄存器(CCR)控制,控制的参数主要为时钟频率。
时钟控制寄存器如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020072410235421.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ZpbGFfdw==,size_16,color_FFFFFF,t_70
在快速模式下可选择 SCL 时钟的占空比,可选 Tlow/Thigh=2 或 Tlow/Thigh=16/9 模式。
STM32 的 I2C 外设都挂载在 APB1 总线上,因此,使用APB1的时钟源PCLK1=42MHz,若要配置快速模式400Kbit/s,计算如下:
(1)PCLK 时钟周期: TPCLK1 = 1/42000000
(2)目标 SCL 时钟周期: TSCL = 1/400000
(3)选择Tlow/Thigh=2
(4)SCL 时钟周期内的高电平时间: THIGH = TSCL/3
(5)SCL 时钟周期内的低电平时间: TLOW = 2TSCL/3
(6)带入公式:Thigh = CCR1/TPCLK1 = TSCL/3
(7) 得到CCR为35
3.数据逻辑控制
I2C 的 SDA 信号主要连接到数据移位寄存器上,数据移位寄存器的数据来源及目标是数据寄存器(DR)、地址寄存器(OAR)、 PEC 寄存器以及 SDA 数据线。
发送:当向外发送数据的时候,数据移位寄存器以“数据寄存器”为数据源,把数据一位一位地通过 SDA 信号线发送出去;
接收:当从外部接收数据的时候,数据移位寄存器把 SDA 信号线采样到的数据一位一位地存储到“数据寄存器”中。
当 STM32 的 I2C 工作在从机模式的时候,接收到设备地址信号时,数据移位寄存器会把接收到的地址与 STM32 的自身的“I2C 地址寄存器”的值作比较,以便响应主机的寻址。
STM32 的自身 I2C 地址可通过修改“自身地址寄存器”修改,支持同时使用两个 I2C 设备地址,两个地址分别存储在 OAR1 和 OAR2 中。
4.整体控制逻辑
整体控制逻辑负责协调整个 I2C 外设,控制逻辑的工作模式根据我们配置的“控制寄存器(CR1/CR2)”的参数而改变。
在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR1 和 SR2)”
除此之外,控制逻辑还根据要求,负责控制产生 I2C 中断信号、 DMA 请求
及各种 I2C 的通讯信号(起始、停止、响应信号等)。
五.通信过程
使用 I2C 外设通讯时,在通讯的不同阶段它会对“状态寄存器(SR1 及 SR2)”的不同数据位写入参数,我们通过读取这些寄存器标志来了解通讯状态。
1.主发送器
在收发过程中,主要对应着一些事件中断如下所述:
(1)控制产生起始信号(S),当发生起始信号后,它产生事件“EV5”,并会对 SR1 寄存器的“SB”位置 1,表示起始信号已经发送;
(2) 紧接着发送设备地址并等待应答信号,若有从机应答,则产生事件“EV6”及“EV8”,这时 SR1 寄存器的“ADDR”位及“TXE”位被置 1, ADDR 为 1 表
示地址已经发送, TXE 为 1 表示数据寄存器为空;
(3) 以上步骤正常执行并对 ADDR 位清零后,我们往 I2C 的“数据寄存器 DR”写入要发送的数据,这时 TXE 位会被重置 0,表示数据寄存器非空, I2C 外设通过SDA 信号线一位位把数据发送出去后,又会产生“EV8”事件,即 TXE 位被置 1,重复这个过程,就可以发送多个字节数据了;
(4) 当我们发送数据完成后,控制 I2C 设备产生一个停止信号§,这个时候会产生EV2 事件, SR1 的 TXE 位及 BTF 位都被置 1,表示通讯结束。
假如我们使能了 I2C 中断,以上所有事件产生时,都会产生 I2C 中断信号,进入同一个中断服务函数,到 I2C 中断服务程序后,再通过检查寄存器位来了解是哪一个事件。
2.主接收器
接收端时,除了有最开始的EV5事件表起始信号,EV6事件表示地址发送完成信号,还有如下:
(1) 从机端接收到地址后,开始向主机端发送数据。当主机接收到这些数据后,会产生“EV7”事件, SR1 寄存器的 RXNE 被置 1,表示接收数据寄存器非空,我们读取该寄存器后,可对数据寄存器清空,以便接收下一次数据。此时我们可以控制 I2C 发送应答信号(ACK)或非应答信号(NACK),若应答,则重复以上步骤接收数据,若非应答,则停止传输;
(2) 发送非应答信号后,产生停止信号§,结束传输。
六.总结在I2C-EEPROM学习时的主要函数
首先附一张EEPROM的硬件连接图(型号:AT24C02)
实验中 STM32 的 I2C 外设采用主模式,分别用作主发送器和主接收器,通过查询事件的方式来确保正常通讯。
此芯片的地址型号为如下:
前四位固定,后三位按连接均为0,因此地址为1010000,当为读模式时,其值为0xA1;写模式为,0xA0。
初始化结构体,GPIO等不再赘述。
(1)向 EEPROM 写入一个字节的数据
//addr:写入的存储单元地址
//data:写入的数据
uint8_t EEPROM_Byte_Write(uint8_t addr,uint8_t data)
{//产生起始信号I2C_GenerateSTART(EEPROM_I2C,ENABLE);count_wait = TIME_OUT;//等待EV5事件直到成功while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS){count_wait--;if(count_wait==0){return ERROR_Call(1);}}//要发送的EEPROM设备地址 I2C_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_ADDR,I2C_Direction_Transmitter);count_wait = TIME_OUT;//等待EV6事件直到成功 while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS){count_wait--;if(count_wait==0){return ERROR_Call(2);}}//发送数据的存储地址,调用库函数I2C_SendDataI2C_SendData(EEPROM_I2C,addr);count_wait = TIME_OUT;//等待EV8_2事件while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS){count_wait--;if(count_wait==0){return ERROR_Call(3);}}//发送数据I2C_SendData(EEPROM_I2C,data);count_wait = TIME_OUT;//等待EV8_2事件while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS){count_wait--;if(count_wait==0){return ERROR_Call(4);}}//产生结束信号I2C_GenerateSTOP(EEPROM_I2C,ENABLE);//等待写入完成return Wait_For_Standby();
}
(2)Wait_For_Standby()函数
//等待内部写入操作完成,当完成时,从设备会应答发送的从设备地址信号
//返回0正常,非0失败
uint8_t Wait_For_Standby(void)
{uint32_t check_count=0xFFFFF;while(check_count--){//产生起始信号I2C_GenerateSTART(EEPROM_I2C,ENABLE);count_wait = TIME_OUT;///等待EV5事件while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS){count_wait--;if(count_wait==0){return ERROR_Call(11);}}//发送从设备地址I2C_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_ADDR,I2C_Direction_Transmitter);count_wait = TIME_OUT;//等待EV6事件,若检测到响应,说明内部写时序完毕while(count_wait--){if(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == SUCCESS){//注意要停止通信I2C_GenerateSTOP(EEPROM_I2C,ENABLE);return 0;}}}//注意要停止通信I2C_GenerateSTOP(EEPROM_I2C,ENABLE);return 1;}
(3)页写入函数EEPROM_Page_Write(数据量<=8,一页最多8个字节)
//addr:存储单元首地址
//data:写入多数据的指针
//size:数据量
//return:0正常
uint8_t EEPROM_Page_Write(uint8_t addr,uint8_t* data,uint8_t size)
{//起始信号I2C_GenerateSTART(EEPROM_I2C,ENABLE);count_wait = TIME_OUT;while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS){count_wait--;if(count_wait==0){return ERROR_Call(12);}}I2C_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_ADDR,I2C_Direction_Transmitter);count_wait = TIME_OUT;while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS){count_wait--;if(count_wait==0){return ERROR_Call(13);}}I2C_SendData(EEPROM_I2C,addr);count_wait = TIME_OUT;while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS){count_wait--;if(count_wait==0){return ERROR_Call(14);}}//--------------------------------------------------------while(size--){//按字节大小写入I2C_SendData(EEPROM_I2C,*data);count_wait = TIME_OUT;while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS){count_wait--;if(count_wait==0){return ERROR_Call(15);}}data++;}I2C_GenerateSTOP(EEPROM_I2C,ENABLE);return Wait_For_Standby();
}
(4)快速写入多字节(传入参数和页写入一样)
(数据量为256个)
#define EEPROM_PAGE_SIZE 8
uint8_t EEPROM_Buffer_Write(uint8_t addr,uint8_t* data,uint16_t size)
{//算出写入的地址和页的最后差几个位置,如果为0则刚好对齐,不为零则从写入的地址开始写入填满一页保证对齐;uint8_t single_addr = addr%EEPROM_PAGE_SIZE;if(single_addr==0){//当对齐时算出有几页,多余出几个字节不满一页uint8_t num_of_page = size/EEPROM_PAGE_SIZE;uint8_t single_byte = size%EEPROM_PAGE_SIZE;while(num_of_page--){//页写入EEPROM_Page_Write(addr,data,EEPROM_PAGE_SIZE);//等待写入完毕Wait_For_Standby();//递增addr+=EEPROM_PAGE_SIZE;data+=EEPROM_PAGE_SIZE;}//写不满一页的数据EEPROM_Page_Write(addr,data,single_byte); Wait_For_Standby();}//写入地址不对齐else{ //写入 页满量和写入地址的差值uint8_t first_size = EEPROM_PAGE_SIZE-single_addr;EEPROM_Page_Write(addr,data,first_size);addr+=first_size;data+=first_size;//剩余数据量uint8_t left = size-first_size;//和上面页对齐一样继续算有多少页和不足一页的余量uint8_t num_of_page = left/EEPROM_PAGE_SIZE;uint8_t single_byte = left%EEPROM_PAGE_SIZE;Wait_For_Standby();while(num_of_page--){EEPROM_Page_Write(addr,data,EEPROM_PAGE_SIZE);Wait_For_Standby();addr+=EEPROM_PAGE_SIZE;data+=EEPROM_PAGE_SIZE;}EEPROM_Page_Write(addr,data,single_byte);Wait_For_Standby();}return 0;
}
(5)多字节读取(传入参数和前面一样)
uint8_t EEPROM_Buffer_Read(uint8_t addr,uint8_t *data,uint16_t size)
{I2C_GenerateSTART(EEPROM_I2C,ENABLE);count_wait = TIME_OUT;while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS){count_wait--;if(count_wait==0){return ERROR_Call(16);}}I2C_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_ADDR,I2C_Direction_Transmitter);count_wait = TIME_OUT;while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS){count_wait--;if(count_wait==0){return ERROR_Call(17);}}I2C_SendData(EEPROM_I2C,addr);count_wait = TIME_OUT;while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS){count_wait--;if(count_wait==0){return ERROR_Call(18);}}**//读取时发送完要读取的存储地址后要重新发送起始信号**I2C_GenerateSTART(EEPROM_I2C,ENABLE);count_wait = TIME_OUT;while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS){count_wait--;if(count_wait==0){return ERROR_Call(19);}}I2C_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_ADDR,I2C_Direction_Receiver);count_wait = TIME_OUT;while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED ) != SUCCESS){count_wait--;if(count_wait==0){return ERROR_Call(20);}}/********************************************************************************/while(size--){if(size==0){//非应答信号,告诉发送端不发了I2C_AcknowledgeConfig(EEPROM_I2C,DISABLE);}else{//继续应答I2C_AcknowledgeConfig(EEPROM_I2C,ENABLE);}count_wait = TIME_OUT;while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS){count_wait--;if(count_wait==0){return ERROR_Call(21);}}//调用库函数I2C_ReceiveData读取*data = I2C_ReceiveData(EEPROM_I2C);data++;}I2C_GenerateSTOP(EEPROM_I2C,ENABLE);return 0;
}
(6)main函数
int main(void)
{ uint16_t i;uint8_t data[TEST_SIZE];uint8_t buff[TEST_SIZE];//初始化...Debug_USART_Config();EEPROM_I2C_Config();printf("I2C实验开始\n");for(i=0;i<TEST_SIZE;i++){buff[i]=i;}EEPROM_Byte_Write(0x01,0x15);EEPROM_Random_Read(0x01,data);printf("单字节,data=0x%x\n",data[0]);//EEPROM_Page_Write(0x00,buff,8);EEPROM_Buffer_Write(0x02,buff,TEST_SIZE);EEPROM_Buffer_Read(0x02,data,TEST_SIZE);printf("多字节完成\n");for(i=0;i<TEST_SIZE;i++){printf("0x%02x ",data[i]);}while(1){ }
}