【嵌入式】wear-leveling design for flash-based NVRAM

This experiment is designed based on STM32F030K6T6 MCU.

Use embedded flash memory of MCU as NVRAM is a cheap and convenient solution, however, nor-flash with a limited erase life cycle is vulnerable under frequent write scenarios. Therefore, I came up with a simple wear-leveling design for flash-based NVRAM.

The idea is based on the truth that an erased page/sector of a nor-flash is filled with all 0xFF, and what the program(write) operation do is just to clear bit 1 to bit 0 where needed. For example, writing 0xAA(B10101010) to a erase byte area(0xFF) means clearing bits 0,2,4,6 while leaving other bits in the previous 1 status.

Using this feature, I added a field "next" in my system configuration structure which points to the next configuration node and thus creates a single-link list. If the "next" field is 0xFFFFFFFF(erased but not programmed, can be programmed another time), it means this configuration node is the active node that actually stores the configuration. Otherwise, the "next" points to the address of the next node in the link list. After powering up, the system has to find the active node in the link list first. As for changing and storing system configuration, we do not need to erase the whole page each time, instead, we can use the space right after the currently active node and create a new node. Then, we have to change the "next" field in the active node to the address of the newly created node which means this node is no longer an active node because its "next" is not 0xFFFFFFFF anymore. Since then, the active node is changed, and the new active node is filled with 0xFF, we can program it as we want. Note that, the whole NVRAM page/sector is previously been erased and a default configuration node is programmed in the start address of the page. If the pointer "next" exceeds the address range of the valid NVRAM page, we have the roll the pointer "next" back to the start address of the NVRAM page and perform a page erase before starting from the first node again. In consequence, the whole NVRAM page erasing frequency is reduced:
TRUNC( NVRAM page size  /  configuration node size ) times which increases the life cycle of the page. (same idea can be realized if configuration node size is larger than a single page size)

Header file:

#ifndef __NVRAM_H__
#define __NVRAM_H__

#include "stm32f0xx.h"
#include "stdint.h"
#include "bsp.h"


#define CONFIGURATION_PAGE			30
#define CONFIGURATION_PAGE_ADDR		(0x08000000 + 1024 * CONFIGURATION_PAGE)
#define CONFIGURATION_PAGE_SIZE		1024

#define NVRAM_ACTIVE_BLOCK_INDICATOR    0xFFFFFFFF

typedef struct
{
    char name[32];
    uint32_t rx_addr;
    uint8_t rf_ch;
    uint8_t tx_power;
    uint32_t freq_tim1;
    uint32_t freq_tim3;
    struct
    {
        uint16_t min_throttle;  // high pulse time of the min throttle in us
        uint16_t max_throttle;  // high pulse time of the max throttle in us
    } channels[8];
    uint32_t crc32;

    uint32_t next;      // Designed for wear-leveling. 
                        // 0xFFFFFFFF (erased but not modified) means the current structure is the active node 
                        // which stores the data, values other than 0x00 represent the address of the next node to check.
} sysconfig_t;

extern sysconfig_t *sysconfig;

int nvram_init(void);
void nvram_dump(void);
int nvram_check(void);
int nvram_restore(void);
int nvram_write(sysconfig_t *config);


#endif

Source file:

#include "bsp.h"
#include "binary.h"
#include "util_queue.h"
#include "stdarg.h"
#include "SEGGER_RTT.h"
#include "string.h"
#include "stdio.h"
#include "delay.h"
#include "nvram.h"

sysconfig_t *sysconfig = (sysconfig_t *)CONFIGURATION_PAGE_ADDR;
static const sysconfig_t default_sysconfig = 
{
    .name = "Recer No.1 GOGOGO",
    .rx_addr = 0x12346578,
    .rf_ch = 100,
    .tx_power = 3,
    .freq_tim1 = 10000,
    .freq_tim3 = 50,
    .channels = 
    {
        [0] = {.min_throttle = 1000,    .max_throttle = 2000},      //TIM3
        [1] = {.min_throttle = 1000,    .max_throttle = 2000},      //TIM3
        [2] = {.min_throttle = 0,       .max_throttle = 100},       //TIM1
        [3] = {.min_throttle = 0,       .max_throttle = 100},       //TIM1
        [4] = {.min_throttle = 0,       .max_throttle = 100},       //TIM1
        [5] = {.min_throttle = 0,       .max_throttle = 100},       //TIM1
        [6] = {.min_throttle = 1000,    .max_throttle = 2000},      //TIM3
        [7] = {.min_throttle = 1000,    .max_throttle = 2000},      //TIM3
    },
    .crc32 = 0,
    .next = NVRAM_ACTIVE_BLOCK_INDICATOR,
} ;

int nvram_init(void)
{
    while (1) {
        while(nvram_check())
        {
            LOGW("Restore default config\r\n");
            nvram_restore();
            delay_ms(1000);
        }

        LOGI("sysconfig:");

        while ((uint32_t)sysconfig >= CONFIGURATION_PAGE_ADDR &&
                (uint32_t)sysconfig <= CONFIGURATION_PAGE_ADDR + CONFIGURATION_PAGE_SIZE - sizeof(sysconfig_t)) {
            printf("%.8X -> ", (uint32_t)sysconfig);

            if (sysconfig->next == NVRAM_ACTIVE_BLOCK_INDICATOR) {
                break;
            } else {
                sysconfig = (sysconfig_t *)(sysconfig->next);
            }
        }

        printf("\r\n");

        if ((uint32_t)sysconfig >= CONFIGURATION_PAGE_ADDR &&
                (uint32_t)sysconfig <= CONFIGURATION_PAGE_ADDR + CONFIGURATION_PAGE_SIZE - sizeof(sysconfig_t)) {
            nvram_dump();
            break;
        } else {
            LOGE("No valid config node was found %.8X\r\n", (uint32_t)sysconfig);
            LOGW("Restore default config\r\n");
            nvram_restore();
            delay_ms(10000);
        }
    }
    return 0;
}

void nvram_dump(void)
{
    LOGI("System configuration:%.8X\n", (uint32_t)sysconfig);
    LOGI("name:%s\n", sysconfig->name);
    LOGI("rx_addr:%.8X\n", sysconfig->rx_addr);
    LOGI("rf_ch:%d\n", sysconfig->rf_ch);
    LOGI("tx_power:%d\n", sysconfig->tx_power);
    LOGI("freq_tim1:%d\n", sysconfig->freq_tim1);
    LOGI("freq_tim3:%d\n", sysconfig->freq_tim3);
    for (int i = 0; i < 8; i++) {
        LOGI("ch[%d]:min_throttle:%-5d,max_throttle:%-5d\n", i, 
                sysconfig->channels[i].min_throttle,
                sysconfig->channels[i].max_throttle);
    }
    LOGI("next:%.8X\n", sysconfig->next);
}

// Check if the start of the CONFIGURATION_PAGE has a effecive configuration structure - correct CRC32 value
int nvram_check(void)
{
    uint32_t *p = (uint32_t *)CONFIGURATION_PAGE_ADDR;
    CRC_ResetDR();
    for (int i = 0; i < sizeof(sysconfig_t) / 4 - 2; i++) { // skip the CRC field and next field
        CRC_CalcCRC(*p++);
    }

    uint32_t CRC32;
    CRC32 = CRC_GetCRC();
    // LOGI("%.8X, %.8X\n", CRC32, sysconfig->crc32);

    if(CRC32 == sysconfig->crc32) {
        return 0;
    } else {
        LOGE("parameter verify error\r\n");
        return 1;
    }
}

// Write default configuration to the start of the CONFIGURATION_PAGE
int nvram_restore(void)
{
    uint32_t CRC32;
    FLASH_Status st;
    FLASH_Unlock();
//     FLASH_ClearFlag();
    st = FLASH_ErasePage(CONFIGURATION_PAGE_ADDR);
//     debug("%d\r\n",st);
    if(st != FLASH_COMPLETE)
    {
        LOGI("%s,%s,%d\r\n",__FILE__,__FUNCTION__,__LINE__);
        return 1;
    }

    uint32_t *p = (uint32_t *)&default_sysconfig;
    uint32_t addr = CONFIGURATION_PAGE_ADDR;

    CRC_ResetDR();
    for (int i = 0; i < sizeof(sysconfig_t) / 4 - 2; i++) { // skip the CRC field and next field
        CRC_CalcCRC(*p);
        st = FLASH_ProgramWord(addr, *p++);
        if(st != FLASH_COMPLETE)
            goto program_error;
        addr += 4;
    }

    CRC32 = CRC_GetCRC();
    LOGI("CRC32:%.8X\r\n",CRC32);
    st = FLASH_ProgramWord(addr, CRC32);
    if(st != FLASH_COMPLETE)
        goto program_error;

    sysconfig = (sysconfig_t *)CONFIGURATION_PAGE_ADDR;
        
    FLASH_Lock();
    return 0;

program_error:
    LOGI("%s,%s,%d\r\n",__FILE__,__FUNCTION__,__LINE__);
    FLASH_Lock();
    return 1;
}

int nvram_write(sysconfig_t *config)
{
    if (!config) {
        LOGE("invalid param\n");
        return -1;
    }

    LOGI("sysconfig:%.8X\n", (uint32_t)sysconfig);
    LOGI("next:%.8X\n", (uint32_t)sysconfig + sizeof(sysconfig_t));


    uint32_t CRC32;
    FLASH_Status st;
    FLASH_Unlock();

    uint32_t *p = (uint32_t *)config;
    uint32_t addr = (uint32_t)sysconfig + sizeof(sysconfig_t);
    LOGI("addr:%.8X\n", addr);

    if (addr >= CONFIGURATION_PAGE_ADDR &&
        addr <= CONFIGURATION_PAGE_ADDR + CONFIGURATION_PAGE_SIZE - sizeof(sysconfig_t)) {
        st = FLASH_ProgramWord((uint32_t)&(sysconfig->next), addr);
        if(st != FLASH_COMPLETE)
            goto program_error;
        sysconfig = (sysconfig_t *)((uint32_t)sysconfig + sizeof(sysconfig_t));
    } else {
        LOGW("address overflow\n");
        st = FLASH_ErasePage(CONFIGURATION_PAGE_ADDR);
        if(st != FLASH_COMPLETE) {
            LOGI("%s,%s,%d\r\n",__FILE__,__FUNCTION__,__LINE__);
            return 1;
        }
        addr = CONFIGURATION_PAGE_ADDR;
        sysconfig = (sysconfig_t *)addr;
    }



    CRC_ResetDR();
    for (int i = 0; i < sizeof(sysconfig_t) / 4 - 2; i++) { // skip the CRC field and next field
        CRC_CalcCRC(*p);
        st = FLASH_ProgramWord(addr, *p++);
        if(st != FLASH_COMPLETE)
            goto program_error;
        addr += 4;
    }

    CRC32 = CRC_GetCRC();
    LOGI("CRC32:%.8X\r\n",CRC32);
    st = FLASH_ProgramWord(addr, CRC32);
    if(st != FLASH_COMPLETE)
        goto program_error;


    nvram_dump();


    FLASH_Lock();
    return 0;

program_error:
    LOGI("%s,%s,%d\r\n",__FILE__,__FUNCTION__,__LINE__);
    FLASH_Lock();
    return 1;
}

sysconfig_t * nvram_get_config(void)
{
    return sysconfig;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当涉及嵌入式系统中的闪存(flash)时,以下是一些详细的知识点: 1. 闪存类型:常见的嵌入式系统闪存类型包括 NOR Flash 和 NAND Flash。NOR Flash 适用于执行代码和存储引导程序等任务,而 NAND Flash 则适用于大容量数据存储。它们在结构、工作原理和性能方面有所不同。 2. 块和页:闪存被划分为块和页。块是闪存的最小擦除单元,通常为64KB或更大。页是闪存的最小编程单元,通常为512字节或更大。擦除操作会将整个块中的所有页数据擦除,而编程操作可以单独写入一页数据。 3. 寿命和耐用性:闪存具有有限的擦写寿命,每个块可以擦写的次数有限。因此,在设计嵌入式系统时需要考虑最小化对闪存的擦写操作,以延长其寿命。常见的方法包括使用 wear leveling 技术来平衡块的使用,以及使用数据压缩和错误检测纠正码等技术来减少对闪存的写入量。 4. 编程和擦除:要写入或更新闪存中的数据,需要执行编程操作。编程操作是将数据写入到一页中。如果要擦除闪存中的数据,需要执行擦除操作,将整个块的数据擦除为初始状态。擦除操作比编程操作耗时更长,且会导致整个块中的所有页数据丢失。 5. 引导加载程序:闪存通常用于存储引导加载程序(bootloader),该程序负责引导和初始化嵌入式系统。引导加载程序通常位于闪存的固定地址,并在系统启动时被加载和执行。 6. 文件系统:在嵌入式系统中,闪存也可以用作存储文件系统。常见的文件系统包括 FAT、ext2/3/4 等。文件系统管理闪存中的文件和目录结构,使嵌入式系统能够方便地读取和写入文件。 7. 可靠性和安全性:闪存中的数据可以受到各种因素影响,如电源故障、物理损坏或意外擦写。为了确保数据的可靠性和安全性,可以使用数据备份、校验和错误检测纠正码等技术。 这些是嵌入式系统中关于闪存的一些详细知识点,设计和管理闪存在嵌入式系统开发中非常重要。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值