如何在电脑上模拟仿真DataFlash和EEPROM模块?(附源码)

文摘   科技   2023-09-04 08:32   广东  

不久之前,我在《揭秘MCU内置Data Flash》讲解了MCU内置Flash的实现原理概念,同时也提到如何在电脑上仿真这个DataFlash的话题。

今天就详细讲解下,这个模拟仿真问题。

温馨提示:文末附源码下载方式

更多关于Fee、Ea和NvM相关文章可参考:

1.要模拟EEPROM和DataFlash的哪些功能

最直接了当的理解,EEPROM和DataFlash在硬件上无非就是存储,那么存储就是一个Read,还有一个Write,顶多再加一个Erase。

相当简单,我在PC上直接用File操作就行了,C语言就有个FILE的操作,用这两个接口就可以搞定。

size_t  fread (void *__restrict, size_t _size, size_t _n, FILE *__restrict);size_t  fwrite (const void *__restrict , size_t _size, size_t _n, FILE *);

似乎很简单的样子,废话少说,接下来就直接看看怎么写这个代码。

2.模拟EEPROM操作

这个是最简单的,直接Read/WritePC上的File就行了。MCU操作EEPROM也很简单,也是一个Read和一个Write,只是中间是通过IIC或者SPI进行的,我们这里可以省去IIC或SPI的通信。

(1)在PC上预先创建一个文件

文件名字姑且叫EEP_MEM.data吧,定义其大小位64KB。

怎么创建这个文件呢,很简单,可以用C语言写个程序create一个文件,然后填充0xFF。当然也可以用其他语言,例如python,两行代码就搞定。

with open('./EEP_MEM(new).data','wb') as f:    f.write(b'\xFF'*64*1024)

(2)初始化

这个可有可无。ATUOSAR的EEPROM模块有初始化动作的,可以让它调用这个初始化,也可以注释为空。当然,这里加个容错措施,防止文件不存在。

void eep_sim_init(void){    FILE* f = fopen("./EEP_MEM.data", "rb");    if (f)    {        fclose(f);    }    else    {        printf("EEP_MEM.data is not exist.");        f = fopen("./EEP_MEM.data", "wb+");        if(f)        {            const unsigned char data[16] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};            for(int i = 0; i < 64*1024/16; i++) fwrite(data,16,1,f);            fclose(f);            printf("Create a EEP_MEM.data with filling 64KB 0xFF");        }        printf("\r\n");    }}

(3)读写操作

这个就比较关键了,EEPROM的操作最终就是靠这两个接口,不过也是很简单的。

void eep_sim_write(unsigned short addr, const unsigned char* data, int len){    FILE* f = fopen("./EEP_MEM.data", "rb+");    if (f)    {        fseek(f, addr, SEEK_SET);        int cur = ftell(f);        fwrite(data, len, 1, f);        fclose(f);    }    printf("EEPROM Write data to  0x%04X: ", addr);    MEM_PRINT_DATA(data, len);}
void eep_sim_read(unsigned short addr, unsigned char* data, int len){ FILE* f = fopen("./EEP_MEM.data", "rb"); if (f) { fseek(f, addr, SEEK_SET); fread(data, len, 1, f); fclose(f); } printf("EEPROM Read data from 0x%04X: ", addr);    MEM_PRINT_DATA(data, len);}

为了看到实际操作过程,可以添加个log输出的宏MEM_PRINT_DATA。

#define MEM_PRINT_DATA(data, len) do{for(int i =0; i < len; i++){ printf("%02X ", data[i]);}printf("\n");}while(0)

很简单吧,跟你在电脑上读写文件操作写的代码一样的。

(4)控制操作

以上三步就完了么?但是还有一些控制处理信息呢。什么呢?

这个跟你用的EEPROM模块的控制方式有关,例如一些回调处理,或者ACK之类的。

所以,还要多模拟一个接口。

void eep_sim_ack(void){    Eep_xxx_ComEnd();    // printf("EEP Ack\n");}

Eep_xxx_ComEnd()这个就是EEPROM操作结束的通知处理函数,这个函数名根据你用的EEPROM模块或方案而定,我这里只是举个例子。

除此之外,在Read/Write接口也要添加这个处理通知函数。

void eep_sim_write(unsigned short addr, const unsigned char* data, int len){    // ...    Eep_xxx_ComEnd();}
void eep_sim_read(unsigned short addr, unsigned char* data, int len){    // ...    Eep_xxx_ComEnd();}

(5)模拟函数的调用

由于我们去掉了IIC或SPI通信,需要在EEPROM模块直接调用这里的模拟函数。这个步骤取决于你的EEPROM模块的实现。

例如,EEPROM模块里有个这样的函数

#include "mem_simulator.h"Std_ReturnType Eep_xxx_SetDataAndStartTransfer(uint8 Command,               Eep_xxx_AddressType Address,               Eep_xxx_I2cConstDataPtrType SrcDataBufferPtr,               Eep_xxx_I2cDataPtrType DesDataBufferPtr,               I2c_DataLengthType Length){    //...    {        case EEP_xxx_CMD_READ:            //...            eep_sim_read(eep_addr, DesDataBufferPtr, Length);            break;        case EEP_xxx_CMD_WRITE:            //...            eep_sim_write(eep_addr, SrcDataBufferPtr, Length);            break;        case EEP_xxx_CMD_ACK:            //...            eep_sim_ack();            break;        default: /* COV_EEP_DEFAULT */            break;    }    // ...}

好了,一个EEPROM的模拟就这样搞定了,你可以按照上面的案例代码来做测试了,这也很方便研究EEPROM模块的读写行为,什么磨损均衡的细节都可以摸清楚了。

温馨提示:在文末有这个模拟源码的下载方式。

3.模拟DataFlash操作

一开始我以为模拟DataFlash比模拟EEPROM简单,最后我错了。

DataFlash最终的操作也是读写不假,顶多加个擦除动作。但是呢,这里面还涉及Flash的空白检查,比较操作等等。

为了系统完整地应对AUTOSAR的Fls模块接口,我们要看看Fls有什么要求做的,有哪些接口需要模拟。

   /*  Read Service  */ Fls_Read,    /*  Write Service  */ Fls_Write,    /*  Compare Service  */ Fls_Compare,    /*  Erase Service  */ Fls_Erase,    /*  Blank Check Service  */ Fls_BlankCheck,    /*  Get Status Service  */ Fls_GetStatus,    /*  Get Job Result Service  */ Fls_GetJobResult

其中,这个Fls_BlankCheck尤为关键,其定义是这样的:

Service name

Fls_BlankCheck

Syntax

Std_ReturnType Fls_BlankCheck(

Fls_AddressType TargetAddress,

Fls_LengthType  Length)

Description

The function Fls_BlankCheck  shall verify, whether a given memory area has been erased but  not (yet) programmed. The function shall limit the maximum number of  checked flash  cells per main function cycle to the configured value  FlsMaxReadNormalMode or FlsMaxReadFastMode respectively.

这个跟DataFlash的特性有关。

以下,以RH850的DataFlash为例来做讲解。

对于原理其Flash的Sector/Page,以及磨损均衡原理,请查阅之前的文章讲解,本文不再累述。

1)模拟Fls_Read

这个比较简单,跟EEPROM的Read类似,像这样

uint8_t Fls_Read (uint32_t SourceAddress, uint8_t* TargetAddressPtr, uint32_t Length){    boolean ret = FALSE;    const char* fpath = "./FLS_MEM.data";    const char* name = "FLASH";    FILE* f = fopen(fpath, "rb");    if (f)    {        fseek(f, addr, SEEK_SET);        fread(data, len, 1, f);        fclose(f);        printf("%s Read data from 0x%04X: ", name, addr);        MEM_PRINT_DATA(data, len);        ret = TRUE;    }    else    {        printf("%s open file failed! \r\n", __FUNCTION__);        ret = FALSE;    }    Fls_GenJobResult = ret? MEMIF_JOB_OK : MEMIF_JOB_FAILED;    return 0;}

这里注意,操作完后,需要更新Fls_GenJobResult的状态,因为Fee模块是需要获取这个状态做判断的。

2模拟Fls_Write

这个Write也跟Read类似,但有一点点复杂,写入数据后,需要更新这块空间的状态,即下面代码中的fls_update_blank_status

uint8_t Fls_Write(uint32_t TargetAddress, const uint8_t* SourceAddressPtr, uint32_t Length){    boolean ret = FALSE;    const char* fpath = "./FLS_MEM.data";    const char* name = "FLASH";    FILE* f = fopen(fpath, "rb+");    if (f)    {        fseek(f, addr, SEEK_SET);        int cur = ftell(f);        fwrite(data, len, 1, f);        fclose(f);        printf("%s Write data to  0x%04X: ", name, addr);        MEM_PRINT_DATA(data, len);        ret = TRUE;    }    else    {        printf("%s open file failed! \r\n", __FUNCTION__);        ret = FALSE;    }    if(ret)    {        ret = fls_update_blank_status(TargetAddress, Length);    }    Fls_GenJobResult = ret? MEMIF_JOB_OK : MEMIF_JOB_FAILED;}

为什么要有个fls_update_blank_status?干什么用的?

其实就是因为DataFlash的Blank状态,有些MCU的内置DataFlash的Blank就相当于全0xFF,但有些不是,例如RH850 MCU的DataFlash的Blank状态的值是不确定的,无法通过Flash的值来判断其是否为Blank。

也就是说,对于RH850来说,整片DataFlash写入全0xFF,并不能说明这是Blank,倒过来,将DataFlash整片擦除后,读出来的值,并不全是0xFF。

所以,需要通过其他途径来区分DataFlash擦除和未擦除的情况。

又因为,擦除RH850内DataFlash的最小单位Sector大小是64 Bytes,而写入的最小单位Page大小是4 Bytes(一般称这种DataFlash叫SmallSector Flash,相对其他的最小Sector和Page都比较大的Flash例如NXP的S32K的DataFlash最小擦除Sector为8 KB来说的)。

于是,我们可以在FLS_MEM.data文件中的末尾添加额外的内容来记录这个擦除状态。例如用一个bit对应DataFlash内容中的4个Byte,bit=0表示这4个byte被擦除过并未写入任何内容,bit=1表示写入过内容了,不是Blank。这样,对于64KB的DataFlash来说,需要64KB /4/8=2KB的额外空间来记录DataFlash的Blank状态。

FLS_MEM.data文件镜像图

更详细的描述讲解,可以观看这个视频

这就是这个fls_update_blank_status缘故了,这个函数要怎么实现呢,直接上代码:

嵌入式软件实战派
专注嵌入式软件开发领域知识传授,包括C语言精粹,RTOS原理与使用,MCU驱动开发,AUTOSAR搭建,软件架构方法设计等