1. OpenOCD Flash驱动开发概述
在嵌入式系统开发中,Flash存储器编程是至关重要的一环。OpenOCD作为开源的片上调试工具,提供了强大的Flash编程支持。本章将深入探讨如何为OpenOCD添加新的Flash驱动,这是掌握OpenOCD高级开发的关键技能。
Flash驱动是OpenOCD与目标芯片Flash存储器交互的桥梁。它需要处理各种Flash操作,包括擦除、编程、验证和保护等。不同厂商的Flash控制器架构差异很大,因此需要针对特定芯片实现定制化的驱动。
2. Flash驱动开发基础
2.1 Flash驱动架构
OpenOCD的Flash驱动采用模块化设计,主要包含以下核心组件:
- 驱动注册机制:每个Flash驱动都需要通过
struct flash_driver结构体向系统注册 - 命令处理:实现标准Flash操作命令和芯片特有命令
- 算法封装:将芯片特定的编程算法封装成通用接口
典型的Flash驱动文件结构如下:
c复制// 驱动声明
FLASH_BANK_COMMAND_HANDLER(driver_name_flash_bank_command);
// 驱动操作结构体
static struct flash_driver driver_name_flash = {
.name = "driver_name",
.commands = driver_name_command_handlers,
.flash_bank_command = driver_name_flash_bank_command,
.erase = driver_name_erase,
.protect = driver_name_protect,
.write = driver_name_write,
.read = default_flash_read,
.probe = driver_name_probe,
.auto_probe = driver_name_auto_probe,
.erase_check = default_flash_blank_check,
.protect_check = driver_name_protect_check,
.info = driver_name_info,
};
2.2 开发环境准备
在开始开发前,需要搭建完整的OpenOCD开发环境:
- 获取OpenOCD源码:
bash复制git clone git://git.code.sf.net/p/openocd/code openocd
cd openocd
./bootstrap
./configure
make
- 准备调试硬件(如J-Link、ST-Link等)
- 目标开发板(包含待支持Flash芯片)
提示:建议在开发过程中启用OpenOCD的调试日志,可通过
-d参数开启,这有助于排查驱动问题。
3. Flash驱动实现详解
3.1 驱动注册与初始化
每个Flash驱动都需要实现flash_bank_command处理函数,这是驱动的入口点。以AT91SAM3系列为例:
c复制FLASH_BANK_COMMAND_HANDLER(at91sam3_flash_bank_command)
{
struct at91sam3_flash_bank *at91sam3_info;
if (CMD_ARGC < 6)
return ERROR_COMMAND_SYNTAX_ERROR;
at91sam3_info = malloc(sizeof(struct at91sam3_flash_bank));
bank->driver_priv = at91sam3_info;
/* 初始化Flash参数 */
at91sam3_info->probed = 0;
at91sam3_info->chip_model = NULL;
return ERROR_OK;
}
3.2 Flash探测与识别
probe函数负责检测和识别Flash芯片,这是驱动的核心功能之一:
c复制static int at91sam3_probe(struct flash_bank *bank)
{
struct at91sam3_flash_bank *at91sam3_info = bank->driver_priv;
uint32_t chip_id;
int retval;
/* 读取芯片ID寄存器 */
retval = target_read_u32(target, AT91SAM3_CHIPID_CIDR, &chip_id);
if (retval != ERROR_OK)
return retval;
/* 解析芯片型号 */
switch (chip_id & AT91SAM3_CHIPID_MASK) {
case AT91SAM3U4E_CIDR:
at91sam3_info->chip_model = "AT91SAM3U4E";
bank->size = 256 * 1024; // 256KB
bank->num_sectors = 32; // 16 sectors per bank × 2 banks
break;
// 其他型号处理...
default:
LOG_ERROR("Unsupported AT91SAM3 chip ID: 0x%08" PRIx32, chip_id);
return ERROR_FAIL;
}
/* 设置扇区信息 */
bank->sectors = malloc(sizeof(struct flash_sector) * bank->num_sectors);
for (int i = 0; i < bank->num_sectors; i++) {
bank->sectors[i].size = 8 * 1024; // 8KB/sector
bank->sectors[i].offset = i * 8 * 1024;
bank->sectors[i].is_erased = -1;
bank->sectors[i].is_protected = 0;
}
at91sam3_info->probed = 1;
return ERROR_OK;
}
3.3 擦除操作实现
Flash擦除通常有扇区擦除和整片擦除两种模式。以下是AT91SAM3的擦除实现:
c复制static int at91sam3_erase(struct flash_bank *bank, int first, int last)
{
struct target *target = bank->target;
uint32_t erase_cmd;
int retval;
/* 检查参数 */
if (bank->target->state != TARGET_HALTED) {
LOG_ERROR("Target not halted");
return ERROR_TARGET_NOT_HALTED;
}
/* 执行擦除 */
for (int i = first; i <= last; i++) {
/* 设置擦除命令 */
erase_cmd = AT91SAM3_FCR_FCMD_ERASE | AT91SAM3_FCR_FKEY_PASSWD;
erase_cmd |= (i << 8); // 扇区号
retval = target_write_u32(target, AT91SAM3_FCR, erase_cmd);
if (retval != ERROR_OK)
return retval;
/* 等待擦除完成 */
retval = at91sam3_wait_status_busy(bank, 1000);
if (retval != ERROR_OK)
return retval;
}
return ERROR_OK;
}
3.4 编程操作实现
Flash编程需要特别注意对齐和时序要求。以下是典型的编程实现:
c复制static int at91sam3_write(struct flash_bank *bank, const uint8_t *buffer,
uint32_t offset, uint32_t count)
{
struct target *target = bank->target;
uint32_t write_cmd;
int retval;
/* 检查对齐 */
if (offset % 4 != 0 || count % 4 != 0) {
LOG_ERROR("Write operation not aligned");
return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
}
/* 逐字编程 */
for (uint32_t i = 0; i < count; i += 4) {
uint32_t data = buffer[i] | (buffer[i+1] << 8) |
(buffer[i+2] << 16) | (buffer[i+3] << 24);
/* 写入数据 */
retval = target_write_u32(target, bank->base + offset + i, data);
if (retval != ERROR_OK)
return retval;
/* 发送编程命令 */
write_cmd = AT91SAM3_FCR_FCMD_WP | AT91SAM3_FCR_FKEY_PASSWD;
retval = target_write_u32(target, AT91SAM3_FCR, write_cmd);
if (retval != ERROR_OK)
return retval;
/* 等待编程完成 */
retval = at91sam3_wait_status_busy(bank, 100);
if (retval != ERROR_OK)
return retval;
}
return ERROR_OK;
}
4. 高级功能实现
4.1 保护机制
许多Flash芯片提供硬件保护功能,防止意外修改。实现保护检查函数:
c复制static int at91sam3_protect_check(struct flash_bank *bank)
{
struct target *target = bank->target;
uint32_t protection;
int retval;
retval = target_read_u32(target, AT91SAM3_FSR, &protection);
if (retval != ERROR_OK)
return retval;
for (int i = 0; i < bank->num_sectors; i++) {
bank->sectors[i].is_protected = (protection & (1 << i)) ? 1 : 0;
}
return ERROR_OK;
}
4.2 芯片特有命令
许多Flash控制器提供特有的功能,可以通过自定义命令暴露给用户:
c复制static const struct command_registration at91sam3_command_handlers[] = {
{
.name = "at91sam3",
.mode = COMMAND_ANY,
.help = "AT91SAM3 flash command group",
.usage = "",
.chain = at91sam3_exec_command_handlers,
},
COMMAND_REGISTRATION_DONE
};
static const struct command_registration at91sam3_exec_command_handlers[] = {
{
.name = "gpnvm",
.handler = at91sam3_handle_gpnvm_command,
.mode = COMMAND_EXEC,
.help = "Modify GPNVM bits",
.usage = "set|clear|show bitnum",
},
{
.name = "info",
.handler = at91sam3_handle_info_command,
.mode = COMMAND_EXEC,
.help = "Show chip information",
.usage = "",
},
COMMAND_REGISTRATION_DONE
};
4.3 性能优化技巧
- 缓冲编程:对于支持页编程的Flash,可以实现缓冲写入大幅提高速度
- 并行操作:某些Flash支持同时进行擦除和编程操作
- 智能擦除:在执行擦除前检查扇区是否已经擦除,避免不必要操作
优化后的编程函数示例:
c复制static int at91sam3_write_buffer(struct flash_bank *bank, const uint8_t *buffer,
uint32_t offset, uint32_t count)
{
/* 检查页边界 */
if ((offset % page_size) + count > page_size) {
LOG_ERROR("Write crosses page boundary");
return ERROR_FAIL;
}
/* 一次性写入整个页 */
for (int i = 0; i < page_size; i += 4) {
uint32_t data = ...; // 组合数据
target_write_u32(target, page_buffer + i, data);
}
/* 发送页编程命令 */
uint32_t write_cmd = AT91SAM3_FCR_FCMD_WP | AT91SAM3_FCR_FKEY_PASSWD;
target_write_u32(target, AT91SAM3_FCR, write_cmd);
return at91sam3_wait_status_busy(bank, 100);
}
5. 调试与测试
5.1 常见问题排查
开发Flash驱动时常见问题及解决方法:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 擦除失败 | 扇区保护未解除 | 检查保护寄存器并临时禁用保护 |
| 编程验证失败 | 编程电压不足 | 检查VPP引脚连接和电压 |
| 操作超时 | 时钟配置错误 | 确认Flash控制器时钟频率 |
| 随机数据错误 | 电源不稳定 | 检查电源滤波电容和走线 |
5.2 测试策略
完善的测试应包括以下方面:
- 单元测试:针对每个驱动函数进行独立测试
- 边界测试:测试扇区边界、页边界等特殊情况
- 压力测试:长时间连续擦写测试可靠性
- 异常测试:测试电源波动等情况下的行为
测试脚本示例:
tcl复制# 擦除测试
flash erase_sector 0 0 last
flash verify_image blank.bin 0
# 编程测试
flash write_image test.bin 0 bin
flash verify_image test.bin 0
# 保护测试
flash protect 0 0 3 on
flash info 0 # 确认保护状态
6. 驱动集成与发布
6.1 集成到OpenOCD
完成开发的驱动需要集成到OpenOCD构建系统中:
- 将驱动源文件放入
src/flash/nor/目录 - 修改
src/flash/nor/Makefile.am添加新驱动 - 更新文档
doc/openocd.texi
6.2 编写文档
良好的文档应包括:
- 支持的芯片型号
- 配置示例
- 特有命令说明
- 已知限制
文档示例:
code复制@deffn {Flash Driver} at91sam3
@cindex AT91SAM3
This driver supports the AT91SAM3 microcontroller family from Atmel.
@example
flash bank $_FLASHNAME at91sam3 0x00080000 0 1 1 $_TARGETNAME
@end example
@subsection AT91SAM3-specific commands
@deffn {Command} at91sam3 gpnvm bitnum (set|clear)
Set or clear a GPNVM bit.
@end deffn
@end deffn
7. 实战经验分享
在实际开发中,我总结了以下宝贵经验:
-
时序是关键:严格按照数据手册的时序要求实现,特别是擦除和编程的超时时间。我曾遇到因超时设置过短导致随机编程失败的问题。
-
保护机制要谨慎:某些Flash一旦设置保护就无法再次编程,开发阶段建议先禁用所有保护。
-
充分利用芯片特性:例如,某些Flash支持"写时擦除"功能,可以简化编程流程。
-
测试要充分:除了正常工作条件,还要测试以下极端情况:
- 电源波动时的操作
- 异常中断后的恢复
- 非法地址访问
-
日志要详细:在驱动中添加详细的调试日志,包括寄存器状态和操作步骤,这在排查问题时非常有用。
-
参考现有驱动:OpenOCD已经支持数百种Flash芯片,找到架构相似的芯片驱动作为参考可以事半功倍。
-
社区支持:遇到难题时,OpenOCD的邮件列表和论坛是宝贵的资源。在提问前准备好详细的测试环境和现象描述。
