参考:
[FlashDB]基于GD32纯代码裸机移植FlashDB数据库——四、移植Fal
《CH32FV2x_V3xRM_V1-7》第32 章 闪存及用户选择字(FLASH)
感谢开源项目和其他作者的分享,本文为CH32V203C8T6裸机移植FlashDB (使用片内Flash)的学习过程记录。
0. FlashDB简介
FlashDB 是一款超轻量级的嵌入式数据库,专注于提供嵌入式产品的数据存储方案。与传统的基于文件系统的数据库不同,FlashDB 结合了 Flash 的特性,具有较强的性能及可靠性。并在保证极低的资源占用前提下,尽可能延长 Flash 使用寿命。
FlashDB 提供两种数据库模式:
- 键值数据库 :是一种非关系数据库,它将数据存储为键值(Key-Value)对集合,其中键作为唯一标识符。KVDB 操作简洁,可扩展性强。
- 时序数据库 :时间序列数据库 (Time Series Database , 简称 TSDB),它将数据按照 时间顺序存储 。TSDB 数据具有时间戳,数据存储量大,插入及查询性能高。
1. 移植FAL
新建MounRiver工程,并新建文件夹,添加fal源码到工程中(这里因为是裸机移植,并没有添加src
文件夹中的fal_rtt.c
)。这里我参考了fal_flash_stm32f1_port.c
新建fal_flash_ch32v20x_port.c
文件。
定义 flash 设备
如下,我使用了ch32v203c8t6内部flash的56KB-64KB这8KB的空间。
// fal_flash_ch32v20x_port.c
#include <string.h>
#include <fal.h>
#include <ch32v20x.h>
#define PAGE_SIZE (256u)
static int init(void)
{
/* do nothing now */
return 1;
}
static int ef_err_port_cnt = 0;
int on_ic_read_cnt = 0;
int on_ic_write_cnt = 0;
static void HAL_FeedDog(void)
{
}
static void HAL_Flash_UnLock(void)
{
#if (SystemCoreClock > 96000000)
RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV2;
USART_Printf_Init(115200);
#endif
__disable_irq();
FLASH_Unlock();
}
static void HAL_Flash_Lock(void)
{
FLASH_Lock();
#if (SystemCoreClock > 96000000)
RCC->CFGR0 &= ~(uint32_t)RCC_HPRE_DIV2;
USART_Printf_Init(115200);
#endif
__enable_irq();
}
static void HAL_Flash_UnLock_Fast(void)
{
#if (SystemCoreClock > 96000000)
RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV2;
USART_Printf_Init(115200);
#endif
__disable_irq();
FLASH_Unlock_Fast();
}
static void HAL_Flash_Lock_Fast(void)
{
FLASH_Lock_Fast();
#if (SystemCoreClock > 96000000)
RCC->CFGR0 &= ~(uint32_t)RCC_HPRE_DIV2;
USART_Printf_Init(115200);
#endif
__enable_irq();
}
/**
* @description: 在通用地址空间内进行直接寻址,
* 任何8/16/32位数据的读操作都能访问闪存模块的内容并得到相应的数据
* @param {long} offset
* @param {uint8_t} *buf
* @param {size_t} size
* @return {*}
*/
static int read(long offset, uint8_t *buf, size_t size)
{
size_t i;
uint32_t addr = ch32v20x_onchip_flash.addr + offset;
if (addr % 4 != 0)
ef_err_port_cnt++;
for (i = 0; i < size; i++, buf++, addr++)
{
*buf = *(uint8_t *)addr;
}
on_ic_read_cnt++;
return size;
}
/**
* @description:
* @param {long} offset
* @param {uint8_t} *buf
* @param {size_t} size
* @return {*}
*/
static int write(long offset, const uint8_t *buf, size_t size)
{
size_t i;
uint32_t addr = ch32v20x_onchip_flash.addr + offset;
__attribute__((aligned(4))) uint32_t write_data;
__attribute__((aligned(4))) uint32_t read_data;
if (addr % 4 != 0)
ef_err_port_cnt++;
FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_WRPRTERR);
HAL_Flash_UnLock();
for (i = 0; i < size; i += 4, buf += 4, addr += 4)
{
memcpy(&write_data, buf, 4); // 用以保证HAL_FLASH_Program的write_data是内存首地址对齐
FLASH_ProgramWord(addr, write_data);
read_data = *(uint32_t *)addr;
/* You can add your code under here. */
if (read_data != write_data)
{
HAL_Flash_Lock();
return -1;
}
else
{
// FLash操作可能非常耗时,如果有看门狗需要喂狗,以下代码由用户实现
HAL_FeedDog();
}
}
HAL_Flash_Lock();
on_ic_write_cnt++;
return size;
}
/**
* @description:FLASH_ErasePage_Fast
* Erases a specified FLASH page (1page = 256Byte)
* @param {long} offset
* @param {size_t} size
* @return {*}
*/
static int erase(long offset, size_t size)
{
uint32_t addr = ch32v20x_onchip_flash.addr + offset;
// uint8_t flash_status;
size_t erase_pages, i;
// 计算要擦除的page
erase_pages = size / PAGE_SIZE;
if (size % PAGE_SIZE != 0)
{
erase_pages++;
}
uint32_t page_address;
HAL_Flash_UnLock_Fast();
// 一次擦出一个扇区, 以执行一次喂狗,防止超时
for (i = 0; i < erase_pages; i++)
{
page_address = addr + (PAGE_SIZE * i);
FLASH_ErasePage_Fast(page_address);
// if (flash_status != HAL_OK)
// {
// HAL_FLASH_FastLock();
// return -1;
// }
// else
// {
// FLash操作可能非常耗时,如果有看门狗需要喂狗,以下代码由用户实现
// HAL_FeedDog();
// }
}
HAL_Flash_Lock_Fast();
return size;
}
/*
"ch32v20x_onchip_flash" : Flash 设备的名字。
0x0800E000: 对 Flash 操作的起始地址。(ch32v203c8t6的Flash空间的56KB-64KB)
1024 * 8:Flash 的总大小(8KB)。
256:Flash 块/扇区大小(ch32v20x使用快速编程采用页操作方式,经过特定序列解锁后,可执行单次最少256 字节擦除,
所以擦除粒度为最大块的大小:256B)。
{init, read, write, erase} :Flash 的操作函数。 如果没有 init 初始化过程,第一个操作函数位置可以置空。
8 : 设置写粒度,单位 bit, 0 表示未生效(默认值为 0 ),该成员是 fal 版本大于 0.4.0 的新增成员。各个 flash 写入粒度不尽相同,可通过该成员进行设置,以下列举几种常见 Flash 写粒度:
nor flash: 1 bit
stm32f2/f4: 8 bit
stm32f1: 32 bit
stm32l4: 64 bit
ch32v20x使用标准编程方式,CPU 可以单次2 字节方式执行编程,但这里我用FLASH_ProgramWord()函数,4字节方式即32bit
*/
// 1.定义 flash 设备
const struct fal_flash_dev ch32v20x_onchip_flash =
{
.name = "ch32v20x_onchip_flash",
.addr = 0x0800E000,
.len = 1024 * 8,
.blk_size = PAGE_SIZE,
.ops = {init, read, write, erase},
.write_gran = 32
};
定义 flash 设备表和定义 flash 分区表
\\ fal_cfg.h
#ifndef _FAL_CFG_H_
#define _FAL_CFG_H_
#include <ch32v20x.h>
#define FAL_DEBUG 1
#define FAL_PART_HAS_TABLE_CFG
/* ===================== Flash device Configuration ========================= */
extern const struct fal_flash_dev ch32v20x_onchip_flash;
/* flash device table */
#define FAL_FLASH_DEV_TABLE \
{ \
&ch32v20x_onchip_flash, \
}
/* ====================== Partition Configuration ========================== */
#ifdef FAL_PART_HAS_TABLE_CFG
/* partition table */
#define FAL_PART_TABLE \
{ \
{FAL_PART_MAGIC_WORD, "KVDB", "ch32v20x_onchip_flash", 0, 4*1024, 0}, \
{FAL_PART_MAGIC_WORD, "TSDB", "ch32v20x_onchip_flash", 4*1024, 4*1024, 0}, \
}
#endif /* FAL_PART_HAS_TABLE_CFG */
#endif /* _FAL_CFG_H_ */
FAL移植测试
修改main.c
如下:
#include "debug.h"
#include "fal.h"
/*********************************************************************
* @fn main
*
* @brief Main program.
*
* @return none
*/
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Delay_Init();
USART_Printf_Init(115200);
printf("SystemClk:%d\r\n", SystemCoreClock);
printf("This is FlashDB use ch32v20x_onchip_flash example\r\n");
fal_init();
while(1)
{
}
}
如下添加-print-memory-usage
即可查看编译结果的空间占用情况。
在默认配置下移植FAL的空间占用如下:
2. 移植FlashDB
添加FlashDB源码到工程中如下:
修改 fdb_cfg.h
文件
宏定义flash写粒度(位于Line42)
测试FlashDB
添加samples
文件夹。
修改main.c
:
// main.c
#include "debug.h"
#include "fal.h"
#include "flashdb.h"
#define FDB_LOG_TAG "[main]"
static uint32_t boot_count = 0;
static time_t boot_time[10] = {0, 1, 2, 3};
/* default KV nodes */
static struct fdb_default_kv_node default_kv_table[] = {
{"username", "yzy", 0}, /* string KV */
{"password", "123456", 0}, /* string KV */
{"boot_count", &boot_count, sizeof(boot_count)}, /* int type KV */
{"boot_time", &boot_time, sizeof(boot_time)}, /* int array type KV */
};
/* KVDB object */
static struct fdb_kvdb kvdb = { 0 };
/* TSDB object */
struct fdb_tsdb tsdb = { 0 };
/* counts for simulated timestamp */
static int counts = 0;
extern void kvdb_basic_sample(fdb_kvdb_t kvdb);
extern void kvdb_type_string_sample(fdb_kvdb_t kvdb);
extern void kvdb_type_blob_sample(fdb_kvdb_t kvdb);
extern void tsdb_sample(fdb_tsdb_t tsdb);
static void lock(fdb_db_t db)
{
__disable_irq();
}
static void unlock(fdb_db_t db)
{
__enable_irq();
}
static fdb_time_t get_time(void)
{
/* Using the counts instead of timestamp.
* Please change this function to return RTC time.
*/
return ++counts;
}
uint8_t flashdb_test_kvdb(void)
{ /* KVDB Sample */
fdb_err_t result;
struct fdb_default_kv default_kv;
default_kv.kvs = default_kv_table;
default_kv.num = sizeof(default_kv_table) / sizeof(default_kv_table[0]);
/* set the lock and unlock function if you want */
fdb_kvdb_control(&kvdb, FDB_KVDB_CTRL_SET_LOCK, (void *)lock);
fdb_kvdb_control(&kvdb, FDB_KVDB_CTRL_SET_UNLOCK, (void *)unlock);
/* Key-Value database initialization
*
* &kvdb: database object
* "env": database name
* "fdb_kvdb1": The flash partition name base on FAL. Please make sure it's in FAL partition table.
* Please change to YOUR partition name.
* &default_kv: The default KV nodes. It will auto add to KVDB when first initialize successfully.
* NULL: The user data if you need, now is empty.
*/
result = fdb_kvdb_init(&kvdb, "KVDB", "KVDB", &default_kv, NULL);
if (result != FDB_NO_ERR) {
return -1;
// printf("\r\n FDB_NO_ERR = SET !!! \r\n");
}
/* run basic KV samples */
kvdb_basic_sample(&kvdb);
/* run string KV samples */
kvdb_type_string_sample(&kvdb);
/* run blob KV samples */
kvdb_type_blob_sample(&kvdb);
return 0;
}
uint8_t flashdb_test_tsdb(void)
{ /* TSDB Sample */
fdb_err_t result;
/* set the lock and unlock function if you want */
fdb_tsdb_control(&tsdb, FDB_TSDB_CTRL_SET_LOCK, (void *)lock);
fdb_tsdb_control(&tsdb, FDB_TSDB_CTRL_SET_UNLOCK, (void *)unlock);
/* Time series database initialization
*
* &tsdb: database object
* "log": database name
* "fdb_tsdb1": The flash partition name base on FAL. Please make sure it's in FAL partition table.
* Please change to YOUR partition name.
* get_time: The get current timestamp function.
* 128: maximum length of each log
* NULL: The user data if you need, now is empty.
*/
result = fdb_tsdb_init(&tsdb, "TSDB", "TSDB", get_time, 128, NULL);
/* read last saved time for simulated timestamp */
fdb_tsdb_control(&tsdb, FDB_TSDB_CTRL_GET_LAST_TIME, &counts);
if (result != FDB_NO_ERR) {
return -1;
// printf("\r\n FDB_NO_ERR = SET !!! \r\n");
}
/* run TSDB sample */
tsdb_sample(&tsdb);
return 0;
}
/*********************************************************************
* @fn main
*
* @brief Main program.
*
* @return none
*/
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Delay_Init();
USART_Printf_Init(115200);
printf("SystemClk:%d\r\n", SystemCoreClock);
printf("This is FlashDB use ch32v20x_onchip_flash example\r\n");
fal_init();
/* kvdb?? */
flashdb_test_kvdb();
/* tsdb?? */
flashdb_test_tsdb();
while (1)
{
}
}
空间占用如下:
正常的话效果如下:
但有时候会有这些写入错误出现,待解决。。。