打造一个通用性MCU架构,支持CX32/AT32/NRF51/NRF52等。 OS支持RTX4/RTX5/FreeRtos。 采用VsCode+GCC组合,VsCode+KEIL5,超强开发方式。 QQ群:524408033

LiSun

打造一个通用性MCU架构,支持CX32/AT32/NRF51/NRF52等。 OS支持RTX4/RTX5/FreeRtos。 采用VsCode+GCC组合,VsCode+KEIL5,超强开发方式。 QQ群:524408033

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

[单片机框架] [kv_sys] 实现一个简易KV键值系统

[单片机框架] [kv_sys] 实现一个简易KV键值系统(升级版)

本版本改为数据任意长度,灵活性更高.

版本Code byteRO Data byteRW Data byteZI Data byte
初版4981600
升级版5821600
最终版9762491
  1. 功能:
    简易设置KEY和VAL,自动垃圾回收。至少需要占用两页FLASH空间。
    平衡flash读写,提高flash擦写寿命

  2. 支持平台:
    各类单片机

  3. Env 小型KV数据库,支持 写平衡(磨损平衡) 及掉电保护模式
    让Flash变为NoSQL(非关系型数据库)模型的小型键值(Key-Value)存储数据库。在产品上,能够更加简捷的实现 设定参数 或掉电保存的功能。

  4. 注意项:读取时,一定是4字节对齐的。即读取0xff00,正确。 读取0xff01,错误。会导致程序跑飞。

/********************************************************************************
 * @file    kv_sys_m.c
 * @author  jianqiang.xue
 * @version V1.0.0
 * @date    2022-09-14
 * @brief   KV键值最小系统(不固定长度) https://lisun.blog.csdn.net/article/details/121140849
 * 1. 读取时,一定是4字节对齐的。即读取0xff00,正确。 读取0xff01,错误。会导致程序跑飞。
 * 2. todo 未完成的东西,数据指针转换为实体返回。
 ********************************************************************************/
/* Includes ------------------------------------------------------------------*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "bsp_flash.h"

/* Private Includes ----------------------------------------------------------*/
#include "kv_sys.h"

#ifdef LISUN_SDK
#include "ls_syscfg.h"
#else
#define LS_FLASH_PAGE_SIZE    512
#define LS_FLASH_KV_PAGE      3
#endif

/* Private Define ------------------------------------------------------------*/
// KV系统总共可以使用N字节
#define LS_KV_SUM_SIZE        (LS_FLASH_PAGE_SIZE * (LS_FLASH_KV_PAGE - 1))

#ifndef LISUN_SDK
#define LS_KV_BASE_ADDR       0x00 // 自行修改
#define LS_KV_BACK_ADDR       (LS_KV_BASE_ADDR + LS_KV_SUM_SIZE)
#endif

// PACK除了数据以外的字节 头字节(4byte) + (key_id + is_en + len + sum)(4byte)
#define KV_PACK_NO_DATA_BYTE  8
#define KV_PACK_HEAD_BYTE     4
#define KV_PACK_INFO_BYTE     4 // (key_id + is_en + len + sum)(4byte)

// 对齐4字节,不足4字节,补齐4字节
#define ALIGNED_4(num)       if ((num % 4) > 0) num += (4 - (i % 4));

static const char valid_space[9] = {
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

/* Private Function Prototypes -----------------------------------------------*/
/**
 * @brief  计算校验值(累计两段值)
 * @param  data: 待校验的数据头指针
 * @param  len: 数据长度
 * @retval 校验和(低八位)
 */
static uint8_t compute_checksum(kv_sys_m_t* kv_head) {
    uint16_t sum = 0;
    sum = kv_head->key_id + kv_head->is_en + kv_head->len;
    for (uint8_t i = 0; i < kv_head->len - KV_PACK_INFO_BYTE; i++)
        sum += *(kv_head->buff + i);
    return (uint8_t)(sum & 0x00FF);
}

static void read_flash_pack(kv_sys_m_t* kv, uint32_t addr) {
    memcpy(kv, (kv_sys_m_t*)(addr), sizeof(kv_sys_m_t) - 4);
    kv->buff = (uint8_t*)(addr + KV_PACK_HEAD_BYTE + KV_PACK_INFO_BYTE);
}

/**
 * @brief  验证区域有效性
 * @param  kv_head: 数据头指针
 * @param  location: 当前flash位置(相对位置)
 * @retval 0--失败  1--成功
 */
static bool check_valid(kv_sys_m_t* kv_head, uint32_t *location) {
    if (kv_head->head != KV_SYS_PACK_HEAD) {
        *location += 4;
        return false;
    }
    // 先确保数据完整性,才能进行判断。4-->头字节(4byte)  |  3-->(key_id + is_en + len)
    uint8_t sum = compute_checksum(kv_head);
    if (kv_head->sum != sum) {
        // 指针移动,寻找头。 头字节(4byte) + (key_id + is_en + len + sum)(4byte)
        // (为什么不直接移动到下个位置?因为无法确保len位有效)
        *location += KV_PACK_NO_DATA_BYTE;
        return false;
    }
    if (kv_head->is_en != 0xFF) {
        // 指针移动到下一个数据包头 数据长度
        *location += (kv_head->len + KV_PACK_NO_DATA_BYTE);
        return false;
    }
    return true;
}

// 寻找指定键值
static void* find_kv_addr(uint8_t key_id) {
    uint32_t i = 0;
    while (i < LS_KV_SUM_SIZE) {
        kv_sys_m_t kv_head;
        read_flash_pack(&kv_head, LS_KV_BASE_ADDR + i);
        // 判断flash数据是否有效
        if (!check_valid(&kv_head, &i))
            goto end;
        // 判断键值是否匹配
        if (kv_head.key_id != key_id) {
            // 指针移动到下一个数据包头 数据长度 + KV_PACK_NO_DATA_BYTE
            i += (kv_head.len + KV_PACK_NO_DATA_BYTE);
            goto end;
        } else
            return (void*)(LS_KV_BASE_ADDR + i);
        // 指针移动到下一个字
        i += 4;
end:
        ALIGNED_4(i);
    }
    return NULL;
}

// 寻找空白区
static void* find_blank_addr(void) {
    uint32_t i = 0;
    while (i < LS_KV_SUM_SIZE) {
        kv_sys_m_t kv_head;
        read_flash_pack(&kv_head, LS_KV_BASE_ADDR + i);
        if (kv_head.head == KV_SYS_PACK_HEAD) {
            // 先确保数据完整性,才能进行判断。
            uint8_t sum = compute_checksum(&kv_head);
            if (kv_head.sum != sum) {
                // 指针移动,寻找头。 (为什么不直接移动到下个位置?因为无法确保len位有效)
                i += KV_PACK_NO_DATA_BYTE;
            } else {
                // 指针移动到下一个数据包头 数据长度 + KV_PACK_NO_DATA_BYTE
                i += (kv_head.len + KV_PACK_NO_DATA_BYTE);
            }
            goto end;
        } else if (kv_head.head == 0xFFFFFFFF) {
            // 确保至少有9个有效字节,能存放一个字节
            if ((i + 9) >= LS_KV_SUM_SIZE)
                return NULL;
            if (memcmp((void*)(LS_KV_BASE_ADDR + i), valid_space, 9) == 0)
                return (void*)(LS_KV_BASE_ADDR + i);
        }

        // 指针移动到下一个字
        i += 4;
end:
        ALIGNED_4(i);
    }
    return NULL;
}

/* Public Function Prototypes ------------------------------------------------*/

/**
 * @brief  FLASH垃圾回收 (本回收机制,可能导致每页中会出现空白字段,可能写入字段小于空白页,可插入)
 */
int kv_gc_env(void) {
    bsp_flash_erase_page(LS_KV_BACK_ADDR, 1);
    while (bsp_flash_is_busy());

    uint8_t kv_page_tick = 0;
    uint32_t back_byte = 0;

    uint32_t i = 0;
    while (i < LS_KV_SUM_SIZE) {
        kv_sys_m_t kv_head;
        read_flash_pack(&kv_head, LS_KV_BASE_ADDR + i);
        // 判断flash数据是否有效
        if (!check_valid(&kv_head, &i))
            goto end;
        uint8_t wish_write_byte = kv_head.len + KV_PACK_NO_DATA_BYTE;
        // 当备份页满时,清理前面页数,然后备份当前数据
        if (back_byte + wish_write_byte > LS_FLASH_PAGE_SIZE) {
            kv_sys_m_t kv_sys;
            memcpy(&kv_sys, &kv_head, sizeof(kv_sys_m_t));
            kv_sys.buff = malloc(kv_head.len);
            if (kv_sys.buff == NULL) return -2;
            memcpy(kv_sys.buff, kv_head.buff, kv_head.len);
            kv_sys.sum = *(kv_sys.buff + kv_head.len);

            if (kv_page_tick >= LS_FLASH_KV_PAGE - 1) {
                free(kv_sys.buff);
                return -1;
            }
            // 备份页搬运到有效页
            bsp_flash_carry(LS_KV_BASE_ADDR + kv_page_tick * LS_FLASH_PAGE_SIZE, LS_KV_BACK_ADDR, LS_FLASH_PAGE_SIZE);
            kv_page_tick++;
            back_byte = 0;
            // 刚刚备份的有效数据写到到备份区 (由于RAM数据不连续,需要分段写 todo) (4 --> *buff 指针4字节)
            bsp_flash_write_nbyte_s(LS_KV_BACK_ADDR + back_byte, (uint8_t*)&kv_sys, sizeof(kv_sys_m_t) - 4);
            back_byte += sizeof(kv_sys_m_t) - 4;
            bsp_flash_write_nbyte_s(LS_KV_BACK_ADDR + back_byte, kv_sys.buff, kv_sys.len);
            back_byte += kv_sys.len;
            free(kv_sys.buff);
        } else {
            // 搬运有效数据
            bsp_flash_write_nbyte_s(LS_KV_BACK_ADDR + back_byte, (uint8_t*)&kv_head, kv_head.len + KV_PACK_NO_DATA_BYTE);
            back_byte += kv_head.len + KV_PACK_NO_DATA_BYTE;
        }
        // 指针移动到下一个字
        i += 4;
end:
        ALIGNED_4(i);
    }

    if (back_byte != 0) {
        if (kv_page_tick >= LS_FLASH_KV_PAGE - 1)
            return -1;
        bsp_flash_carry(LS_KV_BASE_ADDR + kv_page_tick * LS_FLASH_PAGE_SIZE, LS_KV_BACK_ADDR, LS_FLASH_PAGE_SIZE);
        kv_page_tick++;
        back_byte = 0;
    }
    // 清理未使用的空间
    for (uint8_t i = kv_page_tick; i < LS_FLASH_KV_PAGE - 1; i++)
        bsp_flash_erase_page(LS_KV_BASE_ADDR + kv_page_tick * LS_FLASH_PAGE_SIZE, 1);
    while (bsp_flash_is_busy());
    return 0;
}

/**
 * @brief  [上电调用] 检测当前KV键值是否异常 如:掉电导致异常,则进行数据恢复
 */
void kv_gc_check(void) {
    kv_sys_m_t* kv = find_blank_addr(); // 得到空白块
    // 如果数据满了,则进行垃圾回收处理
    if (kv == NULL)
        kv_gc_env();
    else {
        // 判断备份区是否有残余
        kv = (kv_sys_m_t*)LS_KV_BACK_ADDR;
        if (kv->head == KV_SYS_PACK_HEAD) {
            for (uint32_t i = 0; i < LS_FLASH_PAGE_SIZE; i++) {
                kv_sys_m_t kv_head;
                read_flash_pack(&kv_head, LS_KV_BACK_ADDR + i);
                if (!check_valid(&kv_head, &i))
                    continue;
                kv_set_env(kv_head.key_id, kv_head.buff, kv_head.len);
            }
            bsp_flash_erase_page(LS_KV_BACK_ADDR, 1);
        }
    }
}

/**
 * @brief  从FLASH中获取KV值
 * @param  key_id: KEY ID
 * @retval 数据指针
 */
void* kv_get_env(uint8_t key_id) {
    if (key_id == 0 || key_id == 255)
        return NULL;
    kv_sys_m_t *kv = (kv_sys_m_t*)find_kv_addr(key_id);
    return kv != NULL ? &(kv->buff) : NULL;
}

/**
 * @brief  从FLASH中删除某KV值
 * @param  key_id: KEY ID
 * @retval 1--成功 0--失败
 */
bool kv_del_env(uint8_t key_id) {
    bool state = false;
    while (1) {
        kv_sys_m_t* kv_head = (kv_sys_m_t*)find_kv_addr(key_id);
        if (kv_head == NULL) break;
        uint32_t temp_addr = (uint32_t)kv_head + 7;
        state = bsp_flash_write_byte(temp_addr, 0x00); // 将之前值标记为无效
    }
    return state;
}

/**
 * @brief  KV值写入Flash
 * @param  key_id: KEY ID
 * @param  *data: 数组指针
 * @param  len: 数据长度
 */
bool kv_set_env(uint8_t key_id, void* data, uint8_t len) {
    static bool kv_set_state = false;  // false--free   true--bus
    kv_sys_m_t kv_sys_temp = {0};
    // 检测ID是否异常
    if (key_id == 0 || key_id == 255)
        return false;
    // 检测参数和当前状态是否异常
    if ((kv_set_state) || (LS_KV_SUM_SIZE == 0))
        return false;
    kv_set_state = true;
    // 检测KEY_ID是否存在
    // 判断数据是否相同
    uint8_t* old = kv_get_env(key_id);
    if (old != NULL) {
        if (memcmp(data, old, len) == 0) {
            // 数据一致,直接返回
            kv_set_state = false;
            return true;
        }
    }
    kv_del_env(key_id);
    // 得到空白块
    kv_sys_m_t* kv = find_blank_addr();
    // 如果数据满了,则进行垃圾回收处理
    if (kv == NULL) {
        kv_gc_env();
        kv = find_blank_addr();
    }
    // 填充数据
    kv_sys_temp.head   = KV_SYS_PACK_HEAD;
    kv_sys_temp.key_id = key_id;
    kv_sys_temp.len    = len + KV_PACK_INFO_BYTE;
    kv_sys_temp.is_en  = 0xFF;
    kv_sys_temp.buff   = data;
    kv_sys_temp.sum    = compute_checksum(&kv_sys_temp);
    bsp_flash_write_nbyte_s((uint32_t)kv, (uint8_t*)&kv_sys_temp, sizeof(kv_sys_m_t) - 4);
    bsp_flash_write_nbyte_s((uint32_t)kv + (sizeof(kv_sys_m_t) - 4), data, len);
    kv_set_state = false;
    return true;
}

/********************************************************************************
* @file    kv_sys.h
* @author  jianqiang.xue
* @version V1.0.0
* @date    2021-11-03
* @brief   KV键值系统
********************************************************************************/

#ifndef __KV_SYS_H__
#define __KV_SYS_H__

/* Includes ------------------------------------------------------------------*/
#include <stdint.h>
#include <stdbool.h>

#ifdef LISUN_SDK
#include "ls_syscfg.h"
#else
#define LS_FLASH_KV_ONE_PAGE_BYTE 32
#endif
/* Define --------------------------------------------------------------------*/
#define KV_SYS_PACK_HEAD    0xFEEF9581
/* Public Struct -------------------------------------------------------------*/
// 仅适用简易版
typedef struct {
    uint8_t  len;                                    // 实际长度(包含key_id+is_en+buff)
    uint8_t  key_id;                                 // KEY ID [1,254]  0和255不能使用
    uint8_t  is_en;                                  // 是否有效 0--无效 FF--有效
    uint8_t  buff[LS_FLASH_KV_ONE_PAGE_BYTE - 4];    // 实际数据(最大长度)
    uint8_t  sum;                                    // 校验和
} kv_sys_t;

// 仅适用终极版 FE EF 95 81 KEYID is_en len data0 data1 sum
typedef struct {
    uint32_t head;                                   // 头字节 0xFEEF9581(固有)
    uint8_t  sum;                                    // 校验和
    uint8_t  len;                                    // 实际长度(包含key_id+is_en+buff)
    uint8_t  key_id;                                 // KEY ID [1,254]  0和255不能使用
    uint8_t  is_en;                                  // 是否有效 0--无效 FF--有效
    uint8_t  *buff;                                  // 实际数据指针
} kv_sys_m_t;

/* Public Function Prototypes -----------------------------------------------*/

int kv_gc_env(void);
void kv_gc_check(void);
void* kv_get_env(uint8_t key_id);
bool kv_del_env(uint8_t key_id);
bool kv_set_env(uint8_t key_id, void *data, uint8_t len);
#endif

最新代码请去gitee获取:跳转

posted on 2022-10-24 22:54  xuejianqiang  阅读(17)  评论(0编辑  收藏  举报  来源
打造一个通用性MCU架构,支持CX32/AT32/NRF51/NRF52等。 OS支持RTX4/RTX5/FreeRtos。 采用VsCode+GCC组合,VsCode+KEIL5,超强开发方式。 QQ群:524408033