RT-Thread论坛
直播中

那些年儿ing

13年用户 1383经验值
擅长:84784
私信 关注
[问答]

easyflash 写入多条log保存到flash成功,读取多条失败是怎么回事?

在VScode开发软件上,基于GD32F103VC移植完成了RT_Thread Nano 3.0.3后,领导突然要求在W25Q16JV芯片里面保存日志。
我第一时间想到了easyflash,因为很多人都在推,所以我就用了。
按照教程,我只需要log保存功能,使能EF_USING_LOG,ENV跟IAP暂时不需要,所以全部屏蔽了。

  • #ifndef EF_CFG_H_
  • #define EF_CFG_H_

  • /* using ENV function, default is NG (Next Generation) mode start from V4.0 */
  • // #define EF_USING_ENV

  • #ifdef EF_USING_ENV
  • /* Auto update ENV to latest default when current ENV version number is changed. */
  • /* #define EF_ENV_AUTO_UPDATE */
  • /**
  • * ENV version number defined by user.
  • * Please change it when your firmware add a new ENV to default_env_set.
  • */
  • #define EF_ENV_VER_NUM            /* @note you must define it for a value, such as 0 */

  • /* MCU Endian Configuration, default is Little Endian Order. */
  • /* #define EF_BIG_ENDIAN  */

  • #endif /* EF_USING_ENV */

  • /* using IAP function */
  • /* #define EF_USING_IAP */

  • /* using save log function */
  • #define EF_USING_LOG

  • /* The minimum size of flash erasure. May be a flash sector size. */
  • #define EF_ERASE_MIN_SIZE     4096    /* @note you must define it for a value */

  • /* the flash write granularity, unit: bit
  • * only support 1(nor flash)/ 8(STM32f4)/ 32(stm32f1) */
  • #define EF_WRITE_GRAN         1    /* @note you must define it for a value */

  • /*
  • *
  • * This all Backup Area Flash storage index. All used flash area configure is under here.
  • * |----------------------------|   Storage Size
  • * | Environment variables area |   ENV area size @see ENV_AREA_SIZE
  • * |----------------------------|
  • * |      Saved log area        |   Log area size @see LOG_AREA_SIZE
  • * |----------------------------|
  • * |(IAP)Downloaded application |   IAP already downloaded application, unfixed size
  • * |----------------------------|
  • *
  • * @note all area sizes must be aligned with EF_ERASE_MIN_SIZE
  • *
  • * The EasyFlash add the NG (Next Generation) mode start from V4.0. All old mode before V4.0, called LEGACY mode.
  • *
  • * - NG (Next Generation) mode is default mode from V4.0. It's easy to settings, only defined the ENV_AREA_SIZE.
  • * - The LEGACY mode has been DEPRECATED. It is NOT RECOMMENDED to continue using.
  • *   Beacuse it will use ram to buffer the ENV and spend more flash erase times.
  • *   If you want use it please using the V3.X version.
  • */

  • /* backup area start address */
  • #define EF_START_ADDR       (0)      /* @note you must define it for a value */

  • /* ENV area size. It's at least one empty sector for GC. So it's definition must more then or equal 2 flash sector size. */
  • // #define ENV_AREA_SIZE         (2 * EF_ERASE_MIN_SIZE)    /* @note you must define it for a value if you used ENV */

  • /* saved log area size */
  • #define LOG_AREA_SIZE        (200 * EF_ERASE_MIN_SIZE)     /* @note you must define it for a value if you used log */
  • // 不大于512个sector,W25Q16JV共有32个block,每个block有16个sector,每个sector有4096大小

  • /* print debug information of flash */
  • // #define PRINT_DEBUG

  • #endif /* EF_CFG_H_ */


然后在ef_port.c里面移植实现了ef_port_read,ef_port_write,ef_port_erase,ef_port_init等函数,单独测试都没有问题。
我使用的easyflash通过API读取,提示EasyFlash V4.1.99 is initialize success.
直接使用ef_port_write写一条log,ef_port_read读一条log,都没有问题。
但是使用ef_port_write写多条log,ef_port_read读多条log,一直只显示最初的log,后面的全部都不见了。

  • //要写入到W25Q16的字符串数组
  • const uint8_t TEXT_Buffer[]={"CFdino SPI TEST"};
  • const uint8_t TEXT_Buffer1[]={"CF dino SPI TEST111"};
  • const uint8_t TEXT_Buffer2[]={"CF dino SPI TEST222"};
  • const uint8_t TEXT_Buffer3[]={"CF dino SPI TEST333"};

  • #define SIZE sizeof(TEXT_Buffer)
  • #define SIZE1 sizeof(TEXT_Buffer1)
  • #define SIZE2 sizeof(TEXT_Buffer2)
  • #define SIZE3 sizeof(TEXT_Buffer3)

  •      ef_log_clean();
  •      rt_thread_delay(5000);
  •      rt_kprintf("rstart writern");
  •      ID = ef_log_write((uint32_t*)TEXT_Buffer,SIZE);        //  写入log
  •      rt_kprintf("rTEXT_Buffer:%drn",ID);
  •      rt_thread_delay(5);
  •      ID = ef_log_write((uint32_t*)TEXT_Buffer1,SIZE1);        //  写入log
  •      rt_thread_delay(5);
  •      rt_kprintf("rTEXT_Buffer1:%drn",ID);
  •      ID = ef_log_write((uint32_t*)TEXT_Buffer2,SIZE2);        //  写入log
  •      rt_thread_delay(5);
  •      rt_kprintf("rTEXT_Buffer2:%drn",ID);
  •      ID = ef_log_write((uint32_t*)TEXT_Buffer3,SIZE3);        //  写入log
  •      rt_thread_delay(5);
  •      rt_kprintf("rTEXT_Buffer3:%drn",ID);

  •          ef_log_read(0,(uint32_t*)datatemp,SIZE);                    //从倒数第0个索引处开始,读出SIZE个字节
  •          rt_thread_delay(500);
  •          rt_kprintf("index0 data1:%srn",datatemp);

  •          ef_log_read(1,(uint32_t*)datatemp,SIZE1);                    //从倒数第1个索引处开始,读出SIZE个字节
  •         // rt_thread_delay(500);
  •          rt_kprintf("index1 data2:%srn",datatemp);

  •          ef_log_read(2,(uint32_t*)datatemp,SIZE2);                    //从倒数第2个索引处开始,读出SIZE个字节
  •          rt_thread_delay(500);
  •          rt_kprintf("index2 data3:%srn",datatemp);

  •          ef_log_read(3,(uint32_t*)datatemp,SIZE3);                    //从倒数第3个索引处开始,读出SIZE个字节
  •          rt_thread_delay(500);
  •          rt_kprintf("index3 data4:%srn",datatemp);


结果读出来的
index0 datatemp1:CFdino SPI TEST

  • index0 data1:CFdino SPI TEST
  • index1 data2:Fdino SPI TEST
  • index2 data3:dino SPI TEST
  • index3 data4:ino SPI TEST


我以为是写入失败,然后通过ef_log_get_used_size去拿了一下保存的日志长度。12+16+20+20+20 = 88.
长度没有问题。然后通过ef_log_read(0,(uint32_t*)datatemp,88);去读取88个长度,结果还是读出来CFdino SPI TEST,后面的全部没有了。
然后通过ef_port.c里面的ef_port_read去读88个,结果也是CFdino SPI TEST。
通过自己实现的w25q16_read函数去读88个长度,结果也是CFdino SPI TEST。

  • void W25QXX_Read(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
  • {
  •     uint16_t i;
  •     FLASH_CS_RESET;                            // 使能器件
  •     spi_transfer(0x03);                        // 发送读取命令
  •     spi_transfer((uint8_t)((ReadAddr) >> 16)); // 发送24bit地址
  •     spi_transfer((uint8_t)((ReadAddr) >> 8));
  •     spi_transfer((uint8_t)ReadAddr);
  •     for (i = 0; i < NumByteToRead; i++)
  •     {
  •         pBuffer = spi_transfer(0XFF); // 循环读数
  •     }
  •     FLASH_CS_SET;
  • }



请问如何才能正常读取多条log。


回帖(1)

张宇

2025-10-10 17:34:04

针对 EasyFlash 写入多条日志成功但读取多条失败的问题,结合你的描述和配置,以下是一个系统性的排查和解决方案指南:




核心原因分析




  1. SPI Flash 驱动不完整



    • 关键点: W25Q16JV 的 SFUD 驱动未正确实现块擦除(erase)、扇区擦除(erase_chip)或页写入(page_program)函数。

    • 现象: 写入函数可能仅操作了 RAM 缓冲区,实际数据未写入物理介质。




  2. 分区配置冲突



    • 日志区覆盖 ENV 区: 即使你禁用了 EF_USING_ENV,EasyFlash 源码可能仍预定义了 ENV 分区地址,导致日志区被覆盖。

    • 检测: 检查 easyflash/port/ef_port.cef_env_set_default() 是否包含默认分区表。




  3. 缓冲区同步缺失



    • 日志写缓存机制: EasyFlash 默认在缓冲区满或主动调用 ef_log_sync() 时才写入 Flash。

    • 风险: 若未同步时复位,日志会丢失。




  4. Flash 擦除问题



    • 写前需擦除: W25Q16JV 要求写入前擦除扇区(通常 4KB)。

    • 驱动缺陷: 若擦除函数未正确实现,写入数据会失败。




  5. 多任务竞争



    • RTOS 环境风险: 若多个任务同时调用 EasyFlash API 且无锁保护,会导致状态混乱。






详细解决方案


1. 完善 SPI Flash 驱动


确保实现以下关键函数(参考 SFUD 文档):


// 在 sfud_flash.c 中添加 W25Q16JV 专用操作
static sfud_err_t spi_flash_erase_write(sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data) {
    // 实现擦除(按扇区/块)和页写入逻辑
    // 示例代码:
    sfud_err_t err = SFUD_SUCCESS;
    size_t erased_size = 0;
    size_t cur_erase_size = 0;
    while (size > erased_size) {
        cur_erase_size = 4096; // 按 4KB 扇区擦除
        err = sfud_erase(flash, addr + erased_size, cur_erase_size);
        if (err != SFUD_SUCCESS) return err;
        erased_size += cur_erase_size;
    }

    // 写入数据
    err = sfud_write(flash, addr, size, data);
    return err;
}

2. 修正分区配置


显式指定日志区地址(避开潜在 ENV 区):


// 在 ef_cfg.h 中强制定义日志分区起始地址和大小
#define LOG_START_ADDR      (0)         // 从 Flash 起始地址开始
#define LOG_AREA_SIZE       0x100000   // 分配 1MB 空间(W25Q16JV 共 2MB)

3. 主动同步日志缓冲区


写入后立即同步确保数据落盘:


// 写入日志后调用同步
ef_log_debug("System Booted");
ef_log_sync();  // 强制写入到 Flash!

4. 验证 Flash 擦除与写入


添加擦除状态检查逻辑:


// 擦除后读取校验
sfud_err_t result = sfud_sector_erase(flash, addr);
if (result == SFUD_SUCCESS) {
    // 检查擦除后是否为全 FF
    uint8_t buffer[128];
    sfud_read(flash, addr, sizeof(buffer), buffer);
    for (int i = 0; i < sizeof(buffer); i++) {
        if (buffer[i] != 0xFF) {
            rt_kprintf("Erase failed at addr %xn", addr + i);
            break;
        }
    }
}

5. 增加互斥锁保护


在 RT-Thread 中包装 EasyFlash API:


static rt_mutex_t ef_mutex = RT_NULL;

void ef_lock(void) {
    if (ef_mutex == RT_NULL) {
        ef_mutex = rt_mutex_create("ef_lock", RT_IPC_FLAG_PRIO);
    }
    rt_mutex_take(ef_mutex, RT_WAITING_FOREVER);
}

void ef_unlock(void) {
    rt_mutex_release(ef_mutex);
}

// 在 ef_port.c 中设置钩子
void ef_port_init(void) {
    ef_env_set_default();
    ef_log_set_default();
    ef_set_lock(ef_lock, ef_unlock); // 绑定锁函数
}



验证流程




  1. 读写基础测试

    逐扇区测试擦除 → 写入 → 读取的完整性:


    uint8_t test_data[256];
    memset(test_data, 0x55, sizeof(test_data));
    sfud_write(flash, 0x1000, sizeof(test_data), test_data);
    sfud_read(flash, 0x1000, sizeof(test_data), read_buf);
    if (memcmp(test_data, read_buf, sizeof(test_data)) != 0) {
       rt_kprintf("SPI RW TEST FAILED!n");
    }



  2. 监控日志区状态

    通过 JTAG 直接读取 Flash 物理地址,确认数据是否真实写入。




  3. 启用调试信息

    ef_cfg.h 中打开调试输出:


    #define EF_DEBUG_ENABLE
    #define EF_PRINTF(...) rt_kprintf(__VA_ARGS__)

    查看 "EasyFlash" 关键字的调试输出,检查擦除、写入地址是否正确。






补充建议



  • 升级库版本:使用最新版 EasyFlash V5.0.0,其对多扇区操作支持更完善。

  • 减少扇区擦除:调整日志模式为 TSL (Time Sequence Log),避免频繁擦除:
    // ef_cfg.h 中启用
    #define EF_USING_TSL

  • Wear Leveling 检查:长期运行后若出现某区域频繁失败,考虑启用动态均衡算法(需配置 EF_WL_ENABLE)。




通过以上系统性排查和修正,可解决日志写入成功但读取失败的问题。核心在于确保底层 SPI 操作的完整性与分区隔离。特别注意: GD32 的 SPI 时钟偏差可能比 STM32 更大,若驱动移植自 STM32,需降低 SPI 速率或调整采样边沿。

举报

更多回帖

×
20
完善资料,
赚取积分