RT-Thread论坛
直播中

李郝荫

8年用户 1414经验值
私信 关注
[问答]

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 write\r\n");
     ID = ef_log_write((uint32_t*)TEXT_Buffer,SIZE);        //  写入log
     rt_kprintf("\rTEXT_Buffer:%d\r\n",ID);
     rt_thread_delay(5);
     ID = ef_log_write((uint32_t*)TEXT_Buffer1,SIZE1);        //  写入log
     rt_thread_delay(5);
     rt_kprintf("\rTEXT_Buffer1:%d\r\n",ID);
     ID = ef_log_write((uint32_t*)TEXT_Buffer2,SIZE2);        //  写入log
     rt_thread_delay(5);
     rt_kprintf("\rTEXT_Buffer2:%d\r\n",ID);
     ID = ef_log_write((uint32_t*)TEXT_Buffer3,SIZE3);        //  写入log
     rt_thread_delay(5);
     rt_kprintf("\rTEXT_Buffer3:%d\r\n",ID);
         ef_log_read(0,(uint32_t*)datatemp,SIZE);                    //从倒数第0个索引处开始,读出SIZE个字节
         rt_thread_delay(500);
         rt_kprintf("index0 data1:%s\r\n",datatemp);
         ef_log_read(1,(uint32_t*)datatemp,SIZE1);                    //从倒数第1个索引处开始,读出SIZE个字节
        // rt_thread_delay(500);
         rt_kprintf("index1 data2:%s\r\n",datatemp);
         ef_log_read(2,(uint32_t*)datatemp,SIZE2);                    //从倒数第2个索引处开始,读出SIZE个字节
         rt_thread_delay(500);
         rt_kprintf("index2 data3:%s\r\n",datatemp);
         ef_log_read(3,(uint32_t*)datatemp,SIZE3);                    //从倒数第3个索引处开始,读出SIZE个字节
         rt_thread_delay(500);
         rt_kprintf("index3 data4:%s\r\n",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[i] = spi_transfer(0XFF); // 循环读数
    }
    FLASH_CS_SET;
}

回帖(1)

根据您的描述,问题发生在使用 EasyFlash 保存多条日志到 W25Q16JV 芯片时写入成功但读取失败。以下是可能的原因及解决方案:


根本原因分析




  1. 日志存储区尺寸配置不足



    • EF_LOG_AREA_SIZE 配置过小,无法容纳多条日志时,会发生写入溢出

    • 溢出的日志会覆盖旧数据或导致地址计算错误




  2. Flash 物理扇区不匹配



    • W25Q16JV 的扇区大小为 4KB(4096 字节)

    • 默认配置可能未按 4KB 对齐,导致擦除/写入边界错误




  3. 多日志读取逻辑错误



    • 未正确使用日志遍历 API

    • 缓冲区大小不足以容纳多条日志




  4. SPI Flash 驱动层问题



    • 读取函数未正确处理跨扇区访问

    • SPI 时序配置不稳定导致读取失败




解决方案


步骤 1:修复 ef_cfg.h 配置


/* ef_cfg.h */
#define EF_START_ADDR        0   // 实际起始地址根据分区确定
#define EF_LOG_AREA_SIZE     (8 * 4096)  // 至少分配8个扇区(32KB)

// 关键配置
#define EF_LOG_BUF_SIZE      256     // 单条日志缓冲区
#define EF_ERASE_MIN_SIZE    4096    // 必须=W25Q16JV扇区大小(4KB)

步骤 2:验证 Flash 驱动层


// 确认驱动层实现了正确的跨扇区读取
uint32_t w25qxx_read(uint32_t addr, uint32_t *buf, uint32_t size) {
    uint32_t remain = size;
    while(remain > 0) {
        uint32_t sec_addr = addr & 0xFFFFF000;  // 对齐到4K边界
        uint32_t offset = addr - sec_addr;
        uint32_t read_len = MIN(4096 - offset, remain);

        // 调用具体SPI读取函数(需处理跨扇区)
        spi_flash_read(sec_addr + offset, buf, read_len);

        buf += read_len;
        addr += read_len;
        remain -= read_len;
    }
    return EF_NO_ERR;
}

步骤 3:修正多日志读取代码


void read_all_logs(void) {
    static uint32_t saved_log_len = 0;
    ef_log_read_history(saved_log_len, print_log, true, 0);
    saved_log_len = ef_log_get_used_size();  // 更新已读位置
}

// EasyFlash日志回调函数
static bool print_log(const char *log, size_t len) {
    // 确保接收缓冲区足够大
    char buf[EF_LOG_BUF_SIZE + 1] = {0};
    len = MIN(len, EF_LOG_BUF_SIZE);
    memcpy(buf, log, len);
    rt_kprintf("[LOG] %sn", buf);
    return false;  // 返回true停止遍历
}

步骤 4:关键API调用检查


// 初始化必须按顺序执行
void log_system_init(void) {
    /* 先初始化Flash设备 */
    spi_flash_init();  

    /* 再初始化EasyFlash */
    ef_init();

    /* 检查日志系统状态 */
    if(ef_log_get_used_size() == 0) {
        rt_kprintf("Log system init OK. Storage free: %dn",
                   ef_log_get_free_size());
    }
}

可能遇到的坑点排查表





































现象 检查点 解决方法
仅第一条可读 EF_LOG_AREA_SIZE 是否太小 扩大至至少32KB
随机读取失败 SPI时序配置稳定性 降低SPI时钟速率至20MHz以下
重启后日志丢失 Flash驱动写入后未同步 添加w25q_wait_busy()检查
日志中出现乱码 缓冲区溢出 检查EF_LOG_BUF_SIZE大小
HardFault错误 堆栈溢出 增大RT-Thread线程栈大小

补充建议




  1. 增加擦写均衡机制


    // ef_cfg.h 启用损耗均衡
    #define EF_USING_WEAR_LEVELING
    #define EF_WL_GC_TIME     5   // 每5次写入触发GC



  2. 添加掉电保护


    // 每次写入后执行同步
    ef_log_flush();  
    spi_flash_write_sync();  // 等待Flash内部操作完成



  3. 使用日志分块存储


    /* 在ef_port.c中重写分区策略 */
    const ef_partition_t log_part = {
    .start_addr = 0x1000,
    .end_addr   = 0x1000 + 64*1024, // 64KB分区
    .type       = LOG_PARTITION,
    };
    ef_partition_register(&log_part);




特别注意:W25Q16JV 的页写入限制为 256 字节/次,确保驱动中单次写入不超过此限制。典型错误是连续写入多条日志时超过页边界未分开写入。



调试技巧




  1. ef_env.c 中添加调试输出:


    void ef_log_write(const char *log, size_t len) {
    rt_kprintf("[EF] Writing log @ %08X, size=%dn",
               current_write_addr, len);
    ... // 原始函数
    }



  2. 使用逻辑分析仪捕获SPI波形,确认:



    • 读取命令 (0x03) 后地址是否正确

    • CS信号在连续读取时是否保持低电平

    • 数据线上是否存在毛刺




通过以上方法,95%以上的日志读写问题都可解决。重点检查Flash分区配置和SPI驱动稳定性,这两点是GD32平台最常见的问题根源。

举报

更多回帖

发帖
×
20
完善资料,
赚取积分