这次是我本人首次借助AI辅助开发,有较为深刻的使用体会。相较于自主手写代码,初期利用AI编程的工作效率更低,其核心原因在于个人多年编程的习惯与AI辅助开发模式存在一个适配的过程。
一般的嵌入式软件工程师通常采用边设计、边编码、边调整的迭代开发模式,没有完整的需求规范,依托经验完成开发。但AI辅助开发对需求描述的完整性、准确性要求极高,需要提前将架构思路、代码风格、逻辑规则、参数定义等内容清晰、完整地梳理输出,对需求描述的精细化程度有着严格要求。
本人编程还是有一定个人风格,通用AI难以精准匹配个人长期形成的编码风格,想要让AI完全贴合自身开发习惯,存在很大难度。
但不可否认的是,AI辅助编程具备显著优势,生成的代码格式统一、逻辑规范、可读性强,能够规避个人手写代码中不规范、随意性强的问题,有效提升代码的标准化程度与工程质量。
总体而言,若对代码风格、逻辑细节无特殊要求,AI可快速生成规范的程序代码(可用则未必),开发效率极高。但如果对代码编码风格、逻辑细节等有较高定制要求,就需要长期磨合适配,并非短期可以熟练掌握。
0 前言
在嵌入式设备开发中,EEPROM是最常用的用以存储参数、日志的器件。方便起见,开发人员多采用裸地址读写数据。由于裸地址读写方式规范性较弱,极易存在结构混乱、边界无管控、参数无备份校验等诸多问题。因此借用规范的文件管理机制对EEPROM的操作实施管理存在一定的必要性。
在嵌入式设备开发中,EEPROM主要用于存储两类核心数据:参数与日志。
由于参数对可靠性和稳定性要求较高,因此参数存储必须具备校验与备份策略,通常要为参数增加校验和,用于判断参数是否有效。当主参数区校验和异常时,系统应能自动加载备用参数区。
日志数据则以记录时序性数据为核心特征,主要用于记录设备运行状态、故障波形等信息。由于日志数据不允许修改历史内容,因此在存储方式上普遍采用追加写入模式,保证数据按产生顺序不断累积;同时,日志必须携带时间戳或序列号,用于标识记录的先后顺序,支持按时间点、按时间范围进行高效查询,满足设备历史追溯、故障分析、数据回放等业务需求。
1 方案特点以及存储分配说明
结合嵌入式系统资源有限的特点,本方案采用空间连续预分配策略,不考虑碎片机制。该方案逻辑简单、资源占用少,适应资源受限的嵌入式设备。
同时,文件信息表采用顺序存储结构,能保证文件的检索效率。系统还增设数据校验机制,可及时检测数据损坏,进一步提升存储方案的整体可靠性。
EEPROM的地址空间分配如下:
|---|---|----------------------------|
| 0x0000-0x00FF: || 文件系统头 (ST_EFILE_HEADER) |
| 0x0100-0x03FF: || 文件信息表 (最多8个 ST_EFILE_INFO) |
| | 文件0信息 ||
| | ...... ||
| | 文件7信息 ||
| 0x0400-...: || 数据区 |
| | 文件0 ||
| | ...... ||
| | 文件7 ||
| | | |
如上所示,EEPROM的地址空间分为三大部分:其一文件系统头作为整个文件系统的基础信息区,用于判断文件系统是否有效;其二文件信息表作为文件系统的目录与索引区,记录所有已创建文件的基础信息,用户通过检索该表快速定位文件位置;其三数据区作为文件系统的实际内容存储区,用于存放文件的真实数据。
2 文件信息表设计说明
文件信息表是EEPROM文件化管理系统的核心索引载体,承担着存储分区统一管理、文件属性记录、数据合格校验等核心作用。所有EEPROM存储文件均通过该表格统一注册,以替代裸地址读写模式,能有效规范EEPROM的操作管理。
结合参数、日志两类数据类型,文件信息表至少应包括基础属性、参数文件专属属性、日志文件专属属性三大类内容:
2.1 通用基础字段
- 文件类型:区分文件属性,标注为参数文件或日志文件
- 起始地址:记录文件在EEPROM物理存储中的起始偏移地址。
- 文件大小:记录所提供该文件的最大存储空间,用于地址越界检测、防止数据交叉覆盖。
2.2 参数专属字段
- 参数校验码:对整段参数进行运算生成校验和,用于确认该参数区是否有效。
- 主、备参数区起始地址以及单个参数区的存储空间尺寸;
2.3 日志专属字段
- 单条日志尺寸:定义单条日志数据的存储空间大小。
- 日志条目数量:定义本日志文件最多能存储的条目数
- 当前写入索引:记录最新日志的写入位置,实现断电续写、增量追加写入等操作;
- 有效条目数量:记录当前日志文件中已正常写入、可正常查询读取的日志条目数。
3 操作接口设计说明
借鉴文件系统的操作接口,EEPROM文件操作接口至少应该包括:
- 通用基础接口:文件打开功能,针对不同存储类型差异化设计业务接口。
- 参数类文件接口:提供文件读取、数据写入接口,满足配置参数的调取与更新。
- 日志类文件接口:支持数据追加写入,提供时间区间检索、时间起点定长序列检索两种方式。
对于日志类文件的检索机制,由于嵌入式系统内存受限,无法一次性返回大批量的数据。系统会采用游标机制管理检索进度。所谓游标机制即是将查询操作与数据读取动作相互拆分,先通过条件检索锁定目标数据范围,生成查询游标。再依托游标逐条分布读取日志条目。该方式无需一次性加载全部查询结果,可规避系统对大容量内存的要求。
4 文件系统初始化
cpp
typedef struct _ ST_EFILE _HEADER {
unsigned long ulMagic;
unsigned long ulVersion;
unsigned long ulSum;
} ST_EFILE _HEADER;
ST_EFILE _HEADER m_stEFileheader;
void eFile_Init(STDRVB_E2P_MAP stE2PMap)
{
DrvB_E2P_Map(stE2PMap);
if (eFile_ReadHeader() != true) {
eFile_Format();
return;
}
if (m_stEFileheader.ulMagic != EEP_FILE_MAGIC) {
eFile_Format();
return;
}
if (eFile_GetEFileHeaderChkSum(&m_stEFileheader) != m_stEFileheader.ulSum) {
eFile_Format();
return;
}
}
文件系统初始化是启动运行的首要步骤,主要包含两项核心任务:
其一是完成物理层的绑定,指定本文件系统所依附的EEPROM操作接口;
其二是执行文件系统合法性检测,通过读取EEPROM预定区域的检验魔数、校验文件系统基础信息表(ST_EFILE _HEADER)有效性等,判断芯片内是否已经存在合法有效的文件系统。如未能检测到有效文件系统,则需将其格式化。
所谓格式化,将其内容全部清成FFH后,将文件系统基础信息表初始化后写入EEPROM。
5 文件打开
文件打开操作的核心是检索文件信息表,匹配目标文件名。若查询到对应文件且配置相符则直接返回成功,反之返回失败。
5.1 文件信息表
文件信息表紧接于文件系统基础信息表之后,最多可存8个文件,文件信息表格式如下:
cpp
typedef struct {
unsigned char ucName[EEP_FILE_MAX_NAME_LEN]; /* 文件名 */
unsigned long ulStartAddr; /* 文件起始地址 */
unsigned short usId; /* 在文件信息表中的序号 */
unsigned long ulSize; /* 文件总大小(字节) */
EN_EFILE_TYPE enType; /* 文件类型:参数/日志 */
union {
struct {
unsigned long ulBackupAddr; /* 备份区起始地址(参数文件) */
unsigned char ucParamChecksum; /* 参数校验和 */
} stPara;
struct {
unsigned long ulItemSize; /* 单条日志大小 */
unsigned long ulItemCount; /* 日志条目总数 */
unsigned long ulNxtWrItem; /* 下一写入条目 */
unsigned long ulAvailItem; /* 有效条目数量 */
} stLog;
} u;
unsigned char ucChecksum; /* 文件信息校验和 */
}ST_EFILE_INFO;
5.2 文件打开流程
文件句柄是应用程序与存储文件之间的操作索引,用于关联、定位、管理指定文件的全部信息。它的作用是让上层操作无需关心底层物理地址,只需通过句柄即可实现安全读写,系统也可通过句柄快速定位,从而实现地址管理、越界保护等。
在本设计中,直接将文件信息作为文件句柄使用。文件打开流程,即是创建文件句柄的过程:
- 当根据文件名在文件信息表内查找有没有对应的文件,有则获取stInfo,并将ulCurrentAddr、ulCurrentItem清0,返回RET_SUCCESS;
- 无则返回RET_ERROR;
cpp
_Bool eFile_Open(const char *pucName , ST_EFILE_INFO *pstInfo)
{
unsigned long ulAddr;
if (eFile_FindFile(pucName, &ulAddr) != RET_SUCCESS)
{ //搜索文件信息表
return RET_ERROR;
}
if (eFile_ReadFileInfo(ulAddr, pstInfo) != RET_SUCCESS)
{ //读取文件信息
return RET_ERROR;
}
//文件大小合法性判断
if (pstInfo->ulStartAddr + pstInfo->ulSize > DrvB_E2P_GetSize())
return RET_ERROR;
return RET_SUCCESS;
}