万能的Flash烧写程序—基于FLM文件实现
Flash作为一种非易失性(Non-Volatile)存储器,其最显著的特点在于掉电之后,其存储的内容不会丢失。还有一点需要注意的是,对于flash存储器件的数据访问往往都是以块为单位进行操作的,在这里,要对数据读取进行特殊说明,对于Nor Flash器件来说,是支持随机读取的,但Nand Flash器件是不支持的。
由于flash访问的特殊性,一般我们需要设计一套Flash烧写算法,至少要包括flash初始化,read、write、erase这些操作。说到这儿,仿佛一切都还算美好,只需要有一套适配的烧写算法即可。


但是,小编还是不得不泼一盆冷水。算法本身一般情况是无法复用的,每更换一个flash器件就要额外去编写一套新的算法,尽管有会者不难的属性加持,但适配所有的flash器件,工作量也可想而知。


小编今天就为大家提供一个适配几乎任意flash型号的flash程序,之所以说是几乎,不是小编谦虚啊,是因为我们这次要借东风,而这个东风就是Keil的FLM文件。也就是说,只有FLM所支持的,我们才可以使用。

FLM文件是什么?


熟悉Keil的朋友们都知道,当我们要下载编译好的镜像到Flash时,首先要做的一步就是选择合适的Flash下载算法,而这个算法本身就是一个FLM文件:
100840ibl22r8l8rg2hlv8.png


所谓Flash下载算法,是负责擦除或是下载应用数据到flash的一个软件。而Keil往往会集成不少FLM文件以支持大多数的flash型号。


当然,正如前文所述,这些算法也是根据不同型号的flash所编写的。只不过,前人们已经为我们种好了大树,我们可以直接在树下乘凉了。

FLM文件结构


那么FLM文件是怎么个构成呢?Keil为我们提供了新建FLM工程的步骤,有兴趣的朋友可以自行参考。


当然,正是因为Keil规定了FLM文件的构成,它是一成不变的,我们才可以无忧无虑的对文件本身进行解析。


Keil规定,一个FLM文件中一般要有以下函数:
100840xpcw6lhh336srwce.png
其中最为重要的有5个,我们来逐一说明:


1、int Init (unsigned long adr, unsigned long clk, unsigned long fnc);


负责flash器件的初始化工作,其中:


adr: 设备首地址
clk:时钟频率(Hz)
fnc:要执行的flash操作,包括,1:Erase,2:Program,3:Verify


2、int EraseSector (unsigned long adr);
擦除addr所指定地址处的整个sector


3、int ProgramPage (unsigned long adr, unsigned long sz, unsigned char*buf);
对flash进行烧写操作,其中:


adr:待烧写地址
sz:待烧写数据长度
bug:待烧写数据


4、int EraseChip (void); 擦除整块flash
5、int UnInit (unsigned long fnc); Uninit flash, 并根据传入的fnc执行不同的flash后操作,fnc的定义同Init。
下面让我们解析一下现有的FLM文件,以MIMXRT106x_QSPI_4KB_SEC.FLM为例:
打开windows的命令行工具,输入readelf.py MIMXRT106x_QSPI_4KB_SEC.FLM-S:
100840tb488pcnhhvcwp8q.png
没错,这些正是我们需要的,也正是对于Flash进行操作所应该有的基本函数。


FLM文件解析与代码实现


我们下一步要做的就是老生常谈的事儿了,我们要将这些函数从FLM文件中提取出来,此处略过具体实现,可以参考小编上传的代码。如此一来,我们就将所有用到的函数都打包成了一个整体,并记录各个函数所在的位置:
100842d1n8x888u8t9vxvn.png
接下来,为了方便后续使用,定义对应的结构体变量:

  • typedef struct {
  •         int (*Init)(ulong adr, ulong clk, ulong fnc);
  •         int (*UnInit)(ulong fnc);
  •         int (*EraseSector)(ulong adr);
  •         int (*ProgramPage)(ulong adr, ulong sz, uchar* buf);
  •         int (*EraseChip)(void);
  • } flash_ops_t;

[color=rgb(51, 102, 153) !important]复制代码

同时定义一个宏,以对变量进行初始化:

  • #define INIT_FLASH_OPS(ops) \
  •     flash_ops_t ops = {  \
  •         .Init = CAST_VALUE_TO_FUNC(OPS_OFFSET + INIT_OFFSET, ops.Init), \
  •         .UnInit = CAST_VALUE_TO_FUNC(OPS_OFFSET + UNINIT_OFFSET, ops.UnInit), \
  •         .EraseSector = CAST_VALUE_TO_FUNC(OPS_OFFSET + ERASESECTOR_OFFSET, ops.EraseSector), \
  •         .ProgramPage = CAST_VALUE_TO_FUNC(OPS_OFFSET + PROGRAMPAGE_OFFSET, ops.ProgramPage), \
  •         .EraseChip = CAST_VALUE_TO_FUNC(OPS_OFFSET + ERASECHIP_OFFSET, ops.EraseChip),         \
  •     };

[color=rgb(51, 102, 153) !important]复制代码

效果展示


实际使用就很方便了,用户只需要调用一次INIT_FLASH_OPS(ops); 即可实现变量声明已经变量赋值,我们以ProgramPage函数进行展示,代码如下:


#define FLASH_BASE_ADDR(0x60000000U)
#define SECTOR_SIZE     (4096)
#define PAGE_SIZE       (256)
#define OP_NUM          (0U)


首先对sector进行擦除:
ops.EraseSector(FLASH_BASE_ADDR +OP_NUM * SECTOR_SIZE);
100842wqo6uvot1env98i9.png


烧写flash为0x56

  • char *pool = malloc(sizeof(char) * PAGE_SIZE);
  • memcpy(pool, (void*)FLASH_BASE_ADDR, PAGE_SIZE);
  • memset(pool, 0x56, PAGE_SIZE);
  • ops.ProgramPage(FLASH_BASE_ADDR + OP_NUM * PAGE_SIZE, PAGE_SIZE, (uchar*)pool);
  • free(pool);

[color=rgb(51, 102, 153) !important]复制代码

100843jaom8makj9678168.png
烧写成功!


小编这次为大家带来了一种基于Keil的FLM文件进行Flash烧写的新思路,这样一来,在进行Flash操作时,我们可以不必重复造轮子,依靠Keil这颗大树,真的好乘凉。


换句话说,实现了一种几乎万能的Flash下载器,Keil所能支持的Flash器件,我们都可以对其进行操作。本文代码请点击。