FlashTLV-STM32F0内部Flash适配版

原项目链接:https://www.cnblogs.com/yanye0xcc/p/16240531.html

STM32F0内部Flash的限制:
1:只能2字节对齐编程,最小写入单元是2字节。
2:待写入地址的内容必须是0xFFFF,擦除后只能写一次,不支持覆盖写入。
2025-11-21_092513

因此tlv_block数据块结构需要调整,分别使用sts_write变量作为写入完成标记,使用sts_delete作为擦除标记,且擦除标记的优先级是高于写入完成标记。
写入完成时sts_write=TLV_STATE_WRITE,sts_delete=TLV_STATE_NONE(0xFFFF)
标记擦除后sts_write=TLV_STATE_WRITE,sts_delete=TLV_STATE_DELETE

typedef struct _tlv_block {
    // 0x55 0xaa
    uint16_t header;
    uint16_t sts_write;  // TLV_STATE_NONE or TLV_STATE_WRITE
    uint16_t sts_delete; // TLV_STATE_NONE or TLV_STATE_DELETE
    uint16_t tag;
    uint16_t length;
    uint16_t crc16;
    // no store
    uint32_t entity;
} tlv_block_t;

对于数据写入,需要对用户传入的数据长度做2字节的向上取整,那么tlv_block_t::length就不是用户数据实际字节数,而是占用的存储空间字节数。
嵌入式应用场合,大部分是存储结构体变量,存储者和读取者都知道内容的实际长度;当存储变长字符串,tlv_block_t::length总是大于或等于实际字符串长度,读出后使用库函数strlen()计算下真实长度即可。
因此不在tlv_block_t中对真实用户数据长度做存储,也不在存储逻辑中做实际长度和存储长度的转换,简化程序逻辑和降低存储块元数据大小。

bool flash_tlv_append(tlv_sector_t *sector, uint16_t tag, const uint8_t *data, uint16_t length) {
    // 省略其它代码

    length = length + (length & 0x1);

    block.tag = tag;
    block.length = length;
    status = search_tlv(sector, &block, TLV_BLOCK_APPEND);
    if(status == TLV_RESULT_OK) {
        goto LAB_WRITE_TLV;
    }
	// 省略其它代码

代码下载:
https://files.cnblogs.com/files/blogs/575121/flash_tlv_stm32f0.zip

ESP32 NVS存储接口兼容层,进一步简化上层应用操作逻辑。

/*
 * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#ifndef ESP_NVS_H
#define ESP_NVS_H

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

#define NVS_ERR_OK                  0

#define NVS_ERR_INVALID_ARG         100
#define NVS_ERR_INVALID_HANDLE      101
#define NVS_ERR_NOT_ENOUGH_SPACE    102
#define NVS_ERR_REMOVE_FAILED       103
#define NVS_ERR_VALUE_TOO_LONG      104
#define NVS_ERR_INVALID_LENGTH      105
#define NVS_ERR_NOT_FOUND           106
#define NVS_ERR_VERIFY_FAIL         107
#define NVS_ERR_READ_INCPLT         108

typedef uint32_t nvs_err_t;

/**
 * Opaque pointer type representing non-volatile storage handle
 */
typedef void * nvs_handle_t;

/**
 * @brief      Open non-volatile storage with a given namespace from the default NVS partition
 *
 * Multiple internal ESP-IDF and third party application modules can store
 * their key-value pairs in the NVS module. In order to reduce possible
 * conflicts on key names, each module can use its own namespace.
 * The default NVS partition is the one that is labelled "nvs" in the partition
 * table.
 *
 * @param[in]  name   Namespace name. Maximum length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty.
 * @param[out] out_handle       If successful (return code is zero), handle will be
 *                              returned in this argument.
 *
 * @return
 *             - If successful, handle will be returned in this argument.
 */
nvs_handle_t nvs_open(const char* name);

/**
 * @brief       set variable length binary value for given key
 *
 *
 * @param[in]  handle  Handle obtained from nvs_open function.
 * @param[in]  key     Key name, from 0 to 65535.
 * @param[in]  value   The value to set.
 * @param[in]  length  length of binary value to set, in bytes,
 *                     must be 2bytes aligned, 2 ~ 65534
 * @return
 *             - NVS_ERR_OK if value was set successfully
 *             - NVS_ERR_INVALID_HANDLE if handle has been closed or is NULL
 *             - NVS_ERR_NOT_ENOUGH_SPACE if there is not enough space in the
 *               underlying storage to save the value
 *             - NVS_ERR_REMOVE_FAILED if the value wasn't updated because flash
 *               write operation has failed. The value was written however, and
 *               update will be finished after re-initialization of nvs, provided that
 *               flash operation doesn't fail again.
 *             - NVS_ERR_VALUE_TOO_LONG if the value is too long
 */
nvs_err_t nvs_set_blob(nvs_handle_t handle, uint16_t key, const uint8_t* value, uint32_t length);


/**
 * @brief      get blob value for given key
 *
 * These functions retrieve the data of an entry, given its key. If key does not
 * exist, an error is returned.
 *
 * In case of any error, out_value is not modified.
 *
 * All functions expect out_value to be a pointer to an already allocated variable
 * of the given type.
 *
 * nvs_get_blob functions support WinAPI-style length queries.
 * To get the size necessary to store the value, call nvs_get_blob
 * with zero out_value and non-zero pointer to length. Variable pointed to
 * by length argument will be set to the required length. For nvs_get_str,
 * this length includes the zero terminator. When calling nvs_get_str and
 * nvs_get_blob with non-zero out_value, length has to be non-zero and has to
 * point to the length available in out_value.
 * It is suggested that nvs_get/set_str is used for zero-terminated C strings, and
 * nvs_get/set_blob used for arbitrary data structures.
 *
 *
 * @param[in]     handle     Handle obtained from nvs_open function.
 * @param[in]     key        Key name, from 0 to 65535.
 * @param[out]    out_value  Pointer to the output value.
 *                           May be NULL for nvs_get_str and nvs_get_blob, in this
 *                           case required length will be returned in length argument.
 * @param[inout]  length     A non-zero pointer to the variable holding the length of out_value.
 *                           In case out_value a zero, will be set to the length
 *                           required to hold the value. In case out_value is not
 *                           zero, will be set to the actual length of the value
 *                           written. For nvs_get_str this includes zero terminator.
 *
 * @return
 *             - NVS_ERR_OK if the value was retrieved successfully
 *             - NVS_ERR_NOT_FOUND if the requested key doesn't exist
 *             - NVS_ERR_INVALID_HANDLE if handle has been closed or is NULL
 *             - NVS_ERR_VERIFY_FAIL 
 *             - NVS_ERR_READ_INCPLT
 *             - NVS_ERR_INVALID_LENGTH if \c length is not sufficient to store data
 */
nvs_err_t nvs_get_blob(nvs_handle_t handle, uint16_t key, uint8_t* out_value, uint32_t* length);

/**
 * @brief      Erase key-value pair with given key name.
 * @param[in]  handle  Storage handle obtained with nvs_open.
 * @param[in]  key     Key name, from 0 to 65535.
 * @return
 *              - NVS_ERR_OK if erase operation was successful
 *              - NVS_ERR_INVALID_HANDLE if handle has been closed or is NULL
 *              - NVS_ERR_NOT_FOUND if the requested key doesn't exist
 */
nvs_err_t nvs_erase_key(nvs_handle_t handle, uint16_t key);

#ifdef __cplusplus
} // extern "C"
#endif

#endif //ESP_NVS_H
#include "nvs.h"
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include "flash_tlv.h"
#include "flash_map.h"

// 2+2KB
#define NVS_TLV1_MAJOR_SEC   0x0803D000u
#define NVS_TLV1_MINOR_SEC   0x0803D800u
#define NVS_TLV1_PART_SIZE   2048u

// 4+4KB
#define NVS_TLV2_MAJOR_SEC   0x0803E000u
#define NVS_TLV2_MINOR_SEC   0x0803F000u
#define NVS_TLV2_PART_SIZE   4096u

static tlv_sector_t nvs1_sec = {0u, 0u, 0u, 0u, 0u, 0u};
static tlv_sector_t nvs2_sec = {0u, 0u, 0u, 0u, 0u, 0u};

// @param[in] name support "nvs1" and "nvs2"
nvs_handle_t nvs_open(const char* name) {
    int len = strlen(name);
    if(len != 4) {
        return NULL;
    }
    if(strncmp(name, "nvs", 3) != 0) {
        return NULL;
    }
    if(name[3] == '1') {
        if(nvs1_sec.major_sector == 0) {
            flash_tlv_init(&nvs1_sec, NVS_TLV1_MAJOR_SEC, NVS_TLV1_MINOR_SEC, NVS_TLV1_PART_SIZE);
        }
        return (void *)&nvs1_sec;
    }else if(name[3] == '2') {
        if(nvs2_sec.major_sector == 0) {
            flash_tlv_init(&nvs2_sec, NVS_TLV2_MAJOR_SEC, NVS_TLV2_MINOR_SEC, NVS_TLV2_PART_SIZE);
        }
        return (void *)&nvs2_sec;
    }
    return NULL;
}

nvs_err_t nvs_set_blob(nvs_handle_t handle, uint16_t key, const uint8_t* value, uint32_t length) {
    if(handle == NULL) {
        return NVS_ERR_INVALID_HANDLE;
    }
    
    if((length > 65534) || (length & 0x1) || (length == 0)) {
        return NVS_ERR_INVALID_LENGTH;
    }
    
    bool success = flash_tlv_append((tlv_sector_t *)handle, key, value, (uint16_t)length);
    
    return success ? NVS_ERR_OK : NVS_ERR_NOT_ENOUGH_SPACE;
}

nvs_err_t nvs_get_blob(nvs_handle_t handle, uint16_t key, uint8_t* out_value, uint32_t* length) {
    bool success;
    tlv_block_t blk;
    uint32_t readin;
    uint16_t tlvlen;
    
    if(handle == NULL) {
        return NVS_ERR_INVALID_HANDLE;
    }
    
    success = flash_tlv_query((tlv_sector_t *)handle, key, &blk);
    if(!success) {
        return NVS_ERR_NOT_FOUND;
    }
    // Win32API-style length queries
    if(out_value == NULL) {
        if(length != NULL) {
            *length = blk.length;
        }
        return NVS_ERR_OK;
    }
    if(length == NULL || (*length < blk.length)) {
        return NVS_ERR_INVALID_LENGTH;
    }
    
    success = flash_tlv_verify(&blk);
    if(!success) {
        return NVS_ERR_VERIFY_FAIL;
    }
    
    tlvlen = (uint16_t)(*length & 0xFFFF);
    
    readin = flash_tlv_read(&blk, out_value, 0, tlvlen);

    *length = readin;
    
    return (readin == tlvlen) ? NVS_ERR_OK : NVS_ERR_READ_INCPLT;
}

nvs_err_t nvs_erase_key(nvs_handle_t handle, uint16_t key) {
    if(handle == NULL) {
        return NVS_ERR_INVALID_HANDLE;
    }
    
    bool success = flash_tlv_delete((tlv_sector_t *)handle, key);
    
    return success ? NVS_ERR_OK : NVS_ERR_NOT_FOUND;
}

posted @ 2025-11-21 09:58  Yanye  阅读(117)  评论(0)    收藏  举报