瑞萨RA系列FSP库开发实战指南之I2C读写EEPROM实验
21.4.1
硬件设计
野火启明6M5开发板的EEPROM电路图如图所示:

图22‑13a EEPROM硬件连接图
野火启明4M2开发板的EEPROM电路图如图所示:
图22‑13b EEPROM硬件连接图
野火启明2L1开发板的EEPROM电路图如图所示:

图22‑13c EEPROM硬件连接图
EEPROM芯片连接到MCU的引脚如下表所示。
表2:EEPROM引脚
21.4.2
软件设计
使用瑞萨官方提供的FPS库进行编程,瑞萨官方提供的FPS库具有方便、快捷、简洁的特性。
21.4.2.1
新建工程
因为本章节的EEPROM相关实验例程需要用到板子上的串口功能,因此我们可以直接以前面的“19_UART_Receive_Send”工程为基础进行修改。
对于e2studio开发环境:拷贝一份我们之前的e2s工程“19_UART_Receive_Send”“22_EEPROM_Hardware”,然后将工程文件夹重命名为“16_ICU_External_IRQ”,最后再将它导入到我们的e2studio工作空间中。
对于Keil开发环境:拷贝一份我们之前的Keil工程“19_UART_Receive_Send”“22_EEPROM_Hardware”,然后将工程文件夹重命名为“16_ICU_External_IRQ”,并进入该文件夹里面双击Keil工程文件,打开该工程。
21.4.2.2
FSP配置
新建工程后,我们先打开“22_EEPROM_Hardware”项目的FSP配置界面进行配置。新建工程后,
在FSP配置界面里面我们依次点击“Stacks”->“NewStack”->“Connectivity”->“I2CMaster”来配置IIC模块。如图22_14。

图22-14 加入IIC
按照图片顺序依次进行点击然后点击“I2CMaster”在左下角的属性界面里进行配置。如图22_15。

图22-15 配置图
配置完成之后可以按下快捷键“Ctrl+S”保存,最后点右上角的“GenerateProjectContent”按钮,让软件自动生成配置代码即可。
21.4.2.3
R_IIC_MASTER_Write函数
列表1:代码清单22_1:
R_IIC_MASTER_Write结构体
左右滑动查看完整内容
fsp_err_tR_IIC_MASTER_Write(i2c_master_ctrl_t*constp_api_ctrl,uint8_t␣ →*constp_src,uint32_tconst bytes,boolconst restart)
当我们调用该函数,在数据传输的开始时会发送从设备的地址位。之后根据p_src数组发送第一个位在数据位,传输的过程中硬件会自动发送确认位和结束位。传输数据的长度与bytes有关,完成之后restart来决定此次通信之后是否通过发出重复的START条件来保持总线。
注
在调用这个函数之后我们需要延时一段时间或者使用回调函数判断,之后再调用下一段IIC函数,详细的可以看下面IIC的写入代码,原因是因为当你使用了R_IIC_MASTER_Write函数之后,IIC通信还未完成,如果你再次调用其他的IIC函数就会覆盖掉第一次执行的函数,从而出现时序错误。
21.4.2.4
R_IIC_MASTER_Read函数
列表2:代码清单22_2:
R_IIC_MASTER_Read结构体
左右滑动查看完整内容
fsp_err_tR_IIC_MASTER_Read(i2c_master_ctrl_t*constp_api_ctrl,uint8_t*␣ →constp_dest,uint32_tconst bytes,boolconst restart)
当我们调用该函数,在数据传输的开始时会发送从设备的地址位。之后根据p_src数组保存第一个获取的数据,传输的过程中硬件会自动加载应答位和结束位。传输数据的长度与bytes有关,完成之后restart来决定此次通信之后是否通过发出重复的START条件来保持总线。
注
该函数与之前的R_IIC_MASTER_Write函数一样在使用之后需要需要延时一段时间或者使用回调函数判断。
21.4.2.5. 向EEPROM写入一个字节
初始化好I2C外设后,就可以使用I2C通讯了,更具上面两个函数的介绍我们就可以写出EEPROM的写入以及读取函数。我们看看如何向EEPROM写入一个字节的数据,见代码清单22_3。
代码清单 22_3:EEPROM写入一个字节函数
/**
* @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的单字节时序规定,向它写入数据的时候,第一个字节为内存地址,第二个字节是要写入的数据内容。所以我们需要理解:命令、地址的本质都是数据,对数据的解释不同,它就有了不同的功能。
21.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。
代码清单 22‑4 EEPROM的页写入
/**
* @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
}
21.4.2.7. 多字节写入
多次写入数据时,利用EEPROM的页写入方式,避免单字节读写时候的等待。多个数据写入过程 见代码清单22_5。
代码清单 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);
}
}
}
}
21.4.2.8. EEPROM读取函数
从EEPROM读取数据是一个复合的I2C时序,它实际上包含一个写过程和一个读过程, 见图22_18。

图 22‑18 EEPROM数据读取时序
读时序的第一个通讯过程中,使用I2C发送设备地址寻址(写方向),接着发送要读取的“内存地址”;第二个通讯过程中, 再次使用I2C发送设备地址寻址,但这个时候的数据方向是读方向;在这个过程之后,EEPROM会向主机返回从“内存地址”开始的数据, 一个字节一个字节地传输,只要主机的响应为“应答信号”,它就会一直传输下去,主机想结束传输时,就发送“非应答信号”, 并以“停止信号”结束通讯,作为从机的EEPROM也会停止传输。FSP库已经帮我们实现了这一个过程, 我们只是简单封装一下就可以直接使用,实现代码见代码清单22_6。
代码清单 22_6:EEPROM读取函数
/**
* @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的地址、数据格式、数据存储指针、数据大小、超时设置,就可以把想要的数据读回来。
21.4.2.9. EEPROM测试函数
代码清单 22_7: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
}
21.4.2.10. 主函数
代码清单 22_8:主函数
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
}
21.4.3. 下载验证
保证开发板相关硬件连接正确,用Type-C线连接开发板“USB TO UART”接口跟电脑, 在电脑端打开串口调试助手,把编译好的程序下载到开发板, 此时串口调试助手即可收到开发板发过来的数据。 在串口调试助手可看到EEPROM测试的调试信息。如下图所示:







