ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-01-13 17:02:25  阅读:222  来源: 互联网

标签:FLASH uint8 sys 单片机 键值 kv BS KV


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

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

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

  1. 资源占用

    Code (inc. data)RO DataRW DataZI DataDebugObject Name
    5821600333252
    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));

标签:FLASH,uint8,sys,单片机,键值,kv,BS,KV
来源: https://blog.csdn.net/qq_29246181/article/details/122477183

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有