STM32硬件I2C与软件模拟I2C超详解
I2C协议时序详细解剖,硬件I2C的实现,已经软件模拟I2C的实现,以及EEPROM的详细介绍
✅作者简介:嵌入式入坑者,与大家一起加油,希望文章能够帮助各位!!!!
📃个人主页:@rivencode的个人主页
🔥系列专栏:玩转STM32
💬保持学习、保持热爱、认真分享、一起进步!!!
一.I2C协议简介
I2C 通讯协议(Inter-Integrated Circuit)是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备(那些电平转化芯片),现在被广泛地使用在系统内多个集成电路(IC)间的通讯。
I2C只有一跟数据总线 SDA(Serial Data Line),串行数据总线,只能一位一位的发送数据,属于串行通信,采用半双工通信
- 半双工通信:可以实现双向的通信,但不能在两个方向上同时进行,必须轮流交替进行,其实也可以理解成一种可以切换方向的单工通信,同一时刻必须只能一个方向传输,只需一根数据线.
对于I2C通讯协议把它分为物理层和协议层物理层规定通讯系统中具有机械、电子功能部分的特性(硬件部分),确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准(软件层面)。
二.I2C物理层
I2C 通讯设备之间的常用连接方式
(1) 它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
(2) 一个 I2C 总线只使用两条总线线路,一条双向串行数据线SDA(Serial Data Line ),一条串行时钟线SCL(Serial Data Line )。数据线即用来表示数据,时钟线用于数据收发同步
(3) 总线通过上拉电阻接到电源。当 I2C 设备空闲时会输出高阻态
,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平
。
什么是普通的开漏输出详情请参考–》GPIO端口的八种工作模式
开漏输出PMOS不工作
1.当输出寄存器输出高电平,引脚输出高阻态相当于(开路
),假设该引脚接到I2C的SDA总线上,则总线被默认拉成高电平。
2.当输出寄存器输出低电平,引脚输出低电平。
复用功能开漏输出
复用功能模式中,输出使能,输出速度可配置,可工作在开漏模式, 但是输出信号源于其它外设(来自I2C外设),输出数据寄存器 GPIOx_ODR 无效;输入可用,可以通过输入数据寄存器可获取 I/O 实际状态
,但一般直接用外设的寄存器来获取该数据信号
这里SMT32,I2C外设的两个引脚SDA,SCL就要配置成复用功能的开漏输出模式,输出信号源于I2C外设。
为什么引脚要设置成开漏模式
以及为什么两根总线要上拉电阻接高电平,总线默认情况是高电平,详情看下图。
为什么要设备空闲的时候SDA与SCL引脚要输出高阻态(相当于断开与SDA与SCL总线的连接),根本目的就是为了不干扰其他正在通信的设备。
(4) 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线,也就是设备在发送数据之前会检测I2C总线是否忙碌(忙碌总线应该为低电平)。
(5)I2C 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问的,地址也是一个数据,主机可以同过SDA发送这个地址出去,则挂载在总线上的设备会自行匹配,匹配成功之后就可以互相通信了
三.I2C协议层
STM32即可以作为主机,也可以做为从机,我主要介绍STM32作为主机如何进行读写数据。
I2C规定通信时的时钟,起始信号,停止信号只能由主机产生
下面以STM32做为主机,EEPROM存储器作为从机举例
I2C 基本读写过程
- 1.主机写数据到从机
这里发送完最后一个字节时,主机不一定要接收到从机发送的非应答信号才可以发送停止信号,就算从机应答了主机也可以直接发送停止信好终止通讯
其中 S 表示由主机的 I2C 接口产生的传输起始信号(S),这时连接到 I2C 总线上的所有从机都会接收到这个信号。起始信号产生后,所有从机就开始等待主机紧接下来 广播(由SDA线传输数据)
从机地址(SLAVE_ADDRESS)。在 I2C 总线上,每个设备的地址都是唯一的
,当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号(引脚输出高阻态与两根总线断开连接)。
根据 I2C 协议,这个从机地址可以是 7 位或 10 位,从机接收到匹配的地址后,主机或从机会返回一个应答(ACK)或非应答(NACK)信号,只有接收到应答信号后,主机才能继续发送或接收数据。
在地址位之后,是传输方向的选择位,表示后面的数据传输方向
该位为 0 时:主机向从机写数据。
该位为 1 时:主机由从机读数据。
- 2.主机向从机读取数据
记住,数据接收方
要产生应答信号(代表我还要数据)或非应答信号(我不要要数据了),不一定就是主机或从机某一个产生。
- 3.读和写数据混合格式
第一次通讯是确定读写从机设备内部寄存器或存储器的地址,第二次则是读或写
上一次确定内部寄存器或存储器的地址上面的数据。
1.空闲状态
I2C总线的SDA和SCL两条信号线同时处于高电时,则为总线空闲状态,所有挂载在总线上的设备都输出高阻态(相当于断开与总线的连接),两条总线被上拉电阻的把电平拉高。
2.起始信号与停止信号
起始信号:当SCL 线在高电平期间 SDA 线从高电平向低电平切换。
停止信号:当SCL线在高电平期间 SDA 线由低电平向高电平切换。
注意:
起始信号和停止信号是在SCL 是高电平期间,SDA线电平切换的过程,而不是单纯的高低电平。
起始和停止信号只能由主机产生。
3.数据有效性
SDA数据线在 SCL 的每个时钟周期(时钟脉冲)传输一位数据。
-
SCL为高电平期间:SDA 表示的数据有效,此时SDA的电平要稳定,SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。
-
SCL为低电平期间:SDA 的数据无效,一般在这个时候 SDA 进行
电平切换
,为下一次表示数据做好准备。
数据和地址按8位/字节进行传输,先传输数据的高位,每次传输的字节数不受限制。
4.地址及数据方向
I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址(SLAVE_ADDRESS)来查找从机。I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7 位的地址应用比较广泛
。紧跟设备地址的一个数据位用来表示数据传输方向,第 8 位或第 11 位。
- 数据方向位为“1”:表示主机由从机读数据
- 数据方向位为“0”:表示主机向从机写数据
读数据方向时,主机会释放对 SDA 信号线的控制,由从机控制 SDA 信号线(向主机发送数据),主机接收信号,写数据方向时,SDA 由主机控制(向从机发送数据),从机接收信号。
5.应答与非应答信号
I2C 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时,当数据接收端(无论主从机)
接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。
在一个字节传输的8个时钟后的第9个时钟期间,接收器必须回送一个应答位(ACK)或者是非应答位(NACK)给发送器。
在第 9 个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制 SDA,给发送端传输应答或非应答信号
-
SDA 为高电平:表示非应答信号(NACK)
-
SDA为低电平:表示应答信号(ACK)
为什么数据发送端要释放 SDA 的控制权(将SDA总线置为高电平)
四.硬件I2C
在讲硬件I2C之前不得不吐槽一下这个硬件I2C外设,有时候就突然会卡在某个事件的检测,需要关闭电源重新启动才有用,不过虽然可能硬件I2C可能会有问题,可能以后不一定用的到但是我们主要是学习如何用硬件实现I2C协议,对我们以后学别的协议肯定会有帮助。
-
硬件 I2C:是指直接
利用 STM32 芯片中的硬件 I2C 外设
,该硬件 I2C 外设跟 USART串口外设类似,只要配置好对应的寄存器,外设就会产生标准串口协议的时序。使用它的I2C 外设则可以方便地通过外设寄存器来控制硬件I2C外设产生 I2C 协议方式的通讯,而不需要内核直接控制引脚的电平
。 -
软件模拟I2C:即直接使用CPU内核按照 I2C 协议的要求控制GPIO输出高低电平。如控制产生 I2C 的起始信号时,先控制作为 SCL 线的 GPIO 引脚输出高电平,然后控制作为 SDA 线的GPIO引脚在此期间完成由高电平至低电平的切换,最后再控制SCL 线切换为低电平,这样就输出了一个标准的 I2C 起始信号。
硬件 I2C 直接使用外设来控制引脚,可以减轻 CPU 的负担。不过使用硬件I2C 时必须使用某些固定的引脚作为 SCL 和 SDA,软件模拟 I2C 则可以使用任意 GPIO 引脚,相对比较灵活。
I2C外设功能框图(重点)
1.通信引脚
STM32中有两个I2C外设,硬件I2C必须要使用这些引脚,因为这些引脚才连接到I2C引脚,就比如说PB6与PB7引脚就连接到芯片内部的I2C1外设。
就拿正点原子的STM32mini版为例,主机(stm32)使用PB6,PB7作为SCL与SDA引脚,但是PB6,PB7并没有连接到我们要通信的EEPROM的SCL,SDA引脚组成I2C总线,而是PC12与PC11连接到了EEPROM的SCL,SDA引脚,所以我们要把PB6与PB7引脚用杜邦线连接到PC12与PC11,这样就间接将PB6,PB7连接到EEPROM的SCL,SDA引脚上,组成I2C总线。
这一步十分重要,如果你用的I2C1外设与EEPROM通信而没有把PB6,PB7连接到EEPROM的SCL,SDA引脚上不然你代码写出花来都没有用。
原理图:
实物图:
2.时钟控制逻辑
时钟控制寄存器
这里解释一下为什么是用Tpclk1,因为I2C1外设是挂载在APB1总线上的
这里只是演示一下这么计算寄存器写入的值,用库函数我们只要配置好相应寄存器的参数,库函数会帮我计算自动写入的,不要慌。
3.数据控制逻辑
-
当向外发送数据的时候,数据移位寄存器以“数据寄存器”为数据源,把数据一位一位地通过SDA信号线发送出去;
-
当从外部接收数据的时候,数据移位寄存器把SDA信号线采样到的数据一位一位地存储到“数据寄存器”中。
然后通过CPU或DMA向数据寄存器写入或者读出数据(一般保存在一个数组当中)。
数据寄存器DR
自身地址寄存器1
4.整体控制逻辑
这里挑一些重点的寄存器位,我们只需配置好寄存器就可以让I2C外设硬件逻辑自动控制SDA,SCL总线去产生I2C协议的时序如:起始信号、应答信号、停止信号等等
接下来就是了解的知识:
- 总线错误(BERR)
在一个地址或数据字节传输期间,当I2C接口检测到一个外部的停止或起始条件则产生总线错误。此时:
● BERR位被置位为’1’;如果设置了ITERREN位,则产生一个中断;
● 在从模式情况下,数据被丢弃,硬件释放总线:
─ 如果是错误的开始条件
,从设备认为是一个重启动,并等待地址或停止条件。
─ 如果是错误的停止条件
,从设备按正常的停止条件操作,同时硬件释放总线。
● 在主模式情况下,硬件不释放总线,同时不影响当前的传输状态。此时由软件决定是否要中止当前的传输
主机模式与从机模式
- 应答错误(AF)
当STM32检测到一个无应答位时,产生应答错误。此时:
● AF位被置位,如果设置了ITERREN位,则产生一个中断;
● 当发送器接收到一个NACK时,必须复位通讯:
─ 如果是处于从模式,硬件释放总线。
─ 如果是处于主模式,软件必须生成一个停止条件
。
- 过载/欠载错误(OVR)
在从模式下,如果禁止时钟延长,I2C接口正在接收数据时,当它已经接收到一个字节(RxNE=1),但在DR寄存器中前一个字节数据还没有被读出,则发生过载错误。此时:
● 最后接收的数据被丢弃;
● 在过载错误时,软件应清除RxNE位,发送器应该重新发送最后一次发送的字节。
在从模式下,如果禁止时钟延长,I2C接口正在发送数据时,在下一个字节的时钟到达之前,新的数据还未写入DR寄存器(TxE=1),则发生欠载错误。此时:
● 在DR寄存器中的前一个字节将被重复发出;
● 用户应该确定在发生欠载错时,接收端应丢弃重复接收到的数据。发送端应按I2C总线标准在规定的时间更新DR寄存器。
在发送第一个字节时,必须在清除ADDR之后并且第一个SCL上升沿之前写入DR寄存器;如果不能做到这点,则接收方应该丢弃第一个数据
STM32做为从机时写入数据和读出数据时应该连续,取个例子主机要10个字节的数据而你只发5个字节此时就发生欠载错误:在下一个字节的时钟到达之前,新的数据还未写入DR寄存器
5.STM32的I2C外设通信过程(超级重要)
I2C模式选择:
接口可以下述4种模式中的一种运行:
● 从发送器模式
● 从接收器模式
● 主发送器模式
● 主接收器模式
该模块默认地工作于从模式。接口在生成起始条件后自动地从从模式切换到主模式
;当仲裁丢失或产生停止信号时,则从主模式切换到从模式。允许多主机功能。
- 主模式:STM32作为主机通信(发送器与接收器)
- 从模式:STM32作为从机通信(发送器与接收器)
这里我主要将STM32做为主机通信
I2C主模式:
默认情况下,I2C接口总是工作在从模式。从从模式切换到主模式,需要产生一个起始条件。
在主模式时,I2C接口启动数据传输并产生时钟信号
。串行数据传输总是以起始条件开始并以停止条件结束。当通过START位在总线上产生了起始条件,设备就进入了主模式
。
主发送器
- EV5事件
起始条件当BUSY=0时,设置START=1,I2C接口将产生一个开始条件并切换至主模式(M/SL位置位)
一旦发出开始条件,我们需要检测SB是否置1,判断是否成功发送起始信号
● SB位被硬件置位,如果设置了ITEVFEN位,则会产生一个中断。
然后主设备等待读SR1寄存器,紧跟着将从地址写入DR寄存器
- EV6事件
从机地址的发送
● 在7位地址模式时,只需送出一个地址字节。
一旦该地址字节被送出,
─ ADDR位被硬件置位,如果设置了ITEVFEN位,则产生一个中断。
随后主设备等待一次读SR1寄存器,跟着读SR2寄存器。
根据送出从地址的最低位,主设备决定进入发送器模式还是进入接收器模式
● 在7位地址模式时,
─ 要进入发送器模式,主设备发送从地址时置最低位为’0’。
─ 要进入接收器模式,主设备发送从地址时置最低位为’1’
从机地址发送完成从机应答之后检测EV6事件:
确保从机应答,之后才传输下一个数据,如果你不检测万一地址发送失败或者从机无应答,直接就开始传输数据那传给谁??
-
EV8_1事件:
这个检测是地址发送完之后进行检测,其实我们只要检测EV6事件就可以了,因为EV6事件成功之后就已经代表地址(数据)发送出去,而且从机还应答了,地址已经发送完成那肯定数据寄存器,与移位寄存器肯定为空呐,所以不检测也可以。 -
EV8事件
我们在发送完一个数据之后必须判断数据寄存器是否为空,数据寄存器为空(TXE),才能向数据寄存器写入新的数据,不然上一个数据们还没有转移到移位寄存器,CPU又写入一个数据则会覆盖上一个数据。
-
EV8_2事件
在我们发送完最后一个字节之后我们应该检测EV8_2事件,主要检测BTF位。
为什么呢,主要是检测数据移位寄存器的数据全部发送完成,则才算最后一个字节全部发送完毕。 -
关闭通信
在DR寄存器中写入最后一个字节后,通过设置STOP位产生一个停止条件,然后I2C接口将自动回到从模式(M/S位清除)。
主接收器
因为虽然STM32做为接收器,但是STM32是主机,起始信号与发送从机地址都是必须由主机干的活,所以前面EV5,EV6,EV6_1事件与主接收器是一模一样
- EV7事件
主机使能ACK位就可以自动接收完数据产生应答信号。
接收数据之前,判断数据寄存器是否有数据,也就数据寄存器非空(RNXE),CPU就可以读取数据寄存器中的数据啦。
- EV7_1事件
关闭通信
主设备在从设备接收到最后一个字节后发送一个NACK
。接收到NACK后,从设备释放对SCL和SDA线的控制;主设备就可以发送一个停止/重起始条件。
● 为了在收到最后一个字节后产生一个NACK脉冲,在读倒数第二个数据字节之后(在倒数第二个RxNE事件之后)必须清除ACK位。
● 为了产生一个停止/重起始条件,软件必须在读倒数第二个数据字节之后(在倒数第二个RxNE事件之后)设置STOP/START位。
● 只接收一个字节时,刚好在EV6之后(EV6_1时,清除ADDR之后)要关闭应答和停止条件的产生位。在产生了停止条件后,I2C接口自动回到从模式(M/SL位被清除)
这里产生一个NACK其实就是清除ACK位,将ACK位置0,后面接收的一个字节不在产生应答就是非应答咯
然后主机产生停止信号
然后通过判断EV7事件,CPU向数据寄存器读取最后一个字节数据
硬件I2C写代码必须熟练掌握和理解主发送器和主接收器的过程,只要你理解了写代码还不是信手拈来,简简单单,然后写代码你会发送就是上面的过程一模一样
6.I2C初始化结构体
- I2C_ClockSpeed
设置I2C的传输速率,我们写入的这个参数值不得高于400KHz。
在调用初始化函数时,函数会根据我们输入的数值,以及后面输入的占空比参数,经过运算后把时钟因子写入到I2C的时钟控制寄存器CCR。
CCR寄存器不能写入小数类型的时钟因子,影响到SCL的实际频率可能会低于本成员设置的参数值,这时除了通讯稍慢一点以外,不会对I2C的标准通讯造成其它影响。
初始化函数
- I2C_Mode
选择I2C的使用方式,有I2C模式(I2C_Mode_I2C )和SMBus主、从模式(I2C_Mode_SMBusHost、 I2C_Mode_SMBusDevice ) 。
- I2C_DutyCycle
设置I 2 C的SCL线时钟的占空比。该配置有两个选择,分别为低电平时间比高电平时间为2:1 ( I2C_DutyCycle_2)和16:9(I2C_DutyCycle_16_9)。
这个模式随便选反正区别不大。
- I2C_OwnAddress1
配置STM32的I2C设备自己的地址,每个连接到I2C总线上的设备都要有一个自己的地址,作为主机也不例外。
地址可设置为7位或10位,只要该地址是I2C总线上唯一
的即可。
其实可以有两个地址,这里是设置的第一个地址。
第二个地址要另外用库函数设置而且只能是7位
- I2C_Ack_Enable
配置I 2 C应答是否使能,设置为使能则可以发送响应信号。一般配置为允许应答(I2C_Ack_Enable)若STM32接收一个字节数据自动产生应答,必须要使能
- I2C_AcknowledgeAddress
选择I2C的寻址模式是7位还是10位地址。这需要根据实际连接到I2C总线上设备的地址进行选择,这个成员的配置也影响到I2C_OwnAddress1成员,只有这里设置成10位模式时,I2C_OwnAddress1才支持10位地址。
配置完成之后调用一下I2C初始化函数就搞定
记得使能I2C外设
五.EEPROM简介
EEPROM全称: electrically-erasable, and programmable read-only memory --》可电擦除的可编程的只读存储器,这里的只读并不是只能读,是以前ROM不能写只能读,现在的EEPROM已经是可读写的啦,为什么还叫可读:只不过是保留下来的名字而已。
原理图:
WP引脚直接
EEPROM的设备地址(作为从机)
EEPROM中硬件I2C
EEPROM通信的时候也遵循I2C协议,向产生起始信号,停止信号,应答什么的都一样的。
1.STM32向从机EEPROM写入一个字节
2.STM32向从机EEPROM写入多个字节(页写入)
写入的8个字节是连续的地址,不连续的话不能使用页写入
总结:
- 进行页写入时,写入的存储器地址要对齐到8,也就是说只能写入地址为 0 8 16 32… 能整除8
- 页写如只能一次写入8个字节
规定就是规定我也没有办法,不然就会出错
- 确认EEPROM是否写入完成:
这段话什么意思呢:EEPROM做为我们的非易失存储器(掉电不会丢失数据),相当于我们电脑中的硬盘,它的读写速度是非常慢的,所以STM32把数据发送过去之后,必须等待EEPROM去把数据写入自己内部的存储器才能写入下一波数据(可以是单字节写入也可以是页写入),如果不等待EEPROM把上一次的数据写完又去写入EEPROM是不会搭理你的,也就是说EEPROM处于忙碌状态。
检测EEPROM数据是否写入完成:
用STM32主机不断向EEPROM发送起始信号,然后发送EEPROM的设备的地址等待EEPROM的应答信号
,如果不应答,重复在来一遍,直到EEPROM应答则代表EEPROM上一次的数据写入完成,然后才可以传输下一次的数据!!!
3.STM32随机读取EEPROM内部任何地址的数据
4.STM32随机顺序读取EEPROM内部任何地址的数据
EEPROM一共有256个字节对应的地址为(0~255)
当读取到最后一个字节,也就是255地址,第256个字节,在读取又会从头(第一个字节数据)开始读取。
六.硬件I2C读写EEPROM实验
实验目的
STM32作为主机向从机EEPROM存储器写入256个字节的数据
STM32作为主机向从机EEPROM存储器读取写入的256个字节的数据
读写成功亮绿灯,读写失败亮红灯
实验原理
- 硬件设计
原理图
实物图
编程要点
(1) 配置通讯使用的目标引脚为开漏模式;
(2) 编写模拟 I2C 时序的控制函数;
(3) 编写基本 I2C 按字节收发的函数;
(4) 编写读写 EEPROM 存储内容的函数;
(5) 编写测试程序,对读写数据进行校验。
两个引脚PB6,PB7都要配置成复用的开漏输出
这里有一个注意的点,你配置成输出模式,并不会影响引脚的输入功能
详情请看——>GPIO端口的八种工作模式
源码
i2c_ee.h
前面理论已经讲得已经很详细了,直接上代码叭!!
#ifndef __IIC_EE_H
#define __IIC_EE_H
#include "stm32f10x.h"
#include <stdio.h>
//IIC1
#define EEPROM_I2C I2C1
#define EEPROM_I2C_CLK RCC_APB1Periph_I2C1
#define EEPROM_I2C_APBxClkCmd RCC_APB1PeriphClockCmd
#define EEPROM_I2C_BAUDRATE 400000
// IIC1 GPIO 引脚宏定义
#define EEPROM_I2C_SCL_GPIO_CLK (RCC_APB2Periph_GPIOB)
#define EEPROM_I2C_SDA_GPIO_CLK (RCC_APB2Periph_GPIOB)
#define EEPROM_I2C_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define EEPROM_I2C_SCL_GPIO_PORT GPIOB
#define EEPROM_I2C_SCL_GPIO_PIN GPIO_Pin_6
#define EEPROM_I2C_SDA_GPIO_PORT GPIOB
#define EEPROM_I2C_SDA_GPIO_PIN GPIO_Pin_7
//STM32自身地址1 与从机设备地址不相同即可(7位地址)
#define STM32_I2C_OWN_ADDR 0x6f
//EEPROM设备地址
#define EEPROM_I2C_Address 0XA0
#define I2C_PageSize 8
//等待次数
#define I2CT_FLAG_TIMEOUT ((uint32_t)0x1000)
#define I2CT_LONG_TIMEOUT ((uint32_t)(10 * I2CT_FLAG_TIMEOUT))
/*信息输出*/
#define EEPROM_DEBUG_ON 0
#define EEPROM_INFO(fmt,arg...) printf("<<-EEPROM-INFO->> "fmt"\n",##arg)
#define EEPROM_ERROR(fmt,arg...) printf("<<-EEPROM-ERROR->> "fmt"\n",##arg)
#define EEPROM_DEBUG(fmt,arg...) do{\
if(EEPROM_DEBUG_ON)\
printf("<<-EEPROM-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
}while(0)
void I2C_EE_Config(void);
void EEPROM_Byte_Write(uint8_t addr,uint8_t data);
uint32_t EEPROM_WaitForWriteEnd(void);
uint32_t EEPROM_Page_Write(uint8_t addr,uint8_t *data,uint16_t Num_ByteToWrite);
uint32_t EEPROM_Read(uint8_t *data,uint8_t addr,uint16_t Num_ByteToRead);
void I2C_EE_BufferWrite(uint8_t* pBuffer,uint8_t WriteAddr, uint16_t NumByteToWrite);
#endif /* __IIC_EE_H */
i2c_ee.c
#include "i2c_ee.h"
//设置等待时间
static __IO uint32_t I2CTimeout = I2CT_LONG_TIMEOUT;
//等待超时,打印错误信息
static uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode);
void I2C_EE_Config(void)
{
GPIO_InitTypeDef GPIO_InitStuctrue;
I2C_InitTypeDef I2C_InitStuctrue;
//开启GPIO外设时钟
EEPROM_I2C_GPIO_APBxClkCmd(EEPROM_I2C_SCL_GPIO_CLK|EEPROM_I2C_SDA_GPIO_CLK,ENABLE);
//开启IIC外设时钟
EEPROM_I2C_APBxClkCmd(EEPROM_I2C_CLK,ENABLE);
//SCL引脚-复用开漏输出
GPIO_InitStuctrue.GPIO_Mode=GPIO_Mode_AF_OD;
GPIO_InitStuctrue.GPIO_Pin=EEPROM_I2C_SCL_GPIO_PIN;
GPIO_InitStuctrue.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(EEPROM_I2C_SCL_GPIO_PORT,&GPIO_InitStuctrue);
//SDA引脚-复用开漏输出
GPIO_InitStuctrue.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStuctrue.GPIO_Pin = EEPROM_I2C_SDA_GPIO_PIN;
GPIO_InitStuctrue.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(EEPROM_I2C_SDA_GPIO_PORT,&GPIO_InitStuctrue);
//IIC结构体成员配置
I2C_InitStuctrue.I2C_Ack=I2C_Ack_Enable;
I2C_InitStuctrue.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;
I2C_InitStuctrue.I2C_ClockSpeed=EEPROM_I2C_BAUDRATE;
I2C_InitStuctrue.I2C_DutyCycle=I2C_DutyCycle_2;
I2C_InitStuctrue.I2C_Mode=I2C_Mode_I2C;
I2C_InitStuctrue.I2C_OwnAddress1=STM32_I2C_OWN_ADDR;
I2C_Init(EEPROM_I2C,&I2C_InitStuctrue);
I2C_Cmd(EEPROM_I2C,ENABLE);
}
//向EEPROM写入一个字节
void EEPROM_Byte_Write(uint8_t addr,uint8_t data)
{
//发送起始信号
I2C_GenerateSTART(EEPROM_I2C,ENABLE);
//检测EV5事件
while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
//发送设备写地址
I2C_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Transmitter);
//检测EV6事件
while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
//发送要操作设备内部的地址
I2C_SendData(EEPROM_I2C,addr);
while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING )==ERROR);
I2C_SendData(EEPROM_I2C,data);
//检测EV8_2事件
while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED )==ERROR);
//发送停止信号
I2C_GenerateSTOP(EEPROM_I2C,ENABLE);
}
//向EEPROM写入多个字节
uint32_t EEPROM_Page_Write(uint8_t addr,uint8_t *data,uint16_t Num_ByteToWrite)
{
I2CTimeout = I2CT_LONG_TIMEOUT;
//判断IIC总线是否忙碌
while(I2C_GetFlagStatus(EEPROM_I2C, I2C_FLAG_BUSY))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1);
}
//重新赋值
I2CTimeout = I2CT_FLAG_TIMEOUT;
//发送起始信号
I2C_GenerateSTART(EEPROM_I2C,ENABLE);
//检测EV5事件
while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)==ERROR)
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);
}
I2CTimeout = I2CT_FLAG_TIMEOUT;
//发送设备写地址
I2C_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Transmitter);
//检测EV6事件
while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR)
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);
}
I2CTimeout = I2CT_FLAG_TIMEOUT;
//发送要操作设备内部的地址
I2C_SendData(EEPROM_I2C,addr);
//检测EV8事件
while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING )==ERROR)
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(4);
}
while(Num_ByteToWrite)
{
I2C_SendData(EEPROM_I2C,*data);
I2CTimeout = I2CT_FLAG_TIMEOUT;
while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING )==ERROR)
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(5);
}
Num_ByteToWrite--;
data++;
}
I2CTimeout = I2CT_FLAG_TIMEOUT;
//检测EV8_2事件
while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED )==ERROR)
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(6);
}
//发送停止信号
I2C_GenerateSTOP(EEPROM_I2C,ENABLE);
return 1;
}
//向EEPROM读取多个字节
uint32_t EEPROM_Read(uint8_t *data,uint8_t addr,uint16_t Num_ByteToRead)
{
I2CTimeout = I2CT_LONG_TIMEOUT;
//判断IIC总线是否忙碌
while(I2C_GetFlagStatus(EEPROM_I2C, I2C_FLAG_BUSY))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1);
}
I2CTimeout = I2CT_FLAG_TIMEOUT;
//发送起始信号
I2C_GenerateSTART(EEPROM_I2C,ENABLE);
//检测EV5事件
while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT )==ERROR)
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(7);
}
I2CTimeout = I2CT_FLAG_TIMEOUT;
//发送设备写地址
I2C_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Transmitter);
//检测EV6事件等待从机应答
while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED )==ERROR)
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(8);
}
I2CTimeout = I2CT_FLAG_TIMEOUT;
//发送要操作设备内部存储器的地址
I2C_SendData(EEPROM_I2C,addr);
//检测EV8事件
while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING )==ERROR)
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(9);
}
I2CTimeout = I2CT_FLAG_TIMEOUT;
//发送起始信号
I2C_GenerateSTART(EEPROM_I2C,ENABLE);
//检测EV5事件
while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT )==ERROR)
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(10);
}
I2CTimeout = I2CT_FLAG_TIMEOUT;
//发送设备读地址
I2C_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Receiver);
//检测EV6事件
while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED )==ERROR)
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(10);
}
while(Num_ByteToRead--)
{
//是否是最后一个字节,若是则发送非应答信号
if( Num_ByteToRead==0)
{
//发送非应答信号
I2C_AcknowledgeConfig(EEPROM_I2C,DISABLE);
//发送停止信号
I2C_GenerateSTOP(EEPROM_I2C,ENABLE);
}
I2CTimeout = I2CT_FLAG_TIMEOUT;
//检测EV7事件
while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_RECEIVED )==ERROR)
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(10);
}
*data=I2C_ReceiveData(EEPROM_I2C);
data++;
}
//重新开启应答信号
I2C_AcknowledgeConfig(EEPROM_I2C,ENABLE);
return 1;
}
void I2C_EE_BufferWrite(uint8_t* pBuffer,uint8_t WriteAddr, uint16_t NumByteToWrite)
{
u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
//I2C_PageSize=8
Addr = WriteAddr % I2C_PageSize;
count = I2C_PageSize - Addr;
NumOfPage = NumByteToWrite / I2C_PageSize;
NumOfSingle = NumByteToWrite % I2C_PageSize;
/* 写入数据的地址对齐,对齐数为8 */
if(Addr == 0)
{
/* 如果写入的数据个数小于8 */
if(NumOfPage == 0)
{
EEPROM_Page_Write(WriteAddr, pBuffer, NumOfSingle);
EEPROM_WaitForWriteEnd();
}
/* 如果写入的数据个数大于8 */
else
{
//按页写入
while(NumOfPage--)
{
EEPROM_Page_Write(WriteAddr, pBuffer, I2C_PageSize);
EEPROM_WaitForWriteEnd();
WriteAddr += I2C_PageSize;
pBuffer += I2C_PageSize;
}
//不足一页(8个)单独写入
if(NumOfSingle!=0)
{
EEPROM_Page_Write(WriteAddr, pBuffer, NumOfSingle);
EEPROM_WaitForWriteEnd();
}
}
}
/*写的数据的地址不对齐*/
else
{
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / I2C_PageSize;
NumOfSingle = NumByteToWrite % I2C_PageSize;
if(count != 0)
{
EEPROM_Page_Write(WriteAddr, pBuffer, count);
EEPROM_WaitForWriteEnd();
WriteAddr += count;
pBuffer += count;
}
while(NumOfPage--)
{
EEPROM_Page_Write(WriteAddr, pBuffer, I2C_PageSize);
EEPROM_WaitForWriteEnd();
WriteAddr += I2C_PageSize;
pBuffer += I2C_PageSize;
}
if(NumOfSingle != 0)
{
EEPROM_Page_Write(WriteAddr, pBuffer, NumOfSingle);
EEPROM_WaitForWriteEnd();
}
}
}
uint32_t EEPROM_WaitForWriteEnd(void)
{
I2CTimeout = I2CT_FLAG_TIMEOUT;
do
{
I2CTimeout = I2CT_FLAG_TIMEOUT;
//发送起始信号
I2C_GenerateSTART(EEPROM_I2C,ENABLE);
//检测EV5事件
while( I2C_GetFlagStatus(EEPROM_I2C,I2C_FLAG_SB )==RESET)
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(10);
}
I2CTimeout = I2CT_FLAG_TIMEOUT;
//发送设备写地址
I2C_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Transmitter);
}while( (I2C_GetFlagStatus(EEPROM_I2C,I2C_FLAG_ADDR )==RESET) && (I2CTimeout--) );
//发送停止信号
I2C_GenerateSTOP(EEPROM_I2C,ENABLE);
return 1;
}
static uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode)
{
/* Block communication and all processes */
EEPROM_ERROR("I2C 等待超时!errorCode = %d",errorCode);
return 0;
}
main.c
#include "stm32f10x.h"
#include "led.h"
#include "./i2c/i2c_ee.h"
#include <string.h>
#include "usart.h"
#define SOFT_DELAY Delay(0x0FFFFF);
void Delay(__IO u32 nCount);
//声明I2C测试函数
uint8_t I2C_EE_Test(void);
int main(void)
{
//初始化IIC
I2C_EE_Config();
//初始化USART
Usart_Config();
//初始化LED
LED_GPIO_Config();
printf("\r\nIIC读写EEPROM测试实验\r\n");
//读写成功亮绿灯,失败亮红灯
if( I2C_EE_Test()==1 )
{
LED_G(NO);
}
else
{
LED_R(NO);
}
while(1)
{
;
}
}
uint8_t I2C_EE_Test(void)
{
uint8_t ReadData[256]={0};
uint8_t WriteDdta[256]={0};
uint16_t i;
//初始化写入数组
for(i=0;i<256;i++)
{
WriteDdta[i]=i;
}
//向EEPROM从地址为0开始写入256个字节的数据
I2C_EE_BufferWrite(WriteDdta,0,256);
//等待EEPROM写入数据完成
EEPROM_WaitForWriteEnd();
//向EEPROM从地址为0开始读出256个字节的数据
EEPROM_Read(ReadData,0,256);
for (i=0; i<256; i++)
{
if(ReadData[i] != WriteDdta[i])
{
EEPROM_ERROR("0x%02X ", ReadData[i]);
EEPROM_ERROR("错误:I2C EEPROM写入与读出的数据不一致\n\r");
return 0;
}
printf("0x%02X ", ReadData[i]);
if(i%16 == 15)
printf("\n\r");
}
EEPROM_INFO("I2C(AT24C02)读写测试成功\n\r");
return 1;
}
void Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
重点讲一下,如何解决以下页写入问题,实现连续写入
- 进行页写入时,写入的存储器地址要对齐到8,也就是说只能写入地址为 0 8 16 32… 能整除8
- 页写如只能一次写入8个字节
现在来解释代码中下图函数如何解决问题
如果地址对齐:
如果地址不对齐:
实验效果
七.软件模式I2C协议
实验目的
STM32作为主机向从机EEPROM存储器写入256个字节的数据
STM32作为主机向从机EEPROM存储器读取写入的256个字节的数据
读写成功亮绿灯,读写失败亮红灯
实验原理
软件模式I2C由我们CPU来控制引脚产生I2C时序,所以我们随便选引脚都可以,不过你选择的引脚肯定要连接到通信的EEPROM的SCL,SDA引脚上。这里是用了PC12,PC11充当主机STM32SCL,SDA引脚。
- 主机产生起始信号
- 主机产生停止信号
- 主机产生应答信号或非应答信号
- 等待从机EEPROM应答
- 主机发送一个字节给从机
- 主机向EEPROM接收一个字节
value应该初始化为0,我忘了sorry
源码
i2c_gpio.h
#ifndef _I2C_GPIO_H
#define _I2C_GPIO_H
#include "stm32f10x.h"
#define EEPROM_I2C_WR 0 /* 写控制bit */
#define EEPROM_I2C_RD 1 /* 读控制bit */
#define EEPROM_GPIO_PORT_I2C GPIOB
#define EEPROM_RCC_I2C_PORT RCC_APB2Periph_GPIOB
#define EEPROM_I2C_SCL_PIN GPIO_Pin_6
#define EEPROM_I2C_SDA_PIN GPIO_Pin_7
/*当 STM32 的 GPIO 配置成开漏输出模式时,它仍然可以通过读取
GPIO 的输入数据寄存器获取外部对引脚的输入电平,也就是说它同时具有浮空输入模式的
功能*/
#define EEPROM_I2C_SCL_1() EEPROM_GPIO_PORT_I2C->BSRR |= EEPROM_I2C_SCL_PIN /* SCL = 1 */
#define EEPROM_I2C_SCL_0() EEPROM_GPIO_PORT_I2C->BRR |= EEPROM_I2C_SCL_PIN /* SCL = 0 */
#define EEPROM_I2C_SDA_1() EEPROM_GPIO_PORT_I2C->BSRR |= EEPROM_I2C_SDA_PIN /* SDA = 1 */
#define EEPROM_I2C_SDA_0() EEPROM_GPIO_PORT_I2C->BRR |= EEPROM_I2C_SDA_PIN /* SDA = 0 */
#define EEPROM_I2C_SDA_READ() ((EEPROM_GPIO_PORT_I2C->IDR & EEPROM_I2C_SDA_PIN)!=0 ) /* 读SDA口线状态 */
void i2c_Start(void);
void i2c_Stop(void);
void i2c_Ack(void);
void i2c_NAcK(void);
uint8_t i2c_WaitAck(void);
void i2c_SendByte(uint8_t data);
uint8_t i2c_ReadByte(void);
uint8_t i2c_CheckDevice(uint8_t Address);
#endif /* _I2C_GPIO_H */
i2c_gpio.c
#include "i2c_gpio.h"
#include "stm32f10x.h"
void I2c_gpio_config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(EEPROM_RCC_I2C_PORT, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SCL_PIN | EEPROM_I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(EEPROM_GPIO_PORT_I2C, &GPIO_InitStructure);
/* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
i2c_Stop();
}
static void i2c_Delay(void)
{
uint8_t i;
for(i=0;i<10;i++)
{
}
}
void i2c_Start(void)
{
EEPROM_I2C_SCL_1();
EEPROM_I2C_SDA_1();
i2c_Delay();
EEPROM_I2C_SDA_0();
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
}
void i2c_Stop(void)
{
EEPROM_I2C_SDA_0();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SDA_1();
i2c_Delay();
}
void i2c_Ack(void)
{
EEPROM_I2C_SCL_0();
i2c_Delay();
EEPROM_I2C_SDA_0();
i2c_Delay();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
EEPROM_I2C_SDA_1();
i2c_Delay();
}
void i2c_NAcK(void)
{
EEPROM_I2C_SDA_1();
i2c_Delay();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
}
uint8_t i2c_WaitAck(void)
{
uint8_t ret;
EEPROM_I2C_SDA_1();
EEPROM_I2C_SCL_1();
i2c_Delay();
if( EEPROM_I2C_SDA_READ() )
{
ret=1;
}
else
{
ret=0;
}
EEPROM_I2C_SCL_0();
i2c_Delay();
return ret;
}
void i2c_SendByte(uint8_t data)
{
uint8_t i;
for(i=0;i<8;i++)
{
if( data&0x80 )
{
EEPROM_I2C_SDA_1();
}
else
{
EEPROM_I2C_SDA_0();
}
i2c_Delay();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
if( i==7 )
{
EEPROM_I2C_SDA_1();
i2c_Delay();
}
data=data<<1;
}
}
uint8_t i2c_ReadByte(void)
{
uint8_t value=0;
uint8_t i;
for(i=0;i<8;i++)
{
value=value<<1;
EEPROM_I2C_SCL_1();
i2c_Delay();
if( EEPROM_I2C_SDA_READ() )
{
value++;
}
EEPROM_I2C_SCL_0();
i2c_Delay();
}
return value;
}
uint8_t i2c_CheckDevice(uint8_t Address)
{
uint8_t ucACK;
I2c_gpio_config();
i2c_Start();
i2c_SendByte(Address|EEPROM_I2C_WR);
ucACK=i2c_WaitAck();
i2c_Stop();
return ucACK;
}
i2c_ee.h
#ifndef _I2C_EE_H
#define _I2C_EE_H
#include "stm32f10x.h"
#define EEPROM_DEV_ADDR 0xA0 /* 24xx02的设备地址 */
#define EEPROM_PAGE_SIZE 8 /* 24xx02的页面大小 */
#define EEPROM_SIZE 256 /* 24xx02总容量 */
uint8_t ee_Checkok(void);
uint8_t ee_ReadByte( uint8_t *pReaddata,uint16_t Address,uint16_t num );
uint8_t ee_WriteByte( uint8_t *Writepdata,uint16_t Address,uint16_t num );
uint8_t ee_WaitStandby(void);
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize);
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize);
uint8_t ee_Test(void) ;
#endif /* _I2C_EE_H*/
i2c_ee.c
#include "i2c_ee.h"
#include "i2c_gpio.h"
//检测EEPORM是否忙碌
uint8_t ee_Checkok(void)
{
if(i2c_CheckDevice(EEPROM_DEV_ADDR)==0)
{
return 1;
}
else
{
i2c_Stop();
return 0;
}
}
//检测EEPROM写入数完成
uint8_t ee_WaitStandby(void)
{
uint32_t wait_count = 0;
while(i2c_CheckDevice(EEPROM_DEV_ADDR))
{
//若检测超过次数,退出循环
if(wait_count++>0xFFFF)
{
//等待超时
return 1;
}
}
//等待完成
return 0;
}
//向EEPROM写入多个字节
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
{
uint16_t i,m;
uint16_t addr;
addr=_usAddress;
for(i=0;i<_usSize;i++)
{
//当第一次或者地址对齐到8就要重新发起起始信号和EEPROM地址
//为了解决8地址对齐问题
if(i==0 || (addr % EEPROM_PAGE_SIZE)==0 )
{
//循环发送起始信号和EEPROM地址的原因是为了等待上一次写入的一页数据\
写入完成
for(m=0;m<1000;m++)
{
//发送起始地址
i2c_Start();
//发送设备写地址
i2c_SendByte(EEPROM_DEV_ADDR|EEPROM_I2C_WR);
//等待从机应答
if( i2c_WaitAck()==0 )
{
break;
}
}
//若等待的1000次从机还未应答,等待超时
if( m==1000 )
{
goto cmd_fail;
}
//EEPROM应答后发送EEPROM的内部存储器地址
i2c_SendByte((uint8_t)addr);
//等待从机应答
if( i2c_WaitAck()!=0 )
{
goto cmd_fail;
}
}
//发送数据
i2c_SendByte(_pWriteBuf[i]);
//等待应答
if( i2c_WaitAck()!=0 )
{
goto cmd_fail;
}
//写入地址加1
addr++;
}
i2c_Stop();
return 1;
cmd_fail:
i2c_Stop();
return 0;
}
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize)
{
uint16_t i;
i2c_Start();
i2c_SendByte(EEPROM_DEV_ADDR|EEPROM_I2C_WR);
if( i2c_WaitAck()!=0 )
{
goto cmd_fail;
}
i2c_SendByte((uint8_t)_usAddress);
if( i2c_WaitAck()!=0 )
{
goto cmd_fail;
}
i2c_Start();
i2c_SendByte(EEPROM_DEV_ADDR|EEPROM_I2C_RD);
if( i2c_WaitAck()!=0 )
{
goto cmd_fail;
}
for(i=0;i<_usSize;i++)
{
_pReadBuf[i]=i2c_ReadByte();
/* 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack */
if (i != _usSize - 1)
{
// i2c_NAcK(); /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
i2c_Ack(); /* 中间字节读完后,CPU产生ACK信号(驱动SDA = 0) */
}
else
{
i2c_NAcK(); /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
}
}
i2c_Stop();
return 1;
cmd_fail:
i2c_Stop();
return 0;
}
uint8_t ee_Test(void)
{
uint16_t i;
uint8_t write_buf[EEPROM_SIZE];
uint8_t read_buf[EEPROM_SIZE];
/*-----------------------------------------------------------------------------------*/
if (i2c_CheckDevice(EEPROM_DEV_ADDR) == 1)
{
/* 没有检测到EEPROM */
printf("没有检测到串行EEPROM!\r\n");
return 0;
}
/*------------------------------------------------------------------------------------*/
/* 填充测试缓冲区 */
for (i = 0; i < EEPROM_SIZE; i++)
{
write_buf[i] = i;
}
/*------------------------------------------------------------------------------------*/
if (ee_WriteBytes(write_buf, 0, EEPROM_SIZE) == 0)
{
printf("写EEPROM出错!\r\n");
return 0;
}
else
{
printf("写EEPROM成功!\r\n");
}
/*-----------------------------------------------------------------------------------*/
if (ee_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0)
{
printf("EEPROM出错!\r\n");
return 0;
}
else
{
printf("EEPROM成功,数据如下:\r\n");
}
/*-----------------------------------------------------------------------------------*/
for (i = 0; i < EEPROM_SIZE; i++)
{
if(read_buf[i] != write_buf[i])
{
printf("0x%02X ", read_buf[i]);
printf("错误:EEPROM读出与写入的数据不一致");
return 0;
}
printf(" %02X", read_buf[i]);
if ((i & 15) == 15)
{
printf("\r\n");
}
}
printf("EEPROM读写测试成功\r\n");
return 1;
}
main
#include "stm32f10x.h"
#include "led.h"
#include "usart.h"
#include <string.h>
#include "i2c_ee.h"
#include "i2c_gpio.h"
#define SOFT_DELAY Delay(0x0FFFFF);
void Delay(__IO u32 nCount);
int main(void)
{
/* LED 端口初始化 */
LED_GPIO_Config();
/*初始化USART 配置模式为 115200 8-N-1,中断接收*/
USART_Config();
printf("EEPROM 软件模拟i2c测试例程 \r\n");
if(ee_Test() == 1)
{
LED_G(NO);
}
else
{
LED_R(NO);
}
while(1)
{
}
}
void Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
效果与硬件I2C一模一样就不演示了
八.总结
不管是硬件I2C还是软件I2C先不管他们的优缺点,主要我们是要在实现的过程中理解IC2协议这个才是最重要的,反正I2C必须得会因为应用太广泛了,最后如果文章内容有疑问的来评论区一起讨论讨论!!!
更多推荐
所有评论(0)