[单片机框架] [kv_sys] 实现一个简易KV键值系统
Env 小型KV数据库,支持 写平衡(磨损平衡) 及掉电保护模式
让Flash变为NoSQL(非关系型数据库)模型的小型键值(Key-Value)存储数据库。在产品上,能够更加简捷的实现 设定参数 或掉电保存的功能。
功能:
简易设置KEY和VAL,自动垃圾回收。至少需要占用两页FLASH空间。
平衡flash读写,提高flash擦写寿命
-
资源占用
Code (inc. data) RO Data RW Data ZI Data Debug Object Name 582 16 0 0 33 3252 RAM 16 字节 ROM 582字节
-
支持平台
各类单片机 -
函数简洁
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));