风过空庭,字句正徐来。
关于关于本站关于我给我点钱
更多时间线友链文件服务wiki
联系写留言发邮件GitHub
© 2024-2026 yono. | RSS 订阅 | 站点地图 | | Stay hungry. Stay foolish.
Powered by Mix Space&
白い
.
| 粤 ICP 备2024284785号-1 |
正在被0人看爆
纸白微明,未成篇章。

FlashDB 的引入

(已编辑)
59
2
AI·GEN

关键洞察

FlashDB 的引入

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • 前置条件

    这个简单数据库可以以任意存储介质作为实际空间。

    对于 flash,通常的最小擦除单位是扇区。

    如果是片上 flash,至少需要规划预留一个扇区对齐的区域供 FlashDB 使用。

    如果是外部 flash,除了扇区对齐的规划,需要实现并测试读、写、擦除的 API 作为准备工作。

    如果是 eeprom,同样的,实现并测试读、写、擦除的 API 作为准备工作,此时会简单很多,不必考虑扇区对齐问题。擦除 API 是为了兼容 FlashDB 默认存储介质是 flash,采用符合 flash 的行为逻辑,实际上可以将擦除 API 做空操作。

    FlashDB 会自行处理好扇区对齐问题,所以在实现 API 时不必添加扇区对齐的检查,以减小开销。

    引入源码包

    在 armink/FlashDB 可以下载到原始源码包。

    但是他没有 CMakeLists,需要了解其源码划分,才能正确引入所需的源码。

    其实际上必须引入的源码文件夹如下,其他的则可以在工程中删除。下面用更清晰的 ASCII 树展示,方便在 Markdown 中阅读和复制。

    FlashDB/
    ├─ demos/
    ├─ docs/
    ├─ inc/            # 需要加入到 include path
    ├─ port/
    │  └─ fal/
    │     ├─ docs/
    │     ├─ inc/      # 需要加入到 include path
    │     ├─ samples/
    │     └─ src/      # 需要加入到编译的 .c 文件
    ├─ samples/
    ├─ src/            # 需要加入到编译的 .c 文件
    ├─ tests/
    └─ zephyr/
    

    其中标识的 .c 文件需要加入编译,同时加入以下的 includePATH

    FlashDB/inc
    FlashDB/port/fal/inc
    

    FlashDB/inc 中有 fdb_cfg_template.h,复制一份并重命名为 fdb_cfg.h 放在自己的工程中,比如原地,在 includePATH 范围内就好。

    最小起步时其中的默认配置不必修改,只要考虑编写 FDB_PRINT 即可,例如我编写为使用 RTT 打印,或者留空。

    #define FDB_PRINT(...) SEGGER_RTT_printf(0, __VA_ARGS__)
    
    // #define FDB_PRINT(...) \
    //     do                 \
    //     {                  \
    //     }                  \
    //     while(0)
    

    新建一份 fal_cfg.h 放在自己的工程中,例如

    CodeBlock Loading...

    为工程引入 fal

    注意到上面的 fal_cfg.h 其中有这样的内容,其中 FAL_FLASH_DEV_TABLE 是一个必须定义的 falsh 设备表,这里有一个 nor_flash0 设备描述对象。

    这些描述对象与期望操作的 flash 抽象一一对应,比如我这里只有一个 spi-norflash,所以只使用了一个抽象对象。

    CodeBlock Loading...

    在现在,虽然使用了声明,但是这个 flash 抽象依然还没有定义,定义这个抽象内容是使其真正具备真实 flash 操作能力的关键。

    这里我们可以新建一个源码进行整体绑定,使其具有 读/写/擦 接口。比如如下的核心定义,其中

    • .len 是完整 flash 空间大小,
    • .blk_size 是擦除最小单位在 flash 中通常是扇区大小。
    • .ops 是 读/写/擦 回调,在回调中使用前置条件中已实现的 API。
    • .write_gran 是写最小字节数粒度,例如 norflash 此值定义为 1,而片上 flash 可能要求 4/8 字节对齐。
    CodeBlock Loading...

    下面会展示为了支持这个抽象内容的完整源码,在这里有一个重要的问题需要处理。因为 fal 似乎并没有按页逐步写的操作,所以在 norflash 的回调处理中,需要注意不要跨页写,分为多次写使能和写请求。

    CodeBlock Loading...

    为工程引入 FlashDB

    有了此前由下至上的引入和支持,现在可以正式处理 FlashDB 的操作流程了。

    现在需要处理的主要就是按序初始化了,一个最小的可用初始化顺序为

    CodeBlock Loading...

    此后就可以使用这样的原子操作以读写键值

    CodeBlock Loading...

    需要自己做的包装

    1. 依据你的工程框架,可以使用线程锁来额外包装一套 API,以简单支持跨线程的键值读写。
    2. 进阶的,在包装中可以额外增加一个读 API 传参,在未命中键时使用默认值返回。类似 esp32 的 Preferences 数据库,int canBaud = prefs.getInt("canbtaud", 250000);。
    3. 更加进阶的,由于 FlashDB 是一套阻塞操作、SPI 收发相当耗时,在线程中直接使用原子操作将导致毫秒级的阻塞,这是不太能接受的。可以采用独立线程和请求队列,专用于管理 FlashDB 的读写操作。在通常的场景,采用异步使得业务逻辑更通顺。而在必须立即读写并获值参与逻辑时,也可以直接调用原子操作。
    #ifndef _FAL_CFG_H_
    #define _FAL_CFG_H_
    
    #define FAL_PART_HAS_TABLE_CFG
    
    #define FAL_FLASHDB_DEV_NAME       "norflash0"
    #define FAL_FLASHDB_KV_PART_NAME   "fdb_kv_cfg"
    #define FAL_FLASHDB_KV_PART_OFFSET (0x00040000u)
    #define FAL_FLASHDB_KV_PART_LENGTH (0x00040000u)
    
    extern struct fal_flash_dev nor_flash0;
    
    #define FAL_FLASH_DEV_TABLE \
        {                       \
            &nor_flash0,        \
        }
    
    #ifdef FAL_PART_HAS_TABLE_CFG
    #define FAL_PART_TABLE                                                                                                                    \
        {                                                                                                                                     \
            {FAL_PART_MAGIC_WORD, FAL_FLASHDB_KV_PART_NAME, FAL_FLASHDB_DEV_NAME, FAL_FLASHDB_KV_PART_OFFSET, FAL_FLASHDB_KV_PART_LENGTH, 0}, \
        }
    #endif
    
    #endif /* _FAL_CFG_H_ */
    
    extern struct fal_flash_dev nor_flash0;
    
    #define FAL_FLASH_DEV_TABLE \
        {                       \
            &nor_flash0,        \
        }
    
    struct fal_flash_dev nor_flash0 = {
        .name       = FAL_FLASHDB_DEV_NAME,
        .addr       = 0,
        .len        = 1 * 1024 * 1024,
        .blk_size   = DEVICE_SECTOR_SIZE,
        .ops        = {fal_norflash_init,
                         fal_norflash_read, 
                         fal_norflash_write, 
                         fal_norflash_erase},
        .write_gran = FDB_WRITE_GRAN,
    };
    
    #include <NorFlashSpi.h>
    /* Port */
    extern uint32_t FlashSpiTransmitPort(_NOR_FLASH_MESSAGE *pd, const uint8_t *TBuf, uint32_t ui32Len);
    extern uint32_t FlashSpiReceivePort(_NOR_FLASH_MESSAGE *pd, uint8_t *RBuf, uint32_t ui32Len);
    extern uint32_t FlashSpiTransmitReceivePort(_NOR_FLASH_MESSAGE *pd, const uint8_t *TBuf, uint8_t *RBuf, uint32_t ui32Len);
    #include <fal.h>
    #include <fdb_cfg.h>
    #include <includes.h>
    
    extern volatile uint8_t fSpiTxBuf[];
    extern volatile uint8_t fSpiRxBuf[];
    /* 片选使能函数 */
    #define CS_EN( ) HAL_GPIO_WritePin(SPI5_FLASH_CS_GPIO_Port, SPI5_FLASH_CS_Pin, GPIO_PIN_RESET);
    /* 片选失能函数 */
    #define CS_UN( ) HAL_GPIO_WritePin(SPI5_FLASH_CS_GPIO_Port, SPI5_FLASH_CS_Pin, GPIO_PIN_SET);
    /* 定义写入最小单位 */
    #define FLASH_PAGE_SIZE   256
    
    #define FALFDB_TRACE(...) SEGGER_RTT_printf(0, "[FALNOR] " __VA_ARGS__)
    
    static int      fal_norflash_init(void);
    static int      fal_norflash_read(long offset, uint8_t *buf, size_t size);
    static int      fal_norflash_write(long offset, const uint8_t *buf, size_t size);
    static int      fal_norflash_erase(long offset, size_t size);
    
    static uint32_t fal_norflash_wait_idle(void)
    {
        uint32_t i;
    
        fSpiTxBuf[0] = CMD_RDSR1;
        fSpiTxBuf[1] = 0;
    
        CS_EN( );
        if(FlashSpiTransmitReceivePort(NULL, (uint8_t *)fSpiTxBuf, (uint8_t *)fSpiRxBuf, 2) != RETURN_DEFAULT)
        {
            CS_UN( );
            return RETURN_FLASH_READ_ERR;
        }
        CS_UN( );
    
        i = 0;
        while(((fSpiRxBuf[1] & WIP_FLAG) != 0) && (i < 0xFFFF))
        {
            CS_EN( );
            if(FlashSpiTransmitReceivePort(NULL, (uint8_t *)fSpiTxBuf, (uint8_t *)fSpiRxBuf, 2) != RETURN_DEFAULT)
            {
                CS_UN( );
                return RETURN_FLASH_READ_ERR;
            }
            CS_UN( );
            i++;
        }
    
        if((fSpiRxBuf[1] & WIP_FLAG) != 0)
        {
            return RETURN_FLASH_BUSY;
        }
    
        return RETURN_DEFAULT;
    }
    
    struct fal_flash_dev nor_flash0 = {
        .name       = FAL_FLASHDB_DEV_NAME,
        .addr       = 0,
        .len        = 1 * 1024 * 1024,
        .blk_size   = DEVICE_SECTOR_SIZE,
        .ops        = {fal_norflash_init, fal_norflash_read, fal_norflash_write, fal_norflash_erase},
        .write_gran = FDB_WRITE_GRAN,
    };
    
    static int fal_norflash_init(void)
    {
        /* 发送buffer转为 读ID指令 */
        fSpiTxBuf[0] = CMD_RDID;
        fSpiTxBuf[1] = 0;
        fSpiTxBuf[2] = 0;
        fSpiTxBuf[3] = 0;
        fSpiRxBuf[1] = 0;
        fSpiRxBuf[2] = 0;
        fSpiRxBuf[3] = 0;
    
        CS_EN( );
        if(FlashSpiTransmitReceivePort(NULL, (uint8_t *)fSpiTxBuf, (uint8_t *)fSpiRxBuf, 4) != RETURN_DEFAULT) // 收发
        {
            FALFDB_TRACE("[SPI-FLASH]init warn: read id transfer failed\r\n");
        }
        CS_UN( );
    
        uint32_t ChipID = ((uint32_t)fSpiRxBuf[1] << 16) | ((uint32_t)fSpiRxBuf[2] << 8) | fSpiRxBuf[3];
        switch(ChipID)
        {
        case W25Q80_ID:
            nor_flash0.len      = 1 * 1024 * 1024;
            nor_flash0.blk_size = 4 * 1024;
            break;
        case W25Q64_ID:
            nor_flash0.len      = 8 * 1024 * 1024;
            nor_flash0.blk_size = 4 * 1024;
            break;
        case W25Q128_ID:
            nor_flash0.len      = 16 * 1024 * 1024;
            nor_flash0.blk_size = 4 * 1024;
            break;
        case W25Q256_ID:
            nor_flash0.len      = 32 * 1024 * 1024;
            nor_flash0.blk_size = 4 * 1024;
            break;
        case W25Q512_ID:
            nor_flash0.len      = 64 * 1024 * 1024;
            nor_flash0.blk_size = 4 * 1024;
            break;
        default:
            nor_flash0.len      = 0;
            nor_flash0.blk_size = 0;
            FALFDB_TRACE("[SPI-FLASH]init failed: unsupported chip id=0x%08X\r\n", (unsigned)ChipID);
            return -1;
        }
    
        return 0;
    }
    
    static int fal_norflash_read(long offset, uint8_t *buf, size_t size)
    {
        uint32_t addr;
        uint32_t chunk;
        size_t   remaining;
        size_t   copied;
    
        if(fal_norflash_wait_idle( ) != RETURN_DEFAULT)
        {
            FALFDB_TRACE("read failed: flash busy before read\r\n");
            return -1;
        }
    
        addr = (uint32_t)offset;
    
        /* 发送读指令和3字节地址 */
        fSpiTxBuf[0] = CMD_READ;
        fSpiTxBuf[1] = (addr >> 16) & 0xFF;
        fSpiTxBuf[2] = (addr >> 8) & 0xFF;
        fSpiTxBuf[3] = addr & 0xFF;
    
        CS_EN( );
        if(FlashSpiTransmitPort(NULL, (uint8_t *)fSpiTxBuf, 4) != RETURN_DEFAULT)
        {
            CS_UN( );
            FALFDB_TRACE("read failed: cmd tx error off=0x%08X size=%u\r\n", (unsigned)offset, (unsigned)size);
            return -1;
        }
    
        remaining = size;
        copied    = 0;
        while(remaining > 0)
        {
            chunk = (remaining > DEVICE_SECTOR_SIZE) ? DEVICE_SECTOR_SIZE : (uint32_t)remaining;
            if(FlashSpiTransmitReceivePort(NULL, (uint8_t *)fSpiTxBuf, (uint8_t *)fSpiRxBuf, chunk) != RETURN_DEFAULT)
            {
                CS_UN( );
                FALFDB_TRACE("read failed: data rx error off=0x%08X remain=%u\r\n", (unsigned)(addr + copied), (unsigned)remaining);
                return -1;
            }
    
            for(uint32_t i = 0; i < chunk; i++)
            {
                buf[copied + i] = fSpiRxBuf[i];
            }
    
            copied += chunk;
            remaining -= chunk;
        }
    
        CS_UN( );
    
        return (int)size;
    }
    
    static int fal_norflash_write(long offset, const uint8_t *buf, size_t size)
    {
        uint32_t addr;
        size_t   remaining;
        size_t   copied;
    
        if(fal_norflash_wait_idle( ) != RETURN_DEFAULT)
        {
            FALFDB_TRACE("write failed: flash busy before write\r\n");
            return -1;
        }
    
        addr      = (uint32_t)offset;
        remaining = size;
        copied    = 0;
    
        while(remaining > 0)
        {
            uint32_t page_offset = addr % FLASH_PAGE_SIZE;
            uint32_t page_room   = FLASH_PAGE_SIZE - page_offset;
            uint32_t prog_len    = (remaining > page_room) ? page_room : (uint32_t)remaining;
    
            /* 写使能 */
            fSpiTxBuf[0] = CMD_WREN;
            CS_EN( );
            if(FlashSpiTransmitPort(NULL, (uint8_t *)fSpiTxBuf, 1) != RETURN_DEFAULT)
            {
                CS_UN( );
                FALFDB_TRACE("write failed: wren tx error off=0x%08X\r\n", (unsigned)addr);
                return -1;
            }
            CS_UN( );
    
            /* 页编程命令 + 3字节地址 + 数据 */
            fSpiTxBuf[0] = CMD_PP;
            fSpiTxBuf[1] = (addr >> 16) & 0xFF;
            fSpiTxBuf[2] = (addr >> 8) & 0xFF;
            fSpiTxBuf[3] = addr & 0xFF;
            for(uint32_t i = 0; i < prog_len; i++)
            {
                fSpiTxBuf[4 + i] = buf[copied + i];
            }
    
            CS_EN( );
            if(FlashSpiTransmitReceivePort(NULL, (uint8_t *)fSpiTxBuf, (uint8_t *)fSpiRxBuf, 4 + prog_len) != RETURN_DEFAULT)
            {
                CS_UN( );
                FALFDB_TRACE("write failed: pp tx error off=0x%08X size=%u\r\n", (unsigned)addr, (unsigned)prog_len);
                return -1;
            }
            CS_UN( );
    
            if(fal_norflash_wait_idle( ) != RETURN_DEFAULT)
            {
                FALFDB_TRACE("write failed: wait idle timeout off=0x%08X\r\n", (unsigned)addr);
                return -1;
            }
    
            addr += prog_len;
            copied += prog_len;
            remaining -= prog_len;
        }
    
        /* 关闭写使能 */
        fSpiTxBuf[0] = CMD_DISWR;
        CS_EN( );
        if(FlashSpiTransmitPort(NULL, (uint8_t *)fSpiTxBuf, 1) != RETURN_DEFAULT)
        {
            CS_UN( );
            FALFDB_TRACE("write warn: diswr tx error\r\n");
            return -1;
        }
        CS_UN( );
    
        if(fal_norflash_wait_idle( ) != RETURN_DEFAULT)
        {
            FALFDB_TRACE("write failed: flash busy after diswr\r\n");
            return -1;
        }
    
        return (int)size;
    }
    
    static int fal_norflash_erase(long offset, size_t size)
    {
        uint32_t addr;
        uint32_t blk_size;
        size_t   remaining;
    
        blk_size = nor_flash0.blk_size;
    
        if(fal_norflash_wait_idle( ) != RETURN_DEFAULT)
        {
            FALFDB_TRACE("erase failed: flash busy before erase\r\n");
            return -1;
        }
    
        addr      = (uint32_t)offset;
        remaining = size;
    
        while(remaining > 0)
        {
            /* 写使能 */
            fSpiTxBuf[0] = CMD_WREN;
            CS_EN( );
            if(FlashSpiTransmitPort(NULL, (uint8_t *)fSpiTxBuf, 1) != RETURN_DEFAULT)
            {
                CS_UN( );
                FALFDB_TRACE("erase failed: wren tx error off=0x%08X\r\n", (unsigned)addr);
                return -1;
            }
            CS_UN( );
    
            /* 扇区擦除命令 + 3字节地址 */
            fSpiTxBuf[0] = CMD_SE;
            fSpiTxBuf[1] = (addr >> 16) & 0xFF;
            fSpiTxBuf[2] = (addr >> 8) & 0xFF;
            fSpiTxBuf[3] = addr & 0xFF;
    
            CS_EN( );
            if(FlashSpiTransmitPort(NULL, (uint8_t *)fSpiTxBuf, 4) != RETURN_DEFAULT)
            {
                CS_UN( );
                FALFDB_TRACE("erase failed: se tx error off=0x%08X\r\n", (unsigned)addr);
                return -1;
            }
            CS_UN( );
    
            if(fal_norflash_wait_idle( ) != RETURN_DEFAULT)
            {
                FALFDB_TRACE("erase failed: wait idle timeout off=0x%08X\r\n", (unsigned)addr);
                return -1;
            }
    
            addr += blk_size;
            remaining -= blk_size;
        }
    
        /* 关闭写使能 */
        fSpiTxBuf[0] = CMD_DISWR;
        CS_EN( );
        if(FlashSpiTransmitPort(NULL, (uint8_t *)fSpiTxBuf, 1) != RETURN_DEFAULT)
        {
            CS_UN( );
            FALFDB_TRACE("erase warn: diswr tx error\r\n");
            return -1;
        }
        CS_UN( );
    
        if(fal_norflash_wait_idle( ) != RETURN_DEFAULT)
        {
            FALFDB_TRACE("erase failed: flash busy after diswr\r\n");
            return -1;
        }
    
        return (int)size;
    }
    
    struct fdb_kvdb g_flashdb_kvdb;
    
    int32_t ModuleFlashDBInit(void)
    {
        uint32_t  sec_size;
    
        memset(&g_flashdb_kvdb, 0, sizeof(g_flashdb_kvdb));
    
        sec_size = (4 * 1024); // 默认单扇区大小;
        fdb_kvdb_control(&g_flashdb_kvdb, FDB_KVDB_CTRL_SET_LOCK, (void *)module_flashdb_lock);
        fdb_kvdb_control(&g_flashdb_kvdb, FDB_KVDB_CTRL_SET_UNLOCK, (void *)module_flashdb_unlock);
        fdb_kvdb_control(&g_flashdb_kvdb, FDB_KVDB_CTRL_SET_SEC_SIZE, &sec_size);
    
        result = fdb_kvdb_init(&g_flashdb_kvdb, "cfg", FAL_FLASHDB_KV_PART_NAME, NULL, NULL);
        if(result != FDB_NO_ERR)
        {
            FLASHDB_TRACE("init failed: fdb_kvdb_init err=%d\r\n", (int)result);
            return -1;
        }
    
        return 0;
    }
    
    struct fdb_blob blob;
    uint8_t verifyIp[4]   = {192,168,1,100};
    
    fdb_kv_set_blob(&g_flashdb_kvdb, "lan_ip", fdb_blob_make(&blob, verifyIp, sizeof(verifyIp)));
    fdb_kv_get_blob(&g_flashdb_kvdb, "lan_ip", fdb_blob_make(&blob, verifyIp, sizeof(verifyIp)));