大家好,我是痞子衡,是正经搞技术的痞子。今天给大家带来的是痞子衡的个人小项目 - kFlashFile。

痞子衡最近在参与一个基于 i.MXRT1170 的项目,项目有个需求,需要在 Flash 里实时保存一些关键数据(初步设 512 bytes),掉电能恢复。这些数据在访问方式上要友好,最好是很简单的 API 接口,上层无需操心关键这些数据在 Flash 里是如何存储以及具体存储在什么位置,只需在意关键数据保存和读取的操作即可(就像在 RAM 里动态存取那样)。

根据上述需求,痞子衡做了一个参考设计,命名为 kFlashFile,当前是 v1.0 版本。痞子衡写了比较详细的设计文档,特地分享给大家,如果大家有更好的建议和想法,欢迎在文章下面留言。

项目地址:https://github.com/JayHeng/kFlashFile

kFlashFile

一、简介

kFlashFile 是一个基于 NOR Flash 的轻量级文件数据存储方案,用于需要断电数据保存的项目。

kFlashFile 主要为 i.MXRT 系列设计,但其分层框架设计使其也可轻松移植到其他 MCU 平台。

kFlashFile 从设计上分为三层:

  • 最底层是Driver层:即Low-level驱动,这层是MCU相关的,对于i.MXRT来说,就是FlexSPI模块的驱动。
  • 中间是Adapter层:主要用于适配底层Driver,不同MCU其Driver接口函数可能不同,因此会在这一层做到接口统一。
  • 最顶层是API层:纯软件逻辑设计来实现文件数据存储,提供了四个非常简易的API。

二、设计

2.1 API定义

kFlashFile 是一个文件数据存储的设计,file_read()、file_save()是两个必备的 API,此外也提供业界通用 API 接口file_init()、file_deinit()。

  • kflash_file_init(): 用于初次分配Flash空间来存储文件数据,并且指定文件长度。如果当前指定的Flash空间里存在有效文件数据,那么继续复用。
  • kflash_file_read(): 用于获取当前有效存储的文件数据,文件数据可以部分读取。
  • kflash_file_save(): 用于实时写入最新的文件数据,文件数据可以部分更新。
  • kflash_file_deinit(): 用于清除当前分配的Flash空间里的文件数据,以便下次重新分配。
status_t kflash_file_init(kflash_file_t *flashFile, uint32_t memStart, uint32_t memSize, uint32_t fileSize);
status_t kflash_file_read(kflash_file_t *flashFile, uint32_t offset, uint8_t *data, uint32_t size);
status_t kflash_file_save(kflash_file_t *flashFile, uint32_t offset, uint8_t *data, uint32_t size);
status_t kflash_file_deinit(kflash_file_t *flashFile);

2.2 空间分配

kFlashFile 将分配的 Flash 空间分成两个部分,前面是文件数据区(Data Sectors),后面是文件头区(Header Sectors)。

文件数据区:从区内起始地址开始按序存放一份份文件数据,只要文件数据出现无法覆盖的更新(即 Flash 无法改写的特性),便会在下一个新地址重新存储。如果数据区满了,便擦除区内起始地址处的历史文件数据,继续循环存储。

文件头区:区内 Sector 起始地址放一个 Magic 值(4字节),用于标识文件头。然后开始按序记录一份份文件数据在文件数据区里的位置信息(默认用 2byte 去记录一份文件数据的位置)。如果当前 Header Sector 存储满了,便换到下一个 Header Sector 继续记录。

2.3 API主参数

kFlashFile 设计上使用 kflash_file_t 型作为 API 主参数,这个参数原型定义如下:

typedef struct {
uint32_t managedStart;
uint32_t managedSize;
uint32_t activedStart;
uint32_t activedSize;
uint32_t recordedIdx;
uint32_t recordedPos;
uint8_t buffer[KFLASH_MAX_FILE_SIZE];
} kflash_file_t;
  • managedStart: 表示文件存储区映射首地址,即 kflash_file_init() 调用时的 memStart 值加上 Flash 在内存里映射首地址,managedStart 需要以 Flash Sector 大小对齐。
  • managedSize: 表示文件存储区总大小,即 kflash_file_init() 调用时的 memSize 值,需要是 Flash Sector 大小的整数倍。
  • activedStart: 表示当前有效文件数据存储的映射首地址,需要以 Flash Page 大小对齐。
  • activedSize: 表示当前有效文件数据长度,需要是 Flash Page 大小的整数倍。
  • recordedIdx: 表示当前有效文件头所在的 Header Sector 索引。
  • recordedPos: 表示 Header Sector 中用于存储当前有效文件数据位置信息的区域偏移。
  • buffer[]: 当前有效的文件数据暂存区。

三、实现

3.1 Driver层

在 i.MXRT 系列上,kFlashFile 的 Driver 层即 FlexSPI NOR 驱动,这个驱动既可以采用 MCU SDK 版本,也可以采用 BootROM 版本。

此处推荐 BootROM 版本的 FlexSPI NOR 驱动,因为这个驱动历经多个 MCU ROM 的洗礼,已经相当成熟稳定。这里简单讲下其中 Flash 操作的函数:

  • flexspi_nor_flash_erase(uint32_t instance, flexspi_nor_config_t *config, uint32_t start, uint32_t length):这个函数实现Flash擦除,虽然形参里是任意设定的start, address,但实际擦除还是以Sector对齐的,函数内部会对start和address做自动对齐。
  • flexspi_nor_flash_page_program(uint32_t instance, flexspi_nor_config_t *config, uint32_t dstAddr, const uint32_t *src):这个函数实现Flash编程,一次固定写一整个Page大小的数据,即使dstAddr不是以Page对齐,实际写入的Page数据也不会跨物理Page(会自动跳回同一个物理Page首地址,这是Flash自身特性)。

因为 flexspi_nor_flash_page_program() 每次都要固定编程整个 Page 数据,不够灵活,因此我新写了一个 flexspi_nor_flash_program() 函数,这个函数支持编程用户自定义长度的数据,并且支持跨物理 Page 去写:

  • flexspi_nor_flash_program(uint32_t instance, flexspi_nor_config_t *config, uint32_t dstAddr, const uint32_t *src, uint32_t length):

需要特别注意,对于 SDR 模式的 Flash,最小编程长度可以是 1Byte;而 DDR 模式的 Flash,最小编程长度应是 2Bytes(如果这 2Bytes 地址上有一个 Byte 内容是 0xFF,该 Byte 依旧可以被再次编程)。

此外 flexspi_nor_flash_program() 函数有一个限制,即传入的 src 源数据首地址必须 4 字节对齐,哪怕你只想写入 2 个字节,这是 FlexSPI 模块底层对驱动的要求。

3.2 Adapter层

kFlashFile 的 Adapter 层是对 Driver 层做了一层封装,用于屏蔽硬件相关特性。该层与 MCU 以及板载 Flash 型号息息相关。下面的宏定义适用 i.MXRT1170 芯片以及连接在 FlexSPI1 上的 Octal Flash(MX25UM51345):

// 表示 Flash 连接的是 FlexSPI1
#define KFLASH_INSTANCE (1)
// BootROM FlexSPI 驱动对 Octal Flash 支持的简易配置值
#define KFLASH_CONFIG_OPTION (0xc0403007) // FlexSPI1 在系统内存中的映射首地址
#define KFLASH_BASE_ADDRESS (0x30000000)
// 默认的 Flash Sector/Page 大小(如果 Flash 里有 SFDP,则此处定义无效)
#define KFLASH_SECTOR_SIZE (0x1000)
#define KFLASH_PAGE_SIZE (256) // FlexSPI 编程接口对传入的 src 源数据首地址必须 4 字节对齐
#define KFLASH_PROGRAM_ALIGNMENT (4)
// Flash SDR 模式为 1,DDR 模式为 2
#define KFLASH_PROGRAM_UNIT (2)

kFlashFile 的 Adapter 层接口函数如下,参数是硬件无关的,因此上层可以轻松基于这些接口函数做纯软件逻辑设计。

status_t kflash_drv_init(void);
uint32_t kflash_drv_get_info(kflash_mem_info_t flashInfo);
status_t kflash_drv_erase_region(uint32_t start, uint32_t length);
status_t kflash_drv_program_region(uint32_t dstAddr, const uint32_t *src, uint32_t length);

3.3 API层

kFlashFile 的 API 功能设计思路前面介绍过了,这里介绍具体代码实现,先来看几个关键的宏定义:

// 设置 Header Sector 的个数,至少是 2 个
#define KFLASH_HDR_SECTORS (2)
// 设置 Header Sector 中用于存储当前有效文件数据位置信息的区域存储类型
// uint16_t 最多可记录 65536 个位置,最大可支持的 Data 区域大小为 65536 * 文件数据长度
#define KFLASH_HDR_POS_TYPE uint16_t /* uint16_t or uint32_t */
// 设置总分配的 Flash 长度(Data+Header Sector 的个数),至少是 4 个
#define KFLASH_MIN_SECTORS (KFLASH_HDR_SECTORS + 2)
// 设置最大支持的文件数据长度,需是 Flash Page 的整数倍
#define KFLASH_MAX_FILE_SIZE (KFLASH_PAGE_SIZE * 2)

3.3.1 init()

kflash_file_init() 函数处理流程如下:

如果是首次指定 Flash 空间,那么直接将全部空间擦除干净,并在第一个 Header Sector 中写入初始文件头(Magic + 文件数据位置值 0),即最新有效文件数据在 Flash 空间文件数据区的首地址。

这里有一个特殊的设计,文件数据区其实并不是直接存储用户写入的文件数据,而是将用户文件数据全部按位取反之后再存储进 Flash。这里假定用户数据初始应该是全 0,然后更改主要是将 0 值改为其他值,取反之后,正好对应 Flash 里的 bit1 编程为 bit0(Flash 擦除后是全 0xFF),这样可以充分利用 Flash 覆盖操作以减少擦除次数。

函数中比较关键的步骤是找寻当前 Flash 空间中是否存在有效文件数据,方法是遍历 Header Sector,发现存在 Magic 便继续寻找最新文件数据位置信息存放的区域(默认 2 字节),按照前面的设计,只需要按序读取区域内容,直到遇到 0xFFFF 为止。

3.3.2 read()

kflash_file_read() 函数最简单了,直接从缓存区 buffer 里获取数据即可,因为每次更新文件数据操作完成之后都会将最新文件数据放在 buffer 里。

3.3.3 save()

kflash_file_save() 函数是最核心的函数了,这里逻辑比较复杂,涉及文件数据区全部满了之后的动作,以及文件头区某个 Sector 满了的动作。其处理流程如下:

当有一个新文件数据要求保存时,首先会判断这个文件能不能在 Flash 中直接覆盖存储,如果能,那就直接覆盖存储,文件头完全不需要更新,这种情况比较简单。

如果新文件数据无法直接覆盖存储,那么首先判断文件数据区是否满了,如果上一个文件数据已经存在了文件数据区的最后位置,此时需要擦除数据区第一个 Sector 从头开始存储。如果没有到最后位置,那就按序往下存储。

新文件数据已经保存到数据区之后,此时需要处理文件头,记录这个新文件数据的位置。如果文件头区已经记录到当前 Sector 的最后位置,需要切换到下一个 Sector 开始存储,切换存储完新位置后,将之前 Sector 擦除。如果没有,那就按序在当前 Sector 继续记录。

3.3.4 deinit()

kflash_file_deinit() 函数也比较简单,就是将文件头区域 Header Sectors 全部擦除即可,文件数据区内容可以不用管,下次重新分配 Flash 时会做擦除。

欢迎订阅

文章会同时发布到我的 博客园主页CSDN主页微信公众号 平台上。

微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。

痞子衡嵌入式:kFlashFile v1.0 - 一个基于Flash的掉电数据存取方案的更多相关文章

  1. 痞子衡嵌入式:导致串行NOR Flash在i.MXRT下无法正常下载/启动的常见因素之SFDP

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是导致串行NOR Flash在i.MXRT下无法正常下载/启动的常见因素之SFDP. i.MXRT系列MCU发布已两年多了,基于i.MXR ...

  2. 痞子衡嵌入式:同一厂商不同系列Flash型号下Dummy Cycle设置方法可能有差异 (以IS25LP064为例)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是同一厂商不同系列Flash型号下Dummy Cycle设置方法的差异. 上一篇文章 <在i.MXRT启动头FDCB里调整Flash ...

  3. 痞子衡嵌入式:恩智浦机器视觉模块OpenMV-RT那些事(1)- 初体验

    大家好,我是痞子衡,是正经搞技术的痞子.本系列痞子衡给大家介绍的是机器视觉模块OpenMV-RT初体验. 近些年机器视觉应用一直是个很火的方向,想象一下机器如果能长上"眼睛",是不 ...

  4. 痞子衡嵌入式:超级下载算法RT-UFL v1.0发布,附J-Link下安装教程

    痞子衡主导的"学术"项目 <RT-UFL - 一个适用全平台i.MXRT的超级下载算法设计> 历时 8 个月终于迎来了 v1.0 版发布,因为是第一个正式版,为了保证质 ...

  5. 痞子衡嵌入式:超级下载算法RT-UFL v1.0在恩智浦MCUXpresso IDE下的使用

    痞子衡主导的"学术"项目 <RT-UFL - 一个适用全平台i.MXRT的超级下载算法设计> v1.0 版发布近 4 个月了,部分客户已经在实际项目开发调试中用上了这个 ...

  6. 痞子衡嵌入式:超级下载算法RT-UFL v1.0在IAR EW for Arm下的使用

    痞子衡主导的"学术"项目 <RT-UFL - 一个适用全平台i.MXRT的超级下载算法设计> v1.0 版发布近 4 个月了,部分客户已经在实际项目开发调试中用上了这个 ...

  7. 痞子衡嵌入式:超级下载算法RT-UFL v1.0在Keil MDK下的使用

    痞子衡主导的"学术"项目 <RT-UFL - 一个适用全平台i.MXRT的超级下载算法设计> v1.0 版发布近 4 个月了,部分客户已经在实际项目开发调试中用上了这个 ...

  8. 痞子衡嵌入式:超级下载算法RT-UFL v1.0在Segger Ozone下的使用

    痞子衡主导的"学术"项目 <RT-UFL - 一个适用全平台i.MXRT的超级下载算法设计> v1.0 版发布近 4 个月了,部分客户已经在实际项目开发调试中用上了这个 ...

  9. 痞子衡嵌入式:一个关于Segger J-Flash在Micron Flash固定区域下载校验失败的故事(SR寄存器BP[x:0]位)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家讲的是一个关于Segger J-Flash在Micron Flash固定区域下载校验失败的故事. 痞子衡最近在支持一个 i.MXRT1170 欧 ...

随机推荐

  1. DDD之3实体和值对象

    图中是一个别墅的模型,代表实体,可以真实的看得到.那么在DDD设计方法论中,实体和值对象是什么呢? 背景 实体和值对象是领域模型中的领域对象,是组成领域模型的基础单元,一起实现实体最基本的核心领域逻辑 ...

  2. JavaScript实现登录滑动验证

    来自于GitHub, 如何快速访问GitHub 先附上效果图 划到一半停止回自己回去的 PS: 附上代码,有需要自己更改, <!DOCTYPE html> <html lang=&q ...

  3. Java实现 蓝桥杯 算法训练 最大最小公倍数

    算法训练 最大最小公倍数 时间限制:1.0s 内存限制:256.0MB 问题描述 已知一个正整数N,问从1~N中任选出三个数,他们的最小公倍数最大可以为多少. 输入格式 输入一个正整数N. 输出格式 ...

  4. Java实现 蓝桥杯 蓝桥杯VIP 基础练习 数的读法

    问题描述 当输入12 3456 7009时,会给出相应的念法: 十二亿三千四百五十六万七千零九 用汉语拼音表示为 shi er yi san qian si bai wu shi liu wan qi ...

  5. Java实现 蓝桥杯VIP 算法训练 奇偶判断

    问题描述 能被2整除的数称为偶数,不能被2整除的数称为奇数.给一个整数x,判断x是奇数还是偶数. 输入格式 输入包括一个整数x,0<=x<=100000000. 输出格式 如果x是奇数,则 ...

  6. Java实现 蓝桥杯VIP 算法提高 插入排序

    算法提高 插入排序 时间限制:1.0s 内存限制:256.0MB  插入排序 问题描述 排序,顾名思义,是将若干个元素按其大小关系排出一个顺序.形式化描述如下:有n个元素a[1],a[2],-,a[ ...

  7. JQuery实现对html结点的操作(创建,添加,删除)

    效果图: <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <ti ...

  8. Java实现 LeetCode 5 最长回文子串

    5. 最长回文子串 给定一个字符串 s,找到 s 中最长的回文子串.你可以假设 s 的最大长度为 1000. 示例 1: 输入: "babad" 输出: "bab&quo ...

  9. java实现第四届蓝桥杯颠倒的价牌

    颠倒的价牌 题目描述 小李的店里专卖其它店中下架的样品电视机,可称为:样品电视专卖店. 其标价都是4位数字(即千元不等). 小李为了标价清晰.方便,使用了预制的类似数码管的标价签,只要用颜色笔涂数字就 ...

  10. 哪些年,我们玩过的Git

    作者:玩世不恭的Coder公众号:玩世不恭的Coder时间:2020-06-05说明:本文为原创文章,未经允许不可转载,转载前请联系作者 哪些年,我们玩过的Git 前言一.前期工作常用基本概念的理解G ...