目录
[一、FlashDB 简介](#一、FlashDB 简介)
[1.1 FlashDB 简介](#1.1 FlashDB 简介)
[二、FlashDB 的初始化](#二、FlashDB 的初始化)
[2.1 向 FlashDB 输入 FLASH 的配置信息](#2.1 向 FlashDB 输入 FLASH 的配置信息)
[2.2 FlashDB 的初始化](#2.2 FlashDB 的初始化)
[2.3 框架的数据库初始化流程](#2.3 框架的数据库初始化流程)
[2.3.1 main()](#2.3.1 main())
[2.3.2 HalInit()](#2.3.2 HalInit())
[2.3.3 AppStoreInit()](#2.3.3 AppStoreInit())
[2.3.4 FlashdbDatabaseInit()](#2.3.4 FlashdbDatabaseInit())
[2.3.5 flashdb_init()](#2.3.5 flashdb_init())
[2.3.6 总结](#2.3.6 总结)
[3.1 参数的初始化](#3.1 参数的初始化)
[2.3.1 main()](#2.3.1 main())
[3.1.2 HalInit()](#3.1.2 HalInit())
[3.1.3 AppParamInit()](#3.1.3 AppParamInit())
[3.1.4 MotorCtrlInit()](#3.1.4 MotorCtrlInit())
[3.1.5 AppStoreInit()](#3.1.5 AppStoreInit())
[3.1.6 flash_param_update()](#3.1.6 flash_param_update())
[3.1.7 app_param_update()](#3.1.7 app_param_update())
[3.1.8 总结](#3.1.8 总结)
[4.1 原理概述](#4.1 原理概述)
[4.2 get() / set() 标准读写接口](#4.2 get() / set() 标准读写接口)
[4.3 命令配置](#4.3 命令配置)
[4.4 1ms 循环定时储存](#4.4 1ms 循环定时储存)
一、FlashDB 简介
1.1 FlashDB 简介
FlashDB 是一款超轻量级的嵌入式数据库,专注于提供嵌入式产品的数据存储方案。FlashDB 不仅支持传统的基于文件系统的数据库模式,而且结合了 Flash 的特性,具有较强的性能及可靠性。并在保证极低的资源占用前提下,尽可能延长 Flash 使用寿命。
FlashDB 提供两种数据库模式:
- 键值数据库 :是一种非关系数据库,它将数据存储为键值 (Key-Value) 对集合,其中键作为唯一标识符。KVDB 操作简洁,可扩展性强。
- 时序数据库 :时间序列数据库 (Time Series Database , 简称 TSDB) ,它将数据按照 时间顺序存储 。TSDB 数据具有时间戳,数据存储量大,插入及查询性能高。
cpp
servo_hub\board_public\flash_db\inc\fdb_def.h
在文件第 24 行:
cpp
#define FDB_SW_VERSION "2.1.1"
本项目使用 FlashDB 2.1.1
二、FlashDB 的初始化
2.1 向 FlashDB 输入 FLASH 的配置信息
在文件第 3 行:
cpp
servo_hub-master\embedded_project\Y01\Board\bsp_flash.c
cpp
struct fal_flash_dev nor_flash0 =
{
.name = FAL_USING_NOR_FLASH_DEV_NAME,
.addr = 0,
.len = 8 * 1024 * 1024,
.blk_size = 4 * 1024,
.ops = {init, read, write, erase},
.write_gran = FDB_WRITE_GRAN
};
从上到下分别规定了 flash 名字、起始物理地址偏移、总容量、最小擦除单元、四个硬件操作
|-------------|----------------------------|---------------------------------------------------------|
| 字段 | 值 | 含义 |
| .name | "norflash0" | FAL 内部通过这个名字查找设备(分区表里的 "norflash0" 就是对应这里) |
| .addr | 0 | 这块 Flash 从地址偏移 0 开始,即整块都可用 |
| .len | 8MB | 物理芯片总大小 8MB(实际型号是 GD25Q64,即 GigaDevice 64Mbit = 8MB) |
| .blk_size | 4096 (4KB) | 最小擦除粒度是一个扇区 4KB,erase 操作必须按 4KB 对齐 |
| .ops | {init, read, write, erase} | {init, read, write, erase} 指向下面 4 个静态函数,是真正操作 SPI 硬件的代码 |
| .write_gran | 1bit | NorFlash 可以按 bit 写入(写 1→0),写粒度最细 |
在本文件内实现了 init()、read()、write()、erase() 的四个函数,其实现方式均使用 STM32 的 HAL 库实现,在这里不过多赘述。
因为我们将这些数据存储到外挂 FLASH 中,对于 STM32 H7 系列芯片来说,访问地址 addr. 就是 0x00。

在文件第 3 行:
cpp
servo_hub-master\embedded_project\Y01\Board\fal_cfg.h
cpp
/* flash device table */
#define FAL_FLASH_DEV_TABLE \
{ \
&nor_flash0, \
}
对 nor_flash0 的地址取了一个宏定义。
最后在文件第 19 行:
cpp
servo_hub-master\board_public\flash_db\port\fal\src\fal_flash.c
cpp
static const struct fal_flash_dev * const device_table[] = FAL_FLASH_DEV_TABLE;
所以 我们的 nor_flash0 变量最终被定义为 fal_flash_dev。
2.2 FlashDB 的初始化
在文件第 26 行:
cpp
servo_hub-master\servo_hub-master\embedded_project\Y01\Board\fal_cfg.h
它的作用是把一块大容量的 Flash 芯片像电脑硬盘一样分区。
cpp
#define FAL_PART_TABLE \
{ \
{FAL_PART_MAGIC_WORD, "bl", "norflash0", 0, 128*1024, 0}, /* Bootloader: 128K */ \
{FAL_PART_MAGIC_WORD, "app", "norflash0", 128*1024, 2048*1024, 0}, /* Application: 2048K */ \
{FAL_PART_MAGIC_WORD, "app_backup", "norflash0", 2176*1024, 1024*1024, 0}, /* Application Backup: 1024K */ \
{FAL_PART_MAGIC_WORD, "kv_param", "norflash0", 3200*1024, 2048*1024, 0}, /* kv_param: 2048K */ \
/* ... more partition norflash0 max size: 8192K */ \
}
在文件第 49 行:
cpp
servo_hub-master\board_public\flash_db\port\fal\src\fal_partition.c
此处是 FlashDB 的定义:
cpp
static const struct fal_partition partition_table_def[] = FAL_PART_TABLE;
其通过下面的流程:

最后初始化这个值:
cpp
part_flash_cache[]
把它的地址交给运行时的全局指针 partition_table[],后续所有分区操作都走这个指针。
2.3 框架的数据库初始化流程
2.3.1 main()
cpp
int main(void)
{
...
// 硬件及应用初始化
HalInit();
...
}
2.3.2 HalInit()
cpp
void HalInit(void)
{
...
AppStoreInit();
...
}
2.3.3 AppStoreInit()
如果 flash 中有绑定的 key-value 值,并且 flash 中有数据,这个函数会帮我们从 flash 中读取出来到目标结构体中。
cpp
void AppStoreInit(void)
{
flash_param_update(); // 在初次存储KV还未被创建时,将参数默认值更新到kFlashStorage后写入flash
FlashdbDatabaseInit();
set_app_Storage_status(FLASH_STORE_STATUS_IDLE);
set_app_Storage_cmd(FLASH_STORE_CMD_READ_PARAM);
AppStoreUpdata1ms();
set_app_Storage_cmd(FLASH_STORE_CMD_READ_ERROR);
AppStoreUpdata1ms();
set_app_Storage_cmd(FLASH_STORE_CMD_READ_TQ_FC);
AppStoreUpdata1ms();
}
2.3.4 FlashdbDatabaseInit()
根据 key 索引注册表,将 key 名称和 结构体绑定。
|--------------------------------|-------------|-----------------------------------------|------------------------|
| Key 索引 | Key 名称 | 存储内容 | 说明 |
| FLASHDB_KEY_INDEX_ALL_PARAM | AllParam | kFlashStorage | 全量系统参数(如 PID 参数、电机参数等) |
| FLASHDB_KEY_INDEX_ERROR_RECORD | ErrorRecord | kFlashHistoricalInfo.Error_records_list | 历史故障记录列表 |
| FLASHDB_KEY_INDEX_TQ_FC_TABLE | TqFcTable | kAxisDw.tq_fc_id_InstanceData.rtdw | Simulink 生成的运行时数据 |
cpp
static void FlashdbDatabaseInit(void)
{
if (bsp_flashdb_key_register(FLASHDB_KEY_INDEX_ALL_PARAM, "AllParam", \
&kFlashStorage, sizeof(kFlashStorage)) != FLASHDB_NO_ERR)
{
sys_set_bsp_error_state(ERROR_FLASH_STORE, ERROR_SET);
return;
}
if (bsp_flashdb_key_register(FLASHDB_KEY_INDEX_ERROR_RECORD, "ErrorRecord", \
&kFlashHistoricalInfo.Error_records_list, \
sizeof(kFlashHistoricalInfo.Error_records_list)) != FLASHDB_NO_ERR)
{
sys_set_bsp_error_state(ERROR_FLASH_STORE, ERROR_SET);
return;
}
if (bsp_flashdb_key_register(FLASHDB_KEY_INDEX_TQ_FC_TABLE, "TqFcTable", \
&kAxisDw.tq_fc_id_InstanceData.rtdw, \
sizeof(kAxisDw.tq_fc_id_InstanceData.rtdw)) != FLASHDB_NO_ERR)
{
sys_set_bsp_error_state(ERROR_FLASH_STORE, ERROR_SET);
return;
}
if (bsp_flashdb_init() != FLASHDB_NO_ERR)
{
sys_set_bsp_error_state(ERROR_FLASH_STORE, ERROR_SET);
return;
}
}
这个函数最终往 default_kv_table[] 表里注册了我们的 key 信息,并且又将我们的 kAxisDw 中的地址与之对应关联。
2.3.5 flashdb_init()
这个函数的作用是初始化 FlashDB 键值数据库 (KVDB),用于在 Flash 存储器中持久化保存参数。
cpp
/**
* @brief Flashdb初始化
* @param void
* @retval fdb_err_t
* @note
*/
fdb_err_t flashdb_init(void)
{
struct fdb_kv kv;
struct fdb_blob blob;
default_kv.kvs = default_kv_table;
default_kv.num = FLASHDB_KEYS_NUM;
/* 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
* "YuanHub": database name
* "kv_param": 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.
*/
fdb_err_code = fdb_kvdb_init(&kvdb, "YuanHub", "kv_param", &default_kv, NULL);
if (fdb_err_code != FDB_NO_ERR)
{
return fdb_err_code;
}
/* check if the default kv nodes are in the kvdb */
for (uint8_t index = 0; index < default_kv.num; index++)
{
if (fdb_kv_get_obj(&kvdb, default_kv.kvs[index].key, &kv) == NULL)
{
fdb_err_code = fdb_kv_set_blob(&kvdb, default_kv.kvs[index].key, \
fdb_blob_make(&blob, default_kv.kvs[index].value, default_kv.kvs[index].value_len));
if (fdb_err_code != FDB_NO_ERR)
{
return fdb_err_code;
}
}
}
return fdb_err_code;
}
2.3.6 总结
至此,FlashDB 初始化完成,流程如下表,我跳过了一部分:
| 函数接口 | 功能详细说明 |
|---|---|
| main() | 程序起点。负责调用 HalInit 开启系统硬件与软件环境初始化,是整个生命周期的入口。 |
| HalInit() | 初始化总调度。按照预设优先级统一编排各功能模块(如时钟、外设、中间件等)的启动顺序,确保底层依赖正确。 |
| AppStoreInit() | 存储管理总入口。管理所有业务参数的持久化逻辑;初始化完成后会立即将 Flash 数据同步至 RAM,供业务逻辑直接访问。 |
| FlashdbDatabaseInit() | 业务数据库配置。向底层注册所有需要持久化的 Key-Value(键值对),并正式启动数据库引擎,建立业务逻辑与存储的映射关系。 |
| flashdb_init() | 库核心启动。负责注册线程安全的互斥锁,执行 FlashDB 核心初始化,并对库中缺失的 Key 执行默认值的补写操作。 |
| fdb_kvdb_init() | KVDB 挂载。具体初始化 FlashDB 的 KV 数据库模式,定位并挂载名为 "kv_param" 的 Flash 物理分区。 |
三、参数列表和项目中的初始化
3.1 参数的初始化
2.3.1 main()
cpp
int main(void)
{
...
// 硬件及应用初始化
HalInit();
...
}
3.1.2 HalInit()
cpp
void HalInit(void)
{
...
AppParamInit();
MotorCtrlInit(); //电机控制相关初始化
...
AppStoreInit();
...
}
3.1.3 AppParamInit()
对 kAppStatusInfo 的变量赋初值,这将是存到 flash 当中的结构体:
cpp
void AppParamInit(void)
{
// 初始化控制字
kAppControlWord.Controlword = 0; // 控制字
kAppControlWord.Halt_running_cmd = 0; // 运行中暂停控制命令
// 初始化状态信息
kAppStatusInfo.Statusword = 0; // 状态字
kAppStatusInfo.Error_word = 0; // 错误码
kAppStatusInfo.Alarm_word = 0; // 警告码
kAppStatusInfo.DC_link_circuit_voltage = 0.0f; // 母线电压当前值
kAppStatusInfo.Drive_accumulated_heat = 0.0f; // 驱动器热量累计值
kAppStatusInfo.Drive_temperature = 0.0f; // 驱动器当前温度
kAppStatusInfo.Motor_temperature = 0.0f; // 电机当前温度
kAppStatusInfo.Mcu_temperature = 0.0f; // MCU当前温度
kAppStatusInfo.Digital_io_inputs_status = 0; // 数字输入状态
kAppStatusInfo.Brake_state = BRAKE_STATE_ENGAGED;// 抱闸状态
...
}
3.1.4 MotorCtrlInit()
对程序的运行变量赋初值,这些变量不写入 flash 中:
cpp
void MotorCtrlInit(void)
{
// 对控制层进行初始化
MotorCtlSmInit(&kAxis, &kAxisDw);
// 初始化硬件默认参数配置
// 电机参数
kAxis.pmsm_config.tc_s = 1.0f / CURRENT_FREQUENCY_HZ; // 电流环周期
kAxis.pmsm_config.tp_s = 1.0f / POSITION_FREQUENCY_HZ; // 位置速度环周期
kAxis.pmsm_config.j = PMSM_J;
kAxis.pmsm_config.b = 0.0001f;
kAxis.pmsm_config.fc = 0.001f;
kAxis.pmsm_config.ld = PMSM_Lp2p * 0.5f;
kAxis.pmsm_config.lq = PMSM_Lp2p * 0.5f;
kAxis.pmsm_config.peak_current = PMSM_PEAK_CURRENT;
kAxis.pmsm_config.pn = PMSM_PN;
kAxis.pmsm_config.r = PMSM_Rp2p * 0.5f;
kAxis.pmsm_config.rated_current = PMSM_RATED_CURRENT;
kAxis.pmsm_config.speed_max_rpm = PMSM_SPEED_MAX_RPM;
kAxis.pmsm_config.enc_line_p_n = PMSM_MOTOR_ENC_LINE_P_N;
kAxis.pmsm_config.kt = PMSM_TORQUE_CONSTANT_MNM_A * 0.001f;
}
3.1.5 AppStoreInit()
如果 flash 中有绑定的 key-value 值,并且 flash 中有数据,这个函数会帮我们从 flash 中读取出来到目标结构体中:
cpp
void AppStoreInit(void)
{
flash_param_update(); // 在初次存储KV还未被创建时,将参数默认值更新到kFlashStorage后写入flash
FlashdbDatabaseInit();
set_app_Storage_status(FLASH_STORE_STATUS_IDLE);
set_app_Storage_cmd(FLASH_STORE_CMD_READ_PARAM);
AppStoreUpdata1ms();
set_app_Storage_cmd(FLASH_STORE_CMD_READ_ERROR);
AppStoreUpdata1ms();
set_app_Storage_cmd(FLASH_STORE_CMD_READ_TQ_FC);
AppStoreUpdata1ms();
}
3.1.6 flash_param_update()
在初次存储 KV 还未被创建时,将参数默认值更新到 kFlashStorage 后写入 flash:
cpp
void flash_param_init(void)
{
kFlashStorage.input_shaping_config_wn_Hz = kAxis.input_shaping_config.wn_Hz;
kFlashStorage.input_shaping_config_run_frq_Hz = kAxis.input_shaping_config.run_frq_Hz;
kFlashStorage.pos_speed_ctl_config_aff = kAxis.pos_speed_ctl_config.aff;
kFlashStorage.pos_speed_ctl_config_vff = kAxis.pos_speed_ctl_config.vff;
kFlashStorage.pos_speed_ctl_config_j_kt = kAxis.pos_speed_ctl_config.j_kt;
...
}
3.1.7 app_param_update()
将 kAppControlWord 中结构体的内容同步到 axis 结构体中:
cpp
void app_param_update(void)
{
set_app_Controlword(kAppControlWord.Controlword);
set_app_Statusword(kAppStatusInfo.Statusword);
set_app_Error_word(kAppStatusInfo.Error_word);
set_app_Modes_of_operation(kAppOpMode.Modes_of_operation);
set_app_Load_encoder_resolution(kAppEncoderConfig.Load_encoder_resolution);
set_app_Motor_encoder_resolution(kAppEncoderConfig.Motor_encoder_resolution);
...
}
比如在 set_app_Controlword() 函数中,val 被赋值给 kAppControlWord 和 axis 中的结构体内:
cpp
uint32_t set_app_Controlword(uint16_t val)
{
...
kAppControlWord.Controlword = val;
/* USER CODE BEGIN set_app_Controlword 1 */
axis->motor_ctl_sm_input.motor_enable = val;
/* USER CODE END set_app_Controlword 1 */
...
}
3.1.8 总结
| 项目(函数名) | 文件及行号 | 数据流向 | 操作的数据层 |
|---|---|---|---|
| AppParamInit() | data_param.c:41 | 宏硬编码 -> kAppXxx RAM | 应用层(CiA 402 对象字典) |
| MotorCtrlInit() | motor_ctl_loop.c:31 | 宏硬编码 -> kAxis RAM | 控制层(Simulink 算法数据库) |
| AppStoreInit() | app_store_param.c:435 | Flash -> kFlashStorage -> RAM -> 通信数据库 | 存储层(Flash 持久化) |
| app_param_update() | data_param.c:357 | kAppXxx RAM -> 通信数据库 | 通信层(数据库同步) |
四、使用这些数据
4.1 原理概述
所有模块统一通过标准化的 set/get 接口访问这个仓库,彼此之间完全解耦。
cpp
通信层(CANopen/EtherCAT/MAVLink)
↕ set_app_XXX() / get_app_XXX()
│
┌────▼──────────────────────────────┐
│ database/data_param │ ← 参数中枢(类OD实现)
│ kAppControlWord │
│ kAppStatusInfo │
│ kAppMotionParam ... │
│ kAppMotorConfig │
└────┬──────────────────────────────┘
↕ 直接访问 kAxis / kFlashStorage
│
应用层(app_scheduler / app_status_check 等)
控制层(motor_ctl_loop / motor_ctl_sm)
4.2 get() / set() 标准读写接口
cpp
uint16_t get_app_Controlword(void)
{
/* USER CODE BEGIN get_app_Controlword */
/* USER CODE END get_app_Controlword */
return kAppControlWord.Controlword;
}
cpp
uint32_t set_app_Controlword(uint16_t val)
{
// 1. 范围检测
if (val > 3) return APP_PARAM_OUT_OF_RANGE;
// 2. 权限检测(控制层接管时只读)
if (get_app_Internal_control_authority() == INTERNAL_CONTROL_CTRL)
return APP_PARAM_READ_ONLY;
// 3. 写入结构体
kAppControlWord.Controlword = val;
// 4. 立即同步到控制层
axis->motor_ctl_sm_input.motor_enable = val;
return APP_PARAM_SUCCESS;
}
4.3 命令配置
以 Mavlink 的写入值举例:
cpp
void MavlinkRecvCallback(Axis *axis, AxisDw *axis_dw, uint8_t rx_data[], uint32_t len)
{
...
case MAVLINK_MSG_ID_SystemCmd:
mavlink_msg_systemcmd_decode(&msg, (mavlink_systemcmd_t *)&system_cmd_t);
set_app_Sys_cmd(system_cmd_t.Sys_cmd);
system_cmd_t.Sys_cmd = get_app_Sys_cmd();
mavlink_msg_systemcmd_encode(sys_id, comp_id, &send_msg, (mavlink_systemcmd_t *)&system_cmd_t);
break;
...
}
通过 set_app_Sys_cmd() 函数便修改了 kSystemStatus.Storage_status 的值:
cpp
uint32_t set_app_Sys_cmd(uint8_t val)
{
/* USER CODE BEGIN set_app_Sys_cmd 0 */
/* USER CODE END set_app_Sys_cmd 0 */
kSystemCmd.Sys_cmd = val;
/* USER CODE BEGIN set_app_Sys_cmd 1 */
switch (val)
{
case APP_SYSTEM_CMD_NONE:
// Do nothing
break;
case APP_SYSTEM_CMD_HOMING:
// 设置当前位置为系统零点
// 负载端累计的绝对位置
// 设置当前目标位置为0
kAppBaseConfig.Home_position_offset_value = kAppMotionInfo.Position_actual_value_inc;
set_app_Target_position(0);
break;
case APP_SYSTEM_CMD_SAVE_CONFIG:
if (axis->motor_ctl_sm_output.state == MOTOR_CTL_SM_MOTOR_ENABLE)
{
set_app_Storage_status(FLASH_STORE_STATUS_WARNING);
return APP_PARAM_WRITE_STATE_ERROR;
}
set_app_Storage_cmd(FLASH_STORE_CMD_WRITE_PARAM);
break;
case APP_SYSTEM_CMD_REBOOT:
bsp_system_reset();
break;
case APP_SYSTEM_CMD_SAVE_TQ_FC_TABLE:
if (axis->motor_ctl_sm_output.state == MOTOR_CTL_SM_MOTOR_ENABLE)
{
set_app_Storage_status(FLASH_STORE_STATUS_WARNING);
return APP_PARAM_WRITE_STATE_ERROR;
}
set_app_Storage_cmd(FLASH_STORE_CMD_WRITE_TQ_FC);
break;
case APP_SYSTEM_CMD_TQ_FC_TABLE_CLEAR:
if (axis->motor_ctl_sm_output.state == MOTOR_CTL_SM_MOTOR_ENABLE)
{
set_app_Storage_status(FLASH_STORE_STATUS_WARNING);
return APP_PARAM_WRITE_STATE_ERROR;
}
set_app_Storage_cmd(FLASH_STORE_CMD_ERASE_TQ_FC);
break;
case APP_SYSTEM_CMD_ERROR_RECORD_CLEAR:
if (axis->motor_ctl_sm_output.state == MOTOR_CTL_SM_MOTOR_ENABLE)
{
set_app_Storage_status(FLASH_STORE_STATUS_WARNING);
return APP_PARAM_WRITE_STATE_ERROR;
}
set_app_Storage_cmd(FLASH_STORE_CMD_ERASE_ERROR);
break;
case APP_SYSTEM_CMD_FACTORY_RESET:
if (axis->motor_ctl_sm_output.state == MOTOR_CTL_SM_MOTOR_ENABLE)
{
set_app_Storage_status(FLASH_STORE_STATUS_WARNING);
return APP_PARAM_WRITE_STATE_ERROR;
}
set_app_Storage_cmd(FLASH_STORE_CMD_ERASE_PARAM);
break;
default:
break;
}
/* USER CODE END set_app_Sys_cmd 1 */
return APP_PARAM_SUCCESS;
}
4.4 1ms 循环定时储存
最后每隔 1ms调用这个更新函数,根据标志位读或者写入 flash 相关的值:
cpp
/**
* @brief 非实时任务1ms循环
*/
void UnrealTimeBase1ms(void)
{
...
// 9. flash参数存储
AppStoreUpdata1ms();
...
}