目录
一.I2C协议
1.什么是I2C协议?
2.物理层特性
二.协议层
1.I2C读写过程
2.I2C外设
3.I2C外设通讯过程
三.I2C库函数
四.EEPROM
写操作:
读操作:
五.编程
六.遇到了哪些问题
一.I2C协议
1.什么是I2C协议?
各个IC之间需要进行数据交流,为了使它们互联互通,I2C出现了。I2C是通用的简单的双向两线制总线协议。在硬件方面,需要的管脚少,连接线和面积小。在软件开发时,可以使用同一个I2C驱动库来驱动不同的器件。
2.物理层特性
SCL:串行时钟总线;时钟用于数据收发同步。
SDA:双向串行数据总线;
(1)主机和从机之间的通讯方式:每个连接到总线的设备都有一个地址,利用这个地址进行访问。(地址就是一个普通的数据);多个主机同时使用总线时,为了防止数据冲突,用仲裁方式决定由哪个设备占用总线。
(2)传输模式:常用的快速模式为400Kbit/s
(3)连接到相同总线的IC数量受到总线的最大电容400pF限制
I2C时,要SCL和SDA管脚配置成开漏输出;添加上拉电阻;在仲裁时利用了线与特性。此时线与逻辑实现不需要与门,在原本上拉电阻和开漏结构及总线基础上就可以实现。
二.协议层
I2C的协议层包括通讯的起始位,停止位,数据有效性,响应,仲裁,时钟同步和地址广播等。
1.I2C读写过程
读过程:从机发送数据给主机
写过程:主机发送数据给从机。
S | SLAVE ADDRESS | R/W- | A | DATD | A | DATA | A/A- | P |
复合格式:有两个起始信号。先写,写入数据是从机地址;再读,读入从机内数据。整个过程需要一个地址来找数据,因此有复合格式。
S:表示开始传输信号。1位
SLAVE ADDRESS:通讯的从机地址。7位或10位
R/W-:选择读或写。1位。取1为读。
A:应答信号; A/A-:应答或非应答信号
P:停止信号
7(或10)位从机地址+1个读写位=8位的读/写地址
当SCL高电平时,SDA信号有效。SDA为高电平则传输数据1,低电平则传输数据0,由高转低则表示起始信号,由低转高表示停止信号。在SCL低电平时,SDA在此时一般进行电平切换,为下一次表示数据做准备。
响应:在SDA线上传输完8位读写地址之后,发送端会释放SDA的控制权,由数据接收端控制SDA。由上拉电阻上拉成高电平(高阻态状态),高电平表示未应答。当从机检测到是自己的地址时,将SDA的电平拉低为0,表示有设备响应。其他设备想在此时传输数据时,无法将SDA的电平拉高后拉低,无法传输数据。
2.I2C外设
软件模拟协议:控制SDA和SCL的电平->使用CPU直接控制通讯引脚的电平
硬件实现协议:由STM32的I2C片上外设专门负责实现I2C通讯协议,只要配置好该外设,它会自动根据协议要求产生信号,收发数据并缓存起来。CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种硬件外设处理I2C协议的方法使软件设计更加简单。
- 1.通讯引脚 SDA,SCL,SMBA(不常用),查表看对应的引脚,I2C有两套通讯线路。
- 2.时钟控制逻辑 CCR控制I2C时钟频率。具体过程查看CCR寄存器。主模式选项,占空比(低电平时间/高电平时间),CCR写入值的计算。可以使用库函数来便捷计算。
- 3.数据控制逻辑 数据寄存器(8位有效)
- 4.整体控制逻辑 控制寄存器start,stop; 状态寄存器TxE,RxNE,AF,ADDR,BUSY
3.I2C外设通讯过程
STM32作为主发送器:
在I2C通讯时,在通讯的不同阶段,它会对状态寄存器写入不同的数据,通过读取寄存器标志来判断寄存器状态。在发送结束信号之前,需要检测BTF(字节发送结束,表示所有数据都发送出去了)。可使用STM32标准库函数来直接检测这些事件的复合标志。每次读取标志位之后,都要清除。
上一行是I2C的传输数据,下一行是状态寄存器在不同阶段的动作。
STM32作为主接收器:
三.I2C库函数
I2C初始化结构体:ClockSpeed,Mode,DutyCycle(占空比), ownAddress(I2C设备地址), Ack(使能,允许应答), AcknowledgeAddress(指定地址长度)
- ClockSpeed:支持标准模式100khz,快速模式400khz.
- Mode:I2C模式,SMB主模式,SMB从模式
- DutyCycle:有2:1和16:9两种。占空比是指低电平和高电平时间的比例。2:1和16:9相差不大,可以随便选用。如果高电平的时间过短,则从SDA线上读取数据的时间短,信息传输不完整。
- ownAddress:STM32 I2C自身设备地址,只要在总线上是唯一即可。
- AcknowledgeAddress:选择寻址模式是7位还是10位要根据实际连接到I2C总线上的设备的地址进行选择。
GeneratesSRART()产生起始信号
GeneratesSTOP()产生停止信号
FlagStatus I2C_GetFlagStatus()读寄存器位
I2CSend7bitAddress()
I2CSendData()
I2CAcknowledgeConfig()
四.EEPROM
EEPROM是可重复擦除的存储器 ,掉电可保存。
型号:AT24C02
STM32与EEPROM(AT24C02)通讯时,使用I2C协议。WP写保护,接地不保护。在主机发送开始信号之后,会发送需要通讯的器件地址(EEPROM都要求有8位,包括读写位)。8位器件地址的前四位对于所有串行EEPROM都是一样的1010.接下来的三位是A0,A1,A2为器件地址位,必须与硬件输入引脚保持一致。根据图,此EEPROM的地址为1010,000。
写操作:
1.字节写:在接受器件地址和ACK应答之后,接收8位的字地址,接到这个地址之后EEPROM应答0,然会主机发送8位数据,EEPROM应答0,主机发送停止信号。此时,EEPROM进入内部写周期TWR,将数据写入非易失性存储器中,在此期间的输入都无效,直到写周期完成。
2.页写:和字节写类似。不同在于,在主机把第一批数据发送出去,EEPROM应答之后,主机不会发送停止信号,而是接着发送7个(AT24C02)数据。EEPROM收到数据之后都会应答0,最后一个数据发送出去且EEPROM应答之后,主机会发送停止信号。接收到每个数据之后,字地址第三位(AT24C02)自动加1,高位不变,维持在当前页。当字地址达到该页边界地址时,随后的数据会写入该页的页首。如果超过8(AT24C02)个,字地址会回转到该页的首字节,将先前的字节覆盖。
3.应答查询:EEPROM进入内部写周期,输入都无效,此时要应答查询,保证输入的数据是有效的。查询ACK状态。
读操作:
读操作和写操作初始化相同,只有读写位不同。
1.当前地址读:
内部地址计数器保存上次访问的最后一个地址+1的值。只要芯片有电,该地址就一直保存。当读到最后一页的最后字节,地址会转到0,当写到页尾的最后一个字节,地址会转到该页的首字节。
主机发送开始信号,发送器件地址,EEPROM应答ACK后,当前地址的数据就随时钟发送出去。主机不用应答,只需要发送停止信号。
2.随机读:主机发送开始信号,先写一个目标字地址,EEPROM接收器件地址并应答。主机发送第二个开始信号,主机再一次发送器件地址,EEPROM应答,并随时钟送出数据。主机不用应答,只需要发送停止信号。
3.顺序读:可以通过当前地址读或者随机读启动。主机接收到一个数据之后,应答ACK。只要EEPROM接收到ACK,将自动增加字地址,随时钟发送后面的数据。主机不需应答,只需发送停止信号。
五.编程
EEPROM和STM32之间使用I2C通讯。使用硬件实现协议。
I2C和USART的程序相似,可类比学习。
1.初始化I2C相关的GPIO
2.配置I2C外设的工作模式
3.编写I2C写入EEPROM的byte write函数,编写读取的random read函数(简单但效率低);编写page write和seq read函数并校验(提高工作效率)
4.使用两个读写函数进行读写校验,需要写EEPROM内部时序完成的等待函数。
六.遇到了哪些问题
1.EV5事件while循环卡死:从原理上讲,STM32发送开始信号给EEPROM,检测SR1的SB是否为1来判断信号是否发出。软件读取SR1寄存器后,写数据寄存器的操作将清除该位。GetFlag库函数中,SB宏定义为0x10000001,右移28位得到0x1!=0,则I2C首地址+0x14,找到SR1。SB与上0x00ffffff,表示取低24位,即00000001,与上SR1,表示取SR1的最低位。若结果不为0,return SET。
解决办法:当GPIO管脚设置为10KHZ,且在各个EVn事件中全都使用I2C_CheckEvent库函数来检测,程序执行正常。
为什么使用GetFlag库函数来检测各个EVn事件会卡死在while循环里?GetFlag库函数和I2C_CheckEvent库函数有什么区别?
EV5检测位宏定义为0x00030001,I2C的SR2寄存器数据左移16位或上SR1数据后与上0x00ffffff,表示取SR2低8位和SR1全16位。得到的数据如果和EV5宏定义相同即成功。只要SR2低两位(BUSY总线忙,MSL处于主模式)和SR1低1位(SB发送起始信号完成)是1,则成功。 GetFlag库函数只检测SR1->SB位是否置一,不关心其他位。在检测SB位的效果上,I2C_CheckEvent库函数和GetFlag库函数是一样的。在检测EV6时,I2C_CheckEvent库函数检测SR1低三位,SR2的1和7位,GetFlag库函数检测SR2的1位。理论上说,在检测EV6时,I2C_CheckEvent库函数和GetFlag库函数效果是一样的。但是程序下载进去后,无法实现。
接下来的EVn事件,同样地,也是使用I2C_CheckEvent库函数才能正确执行。
2.在EEPROM的等待内部时序结束的函数中,使用GetFlag库函数检测,否则卡死。
3.EEPROM写入和读出函数:PageWrite(uint8_t BYTE_WRITE_ADDR, uint8_t* Data, uint8_t numtowrite )
RandomRead( uint8_t BYTE_READ_ADDR,uint8_t *data,uint8_t numByteToRead )
表示data是uint_8t*类型的指针,指向data的首地址,在数据读写时,指针移动一次,跳过uint_8的内存大小,将data的内容逐一读写。
七.软件模拟I2C
软件模拟I2C是使用板上任意两个引脚作为I2C接口SDA,SCL。将这两个引脚拉低拉高来模拟实现SDA,SCL的高低变化,从而达到模拟I2C通讯的效果。
软件模拟I2C时,两个引脚一样地配置为开漏输出。使用开漏输出的原因同样是要实现线与。当一个设备把总线拉低时,如果有其它设备读取,则不会损坏电路。开漏输出需要在外部连接上拉电阻。如果在此处中采用推挽输出,那么,可想而知,在有多个设备的时候,无法线与,会出现电流倒灌,电路短路的问题。但是,当只有一个从设备的时候,我们不需要担心是否有线与的功能,所以,在这个时候,按理来说,是可以使用推挽输出的。但是,要特别注意的是,使用推挽输出的时候,我们需要配置输入和输出模式。这是为什么呢?因为配置为推挽输出,这个引脚只能作为输出,无法读取电平。所以,在接收数据的时候,我们需要把模式改为上拉输入模式。那为什么开漏输出不需要改变输入输出模式呢?因为开漏输出的内部电路结构:引脚处电平完全由外部电平决定。而推挽输出的内部电路的特点是强上拉,强下拉,完全由内部电平决定,无论外部电平是什么。
(2条消息) 开漏输出、推挽输出的区别_Daniel雨林的博客-CSDN博客
(2条消息) GPIO 口的输入,输出模式及其说明_gpio端口模式利用gpio_init_星空闪耀&的博客-CSDN博客
因此,只需要配置好GPIO,编写两个引脚高低电平变化的I2C开始函数,应答和非应答函数,停止函数,发送和接收函数就可以实现I2C读写操作。那么,在这个过程中,按照SDA和SCL的电平变化来编写函数是至关重要的。首先,要熟悉SDA和SCL配合工作的过程。(不再需要打开IIC的时钟,不用再配置IIC的结构体)
数据的有效性:SDA的数据必须在SCL的高电平周期保持稳定。数据线的高低变化只有在SCL是低电平时才能进行。
起始:SCL为高时,SDA由高变低。
停止:SCL为高时,SDA由低变高。
传输数据的字节格式:发送到SDA线上的字节必须是8位,字节数不受限制。每个字节后要有一个响应位。首先传输数据的最高位。文章来源:https://www.toymoban.com/news/detail-602547.html
响应:数据传输必须带响应。响应期间,发送器释放SDA(高),接收器将SDA拉低文章来源地址https://www.toymoban.com/news/detail-602547.html
到了这里,关于STM32---I2C的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!