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)

【嵌入式】wear-leveling design for flash-based NVRAM_#include

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;
}
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6