在PC端软件开发中,有时需要模拟嵌入式设备中Flash存储器的读写行为。为了实现这一目标,选择一个合适的文件格式来承载数据至关重要。常见的候选格式有JSON、XML、BIN、HEX和S19等。
- JSON格式:优点是可直接用文本编辑器查看,结构清晰。缺点是在嵌入式或C语言环境中,成熟的C语言解析库相对较少。
- XML格式:可读性较好,但格式稍显冗长。
- BIN格式:作为纯粹的二进制文件,它缺乏地址信息,不适合直接模拟需要寻址操作的Flash。
- HEX与S19格式:这两种是嵌入式领域常见的烧录文件格式,都能携带完整的地址和数据信息。相比之下,S19格式的语法更为简洁明了。
因此,选择S19格式文件来模拟Flash的擦除、编程和读取操作,是一种贴合实际需求且易于实现的方案。

(图片来源网络,仅供示意)
当然,模拟Flash并非仅有文件流这一种方式。另一种更高效的方案是使用共享内存技术来充当Flash的存储介质,但这通常需要编写复杂的底层驱动,对开发者的系统编程和驱动开发功底要求较高。本文介绍的基于文件流的方法,在便捷性和通用性上更具优势。
物理Flash的核心特性
要准确模拟,首先需要理解真实Flash存储器的物理行为:
- 本质特性:Flash存储器在物理上只能将比特位从“1”写为“0”,而不能从“0”写回“1”。因此,“写入”操作实际上分为两步:先擦除(将整个扇区或块恢复为全“1”状态),再编程(将需要的数据位写“0”)。
- 擦除操作(块擦除):擦除必须以块(或扇区)为单位进行,并且要求起始地址必须与块边界对齐。
- 写入操作:过程相对复杂:
- 根据目标地址定位数据所在的块,并判断数据长度是否跨越多个块。
- 读出目标块内的所有原有数据。
- 将待写入的新数据与原有数据合并更新。
- 擦除整个目标块。
- 将更新后的完整块数据重新编程写入。
- 若数据跨块,则需要分别处理首块和尾块的边界情况。
- 读取操作:最为简单,可以直接根据地址和长度读出对应数据。
虚拟Flash的设计与规则
在软件模拟层面,我们操作的是S19文件(本质是文件流)。模拟的重点在于实现逻辑功能,可以适当简化物理限制,约定以下规则:
- 默认值:如果S19文件中不存在某个地址的数据记录,则默认该地址的值为
0xFF(即擦除状态)。
- 内存映射:将S19文件的内容加载到动态分配的内存块中。后续所有的擦除、写入、读取操作都直接针对这块内存数据进行。
- 擦除:将指定地址和长度范围内的数据设置为
0xFF。
- 写入:更新指定地址和长度的数据,并立即将变更同步保存回S19文件。
- 读取:从指定地址读取任意长度的数据。
核心设计思路
- 将S19文件解析并加载到动态分配的内存中(实现按需映射)。
- 使用“基址+偏移量”的方式管理内存数据,模拟Flash的线性地址空间。
- 所有操作完成后,将内存中的数据重新生成标准格式的S19文件进行保存。
代码实现:基于AI辅助开发
借助大语言模型的代码生成能力,可以快速构建功能原型。以下是通过提示词“帮我实现块擦除、写数据、读数据的接口”生成的C语言代码,并经过人工优化和整理。
头文件定义 (s19_ops.h):
#ifndef S19_OPS_H
#define S19_OPS_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdlib.h>
#include <stdint.h>
// 内存映像参数(可根据需要调整)
#define MEM_BASE_ADDR 0x08000000
#define FLS_TOTAL_SIZE (16 * 1024) // 16KB
#define FLS_SECTOR_SIZE 256U // 扇区大小
#define FLS_PAGE_SIZE 8U // 页大小(写入粒度)
// S19 上下文结构体
typedef struct s19_ctx {
uint8_t* memory; // 内存映像
uint8_t* valid; // 数据有效性标记
uint32_t base_addr;
size_t size;
int loaded;
uint32_t entry_addr; // 记录入口地址
} s19_ctx_t;
// 接口函数声明
int s19_init(s19_ctx_t* ctx, uint32_t base_addr, size_t size);
void s19_deinit(s19_ctx_t* ctx);
int s19_load(s19_ctx_t* ctx, const char* filename);
int s19_save(s19_ctx_t* ctx, const char* filename, uint32_t entry_addr);
int s19_erase_block(s19_ctx_t* ctx, uint32_t addr, size_t len);
int s19_write_data(s19_ctx_t* ctx, uint32_t addr, const uint8_t* data, size_t len);
int s19_read_data(s19_ctx_t* ctx, uint32_t addr, size_t len, uint8_t* out_buf);
#ifdef __cplusplus
}
#endif
#endif // S19_OPS_H
源文件实现 (s19_ops.c):
#include “s19_ops.h”
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ------------------ 工具函数 ------------------
static int _addr_to_offset(const s19_ctx_t* ctx, uint32_t addr) {
if (addr < ctx->base_addr || addr >= ctx->base_addr + ctx->size) {
return -1;
}
return (int)(addr - ctx->base_addr);
}
static int _hex_char_to_int(char c) {
if (c >= ’0’ && c <= ’9’) return c - ’0’;
if (c >= ’A’ && c <= ’F’) return c - ’A’ + 10;
if (c >= ’a’ && c <= ’f’) return c - ’a’ + 10;
return -1;
}
static int _parse_hex_byte(const char* str, size_t* pos, uint8_t* out) {
if (str[*pos] == ’\0’ || str[*pos + 1] == ’\0’) return -1;
int hi = _hex_char_to_int(str[*pos]);
int lo = _hex_char_to_int(str[*pos + 1]);
if (hi < 0 || lo < 0) return -1;
*out = (uint8_t)((hi << 4) | lo);
*pos += 2;
return 0;
}
// 解析一行S-record
static int _parse_srec_line(const char* line, uint32_t* out_addr, uint8_t* data, size_t* data_len, uint32_t* out_entry_addr) {
// ... (详细解析逻辑,包括类型判断、地址解析、数据提取及校验和验证)
return 0; // 成功返回0,失败返回-1
}
// 写S3记录(数据记录)
static int _write_s3_record(FILE* fp, uint32_t addr, const uint8_t* data, size_t len) {
uint8_t byte_count = (uint8_t)(4 + len + 1);
uint32_t sum = byte_count;
sum += (addr >> 24) & 0xFF;
sum += (addr >> 16) & 0xFF;
sum += (addr >> 8) & 0xFF;
sum += addr & 0xFF;
for (size_t i = 0; i < len; ++i) sum += data[i];
uint8_t checksum = (~sum) & 0xFF;
fprintf(fp, “S3%02X%08X”, byte_count, addr);
for (size_t i = 0; i < len; ++i) fprintf(fp, “%02X”, data[i]);
fprintf(fp, “%02X\n”, checksum);
return 0;
}
// 写S7记录(结束记录)
static int _write_s7_record(FILE* fp, uint32_t entry_addr) {
uint8_t byte_count = 5;
uint32_t sum = byte_count;
sum += (entry_addr >> 24) & 0xFF;
sum += (entry_addr >> 16) & 0xFF;
sum += (entry_addr >> 8) & 0xFF;
sum += entry_addr & 0xFF;
uint8_t checksum = (~sum) & 0xFF;
fprintf(fp, “S7%02X%08X%02X\n”, byte_count, entry_addr, checksum);
return 0;
}
// ------------------ 公共接口实现 ------------------
int s19_init(s19_ctx_t* ctx, uint32_t base_addr, size_t size) {
if (!ctx || size == 0) return -1;
ctx->memory = calloc(size, 1);
ctx->valid = calloc(size, 1);
if (!ctx->memory || !ctx->valid) {
free(ctx->memory);
free(ctx->valid);
return -1;
}
memset(ctx->memory, 0xFF, size); // 初始化为Flash擦除状态
ctx->base_addr = base_addr;
ctx->size = size;
ctx->loaded = 0;
ctx->entry_addr = base_addr;
return 0;
}
void s19_deinit(s19_ctx_t* ctx) {
if (ctx) {
free(ctx->memory);
free(ctx->valid);
memset(ctx, 0, sizeof(*ctx));
}
}
int s19_load(s19_ctx_t* ctx, const char* filename) {
// ... (打开文件,逐行解析S19记录,将数据加载到memory,并标记valid)
return 0;
}
int s19_save(s19_ctx_t* ctx, const char* filename, uint32_t entry_addr) {
// ... (将memory中标记为valid的数据,按地址连续块写入S3记录,最后写入S7结束记录)
return 0;
}
int s19_erase_block(s19_ctx_t* ctx, uint32_t addr, size_t len) {
if (!ctx || !ctx->loaded || len == 0) return -1;
int offset = _addr_to_offset(ctx, addr);
if (offset < 0 || offset + (int)len > (int)ctx->size) return -1;
memset(ctx->valid + offset, 0, len); // 清除有效标记
memset(ctx->memory + offset, 0xFF, len); // 数据置为FF
return 0;
}
int s19_write_data(s19_ctx_t* ctx, uint32_t addr, const uint8_t* data, size_t len) {
if (!ctx || !ctx->loaded || !data || len == 0) return -1;
int offset = _addr_to_offset(ctx, addr);
if (offset < 0 || offset + (int)len > (int)ctx->size) return -1;
memcpy(ctx->memory + offset, data, len);
memset(ctx->valid + offset, 1, len); // 设置有效标记
return 0;
}
int s19_read_data(s19_ctx_t* ctx, uint32_t addr, size_t len, uint8_t* out_buf) {
if (!ctx || !ctx->loaded || !out_buf || len == 0) return -1;
int offset = _addr_to_offset(ctx, addr);
if (offset < 0 || offset + (int)len > (int)ctx->size) return -1;
for (size_t i = 0; i < len; ++i) {
out_buf[i] = ctx->valid[offset + i] ? ctx->memory[offset + i] : 0xFF;
}
return 0;
}
功能测试与验证
以下测试代码演示了如何使用上述接口进行完整的虚拟Flash操作。
#include “s19_ops.h”
#include <stdio.h>
s19_ctx_t flash_ctx;
int main() {
// 1. 初始化并加载(或创建)S19文件
s19_init(&flash_ctx, MEM_BASE_ADDR, FLS_TOTAL_SIZE);
s19_load(&flash_ctx, “DFlash.s19”);
// 2. 测试读取初始值(应为0xFF)
uint8_t buf[4];
if (s19_read_data(&flash_ctx, 0x08000000, 4, buf) == 0) {
printf(“初始读: %02X %02X %02X %02X\n”, buf[0], buf[1], buf[2], buf[3]);
}
// 3. 测试擦除操作
s19_erase_block(&flash_ctx, 0x08001000, 16);
// 4. 测试写入操作
uint8_t new_data[] = {0xAA, 0xBB, 0xCC, 0xDD};
s19_write_data(&flash_ctx, 0x08002000, new_data, 4);
// 5. 保存更改到文件
s19_save(&flash_ctx, “DFlash.s19”, 0x08000000);
// 6. 重新加载并验证写入的数据
s19_load(&flash_ctx, “DFlash.s19”);
if (s19_read_data(&flash_ctx, 0x08002000, 4, buf) == 0) {
printf(“写入后读: %02X %02X %02X %02X\n”, buf[0], buf[1], buf[2], buf[3]);
}
// 7. 测试部分擦除和非常规地址写入
s19_erase_block(&flash_ctx, 0x08002000, 2);
s19_write_data(&flash_ctx, 0x08003001, new_data, 4);
s19_save(&flash_ctx,“DFlash.s19”, 0x08000000);
// 8. 释放资源
s19_deinit(&flash_ctx);
return 0;
}
运行上述测试程序,控制台预期输出如下,表明读写操作符合逻辑:

同时,在当前目录下会生成(或更新)一个名为DFlash.s19的文件,其内容包含了我们通过程序操作的所有有效数据记录,格式标准,可供其他烧录工具直接使用。通过这个基础的C语言项目,我们成功构建了一个轻量级的Flash行为模拟器。
