打造一个通用性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

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

Env 小型KV数据库,支持 写平衡(磨损平衡) 及掉电保护模式

让Flash变为NoSQL(非关系型数据库)模型的小型键值(Key-Value)存储数据库。在产品上,能够更加简捷的实现 设定参数 或掉电保存的功能。

功能:

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

介绍

  1. 资源占用

    Code (inc. data)RO DataRW DataZI DataDebugObject Name
    4981600333252
    RAM 16 字节  ROM 约500字节
    
  2. 支持平台
    各类单片机

  3. 函数简洁

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);
  1. 使用方式
// 存入
kv_set_env(BS_KV_KEY_TEMP_INFO, "ABCDAD", strlen("ABCDAD"));

// 读取
uint8_t *p;
uint8_t buff[10];
p = kv_get_env(BS_KV_KEY_TEMP_INFO);
if (p != NULL)
{
	memcpy(buff, p, 10);
}
  1. 禁止在中断服务中使用!
/********************************************************************************
* @file    kv_sys.c
* @author  jianqiang.xue
* @version V1.0.0
* @date    2021-11-03
* @brief   KV键值最小系统
********************************************************************************/
/* Includes ------------------------------------------------------------------*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

#include "bsp_flash.h"
#include "log.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)

#if BOOT_SUPPORT
#include "os_api.h"
#define delay                    os_delay
#else
#include "main.h"
#define delay                    delay_ms
#endif

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;
    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 ------------------------------------------------*/

void kv_gc_env(void)
{
    bsp_flash_erase_page(BS_KV_BACK_ADDR, 1);
    delay(50);
    kv_sys_t *kv;
    uint8_t sum = 0;
    uint8_t kv_page_tick = 0;
    uint8_t back_tick = 0; // KV_BACK_SUM_NUM
    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);
    }
    delay(50);
}

/**
 * @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;
}

bool kv_del_env(uint8_t key_id)
{
    bool state;
    kv_sys_t *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);
    }
    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_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

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