1、IIC协议介绍
1.物理层
![](https://img-blog.csdnimg.cn/img_convert/3314e2e8bf89f2ebc20675277887e4e1.png)
串行同步全双工
它的物理层有如下特点:
(1) 它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
(2) 一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
(3) 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
(4) 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
这里是线与的特性,线与功能主要用于有多个电路对同一信号进行拉低操作的场合,如果本电路不想拉低,就输出高电平,因为OPEN-DRAIN上面的管子被拿掉,高电平是靠外接的上拉电阻实现的。 (而正常的CMOS输出级,如果出现一个输出为高另外一个为低时,等于电源短路。
线与可以避免下面这种电源短路的情况,线与只要有一个低电平,总线就会被拉低,其余设备即使是高阻态,也不会形成回路,总线依旧是低电平。
线与这块可以看下GPIO那块,GPIO那块理解了,这里就差不多。
![](https://img-blog.csdnimg.cn/img_convert/a5742847169b522d2308e8cea6f006e1.png)
(5) 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
空闲时设备输出高阻态,当与主机通信时,会将总线拉低,这时候当在有别的设备想与主机进行通信时,就会先检测总线的状态,如果为低,则需要等待当前设备通信完成才能通信
(6) 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
(7) 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。
2.协议层
2.1基本读写过程
![](https://img-blog.csdnimg.cn/img_convert/7699b486b158d4de9bdef88e9d6812ae.png)
先由主机的 I2C 接口发送的传输起始信号,I2C 总线上所有从机都会接收到这个信号,起始信号产生后所有从机就开始等待主机紧接下来广播 的从机地址信号,在 I2C 总线上,每个设备的地址都是唯一的, 当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号。根据 I2C 协议,这个从机地址可以是 7 位或 10 位。地址之后,就是读写位,0代表写,主机向从机写数据;1代表读,数据传输方向是由主机传输至从机,这里选择0,一般一个字节是8位,所以有时候读写位会和7位地址放到一起,需要注意一下。从机接收到地址后,会返回给主机一个应答信号,ACK是应答,NACK是非应答,只有接收到应答信号后从机才能将数据发送给主机,应答信号以后就是数据数据包大小为8位,当主机接收到了数据以后,会给从机一个应答,每发完一个数据都要等待应答信号,可以发送N个数据。当传输结束时,主机会发送一个结束信号,表示不再传输数据。注意起始信号和终止信号都由主机发送。
![](https://img-blog.csdnimg.cn/img_convert/46133d122fe2d6cb043a88dcd9a32475.png)
主机发送起始信号,接着发送从机地址,选定从机,发送读写位为0,传输方向是主机向从机写数据,从机给主机一个应答,主机收到应答以后就可以向从机传输数据,每传一个数据就要等待从机的应答,最后主机发送终止信号表示传输完成。
![](https://img-blog.csdnimg.cn/img_convert/6bfb8fdab88c8125e9db338a1d5c1ce4.png)
复合模式需要先向从机写地址,在读里面的数据,有两个起始信号,第一个起始信号后的从机地址选定从机设备,是eeprom还是触摸屏,还是传感器这些从及设备,紧接着会发送一段数据,这个数据表示去到对应从机内部的寄存器或存储器,举个例子,我们想知道班里小明的数学成绩,slave address就代表你选中了小明这个人,那么到底是要知道小明的哪科成绩呢,这里的这段数据就代表数学,如果我们要读取EEPROM里0x78这个地址的数据,EEPROM就是小明,0x78这段地址的寄存器就是数学,选定好要读取的位置以后,就可以对具体的数据进行读写了,这时候再由主机发送起始信号开始对具体位置进行读取写操作。这里要重点理解从机地址和从机内部的存储器或存储器地址的区别。
2.2通讯的起始信号和停止信号
![](https://img-blog.csdnimg.cn/img_convert/efc331982a369cb1808acc293b57ab70.png)
当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。当 SCL 是高电平时 SDA线由低电平向高电平切换,表示通讯的停止。起始和停止信号由主机产生。
起始和停止都需要在时钟线高电平时产生,SDA下降沿表示起始,SDA上升沿表示停止。这样好记一点
2.3数据的有效性
![](https://img-blog.csdnimg.cn/img_convert/e5be5852d0ff28dcff463c4599fa95ef.png)
SCL高电平时,SDA的数据才有效,黄色区域SDA的两条线表示,SDA既可以是高电平,也可以是低电平,高电平就是1,低电平就是0。蓝色区域表示SDA的数据无效,可以在这段时间进行数据变化。
2.4地址及数据方向
I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址(SLAVE_ADDRESS)来查找从机。 I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/W),第 8 位或第 11 位。数据方向位为“ 1”时表示主机由从机读数据,该位为“ 0”时表示主机向从机写数据。
![](https://img-blog.csdnimg.cn/img_convert/e0d1278cd07aac8e7c58c89999830009.png)
读数据方向时,主机会释放对 SDA 信号线的控制,由从机控制 SDA 信号线,主机接收信号,写数据方向时, SDA 由主机控制, 从机接收信号。
2.5响应
![](https://img-blog.csdnimg.cn/img_convert/0a2ced6a7b1f3b1383f838f70bb1ef3f.png)
传输时主机产生时钟,在第 9 个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制 SDA,若 SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。
2、STM32 的 I2C 特性及架构
1.架构剖析
![](https://img-blog.csdnimg.cn/img_convert/52dbb605d2654706f43efd26f2ccd73a.png)
1.1通讯引脚
STM32 芯片有多个 I2C 外设,它们的 I2C 通讯信号引出到不同的 GPIO 引脚上,使用时必须配置到这些指定的引脚,引脚定义在STM32F103xCDE中文手册这个文档里
![](https://img-blog.csdnimg.cn/img_convert/283bfd88bba18f6cef5b8ccadd8b4590.png)
![](https://img-blog.csdnimg.cn/img_convert/cef3783b8904dfe4c1e0b61c390081cb.png)
![](https://img-blog.csdnimg.cn/img_convert/2e82e26559aa25cea5044de1df75e003.png)
1.2时钟控制逻辑
SCL线的时钟信号,由I2C接口根据时钟控制寄存器(CCR)控制,控制的参数主要为时钟频率。
![](https://img-blog.csdnimg.cn/img_convert/d27b81573deffa6eb234f85b368ba7a7.jpeg)
模式选择在第15位F/S,“标准/快速”模式分别 I2C 对应 100/400Kbit/s 的通讯速率。
位14DUTY占空比的选择要在F/S是快速模式下,可选 Tlow/Thigh=2 或 Tlow/Thigh=16/9 模式,对于传感器来说,16/8和16/9都差不多。 Tlow/Thigh就是在一个时钟周期内,低电平和高电平的比值
![](https://img-blog.csdnimg.cn/img_convert/adc705574c395082839fe9cf1ce2231f.png)
时钟周期实际上是12位的配置因子CCR控制的
![](https://img-blog.csdnimg.cn/img_convert/4f34d9c68adf0ce323151aecae455d74.png)
TPCLK1=APB1的时钟,IIC是挂载到APB1总线上的,所以时钟周期是36MHz
![](https://img-blog.csdnimg.cn/img_convert/151eab8cbec7996509969cfbb39df98b.png)
如果在快速模式下,选择占空比为2,时钟为400Kbit/s那么SCL线时钟=2*CCR*TPCLK1=400Kbit/s,还需要进行单位换算,36MHz=36000000Hz,400Kbit/s=400000Hz
![](https://img-blog.csdnimg.cn/img_convert/7a874393e1869fd6652b3319f6853630.png)
![](https://img-blog.csdnimg.cn/img_convert/3cde72a87a703f671de0cabbe034c2d4.png)
1.3数据控制逻辑
I2C 的 SDA 信号主要连接到数据移位寄存器上,数据移位寄存器的数据来源及目标是数据寄存器(DR)、地址寄存器(OAR)、 PEC 寄存器以及 SDA 数据线。当向外发送数据的时候,数据移位寄存器以“数据寄存器”为数据源,把数据一位一位地通过 SDA 信号线发送出去,且高位先行;当从外部接收数据的时候,数据移位寄存器把 SDA 信号线采样到的数据一位一位地存储到“数据寄存器”中。
当 STM32 的 I2C 工作在从机模式的时候,接收到设备地址信号时,数据移位寄存器会把接收到的地址与 STM32 的自身的“ I2C 地址寄存器”的值作比较,以便响应主机的寻址。 STM32 的自身 I2C 地址可通过修改“自身地址寄存器”修改,支持同时使用两个 I2C 设备地址,两个地址分别存储在 OAR1 和 OAR2 中。PEC不讲
1.4整体控制逻辑
整体控制这里,掌握寄存器相应位的意义就好,下面会详细说
![](https://img-blog.csdnimg.cn/img_convert/28510d82cf8b430fec317959d83788c7.jpeg)
2.通讯过程
IIC慢,stm32快,stm32每完成一次任务后,都要读取SR1寄存器的相应位,以确保每一个任务完成,stm32作为IIC的主设备,所以只介绍了主发送器和主接收器
下面是通讯会用到的SR1寄存器
![](https://img-blog.csdnimg.cn/img_convert/a9f192a21c42c54e5b82ea5bb48ca610.png)
![](https://img-blog.csdnimg.cn/img_convert/8f97cdcae4d85d13460d559dce854579.png)
![](https://img-blog.csdnimg.cn/img_convert/6d85ff9d282a12ec029cd02a391ae317.png)
![](https://img-blog.csdnimg.cn/img_convert/049b57f1ca708d86cc2fcff28ecb651e.png)
2.1主发送器
![](https://img-blog.csdnimg.cn/img_convert/a6de1b34796968344b886daa9e5ec5b3.png)
(1)首先产生起始信号S,当起始信号发出后,会产生EV5,EV5是SR1寄存器里的第0位SB位,此时SB=1,表示起始信号以发送。
(2)主设备等待读SR1寄存器,紧接着发送地址和应答信号,如果有从机应答,则产生事件 EV6和 EV8,
此时EV6,EV8都为1,EV6是ADDR位,在收到ACK位以后被置一,表示地址发送完成;EV8表示数据寄存器为空,接下来就可以发送数据1了。
(3)清除ADDR位后, 向数据寄存器DR写入数据,此时TXE位为0(TxE清除方法),表示数据寄存器非空;I2C 外设通过SDA 信号线一位位把数据发送出去,此时产生EV8事件,TXE=1,因为数据发完了,数据寄存器此时又为空。重复这个步骤,即可传输多个数据。
(4)数据完成后,不仅触发EV8事件,还有EV8_2,EV8检测数据寄存器是否为空,EV8_2是BTF位,BTF=1表示字节发送完成,因为数据寄存器为空了,但是数据移位寄存器里可能还在一位一位的传输数据,所以要检测一下数据移位寄存器是否为空,为空则表示数据传输完成了,这时产生一个停止信号,表示通讯结束。
假如我们使能了 I2C 中断,以上所有事件产生时,都会产生 I2C 中断信号,进入同一个中断服务函数,到 I2C 中断服务程序后,再通过检查寄存器位来了解是哪一个事件。
![](https://img-blog.csdnimg.cn/img_convert/cbcb10f4a3df3fb372c6fc4090b974af.png)
![](https://img-blog.csdnimg.cn/img_convert/fd77085d44faad3f60aecb2ece2225c3.png)
2.2主接收器
![](https://img-blog.csdnimg.cn/img_convert/2ab0b266bd08cae5e0c984c8f11187c3.png)
(1)首先还是主机产生起始信号S,当起始信号发出后,会产生EV5,EV5是SR1寄存器里的第0位SB位,此时SB=1,表示起始信号以发送。
(2)紧接着发送地址和应答信号,如果有从机应答,则产生事件 EV6,此时EV6为1,EV6是ADDR位,在收到ACK位以后被置一,表示地址发送完成。
(3)清除ADDR位后, 接收从从机发来的数据,等到主机的应答以后,产生事件EV7,此时RXNE=1,表述数据寄存器非空,意味着数据在SDA线上,经过数据移位寄存器到达数据寄存器。这一步,我们可以控制 I2C 发送应答信号(ACK)或非应答信号(NACK),ACK在CR1寄存器的第10位配置。若应答,则重复以上步骤接收数据,若非应答,则停止传输
(4)为了在收到最后一个字节后产生一个NACK脉冲,在读倒数第二个数据字节之后(在倒数第二个RxNE事件之后)必须清除ACK位。同时为了产生一个停止/重起始条件,软件必须在读倒数第二个数据字节之后(在倒数第二个RxNE事件之后)设置STOP/START位。为了达到这两个目的,在倒数第二个数据字节后产生EV7_1事件,EV7_1表示RxNE=1,读DR寄存器清除该事件。并且设置ACK=0和STOP=1。发送非应答信号后,还会产生EV7,表示最后一个信号也接受到数据寄存器里,再把最后一个数据接收到内存,产生停止信号(P),结束传输。
标志位用完之后记得清除,以便影响下一次通讯。
3、I2C 初始化结构体
![](https://img-blog.csdnimg.cn/img_convert/34625945181a10b800d331b00268299a.png)
(1) I2C_ClockSpeed
设置I2C传输速率,数值小于400都没问题,实际上由于 CCR 寄存器不能写入小数类型的时钟因子,影响到 SCL 的实际频率可能会低于本成员设置的参数值,这时除了通讯稍慢一点以外,不会对 I2C 的标准通讯造成其它影响。,标准快速模式直接通过数值选好了
(2) I2C_Mode
这里的模式不是标准模式和快速模式,而是I2C或SMBus模式的选择,有以下四种模式可以选择stm32f10x_i2c.h可以看到,因为这里用I2C所以选择第一个。I2C不需要在此处区分主从模式,直接设置 I2C_Mode_I2C 即可。
![](https://img-blog.csdnimg.cn/img_convert/ae6dde191524e27a3241ef18881d4f04.png)
(3) I2C_DutyCycle
这里设置I2C 的 SCL 线时钟的占空比。该配置有两个选择,分别为低电平时间比高电平时间为 2: 1 ( I2C_DutyCycle_2)和 16: 9 (I2C_DutyCycle_16_9)。其实这两个模式的比例差别并不大,一般要求都不会如此严格,这里随便选就可以了。
(4) I2C_OwnAddress1
这里配置的是 STM32 的 I2C 设备自己的地址,每个连接到 I2C 总线上的设备都要有
一个自己的地址,作为主机也不例外。 地址可设置为 7 位或 10 位(受下面I2C_AcknowledgeAddress 成员决定),只要该地址是 I2C 总线上唯一的即可。STM32 的 I2C 外设可同时使用两个地址,即同时对两个地址作出响应,这个结构成员I2C_OwnAddress1 配置的是默认的、 OAR1 寄存器存储的地址,若需要设置第二个地址寄存器 OAR2,可使用 I2C_OwnAddress2Config 函数来配置, OAR2 不支持 10 位地址。
![](https://img-blog.csdnimg.cn/img_convert/f4c97ce21be852eced0756de66a1f153.png)
(5) I2C_Ack_Enable
这里设置I2C 的应答,设置为使能则可以发送响应信号。 该成员值一般配置为允许应答(I2C_Ack_Enable),这是绝大多数遵循 I2C 标准的设备的通讯要求,改为禁止应答(I2C_Ack_Disable)往往会导致通讯错误。
(6) I2C_AcknowledgeAddress
这里设置I2C 的寻址模式是 7 位还是 10 位地址。这需要根据实际连接到 I2C 总线上设备的地址进行选择,这个成员的配置也影响到 I2C_OwnAddress1 成员,只有这里设置成10 位模式时,I2C_OwnAddress1 才支持 10 位地址。I2C总线上的数据长度要一样,不一样会出错。
4、EEPROM简介
在笔记一里简单说过EEPROM是什么,这里介绍一下指南者上的EEPROM
![](https://img-blog.csdnimg.cn/img_convert/0362ba1ca6e5478e95d44e632bfeb284.png)
上图是指南者EEPROM的原理图,图在A盘的原理图里,关于芯片的描述在AT24C02.pdf里
先来看芯片的整体描述
![](https://img-blog.csdnimg.cn/img_convert/9db1a9c358d0977d95723db02a43d119.png)
![](https://img-blog.csdnimg.cn/img_convert/9cab297f189889e63528d50f9bc50fbb.png)
01A/02A/04A/08A/16A对应存储大小,对应128/256/512/1024/2048个字节,指南者用的是C02,那么容量就是256字节,可以使用这么大容量的EEPROM保存数据,并且这些数据掉电不会丢失。
下面说一下芯片的具体引脚
![](https://img-blog.csdnimg.cn/img_convert/5d62e94a3c02a3e18d1019db45188c28.png)
表里写的很清楚,每个引脚什么功能,这里说几个之前不知道的,
A0-A2:是地址,设备地址一共有 7 位,其中高 4 位固定为: 1010 b,低 3 位则由 A0/A1/A2信号线的电平决定,见图 23-13,图中的 R/W 是读写方向位,与地址无关。根据原理图可以看见我们这里把A0A1A2全部接地了,所以七位地址就是1010000b,即0x50(b代表二进制,这个二进制数实际上是01010000)
![](https://img-blog.csdnimg.cn/img_convert/01ba2aa2edbdb7bf0a581f27b96794c6.png)
WP:是写保护,此引脚为高电平的时候不能写入数据,低电平的时候可以写入数据,指南者这个芯片用不到,需要的话可以接到GPIO引脚(我估摸着应该用推挽输出,有高电平有低电平嘛)
NC:这个芯片没有,也没有多余的解释,应该就是不连接这个芯片吧
![](https://img-blog.csdnimg.cn/img_convert/811058402f6c516d7529a60caee7ef1d.png)
简单说一下EEPROM的架构,就像前面stm32的架构一样
第一部分是地址比较器,用来和主机发出的设备地址作比较,如果是自己,则进行后续操作
第二部分书计数器,每传输一个数据计数一次,下面会说,顺序写入一次最多可以写八个
第三部分就是EEPROM的地址,可以看出是有x坐标和y坐标的,下面以excel表为例就是因为这个
4.1写数据
其实写入数据这部分,重点在于搞懂这个device address和word address的区别,搞懂了这里,其余的就简单了
4.1.1单字节写入
![](https://img-blog.csdnimg.cn/img_convert/e676e9a2c7d1eecf2f43ff5ef2b024a7.png)
首先主机(MCU)发出的起始位,然后是设备地址(也就是EEPROM的地址),选择写方向(主机向EEPROM里写入数据),主机在接收到从机给的应答以后,在选定具体写入EEPROM的地址(EEPROM也是有存储空间的,那么具体写入存储空间的哪一部分,就由word address指定),主机接收到响应以后就可以开始写入输数据了。
![](https://img-blog.csdnimg.cn/img_convert/96b97c332a70751c7be5e0d3e04c3478.png)
EEPROM内部就像这个excel表格一样,每个位置是有编号的,word address就是指定写入的具体地址
4.1.2顺序写入
![](https://img-blog.csdnimg.cn/img_convert/89d786542a404e60ddd96196bd094427.png)
与之前唯一的不同是,可以连续写入多个数据。
![](https://img-blog.csdnimg.cn/img_convert/d9f8ffa8232db9433b32f787e2e469dc.png)
前面说了EEPROM的内部结构,当想在第五行第二列连续写入多个数据,就可以采用page write的方式
主机(MCU)发出的起始位,然后是设备地址(也就是EEPROM的地址),选择写方向(主机向EEPROM里写入数据),主机在接收到从机给的应答以后,在选定写入数据的起始地址EEPROM的地址(EEPROM也是有存储空间的,那么具体写入存储空间的哪一部分,就由word address指定),主机接收到响应以后就可以顺序写入数据了(最多可以写入八个字节)。但有个问题是此方法必须连续写入,不可以跳着写。
4.2读数据
4.2.1当前地址读取
![](https://img-blog.csdnimg.cn/img_convert/b2fa973e8a4a48e4592922e6814aebb0.png)
一般很少用,就是读取当前刚操作完的地址,很难确定当前地址是哪里,所以很少用
4.2.2随即地址读取(单个字节)
![](https://img-blog.csdnimg.cn/img_convert/dea85f363d45210520cca6f779f61d2b.png)
主机发送起始信号,发送从机设备地址,这里是需要把要读的位置告诉EEPROM所以方向要选择写方向,然后写入要读的具体位置,同时为了区分不是像EEPROM写入数据而是读取数据,所以要有主机再次产生一个起始信号,这次设备地址就选择读方向了。
4.2.3顺序读取(多个字节)
![](https://img-blog.csdnimg.cn/img_convert/03d98a141620fc5229011dc560b49c17.png)
前面和随即地址读取一样,后面就是读取的多个数据,读取的数据没有范围,可以读到EEPROM结束的256,需要注意的是,当地址超过256以后,可能会出现"roll over"的现象,有可能又从0位置开始读取。
5、代码
5.1思路
1.初始化IIC:仿照串口的写,串口不要中断,用写好的收发的那个GPIO,SCL,SDA