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

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

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

版本 Code byte RO Data byte RW Data byte ZI Data byte
初版 498 16 0 0
升级版 582 16 0 0
最终版 976 24 9 1
  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_BYTEi += (kv_head.len + KV_PACK_NO_DATA_BYTE);goto end;} elsereturn (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_BYTEi += (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--buskv_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

[单片机框架] [kv_sys] 实现一个简易KV键值系统(最终版)相关推荐

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

    [单片机框架] [kv_sys] 实现一个简易KV键值系统 Env 小型KV数据库,支持 写平衡(磨损平衡) 及掉电保护模式 让Flash变为NoSQL(非关系型数据库)模型的小型键值(Key-Val ...

  2. etcd - 一个分布式一致性键值存储系统

    etcd - 一个分布式一致性键值存储系统 etcd是一个分布式一致性键值存储系统,用于共享配置和服务发现,专注于: 简单:良好定义的,面向用户的API (gRPC) 安全: 带有可选客户端证书认证的 ...

  3. python 取出字典的键或者值/如何删除一个字典的键值对/如何遍历字典

    先定义一个字典并直接进行初始化赋值 my_dict = dict(name="lowman", age=45, money=998, hourse=None) 1.取出该字典所有的 ...

  4. 微信字 签到 java_java微信签到功能实现:java做的一个简易的微信签到系统

    java微信签到功能实现,现在微信签到功能很流行,这个签到功能帮助微信用户更好的管理自己的微信公众号,那你想知道java微信签到功能如何实现呢,今天小编就特意为大家分享一个关于java微信签到功能实现 ...

  5. java设计按月每天签到_java微信签到功能实现:java做的一个简易的微信签到系统的案例...

    java微信签到功能实现,现在微信签到功能很流行,这个签到功能帮助微信用户更好的管理自己的微信公众号,那你想知道java微信签到功能如何实现呢,今天小编就特意为大家分享一个关于java微信签到功能实现 ...

  6. arduino智能浇花系统_解放双手!自己动手做一个简易智能浇花系统

    原标题:解放双手!自己动手做一个简易智能浇花系统 面对疫情,宅在家的我们可以以各种方式为战"疫"一线的医护工作者.紧急研究病毒的科研人员.口罩厂日夜工作的人们......加油打气. ...

  7. 搭建一个简易的医疗导诊系统

    这里我们来介绍一个简易的医疗导诊系统,基于我们产品有五大测量的功能:心电,血氧,血压,血糖和体脂.这个医疗导诊系统主要是通过用户描述的一些症状来判断用户可能的指标异常,然后引导用户进行相关指标的测量, ...

  8. python取出字典的某个键_python 取出字典的键或者值/如何删除一个字典的键值对/如何遍历字典...

    先定义一个字典并直接进行初始化赋值 my_dict = dict(name="lowman", age=45, money=998, hourse=None) 1.取出该字典所有的 ...

  9. 分布式键值系统Amazon Dynamo简介

    分布式键值系统Amazon Dynamo简介 Dynamo采用的技术 虚拟节点 Gossip协议 NRW Vector Clock 读写流程 参考链接 Dynamo采用的技术 问题 采用的技术 数据分 ...

最新文章

  1. 数据算法算力知识反绎学习
  2. form表单的reset
  3. 基于深度学习的端到端人脸识别技术:全面调研
  4. 在SharePoint Foundation 2010中显示来自其他站点的列表
  5. html中span的值不显示,为什么加上form标签之后就不能在span中显示获得值了?
  6. Android中的Handler, Looper, MessageQueue和Thread
  7. c语言不能在函数中求数组大小,C语言中数组长度不能用变量定义吗?
  8. PCL Examples
  9. VBA一招解决宏病毒
  10. 在线就能用的 SQL 练习平台我给你找好了
  11. bch verilog代码_(15-7-2)BCH Verilog HDL 语言编写的(15,7,2)BCH编码和译码功能 VHDL-FPGA- 272万源代码下载- www.pudn.com...
  12. 武汉大学惯性导航课程精要
  13. python日常应用——pdf拆分和合并 python PyPDF2
  14. iOS深拷贝和浅拷贝
  15. MySQL使用HQL语句实现按中文拼音排序
  16. BT.656、PAL、NTSC标准并行数据结构
  17. True Type 文件格式规范
  18. 突然明白了原来我的QQ密保是这样被盗的
  19. 【UI】锤子手机-坚果手机-文艺青年版-配色色号
  20. aardio名字空间库的扩展方法(五)实例

热门文章

  1. Golang库 - viper读取配置文件
  2. 数据分析:销售数据分析如何做?这篇干货收藏备用!
  3. 云服务器和虚拟主机有什么区别?区别大吗?
  4. 2021SC@SDUSC 开源项目GFPGAN -7-experiments
  5. 硬盘安装FC6 linux
  6. RabbitMQ管理界面操作详情
  7. linux删除重复文件,linuxshell删除重复文件只保留一份
  8. 主编编辑器如何插入动图?
  9. 平面设计工作的8个基本技能
  10. 关于印发《留学回国人员申办上海常住户口实施细则》的通知