工厂模式参数管理系统 - 使用指南
📋 概述
工厂模式参数管理系统为嵌入式设备提供了简单直接的参数存储方案,特别适合在生产制造阶段进行一次性配置。相比于 EasyFlash 的 ENV 系统(支持磨损平衡),工厂模式提供了更简洁高效的直写方案。
核心特性
| 特性 | 工厂模式 | ENV模式 |
|---|---|---|
| 存储位置 | 前4KB (0x08000000 - 0x08000FFF) | 后8KB (0x08001000 - 0x08002FFF) |
| 数据保护 | CRC32校验 | CRC32 + 环保机制 |
| 磨损平衡 | ❌ 不支持 | ✅ 自动支持 |
| 更新频率 | 低(工厂配置) | 高(运行时参数) |
| 参数类型 | 字符串/二进制 | 字符串/二进制 |
| 最大参数数 | 32个 | 不限 |
| 查询速度 | 快(内存缓存) | 快(内存缓存) |
🏗️ 系统架构
内存分布
Flash: 0x08000000
┌─────────────────────────────────┐
│ 工厂模式参数区 4KB │ ← 固定地址写入
│ (不支持磨损平衡) │
│ 0x08000000 - 0x08000FFF │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ ENV参数区 8KB │ ← EasyFlash管理
│ (支持磨损平衡) │
│ 0x08001000 - 0x08002FFF │
└─────────────────────────────────┘
数据格式
工厂区头部(16字节)
[3:0] Magic(0x50434146) - 'FACP'
[5:4] Version(0x0001) - v1.0
[7:6] Count - 参数个数
[11:8] CRC32 - 整体校验和
[15:12] Reserved - 预留
参数项格式
[1:0] KeyLen - key长度(1字节)
[KeyLen] Key - key值(不超过32字节)
[n+1:n] ValLen - value长度(2字节,大端序)
[m:n+2] Value - value值(不超过128字节)
🚀 快速开始
1. 初始化系统
#include <easyflash.h>
// 定义默认参数
static const ef_env default_params[] = {
{"device_id", "1"},
{"hw_version", "v1.0"},
{"sn", "SN000000"},
};
// 初始化
ef_factory_init(default_params, sizeof(default_params) / sizeof(default_params[0]));
2. 写入参数
写入字符串参数
EfErrCode result = ef_factory_write("device_name", "SmartDevice-001", 15);
if (result != EF_NO_ERR) {
printf("Write failed: %d\n", result);
}
写入二进制参数
uint8_t mac_address[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
ef_factory_write("mac_addr", (const void *)mac_address, 6);
写入结构体参数
typedef struct {
uint16_t device_id;
uint32_t serial_number;
} device_info_t;
device_info_t info = {
.device_id = 42,
.serial_number = 0x20260210,
};
ef_factory_write("device_info", (const void *)&info, sizeof(info));
3. 读取参数
读取字符串参数
char device_name[64];
size_t actual_len;
size_t read_len = ef_factory_read("device_name", (void *)device_name,
sizeof(device_name) - 1, &actual_len);
if (read_len > 0) {
device_name[actual_len] = '\0';
printf("Device: %s\n", device_name);
}
读取二进制参数
uint8_t mac[6];
ef_factory_read("mac_addr", (void *)mac, sizeof(mac), NULL);
printf("MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
读取结构体参数
device_info_t info;
ef_factory_read("device_info", (void *)&info, sizeof(info), NULL);
printf("Device ID: %d, Serial: 0x%08X\n", info.device_id, info.serial_number);
4. 保存到Flash
// 修改参数后,必须调用save来持久化到Flash
ef_factory_write("key1", "value1", 6);
ef_factory_write("key2", "value2", 6);
// 一次性保存所有参数到Flash
EfErrCode result = ef_factory_save();
if (result == EF_NO_ERR) {
printf("Saved successfully\n");
}
5. 查询和维护
获取参数个数
uint16_t count = ef_factory_get_count();
printf("Total parameters: %d\n", count);
按索引获取参数
factory_param_item_t item;
for (uint16_t i = 0; i < ef_factory_get_count(); i++) {
if (ef_factory_get_by_index(i, &item)) {
printf("Key: %s, Size: %d\n", item.key, item.value_len);
}
}
打印所有参数
ef_factory_dump(); // 输出十六进制和可读文本
删除参数
ef_factory_delete("old_key");
ef_factory_save(); // 必须保存
重置到默认值
ef_factory_reset_to_defaults(default_params, sizeof(default_params)/sizeof(default_params[0]));
擦除所有参数
ef_factory_erase_all(); // 清空Flash和内存缓存
📝 API 参考
初始化函数
ef_factory_init
EfErrCode ef_factory_init(const ef_env *default_params, size_t default_count);
- 功能:初始化工厂参数系统
- 参数:
default_params:默认参数数组default_count:默认参数个数
- 返回:错误代码
- 注意:应在系统启动时调用一次
读写函数
ef_factory_write
EfErrCode ef_factory_write(const char *key, const void *value_buf, size_t value_len);
- 功能:写入参数到内存缓存
- 参数:
key:参数名称(≤32字节)value_buf:参数值缓冲区value_len:参数值长度(≤128字节)
- 返回:
EF_NO_ERR成功,其他值表示错误 - 注意:写入后需调用
ef_factory_save()才能持久化到Flash
ef_factory_read
size_t ef_factory_read(const char *key, void *value_buf, size_t buf_len,
size_t *actual_len);
- 功能:从缓存读取参数
- 参数:
key:参数名称value_buf:输出缓冲区buf_len:缓冲区大小actual_len:返回实际参数长度(可为NULL)
- 返回:读取的字节数,0表示未找到
ef_factory_delete
EfErrCode ef_factory_delete(const char *key);
- 功能:删除参数
- 返回:成功或错误代码
管理函数
ef_factory_save
EfErrCode ef_factory_save(void);
- 功能:保存所有参数到Flash
- 注意:会擦除整个4KB区域然后重写
ef_factory_dump
void ef_factory_dump(void);
- 功能:打印所有参数到控制台
ef_factory_get_count
uint16_t ef_factory_get_count(void);
- 功能:获取参数总数
ef_factory_get_by_index
bool ef_factory_get_by_index(uint16_t index, factory_param_item_t *item);
- 功能:按索引获取参数
ef_factory_is_valid
bool ef_factory_is_valid(void);
- 功能:检查Flash中的数据是否有效
ef_factory_erase_all
EfErrCode ef_factory_erase_all(void);
- 功能:擦除所有参数
ef_factory_reset_to_defaults
EfErrCode ef_factory_reset_to_defaults(const ef_env *default_params,
size_t default_count);
- 功能:重置为默认参数
💡 使用场景
场景1:生产初始化(工厂)
void factory_init_device(void) {
// 第一步:擦除旧数据
ef_factory_erase_all();
// 第二步:写入设备参数
uint32_t serial_num = get_from_barcode();
uint8_t mac[6];
generate_mac_address(mac);
ef_factory_write("serial_num", &serial_num, sizeof(serial_num));
ef_factory_write("mac_address", mac, 6);
ef_factory_write("hw_version", "v1.0", 4);
ef_factory_write("production_date", "2026-02-10", 10);
// 第三步:保存到Flash
EfErrCode result = ef_factory_save();
// 第四步:验证
if (result == EF_NO_ERR) {
ef_factory_dump(); // 打印验证
printf("Device initialized successfully!\n");
}
}
场景2:运行时访问(设备端)
void device_runtime_access(void) {
// 读取出厂设置
uint32_t serial_num;
uint8_t mac[6];
ef_factory_read("serial_num", &serial_num, sizeof(serial_num), NULL);
ef_factory_read("mac_address", mac, sizeof(mac), NULL);
// 使用参数
printf("Device Serial: %08X\n", serial_num);
printf("MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
场景3:固件升级检验
void verify_firmware_compatibility(void) {
char hw_version[32];
ef_factory_read("hw_version", hw_version, sizeof(hw_version), NULL);
if (is_firmware_compatible(hw_version)) {
printf("Firmware compatible with hardware\n");
} else {
printf("ERROR: Hardware version mismatch!\n");
}
}
⚙️ 配置选项
在 ef_factory.h 中可以配置:
/* 工厂参数区起始地址 */
#define FACTORY_PARAM_ADDR 0x08000000
/* 工厂参数区大小 */
#define FACTORY_PARAM_SIZE (4 * 1024)
/* 最大参数个数 */
#define FACTORY_PARAM_MAX_COUNT 32
/* key最大长度 */
#define FACTORY_PARAM_KEY_MAX_LEN 32
/* 单个value最大长度 */
#define FACTORY_PARAM_VALUE_MAX_LEN 128
🔒 线程安全和中断保护
中断保护
所有Flash操作在执行时自动关闭中断:
ef_port_env_lock(); // 关闭中断
// ... Flash操作 ...
ef_port_env_unlock(); // 开启中断
使用者不需要显式处理,系统自动管理。
多线程访问
如果在多线程环境中使用,建议在应用层添加额外的互斥锁:
extern void user_lock_acquire(void);
extern void user_lock_release(void);
EfErrCode safe_factory_write(const char *key, const void *value, size_t len) {
user_lock_acquire();
EfErrCode result = ef_factory_write(key, value, len);
user_lock_release();
return result;
}
⚠️ 注意事项
关键点
-
写入后必须保存
ef_factory_write()只修改内存缓存- 必须调用
ef_factory_save()才能写入Flash
-
参数大小限制
- Key最大32字节
- Value最大128字节
- 总参数个数最大32个
-
擦写寿命
- 工厂区共4KB
- 每次save都是完整擦除 + 重写
- 仅适合低频更新(推荐<1000次)
-
数据一致性
- 如果save中途掉电,Flash可能处于不一致状态
- 系统具有CRC校验可检测损坏
- 可调用
ef_factory_reset_to_defaults()恢复
-
内存占用
- 系统内存缓存约8KB(32个参数 × 256字节)
- 栈占用约512字节
常见错误
// ❌ 错误:未保存
ef_factory_write("key", "value", 5);
// 掉电 -> 参数丢失!
// ✅ 正确:写入后保存
ef_factory_write("key", "value", 5);
ef_factory_save();
// ❌ 错误:参数值超长
ef_factory_write("key", very_long_buffer, 200); // >128字节
// ✅ 正确:分割写入
ef_factory_write("key1", buffer1, 100);
ef_factory_write("key2", buffer2, 100);
ef_factory_save();
📊 性能指标
| 操作 | 时间 | 备注 |
|---|---|---|
| 初始化 | <10ms | 从Flash加载数据 |
| 内存写入 | <0.1ms | 不涉及Flash |
| 内存读取 | <0.1ms | 直接内存访问 |
| Flash擦除 | ~100ms | 擦除4KB扇区 |
| Flash写入 | ~50ms | 写入数据 |
| 完整Save | ~150ms | 擦除+写入 |
| Dump输出 | <10ms | 打印信息 |
🐛 调试技巧
启用调试日志
在 ef_cfg.h 中定义:
#define PRINT_DEBUG
常用调试序列
// 1. 检查工厂区有效性
if (ef_factory_is_valid()) {
printf("Factory area is valid\n");
} else {
printf("Factory area needs initialization\n");
}
// 2. 转储所有参数
ef_factory_dump();
// 3. 检查参数个数
printf("Parameter count: %d\n", ef_factory_get_count());
// 4. 逐个读取验证
factory_param_item_t item;
for (int i = 0; i < ef_factory_get_count(); i++) {
ef_factory_get_by_index(i, &item);
printf("[%d] %s: %d bytes\n", i, item.key, item.value_len);
}
📚 集成步骤
-
复制文件
ef_factory.h→easyflash/inc/ef_factory.c→easyflash/src/
-
更新头文件
easyflash.h中添加#include <ef_factory.h>
-
配置ef_cfg.h
- 确保
EF_ERASE_MIN_SIZE和EF_WRITE_GRAN正确设置
- 确保
-
初始化调用
- 在
ef_port_init()中调用ef_factory_init()
- 在
-
编译链接
- 将
ef_factory.c加入编译列表
- 将
📞 常见问题
Q: 工厂模式和ENV模式可以同时使用吗?
A: 可以。工厂模式用前4KB,ENV使用后8KB,互不干扰。建议工厂模式存储不变的硬件参数,ENV存储易变的软件参数。
Q: 参数会自动从Flash加载吗?
A: 会。ef_factory_init() 会自动从Flash加载现有数据到内存缓存。
Q: 可以存储任意类型的二进制数据吗?
A: 可以。系统不关心数据内容,只要在大小限制内。
Q: Flash掉电会丢数据吗?
A: ef_factory_save() 时会自动关闭中断保证原子性。建议在电源稳定时调用此函数。
Q: 如何从损坏的状态恢复?
A: 调用 ef_factory_reset_to_defaults() 可恢复到默认值。
📄 许可证
本工厂模式参数管理系统遵循与 EasyFlash 相同的许可证。