22. I2C——读写EEPROM¶
本章参考资料:
若对I2C通讯协议不了解,可先阅读《I2C总线协议》文档的内容学习。
关于EEPROM存储器,请参考“常用存储器介绍”章节,实验中的EEPROM,请参考其规格书《AT24C02》来了解。
22.1. I2C简介¶
I2C 通讯协议(Inter-Integrated Circuit)是由Philips公司开发的,由于它引脚少,硬件实现简单,可扩展性强, 不需要USART、CAN等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。
在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设; 瑞萨的FPS库则是在寄存器与用户代码之间的软件层。对于通讯协议,我们也以分层的方式来理解, 最基本的是把它分为物理层和协议层。物理层规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。 协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。简单来说物理层规定我们用嘴巴还是用肢体来交流, 协议层则规定我们用中文还是英文来交流。
下面我们分别对I2C协议的物理层及协议层进行讲解。
22.1.1. I2C物理层¶
I2C通讯设备之间的常用连接方式见 图22_1。
图 22‑1 常见的I2C通讯系统
它的物理层有如下特点:
它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个I2C通讯总线中,可连接多个I2C通讯设备,支持多个通讯主机及多个通讯从机。
一个I2C总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线 (SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
总线通过上拉电阻接到电源。当I2C设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
具有三种传输模式:标准模式传输速率为100kbit/s ,快速模式为400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多I2C设备尚不支持高速模式。
连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。
22.1.2. 协议层¶
I2C的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。
22.1.2.1. I2C基本读写过程¶
先看看I2C通讯过程的基本结构,它的通讯过程见 图22_2、图22_3 及 图22_4。
图 22‑2 主机写数据到从机
图 22‑3 主机由从机中读数据
图 22‑4 I2C通讯复合格式
图例:
数据由主机传输至从机
S : 传输开始信号
SLAVE_ADDRESS: 从机地址
数据由从机传输至主机
R/W- : 传输方向选择位,1为读,0为写
A/A- : 应答(ACK)或非应答(NACK)信号
P : 停止传输信号
这些图表示的是主机和从机通讯时,SDA线的数据包序列。
其中S表示由主机的I2C接口产生的传输起始信号(S),这时连接到I2C总线上的所有从机都会接收到这个信号。
起始信号产生后,所有从机就开始等待主机紧接下来广播 的从机地址信号 (SLAVE_ADDRESS)。在I2C总线上,每个设备的地址都是唯一的,当主机广播的地址与某个设备地址相同时, 这个设备就被选中了,没被选中的设备将会忽略之后的数据信号。根据I2C协议,这个从机地址可以是7位或10位。
在地址位之后,是传输方向的选择位,该位为0时,表示后面的数据传输方向是由主机传输至从机,即主机向从机写数据。该位为1时,则相反,即主机由从机读数据。
从机接收到匹配的地址后,从机会返回一个应答(ACK)或非应答(NACK)信号,只有接收到应答信号后,主机才能继续发送或接收数据。
写数据
若配置的方向传输位为“写数据”方向,即第一幅图的情况,广播完地址, 接收到应答信号后,主机开始正式向从机传输数据(DATA), 数据包的大小为8位,主机每发送完一个字节数据,都要等待从机的应答信号(ACK), 重复这个过程,可以向从机传输N个数据,这个N没有大小限制。当数据传输结束时,主机向从机发送一个停止传输信号(P),表示不再传输数据。
读数据
若配置的方向传输位为“读数据”方向,即第二幅图的情况,广播完地址, 接收到应答信号后,从机开始向主机返回数据(DATA),数据包大小也为8位, 从机每发送完一个数据,都会等待主机的应答信号(ACK),重复这个过程,可以返回N个数据,这个N也没有大小限制。 当主机希望停止接收数据时,就向从机返回一个非应答信号(NACK),则从机自动停止数据传输。
读和写数据
除了基本的读写,I2C通讯更常用的是复合格式,即第三幅图的情况,该传输过程有两次起始信号(S)。一般在第一次传输中,主机通过 SLAVE_ADDRESS寻找到从设备后,发送一段“数据”,这段数据通常用于表示从设备内部的寄存器或存储器地址(注意区分它与SLAVE_ADDRESS的区别);在第二次的传输中,对该地址的内容进行读或写。也就是说,第一次通讯是告诉从机读写地址,第二次则是读写的实际内容。
以上通讯流程中包含的各个信号分解如下:
22.1.2.2. 通讯的起始和停止信号¶
前文中提到的起始(S)和停止(P)信号是两种特殊的状态,见图 22‑5。当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。当 SCL 是高电平时 SDA 线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。
图 22‑5 起始和停止信号
22.1.2.3. 数据有效性¶
I2C使用SDA信号线来传输数据,使用SCL信号线进行数据同步。见 图22_6。 SDA数据线在SCL的每个时钟周期传输一位数据。传输时,SCL为高电平的时候SDA表示的数据有效,即此时的SDA为高电平时表示数据“1”,为低电平时表示数据“0”。当SCL为低电平时,SDA的数据无效,一般在这个时候SDA进行电平切换,为下一次表示数据做好准备。
图 22‑6 数据有效性
每次数据传输都以字节为单位,每次传输的字节数不受限制。
22.1.2.4. 地址及数据方向¶
I2C总线上的每个设备都有自己的独立地址,主机发起通讯时,通过SDA信号线发送设备地址(SLAVE_ADDRESS)来查找从机。 I2C协议规定设备地址可以是7位或10位,实际中7位的地址应用比较广泛。 紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/),第8位或第11位。数据方向位为“1”时表示主机由从机读数据, 该位为“0”时表示主机向从机写数据。见 图22_7。
图 22‑7 设备地址(7位)及数据传输方向
读数据方向时,主机会释放对SDA信号线的控制,由从机控制SDA信号线,主机接收信号;写数据方向时,SDA由主机控制,从机接收信号。
22.1.2.5. 响应¶
I2C的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时, 当设备(无论主从机)接收到I2C传输的一个字节数据或地址后,若希望对方继续发送数据, 则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输, 则向对方发送“非应答(NACK)”信号,发送方不会继续发送下一个数据,之后主机会产生一个停止的信号,并且结束信号传输。见 图22_8。
图 22‑8 响应与非响应信号
传输时主机产生时钟,在第9个时钟时,数据发送端会释放SDA的控制权,由数据接收端控制SDA,若SDA为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。
22.2. 瑞萨RA6M5的I2C特性及架构¶
如果我们直接控制RA6M5的两个GPIO引脚,分别用作SCL及SDA,按照上述信号的时序要求, 直接像控制LED灯那样控制引脚的输出(若是接收数据时则读取SDA电平),就可以实现I2C通讯。 同样,假如我们按照USART的要求去控制引脚,也能实现USART通讯。所以只要遵守协议,就是标准的通讯, 不管您如何实现它,不管是ST生产的控制器还是ATMEL生产的存储器, 都能按通讯标准交互。
由于直接控制GPIO引脚电平产生通讯时序时,需要由CPU控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。
相对地,还有“硬件协议”方式,RA6M5的I2C片上外设专门负责实现I2C通讯协议, 只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来 ,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。 这种由硬件外设处理I2C协议的方式减轻了 CPU的工作,且使软件设计更加简单。
22.2.1. 瑞萨RA6M5的I2C外设简介¶
瑞萨RA6M5的I2C外设可用作通讯的主机及从机,支持100Kbit/s和400Kbit/s和1Mbit/s的速率,支持7位、10位设备地址, 支持DMA数据传输,并具有数据校验功能。它的I2C外设还支持SMBus2.0协议,SMBus协议与I2C类似, 主要应用于笔记本电脑的电池管理中,本教程不展开,感兴趣的读者可参考《SMBus20》文档了解。
频率配置 |
标准模式 100-kHz |
快速模式 400-kHz |
快速plus模式 1-MHz |
22.2.1.1. 通讯引脚¶
I2C的所有硬件架构都是根据图中左侧SCL线和SDA线展开的(其中的SMBA线用于SMBUS的警告信号,I2C通讯没有使用)。 RA6M5芯片有多个I2C外设,它们的I2C通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚,见 表 22‑1。关于GPIO引脚的复用功能,可查阅《RA6M5 Group User_s Manual》,以它为准。
表 22‑1 RA6M5的I2C引脚(整理自《RA6M5 Group User_s Manual》)
引脚 |
I2C编号 |
||
---|---|---|---|
I2C1 |
I2C2 |
I2C3 |
|
SCL |
P408/P400 |
P512/P205 |
P415/P410 |
SDA |
P407/P401 |
P511/P206 |
P411/P409 |
22.2.2. 瑞萨RA6M5的I2C功能框图¶
ICCR1 |
I2C总线控制寄存器1 |
ICCR2 |
I2C总线控制寄存器2 |
ICMR1 |
I2C总线模式寄存器1 |
ICMR2 |
I2C总线模式寄存器2 |
ICMR3 |
I2C总线模式寄存器3 |
ICFER |
I2C总线功能使能寄存器 |
ICSER |
I2C总线状态使能寄存器 |
ICIER |
I2C总线中断使能寄存器 |
ICSR1 |
I2C总线状态寄存器1 |
ICSR2 |
I2C总线状态寄存器2 |
ICWUR |
I2C总线唤醒单元寄存器 |
ICWUR2 |
I2C总线唤醒单元寄存器2 |
SARLy |
从地址寄存器Ly (y = 0到2) |
SARUy |
从地址寄存器Uy (y = 0到2) |
ICBRL |
I2C总线比特率低寄存器 |
ICBRH |
I2C总线比特率高寄存器 |
ICDRT |
I2C总线发送数据寄存器 |
ICDRR |
I2C总线接收数据寄存器 |
ICDRS |
I2C总线移位寄存器 |
注解
我们这里主要介绍的是瑞萨的fsp库的使用,所以在这里寄存器我们在这里做一些简单的介绍。
22.2.3. 时钟控制逻辑¶
下面将介绍时钟的控制流程
22.2.3.1. IIC起始信号与重启信号¶
当ICCR2中的RS位被设置为1时,IIC会发出一个重启条件请求。当ICCR2的BBSY标志位为1(总线繁忙状态),ICCR2的MST位为1(主模式)时IIC发出重启条件。 发出重启条件:
释放SDAn线路。
确保ICBRL中设置的SCLn线路的低电平周期已过。
释放SCLn线(低电平到高电平)。
在SCLn线上检测高电平,并确保ICBRL中设置的时间和重启条件设置时间经过。
将SDAn线拉低(从高电平到低电平)。
确保ICBRH中设置的时间和重启条件保持时间。
驱动SCLn线低电平(高电平到低电平)。
检测SCLn线上的一个低水平,确保ICBRL中设置的SCLn线的低水平周期已经过去
22.2.3.2. IIC循环发送¶
初始化IIC。
读取ICCR2中的BBSY标志,检查总线是否打开,然后设置ICCR2中的ST位为1(启动条件发出请求)。收到请求后,IIC发出启动条件。与此同时,BBSY位和START为被设置为1,ST位自动设置为0。如果检测到启动条件,并且SDA输出状态的内部电平和SDAn线路上的电平在ST位为1时匹配,IIC就会识别出,由于ST位已经成功完成了请求,启动条件已经成功发出。ICCR2的MST和TRS位自动设置为1,IIC处于主发送模式。当TRS位设置为1时,ICSR2中的TDRE标志也会自动设置为1。
检查ICSR2中的TDRE标志位是否为1,然后将传输值(从地址和R/W#位)写入ICDRT。传输数据写入ICDRT后,TDRE标志位自动设置为0,数据从ICDRT传输到ICDRS后,TDRE标志位再次设置为1。字节后包含从地址和当发送了R/W#位时,TRS位的值会根据发送的R/W#位的值自动更新,选择主发送或主接收模式。如果R/W#为0,则IIC继续主传输模式。如果此时ICSR2.NACKF标志位为1,表示从设备没有识别到该地址,或者通信出现错误,并向ICCR2.SP位写入1发出停止条件。如果要传输10位地址格式的数据,首先将11110b和从地址的高两位和W写入ICDRT作为第一个地址传输。然后将从地址低8位写入ICDRT作为第二个地址传输。
确认ICSR2的TDRE标志位为1后,将传输数据写入ICDRT寄存器。IIC自动保持SCLn线路处于低位,直到传输数据就绪,发出重启条件或停止条件。
当所有要传输的数据字节写入ICDRT寄存器后,等待ICSR2中的TEND标志位的值返回1。确认ICSR2的START标志位为1后,将ICSR2的START标志位设置为0。
设置ICCR2中的RS位为1(重启条件问题请求)。在接收到请求时,IIC发出一个重启条件。
检查ICSR2中的START标志位为1后,将传输值(从地址和R/W#位)写入ICDRT。
22.3. EEPROM简介¶
EEPROM是一种掉电后数据不丢失的存储器,常用来存储一些配置信息,以便系统重新上电的时候加载。 EEPROM芯片最常用的通讯方式就是I2C协议,本小节以EEPROM的读写实验为例子给大家讲解RA6M5的I2C使用方法。 实验中RA6M5的I2C外设采用主机模式,分别用作主发送器和主接收器,通过查询事件的方式来确保正常通讯。
本实验板中的EEPROM芯片(型号:AT24C02)的SCL及SDA引脚连接到了瑞萨RA6M5对应的I2C引脚中,结合上拉电阻,构成了I2C通讯总线,它们通过I2C总线交互。EEPROM芯片的设备地址一共有7位,其中高4位固定为:1010 b,低3位则由A0/A1/A2信号线的电平决定,见 图22_12,图中的R/W是读写方向位,与地址无关。
图 22‑12 EEPROM设备地址(摘自《AT24C02》规格书)
按照我们此处的连接,A0/A1/A2均为0,所以EEPROM的7位设备地址是:1010 000b ,即0x50。由于I2C通讯时常常是地址跟读写方向连在一起构成一个8位数,且当R/W位为0时,表示写方向,所以加上7位地址,其值为“0xA0”,常称该值为I2C设备的“写地址”;当R/W位为1时,表示读方向,加上7位地址,其值为“0xA1”,常称该值为“读地址”。
EEPROM芯片中还有一个WP引脚,具有写保护功能,当该引脚电平为高时,禁止写入数据,当引脚为低电平时,可写入数据,我们直接接地,不使用写保护功能。
关于EEPROM的更多信息,可参考其数据手册《AT24C02》来了解。若您使用的实验板EEPROM的型号、设备地址或控制引脚不一样,只需根据我们的工程修改即可,程序的控制原理相同。
22.4. I2C——读写EEPROM实验¶
22.4.1. 硬件设计¶
野火启明6M5开发板的 EEPROM 电路图如图所示:
图 22‑13a EEPROM硬件连接图
野火启明4M2开发板的 EEPROM 电路图如图所示:
图 22‑13b EEPROM硬件连接图
野火启明2L1开发板的 EEPROM 电路图如图所示:
图 22‑13c EEPROM硬件连接图
EEPROM 芯片连接到 MCU 的引脚如下表所示。
野火启明开发板 |
引脚连接 |
---|---|
启明6M5 |
|
启明4M2 |
|
启明2L1 |
|
22.4.2. 软件设计¶
使用瑞萨官方提供的FPS库进行编程,瑞萨官方提供的FPS库具有方便、快捷、简洁的特性。
22.4.2.1. 新建工程¶
因为本章节的 EEPROM 相关实验例程需要用到板子上的串口功能,因此我们可以直接以前面的“19_UART_Receive_Send”工程为基础进行修改。
- 对于 e2 studio 开发环境:
拷贝一份我们之前的 e2s 工程模板 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “22_EEPROM_Hardware”,最后再将它导入到我们的 e2 studio 工作空间中。
- 对于 Keil 开发环境:
拷贝一份我们之前的 Keil 工程模板 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “22_EEPROM_Hardware”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。
22.4.2.2. FSP配置¶
新建工程后,我们先打开 “22_EEPROM_Hardware” 项目的 FSP 配置界面进行配置。
在 FSP 配置界面里面我们依次点击 “Stacks”-> “New Stack”-> “Connectivity”-> “I2C Master” 来配置IIC模块。如 图22_14。
图 22-14 加入IIC
按照图片顺序依次进行点击 然后点击 “I2C Master” 在左下角的属性界面里进行配置。如 图22_15。
图 22-15 配置图
配置完成之后可以按下快捷键“Ctrl + S”保存, 最后点右上角的 “Generate Project Content” 按钮,让软件自动生成配置代码即可。
22.4.2.3. R_IIC_MASTER_Write 函数¶
fsp_err_t R_IIC_MASTER_Write (i2c_master_ctrl_t * const p_api_ctrl,uint8_t * const p_src,uint32_t const bytes,bool const restart)
当我们调用该函数,在数据传输的开始时会发送从设备的地址位。之后根据p_src数组发送第一个位在数据位,传输的过程中硬件会自动发送确认位和结束位。 传输数据的长度与bytes有关,完成之后restart来决定此次通信之后是否通过发出重复的START条件来保持总线。
注解
在调用这个函数之后我们需要延时一段时间或者使用回调函数判断,之后再调用下一段IIC函数,详细的可以看下面IIC的写入代码, 原因是因为当你使用了R_IIC_MASTER_Write函数之后,IIC通信还未完成, 如果你再次调用其他的IIC函数就会覆盖掉第一次执行的函数, 从而出现时序错误。
22.4.2.4. R_IIC_MASTER_Read 函数¶
fsp_err_t R_IIC_MASTER_Read (i2c_master_ctrl_t * const p_api_ctrl,uint8_t * const p_dest,uint32_t const bytes,bool const restart)
当我们调用该函数,在数据传输的开始时会发送从设备的地址位。之后根据p_src数组保存第一个获取的数据,传输的过程中硬件会自动加载应答位和结束位。 传输数据的长度与bytes有关,完成之后restart来决定此次通信之后是否通过发出重复的START条件来保持总线。
注解
该函数与之前的R_IIC_MASTER_Write函数一样在使用之后需要需要延时一段时间或者使用回调函数判断。
22.4.2.5. 向EEPROM写入一个字节¶
初始化好I2C外设后,就可以使用I2C通讯了,更具上面两个函数的介绍我们就可以写出EEPROM的写入以及读取函数。我们看看如何向EEPROM写入一个字节的数据,见 代码清单22_3。
/**
* @brief 以单字节的方式到I2C EEPROM中
* @param
* @arg address:写地址
* @arg byte:写的数据
* @retval 无
*/
void I2C_EE_ByteWrite(unsigned char address, unsigned char byte)
{
iic_complete = false;
unsigned char send_buffer[2] = {};
send_buffer[0] = address;
send_buffer[1] = byte;
R_IIC_MASTER_Write(&EEPROM_ctrl, &send_buffer[0], 2, false); //每当写完数据 false 总线拉高
while ((I2C_MASTER_EVENT_TX_COMPLETE != g_i2c_callback_event) && timeout_ms)
{
R_BSP_SoftwareDelay(1U, BSP_DELAY_UNITS_MILLISECONDS);
timeout_ms--;
}
timeout_ms = 500;
}
这里我们只是简单调用库函数R_IIC_MASTER_Write就可以实现,通过封装一次使用更为方便。
在这个通讯过程中,RA6M5实际上通过I2C向EEPROM发送了两个数据, 但为何第一个数据被解释为EEPROM的内存地址? 这是由EEPROM的自己定义的单字节写入时序,见 图22_16.
图 22‑16 EEPROM单字节写入时序(摘自《AT24C02》规格书)
EEPROM的单字节时序规定,向它写入数据的时候,第一个字节为内存地址,第二个字节是要写入的数据内容。所以我们需要理解:命令、地址的本质都是数据,对数据的解释不同,它就有了不同的功能。
22.4.2.6. EEPROM的页写入¶
在以上的数据通讯中,每写入一个数据都需要向EEPROM发送写入的地址,我们希望向连续地址写入多个数据的时候,只要告诉EEPROM第一个内存地址address1,后面的数据按次序写入到address2、address3… 这样可以节省通讯的内容,加快速度。为应对这种需求,EEPROM定义了一种页写入时序,见 图22_17。
图 22‑17 EEPROM页写入时序(摘自《AT24C02》规格书)
根据页写入时序,第一个数据被解释为要写入的内存地址address1,后续可连续发送n个数据, 这些数据会依次写入到内存中。其中AT24C02型号的芯片页写入时序最多可以一次发送8个数据(即n = 8 ),该值也称为页大小,某些型号的芯片每个页写入时序最多可传输16个数据。EEPROM的页写入代码实现 见 代码清单22_4。
/**
* @brief 将缓冲区中的数据以页写入的方式写到I2C EEPROM中
* @param
* @arg ptr_write:缓冲区指针
* @arg WriteAddr:写地址
* @arg len:写的长度
* @retval 无
*/
void I2C_EE_Writepage(unsigned char* ptr_write , unsigned char WriteAddr,unsigned char len) //页写入 page 0~31
{
unsigned char send_buffer[9] = {};
send_buffer[0] = WriteAddr;
for(unsigned char i = 0;i<len;i++)
{
send_buffer[1+i] = *(ptr_write+i);
}
R_IIC_MASTER_Write(&EEPROM_ctrl, &send_buffer[0], len+1 , false);
while ((I2C_MASTER_EVENT_TX_COMPLETE != g_i2c_callback_event) && timeout_ms)
{
R_BSP_SoftwareDelay(1U, BSP_DELAY_UNITS_MILLISECONDS);
timeout_ms--;
}
timeout_ms = 500;
I2C_EE_WaitState();
}
22.4.2.7. 多字节写入¶
多次写入数据时,利用EEPROM的页写入方式,避免单字节读写时候的等待。多个数据写入过程 见 代码清单22_5。
/**
* @brief 将缓冲区中的数据写到I2C EEPROM中
* @param
* @arg pBuffer:缓冲区指针
* @arg WriteAddr:写地址
* @arg NumByteToWrite:写的字节数
* @retval 无
*/
void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr,uint16_t NumByteToWrite)
{
uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
Addr = WriteAddr % EEPROM_PAGESIZE;
count = EEPROM_PAGESIZE - Addr;
NumOfPage = NumByteToWrite / EEPROM_PAGESIZE;
NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;
/* If WriteAddr is I2C_PageSize aligned */
if (Addr == 0) {
/* If NumByteToWrite < I2C_PageSize */
if (NumOfPage == 0) {
I2C_EE_Writepage(pBuffer, WriteAddr, NumOfSingle);
}
/* If NumByteToWrite > I2C_PageSize */
else {
while (NumOfPage--) {
I2C_EE_Writepage(pBuffer, WriteAddr, EEPROM_PAGESIZE);
WriteAddr += EEPROM_PAGESIZE;
pBuffer += EEPROM_PAGESIZE;
}
if (NumOfSingle!=0) {
I2C_EE_Writepage(pBuffer, WriteAddr, NumOfSingle);
}
}
}
/* If WriteAddr is not I2C_PageSize aligned */
else {
/* If NumByteToWrite < I2C_PageSize */
if (NumOfPage== 0) {
I2C_EE_Writepage(pBuffer, WriteAddr, NumOfSingle);
}
/* If NumByteToWrite > I2C_PageSize */
else {
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / EEPROM_PAGESIZE;
NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;
if (count != 0) {
I2C_EE_Writepage(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
}
while (NumOfPage--) {
I2C_EE_Writepage(pBuffer, WriteAddr, EEPROM_PAGESIZE);
WriteAddr += EEPROM_PAGESIZE;
pBuffer += EEPROM_PAGESIZE;
}
if (NumOfSingle != 0) {
I2C_EE_Writepage(pBuffer, WriteAddr, NumOfSingle);
}
}
}
}
22.4.2.8. EEPROM读取函数¶
从EEPROM读取数据是一个复合的I2C时序,它实际上包含一个写过程和一个读过程, 见 图22_18。
图 22‑18 EEPROM数据读取时序
读时序的第一个通讯过程中,使用I2C发送设备地址寻址(写方向),接着发送要读取的“内存地址”;第二个通讯过程中, 再次使用I2C发送设备地址寻址,但这个时候的数据方向是读方向;在这个过程之后,EEPROM会向主机返回从“内存地址”开始的数据, 一个字节一个字节地传输,只要主机的响应为“应答信号”,它就会一直传输下去,主机想结束传输时,就发送“非应答信号”, 并以“停止信号”结束通讯,作为从机的EEPROM也会停止传输。FSP库已经帮我们实现了这一个过程, 我们只是简单封装一下就可以直接使用,实现代码见 代码清单22_6。
/**
* @brief 读取I2C EEPROM数据
* @param
* @arg ptr_read:读取缓冲区指针
* @arg address:地址
* @arg byte:读取的字节数
* @retval 无
*/
void I2C_EE_BufferRead(unsigned char* ptr_read,unsigned char address,unsigned char byte)
{
unsigned char send_buffer[2] = {};
unsigned char read_buffer[1] = {};
send_buffer[0] = address;
R_IIC_MASTER_Write(&EEPROM_ctrl, &send_buffer[0], 1, true);
while ((I2C_MASTER_EVENT_TX_COMPLETE != g_i2c_callback_event) && timeout_ms)
{
R_BSP_SoftwareDelay(400U, BSP_DELAY_UNITS_MICROSECONDS);
timeout_ms--;
}
timeout_ms = 500;
R_BSP_SoftwareDelay(250U, BSP_DELAY_UNITS_MICROSECONDS);
R_IIC_MASTER_Read(&EEPROM_ctrl, ptr_read, byte, false);
}
这个函数是在指定的地址读取一个字节的数据,第一个变量EEPROM的地址,最后会返回一个整型的数据。如果不想使用printf函数可以将其进行注释,但需要适当增加延时时间。 这里代码非常简单,我们只需要确定I2C的地址、数据格式、数据存储指针、数据大小、超时设置,就可以把想要的数据读回来。
22.4.2.9. EEPROM测试函数¶
/**
* @brief I2C(AT24C02)读写测试
* @param 无
* @retval 正常返回1 ,不正常返回0
*/
uint8_t I2C_Test(void)
{
uint16_t i;
unsigned char DATA_Size = 30;
unsigned char I2c_Buf_Write[33] = {};
unsigned char I2c_Buf_Read[33] = {};
//将I2c_Buf_Write中顺序递增的数据写入EERPOM中
printf("写入的数据\r\n");
for ( i=0; i<DATA_Size; i++ ) { //填充缓冲
I2c_Buf_Write[i] =i;
printf("0x%02X ", I2c_Buf_Write[i]);
if (i%16 == 15)
printf("\n");
}
I2C_EE_BufferWrite( I2c_Buf_Write, 0x00 , DATA_Size);
//将EEPROM读出数据顺序保持到I2c_Buf_Read中
printf("\r\n读出的数据\n");
R_BSP_SoftwareDelay(10U, BSP_DELAY_UNITS_MILLISECONDS);
I2C_EE_BufferRead(I2c_Buf_Read, 0x00 , DATA_Size);
//将I2c_Buf_Read中的数据通过串口打印
R_BSP_SoftwareDelay(10U, BSP_DELAY_UNITS_MILLISECONDS);
for (i=0; i<DATA_Size; i++) {
if (I2c_Buf_Read[i] != I2c_Buf_Write[i]) {
printf("0x%02X \n", I2c_Buf_Read[i]);
printf("错误:I2C EEPROM写入与读出的数据不一致\n");
printf("%d\n",i);
return 0;
}
printf("0x%02X ", I2c_Buf_Read[i]);
if (i%16 == 15)
printf("\n");
}
printf("\r\nI2C(AT24C02)读写测试成功\n");
return 1;
}
22.4.2.10. 主函数¶
void hal_entry(void)
{
I2C_EE_Init();
Debug_UART4_Init();
printf("欢迎使用野火 RA6M5 开发板。\r\n");
printf("这是一个I2C外设(AT24C02)读写测试例程 \r\n");
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
while (1)
{
I2C_EE_Writedrase();
if (I2C_Test() ==1) {
LED_GREEN;
} else {
LED_RED;
}
while(1);
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
22.4.3. 下载验证¶
保证开发板相关硬件连接正确,用Type-C线连接开发板“USB TO UART”接口跟电脑, 在电脑端打开串口调试助手,把编译好的程序下载到开发板, 此时串口调试助手即可收到开发板发过来的数据。 在串口调试助手可看到EEPROM测试的调试信息。如下图所示: