本文将对I2C协议的定义,总线结构,协议内容以及I2C协议在STM32上的具体使用方法等方面进行详细介绍。
一.I2C通信的定义
二.I2C通信的总线结构

I2C的引脚的工作模式一般是设置为开漏输出,关于什么是开漏输出以及其他GPIO的工作模式可以参照这篇文章 《详解MCU的GPIO引脚的各种配置方式》
下图是I2C引脚开漏输出的内部结构图:
复用开漏输出的情况上面的PMOS不工作
1.当输出寄存器输出高电平,引脚输出高阻态相当于(开路
),假设该引脚接到I2C的SDA总线上,则总线被默认拉成高电平。2.当输出寄存器输出低电平,引脚输出低电平。
所以当SCL和SDA引脚的寄存器都输出高电平时,实际引脚是高阻态,因为引脚外边又接了两个上拉电阻,所以被上拉成高电平了。
当输出寄存器输出低电平时,下面的N-MOS导通接地,所以引脚输出低电平。
多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线,也就是设备在发送数据之前会检测I2C总线是否忙碌(忙碌总线应该为低电平)。
I2C 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问的,地址也是一个数据,主机可以同过SDA发送这个地址出去,则挂载在总线上的设备会自行匹配,匹配成功之后就可以互相通信了。
三.I2C通信的协议
1)空闲状态
I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
2)起始信号与停止信号
起始信号:当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号。
停止信号:当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。

3)应答信号ACK
发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。 应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
发送端发送完一个字节必须要释放数据线SDA,否则可能因为发送端保持的状态是低电平而导致接收端回复不了高电平。
对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。 如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P。

4)数据有效性
I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
即:数据在SCL的上升沿到来之前就需准备好。并在在下降沿到来之前必须稳定。

5)数据的传送
在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发。
四.STM32通过I2C协议通信读写EEPROM的具体方法
1.主机写数据到从机
这里发送完最后一个字节时,主机不一定要接收到从机发送的非应答信号才可以发送停止信号,就算从机应答了主机也可以直接发送停止信好终止通讯
其中 S 表示由主机的 I2C 接口产生的传输起始信号(S),这时连接到 I2C 总线上的所有从机都会接收到这个信号。起始信号产生后,所有从机就开始等待主机紧接下来 广播(由SDA线传输数据)
从机地址(SLAVE_ADDRESS)。在 I2C 总线上,每个设备的地址都是唯一的,当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号(引脚输出高阻态与两根总线断开连接)。
根据 I2C 协议,这个从机地址可以是 7 位或 10 位,从机接收到匹配的地址后,主机或从机会返回一个应答(ACK)或非应答(NACK)信号,只有接收到应答信号后,主机才能继续发送或接收数据。
在地址位之后,是传输方向的选择位,表示后面的数据传输方向
该位为 0 时:主机向从机写数据。
该位为 1 时:主机由从机读数据。
2.主机向从机读取数据

读或写
上一次确定内部寄存器或存储器的地址上面的数据。EEPROM和STM32之间使用I2C通讯。使用硬件实现协议。
1.初始化I2C相关的GPIO
2.配置I2C外设的工作模式
3.编写I2C写入EEPROM的byte write函数,编写读取的random read函数(简单但效率低);编写page write和seq read函数并校验(提高工作效率)
4.使用两个读写函数进行读写校验,需要写EEPROM内部时序完成的等待函数。