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

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

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

功能:
简易设置KEY和VAL,自动垃圾回收。至少需要占用两页FLASH空间。
平衡flash读写,提高flash擦写寿命

  1. 资源占用

    Code (inc. data) RO Data RW Data ZI Data Debug Object Name
    582 16 0 0 33 3252
    RAM 16 字节  ROM 582字节
    
  2. 支持平台
    各类单片机

  3. 函数简洁

void 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);

```c
/************************************FLASH**************************************/
// <h> FLASH
// =======================
//
//  <o> Page Size
//  <i> Default: 0x800 (2K byte)
#define BS_FLASH_PAGE_SIZE                                         (0x800U)

//  <o> Flash Max Size
//  <i> Default: 0x40000 (256K byte)
#define BS_FLASH_MAX_SIZE                                          (0x40000U)

//  <o> Boot Loader Starting Address
//  <i> Default: 0
#define BS_FLASH_START_ADDR                                        (0x8000000)

//  <o> Boot Loader Size
//  <i> Default: 0x3800 (14K byte)
#define BS_FLASH_BOOT_SIZE                                         (0x3800)

//  <o> Kv Flash Page
//  <i> Default: 2
#define BS_FLASH_KV_PAGE                                           (2)

//  <o> Kv One Page Byte
//  <i> Default: 32
#define BS_FLASH_KV_ONE_PAGE_BYTE                                  (32)

//  <o> FLASH_INFO_TYPE(MCU user Kv_Sys / NRF user FDS)
//      <0=> KV_SYS
//      <1=> FDS
#define BS_FLASH_INFO_TYPE                                         (0)

// </h>
/********************************[FLASH INFO]*********************************/
#define BS_FLASH_APP_ADDR                                          (BS_FLASH_START_ADDR + BS_FLASH_BOOT_SIZE)
#define BS_FLASH_APP_SIZE                                          (BS_FLASH_MAX_SIZE - BS_FLASH_BOOT_SIZE - (BS_FLASH_KV_PAGE * BS_FLASH_PAGE_SIZE))
#define BS_FLASH_OTA_ADDR                                          (BS_FLASH_APP_ADDR + BS_FLASH_APP_SIZE / 2)
#define BS_FLASH_END_ADDR                                          (BS_FLASH_START_ADDR + BS_FLASH_MAX_SIZE)

#define BS_KV_BASE_ADDR                                            (BS_FLASH_START_ADDR + BS_FLASH_APP_SIZE)
#define BS_KV_BACK_ADDR                                            (BS_KV_BASE_ADDR + (BS_FLASH_KV_PAGE - 1) * BS_FLASH_PAGE_SIZE)
/********************************************************************************
* @file    kv_sys.c
* @author  jianqiang.xue
* @version V1.0.0
* @date    2021-11-03
* @brief   KV键值最小系统 https://lisun.blog.csdn.net/article/details/121140849
********************************************************************************/
/* Includes ------------------------------------------------------------------*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

#include "bsp_flash.h"

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

#include "business_function.h"
/* Private Define ------------------------------------------------------------*/

// KV系统总共可以使用N字节
#define KV_SUM_SIZE             (BS_FLASH_PAGE_SIZE * (BS_FLASH_KV_PAGE - 1))
// KV系统总共使用键值数量
#define KV_SUM_NUM              (KV_SUM_SIZE / BS_FLASH_KV_ONE_PAGE_BYTE)
// KV系统备份区使用键值数量
#define KV_BACK_SUM_NUM         (BS_FLASH_PAGE_SIZE / BS_FLASH_KV_ONE_PAGE_BYTE)
// KV系统中buff最大长度值
#define KV_BUFF_MAX_SIZE        (BS_FLASH_KV_ONE_PAGE_BYTE - 4)

kv_sys_t kv_sys_temp = {0};
bool kv_set_state    = false;  // flash--free   true--bus
/* Private Function Prototypes -----------------------------------------------*/

static uint8_t compute_checksum(uint8_t *data, uint8_t len)
{
    uint16_t sum = 0;
    for (uint8_t i = 0; i < len; i++)
    {
        sum += *(data + i);
    }
    return (uint8_t)(sum & 0x00FF);
}

static void *find_kv_addr(uint8_t key_id)
{
    kv_sys_t *kv;
    uint8_t sum = 0;
    for (uint8_t i = 0; i < KV_SUM_NUM; i++)
    {
        kv = (kv_sys_t *)(BS_KV_BASE_ADDR + BS_FLASH_KV_ONE_PAGE_BYTE * i);
        if (kv->key_id != key_id)
        {
            continue;
        }
        if (kv->is_enabled != 0xFF)
        {
            continue;
        }
        sum = compute_checksum((uint8_t *)kv, sizeof(kv_sys_t) - 1);
        if (kv->sum == sum)
        {
            return (void *)kv;
        }
    }
    return NULL;
}

static void *find_blank_addr(void)
{
    kv_sys_t *kv = NULL;
    for (uint8_t i = 0; i < KV_SUM_NUM; i++)
    {
        kv = (kv_sys_t *)(BS_KV_BASE_ADDR + BS_FLASH_KV_ONE_PAGE_BYTE * i);
        if (kv->key_id == 0xFF && kv->is_enabled == 0xFF)
        {
            return (void *)kv;
        }
    }
    return NULL;
}

/* Public Function Prototypes ------------------------------------------------*/
/**
 * @brief  FLASH垃圾回收
 */
void kv_gc_env(void)
{
    bsp_flash_erase_page(BS_KV_BACK_ADDR, 1);
    while(bsp_flash_is_busy());
    kv_sys_t *kv         = NULL;
    uint8_t sum          = 0;
    uint8_t kv_page_tick = 0;
    uint8_t back_tick    = 0;
    for (uint8_t i = 0; i < KV_SUM_NUM; i++)
    {
        kv = (kv_sys_t *)(BS_KV_BASE_ADDR + BS_FLASH_KV_ONE_PAGE_BYTE * i);
        if (kv->is_enabled != 0xFF)
        {
            continue;
        }
        // 判断数据的完整性
        sum = compute_checksum((uint8_t *)kv, sizeof(kv_sys_t) - 1);
        if (kv->sum != sum)
        {
            continue;
        }
        // 搬运有效数据
        bsp_flash_write_nbyte_s(BS_KV_BACK_ADDR + back_tick * BS_FLASH_KV_ONE_PAGE_BYTE, (uint8_t *)kv, sizeof(kv_sys_t));
        back_tick ++;
        if (back_tick == KV_BACK_SUM_NUM)
        {
            bsp_flash_carry(BS_KV_BASE_ADDR + kv_page_tick * BS_FLASH_PAGE_SIZE, BS_KV_BACK_ADDR, BS_FLASH_PAGE_SIZE);
            kv_page_tick ++;
            back_tick = 0;
        }
    }
    if (back_tick != 0)
    {
        bsp_flash_carry(BS_KV_BASE_ADDR + kv_page_tick * BS_FLASH_PAGE_SIZE, BS_KV_BACK_ADDR, BS_FLASH_PAGE_SIZE);
        kv_page_tick ++;
        back_tick = 0;
    }
    // 清理未使用的空间
    for (uint8_t i = kv_page_tick; i < BS_FLASH_KV_PAGE - 1; i++)
    {
        bsp_flash_erase_page(BS_KV_BASE_ADDR + kv_page_tick * BS_FLASH_PAGE_SIZE, 1);
    }
    while(bsp_flash_is_busy());
}

/**
 * @brief  [上电调用] 检测当前KV键值是否异常 如:掉电导致异常,则进行数据恢复
 */
void kv_gc_check(void)
{
    kv_sys_t *kv = NULL;
    // 得到空白块
    kv = find_blank_addr();
    // 如果数据满了,则进行垃圾回收处理
    if(kv == NULL)
    {
        kv_gc_env();
    }
    else
    {
        // 判断备份区是否有残余
        kv = (kv_sys_t *)BS_KV_BACK_ADDR;
        if (kv->key_id != 0xFF)
        {
            for (uint8_t i = 0; i < KV_BACK_SUM_NUM; i++)
            {
                kv = (kv_sys_t *)(BS_KV_BACK_ADDR + i * BS_FLASH_KV_ONE_PAGE_BYTE);
                kv_set_env(kv->key_id, kv->buff, kv->len);
            }
            bsp_flash_erase_page(BS_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_t *kv = (kv_sys_t *)find_kv_addr(key_id);
    if (kv != NULL)
    {
        return kv->buff;
    }
    return NULL;
}

/**
 * @brief  从FLASH中删除某KV值
 * @param  key_id: KEY ID
 * @retval 0--成功 1--失败
 */
bool kv_del_env(uint8_t key_id)
{
    bool state = false;
    kv_sys_t *kv;
    for(uint16_t i = 0; i< KV_SUM_NUM; i++)
    {
        kv = (kv_sys_t *)find_kv_addr(key_id);
        uint32_t temp_addr = (uint32_t)kv + (uint8_t)BS_FLASH_KV_ONE_PAGE_BYTE -2;
        if (kv != NULL)
        {
            // 将之前值标记为无效
            state = bsp_flash_write_byte(temp_addr, 0x00);
        }
        else
        {
            break;
        }
    }

    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)
{
    // 检测ID是否异常
    if (key_id == 0 || key_id == 255)
    {
        return false;
    }
    // 检测参数和当前状态是否异常
    if ((len > KV_BUFF_MAX_SIZE) || (kv_set_state) || (KV_SUM_SIZE == 0))
    {
        return false;
    }
    kv_set_state           = true;
    // 检测KEY_ID是否存在
    kv_sys_t *kv = NULL;
    // 判断数据是否相同
    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 = find_blank_addr();
    // 如果数据满了,则进行垃圾回收处理
    if(kv == NULL)
    {
        kv_gc_env();
        kv = find_blank_addr();
    }
    // 填充数据
    kv_sys_temp.key_id     = key_id;
    memset(kv_sys_temp.buff, 0, KV_BUFF_MAX_SIZE);
    memcpy(kv_sys_temp.buff, data, len);
    kv_sys_temp.len        = len;
    kv_sys_temp.is_enabled = 0xFF;
    kv_sys_temp.sum        = compute_checksum((uint8_t *)&kv_sys_temp, sizeof(kv_sys_t) - 1);
    bsp_flash_write_nbyte_s((uint32_t)kv, (uint8_t *)&kv_sys_temp, sizeof(kv_sys_t));
    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>

#include "business_function.h"
/* Public Struct -------------------------------------------------------------*/
typedef struct
{
    uint8_t  key_id;                                 // KEY ID [1,254]  0和255不能使用
    uint8_t  buff[BS_FLASH_KV_ONE_PAGE_BYTE - 4];    // 实际数据
    uint8_t  len;                                    // 实际长度
    uint8_t  is_enabled;                             // 是否有效 0--无效 FF--有效
    uint8_t  sum;                                    // 校验和
} kv_sys_t;

/* Public Function Prototypes -----------------------------------------------*/
void 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

使用例程:

#include "kv_sys.h"

// [上电调用] 检测当前KV键值是否异常 如:掉电导致异常,则进行数据恢复
kv_gc_check();


//---------------读取数据------------------
#define BS_KV_KEY_BOOT_INFO                                        0x01

typedef struct
{
    uint8_t boot_state;       // 0--run app  1--in dfu  2--move ota in app
    uint16_t boot_run_tick;   // 掉电次数
    uint32_t boot_carry_size; // 需要复制的字节数量
    uint16_t app_crc;         // app 运行前先crc校验,防止app损坏
    uint16_t chip_lock;       // 任意值--锁住swo XXXX--特殊码解锁swo
} boot_info_t;

boot_info_t g_boot_info =
{
    .boot_state = BOOT_STATE_RUN_APP,            // 0--run app  1--in dfu  2--move ota in app
};

/**
 * @brief  从FLASH读取boot信息配置
 */
bool flash_read_boot_info(void)
{
    uint8_t *p;
    p = kv_get_env(BS_KV_KEY_BOOT_INFO);
    if (p != NULL)
    {
        memcpy((uint8_t *)&g_boot_info, p, sizeof(boot_info_t));
    }
    else
    {
        memset(&g_boot_info, 0, sizeof(boot_info_t));
    }
    return true;
}

//---------------写入数据------------------
g_boot_info.boot_state = BOOT_STATE_IN_DFU;
kv_set_env(BS_KV_KEY_BOOT_INFO, (uint8_t *)&g_boot_info, sizeof(boot_info_t));
上一篇:ReadWriteLock JUC多线程读写锁


下一篇:NPOI的WORD填表