前言
最近使用到瑞萨R78族的MCU,准备做一个关于掉电保存参数配置的功能,需求大概是对200多个参数在掉电瞬间保存到芯片flash空间中,网上关于瑞萨MCU的flash读写操作教程也比较少,于是笔者结合在开发过程中遇到一些问题,写下这篇文章和大家一起探讨。
FDL和EEL
瑞萨官方为RL78族MCU提供了两个有关flash读写的函数库,分别是FDL(Data Flash Libraries)和EEL(EEPROM Emulation Library)顾名思义就是Flash操作函数库和eeprom操作函数库。这两个函数库及其文档都可以在瑞萨官网进行下载,另一份重要文档就是《数据闪存库的比较和应用》容易被大家忽视。值得注意是EEL是基于FDL的,也就是说使用EEL的话需要加入FDL,使用FDL不用加入EEL。
那么Flash读写方式和eeprom读写方式的区别是什么,事实上,Flash是eeprom的一种,传统的eeprom是可以单字节访问和修改的,这种eeprom一般存储量较小,后来诞生的Flash,直接通过块擦除,大大减少了电路复杂度,增加了存储密度。而瑞萨的Flash Data区都是Flash类型的。
FDL就是提供了Flash空间的操作接口,EEL是一个基于FDL的仿真库,实现类似操作eeprom的接口,换言之实际上还是操作Flash,是瑞萨官方通过程序进行模拟的。
FDL和EEL对比优缺点
官方提供的FDL有三个版本,分别是T01、T02、T04,基于不用FDL的EEL有两个版本,分别是T01、T02,暂时没有T04。不同版本FDL区别如下:
EEL T01和T02资源对比:
EEL的读写速度在文档好像没有列出来,笔者猜可能与对应FDL有直接关系。然后是EEL读写寿命更长:
换言之,FDL在使用上更方便,但是Flash寿命更低,EEL使用相对复杂,但是寿命更长。
FDL移植与改进
下面详细介绍FDL与EEL的移植过程,笔者使用CS+ for CX CA的版本,FDL使用T02版本,编译器是CA78K0R
1.获取FDL
访问瑞萨电子官网,搜索EEL,因为前面介绍了EEL是基于FDL的,所以下载了这个就包含了两个库文件。找到下图的文件并下载
下载好后解压,安装到相应版本CS+:
下一步会让你选择库的存放目录,安装好之后就可以看到库文件夹了:
下面转入到CS+进行移植:
在工程目录上新建EEL和FDL的文件夹,将安装下来的库文件里面的lib和simple里的相关文件通通加入到工程,并新建一个我们自己的函数文件:Save_Data.c和Save_Data.h。
下面开始结束函数的编写:
官方文档介绍了FDL T02的初始化和读写过程,因此笔者的移植也是基于此,首先上流程图:
其次是官方源码:
//官方源码
extern __far const fdl_descriptor_t fdl_descriptor_str;
fdl_status_t my_fdl_status_enu;
__near fdl_request_t request;
fdl_u08 buffer[5];
/* initialization */
my_fdl_status_enu = FDL_Init((__far fdl_descriptor_t*)&fdl_descriptor_str );
if(my_fdl_status_enu != FDL_OK)
ErrorHandler();
FDL_Open();
/* request structure initialization */
request.index_u16 = 0x0000;
request.data_pu08 = (__near fdl_u08*) 0x0000;
request.bytecount_u16 = 0x0000;
request.command_enu = (fdl_command_t)0xFF;
request.status_enu = FDL_ERR_PARAMETER;
/* erase block 0 */
request.index_u16 = 0x0000;
request.command_enu = FDL_CMD_ERASE_BLOCK;
FDL_Execute(&request);
while(request.status_enu == FDL_BUSY)
FDL_Handler();
if(request.status_enu != FDL_OK)
ErrorHandler();
/*blank Check*/
request.index_u16 = 0x0000;
request.bytecount_u16 = 0x0005;
request.command_enu = FDL_CMD_BLANKCHECK_BYTES;
FDL_Execute(&request);
while(request.status_enu == FDL_BUSY)
FDL_Handler();
if(request.status_enu != FDL_OK)
ErrorHandler();
/* write pattern 0x123456789A to idx = 0 */
buffer[0] = 0x12;
buffer[1] = 0x34;
buffer[2] = 0x56;
buffer[3] = 0x78;
buffer[4] = 0x9A;
request.index_u16 = 0x0000;
request.data_pu08 = (__near fdl_u08*)&buffer[0];
request.bytecount_u16 = 0x0005;
request.command_enu = FDL_CMD_WRITE_BYTES;
FDL_Execute(&request);
while(request.status_enu == FDL_BUSY)
FDL_Handler();
if(request.status_enu != FDL_OK)
ErrorHandler();
/*iverify*/
request.index_u16 = 0x0000;
request.bytecount_u16 = 0x0005;
request.command_enu = FDL_CMD_IVERIFY_BYTES;
FDL_Execute(&request);
while(request.status_enu == FDL_BUSY)
FDL_Handler();
if(request.status_enu != FDL_OK)
ErrorHandler();
/* set initial values */
buffer[0] = 0xFF;
buffer[1] = 0xFF;
buffer[2] = 0xFF;
buffer[3] = 0xFF;
buffer[4] = 0xFF;
request.index_u16 = 0x0000;
request.data_pu08 = (__near fdl_u08*)&buffer[0];
request.bytecount_u16 = 0x0005;
request.command_enu = FDL_CMD_READ_BYTES;
FDL_Execute(&request);
if(request.status_enu != FDL_OK)
ErrorHandler();
FDL_Close();
初次看这份源码非常头痛,不知在做什么操作,起始拆分开来看也并不复杂。首先看FDL所提供的函数接口:
需要注意源码中ErrorHandler()函数是报错之后的动作,需要用户自己添加内容,笔者直接设置为return 0;FDL_Execute(&request)函数是根据request结构体参数的不同进行不同动作,可以看上图
源码的功能是对一个buf[5],写进Flash再读出来。结合源码和流程图分析:
1.初始化
/* initialization */
my_fdl_status_enu = FDL_Init((__far fdl_descriptor_t*)&fdl_descriptor_str );
//笔者注:fdl_descriptor_str结构体在simple文件夹的fdl_descriptor.c里定义,作为配置时钟、格式等等
if(my_fdl_status_enu != FDL_OK)
ErrorHandler();
FDL_Open();
/* request structure initialization */
//笔者注:结构体复位,其实感觉可以不用
request.index_u16 = 0x0000;
request.data_pu08 = (__near fdl_u08*) 0x0000;
request.bytecount_u16 = 0x0000;
request.command_enu = (fdl_command_t)0xFF;
request.status_enu = FDL_ERR_PARAMETER;
2.块擦除,前面介绍了Flash的特性就是不能一个个字节修改,只能整块擦除,也就是单个bit的Flash不能0’置‘1’,只能’1’置‘0’,块擦除就是将整块Flash置‘1’,可以看看初始的Flash内存都是0xFF。
/* erase block 0 */
//笔者注:对0x0000的block进行擦除,request.index_u16不等于0x0000会出错,一个block占1K空间
request.index_u16 = 0x0000;
request.command_enu = FDL_CMD_ERASE_BLOCK;
FDL_Execute(&request);
while(request.status_enu == FDL_BUSY)
FDL_Handler();
if(request.status_enu != FDL_OK)
ErrorHandler();
3.写操作
/*blank Check*/
//笔者注:blank检查,如果当前blank不为0xff,就是没有擦除到位,抛出错误
request.index_u16 = 0x0000;
request.bytecount_u16 = 0x0005;
request.command_enu = FDL_CMD_BLANKCHECK_BYTES;
FDL_Execute(&request);
while(request.status_enu == FDL_BUSY)
FDL_Handler();
if(request.status_enu != FDL_OK)
ErrorHandler();
/* write pattern 0x123456789A to idx = 0 */
//笔者注:在0x0000的位置写入5个字节数据,内容起始地址是&buffer[0]
buffer[0] = 0x12;
buffer[1] = 0x34;
buffer[2] = 0x56;
buffer[3] = 0x78;
buffer[4] = 0x9A;
request.index_u16 = 0x0000;
request.data_pu08 = (__near fdl_u08*)&buffer[0];
request.bytecount_u16 = 0x0005;
request.command_enu = FDL_CMD_WRITE_BYTES;
FDL_Execute(&request);
while(request.status_enu == FDL_BUSY)
FDL_Handler();
if(request.status_enu != FDL_OK)
ErrorHandler();
/*iverify*/
//笔者注:验证字节有没有正确写入(还不清楚具体是怎么工作的)
request.index_u16 = 0x0000;
request.bytecount_u16 = 0x0005;
request.command_enu = FDL_CMD_IVERIFY_BYTES;
FDL_Execute(&request);
while(request.status_enu == FDL_BUSY)
FDL_Handler();
if(request.status_enu != FDL_OK)
ErrorHandler();
4.读操作
/* set initial values */
//笔者注:该段代码的逻辑是先对buffer复位,读出内容在对比看是不是和原来一样
buffer[0] = 0xFF;
buffer[1] = 0xFF;
buffer[2] = 0xFF;
buffer[3] = 0xFF;
buffer[4] = 0xFF;
request.index_u16 = 0x0000;
request.data_pu08 = (__near fdl_u08*)&buffer[0];
request.bytecount_u16 = 0x0005;
request.command_enu = FDL_CMD_READ_BYTES;
FDL_Execute(&request);
if(request.status_enu != FDL_OK)
ErrorHandler();
FDL_Close(); //笔者注:FDL保护开启
上面是对FDL函数库的API的分析。
改进
显然官方源码只是展示了函数库的使用流程,在实际应用中并不能很好符合我们的使用习惯,因此需要进行改进。笔者的改进逻辑是将读操作和写操作分离开,同时笔者要保存的参数都是2字节的,因此每次操作只操作两字节读写。同时将FDL的块擦除、保护开启、保护关闭进行函数封装,方便调用。
#include "r_cg_macrodriver.h"
#include "r_cg_userdefine.h"
#include "Save_Data.h"
#include "fdl.h"
//全局变量及函数声明
extern __far const fdl_descriptor_t fdl_descriptor_str;
static fdl_status_t my_fdl_status_enu;
static __near fdl_request_t request;
uint8_t flash_init(void);
void flash_close(void);
uint8_t save_data_r(uint8_t ID, uint16_t *data);
uint8_t save_data_w(uint8_t ID, uint16_t *data);
/*----------------------------------------------------------
函数功能:FDL初始化
输入参数:无
返回值:成功1 失败0
----------------------------------------------------------*/
uint8_t flash_init(void){
/* initialization */
my_fdl_status_enu = FDL_Init((__far fdl_descriptor_t*)&fdl_descriptor_str );
if(my_fdl_status_enu != FDL_OK)
return 0;
FDL_Open();
/* request structure initialization */
request.index_u16 = 0x0000;
request.data_pu08 = (__near fdl_u08*) 0x0000;
request.bytecount_u16 = 0x0000;
request.command_enu = (fdl_command_t)0xFF;
request.status_enu = FDL_ERR_PARAMETER;
return 1;
}
/*----------------------------------------------------------
函数功能:块擦除
输入参数:无
返回值:成功1,失败0
----------------------------------------------------------*/
uint8_t block_erase(uint16_t block){
/* erase block 0 */
request.index_u16 = block;
request.command_enu = FDL_CMD_ERASE_BLOCK;
FDL_Execute(&request);
while(request.status_enu == FDL_BUSY)
FDL_Handler();
if(request.status_enu != FDL_OK)
return 0;
return 1;
}
/*----------------------------------------------------------
函数功能:EEL保护
输入参数:无
返回值:无
----------------------------------------------------------*/
void flash_close(void){
/* 终止FDL ------------------------------------------- */
FDL_Close();
}
/*----------------------------------------------------------
函数功能:向Flash写入两字节数据
输入参数:保存到blank的ID, 内容首地址data
返回值:成功1 失败0
----------------------------------------------------------*/
uint8_t save_data_w(uint8_t ID, uint16_t *data){
/*blank Check*/
request.index_u16 = (uint16_t)ID;
request.bytecount_u16 = 0x0002;
request.command_enu = FDL_CMD_BLANKCHECK_BYTES;
FDL_Execute(&request);
while(request.status_enu == FDL_BUSY)
FDL_Handler();
if(request.status_enu != FDL_OK)
return 0;
/* write pattern to id */
request.index_u16 = (uint16_t)ID;
request.data_pu08 = (__near fdl_u08*)data;
request.bytecount_u16 = 0x0002;
request.command_enu = FDL_CMD_WRITE_BYTES;
FDL_Execute(&request);
while(request.status_enu == FDL_BUSY)
FDL_Handler();
if(request.status_enu != FDL_OK)
return 0;
/*iverify*/
request.index_u16 = (uint16_t)ID;
request.bytecount_u16 = 0x0002;
request.command_enu = FDL_CMD_IVERIFY_BYTES;
FDL_Execute(&request);
while(request.status_enu == FDL_BUSY)
FDL_Handler();
if(request.status_enu != FDL_OK)
return 0;
return 1;
}
/*----------------------------------------------------------
函数功能:从eeprom读两字节数据
输入参数:blank的ID,存放目标首地址data
返回值:成功1 失败0
----------------------------------------------------------*/
uint8_t save_data_r(uint8_t ID, uint16_t *data){
/* 初始化读指令 --------------------------------------------- */
request.index_u16 = (uint16_t)ID;
request.data_pu08 = (__near fdl_u08*)data;
request.bytecount_u16 = 0x0002;
request.command_enu = FDL_CMD_READ_BYTES;
FDL_Execute(&request);
if(request.status_enu != FDL_OK)
return 0;
return 1;
}
测试过程
main函数部分代码,保存test3和test4,读取到test1、test2
uint8_t status = 0;
uint16_t test1 = 0x0;
uint16_t test2 = 0x0;
uint16_t test3 = 0x1234;
uint16_t test4 = 0x5678;
flash_init(); //初始化
block_erase(0x0000); //块擦除
status = save_data_w(0, &test3); //写入
NOP();//不知道为什么读写太快调试会出bug
NOP();
NOP();
status = save_data_w(2, &test4); //写入
flash_close();
flash_init();
status = save_data_r(0, &test1); //读取
NOP();
NOP();
NOP();
status = save_data_r(2, &test2); //读取
flash_close();
调试结果
200个数据写入速度
单位/10ms
也就是将200个数据写入flash需要70ms,在接受范围内。
EEL
EEL可以对照官方文件的流程进行操作,和FDL分析方法一样,有人看再写。