1. STM32F429 SPI Flash下载算法制作背景
在嵌入式开发中,外部SPI Flash经常被用作代码存储或数据存储的扩展。对于STM32F429这类内置Flash容量有限的MCU,将部分代码或数据存放在外部SPI Flash中是常见做法。但MDK(Keil)默认并不支持直接下载程序到外部SPI Flash,这就需要我们制作自定义的下载算法。
提示:下载算法本质上是一段能在目标板上运行的特殊代码,负责与调试器配合完成Flash的擦除、编程和校验操作。
2. 开发环境准备
2.1 硬件需求
- STM32F429开发板(如STM32F429I-DISCO)
- SPI Flash芯片(如W25Q64JV、W25Q128JV等)
- J-Link或ST-Link调试器
2.2 软件需求
- Keil MDK 5.30或更高版本
- STM32CubeMX(可选,用于引脚配置)
- 对应型号的STM32F4xx_DFP设备支持包
3. 下载算法工程创建
3.1 新建MDK工程
- 打开Keil MDK,选择Project -> New μVision Project
- 选择STM32F429xx系列芯片
- 在Manage Run-Time Environment中勾选CMSIS->CORE和Device->Startup
3.2 工程配置调整
在Options for Target中设置:
- Target选项卡:设置正确的ROM和RAM地址范围
- Output选项卡:勾选Create HEX File
- Debug选项卡:选择对应的调试器(J-Link或ST-Link)
4. SPI Flash驱动实现
4.1 硬件接口配置
// SPI引脚配置示例(使用SPI1) void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; SPI_HandleTypeDef hspi1 = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_SPI1_CLK_ENABLE(); // SCK: PA5, MISO: PA6, MOSI: PA7 GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF5_SPI1; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // CS: PA4 GPIO_InitStruct.Pin = GPIO_PIN_4; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; HAL_SPI_Init(&hspi1); }4.2 Flash基本操作函数
需要实现以下关键函数:
- SPI_Flash_Init:初始化SPI接口
- SPI_Flash_Read:读取Flash数据
- SPI_Flash_Write:写入Flash数据
- SPI_Flash_Erase_Sector:擦除扇区
- SPI_Flash_Erase_Chip:全片擦除
5. 下载算法核心实现
5.1 算法文件结构
MDK下载算法需要实现以下关键函数(在FlashDev.c和FlashPrg.c中):
// FlashDev.c中定义设备属性 struct FlashDevice const FlashDevice = { FLASH_DRV_VERS, // Driver Version "STM32F4_SPI_Flash", // Device Name EXTSPI, // Device Type 0x90000000, // Device Start Address 8 * 1024 * 1024, // Device Size (8MB) 4096, // Programming Page Size 0, // Reserved 0xFF, // Initial Content of Erased Memory 100, // Program Page Timeout (ms) 3000, // Erase Sector Timeout (ms) // Sector Map { 0x001000, 0x000000, // 4KB sectors SECTOR_END } }; // FlashPrg.c中实现编程函数 int Init(unsigned long adr, unsigned long clk, unsigned long fnc) { // 初始化SPI和Flash return 0; } int UnInit(unsigned long fnc) { // 清理资源 return 0; } int EraseSector(unsigned long adr) { // 擦除指定地址所在的扇区 return 0; } int ProgramPage(unsigned long adr, unsigned long sz, unsigned char *buf) { // 编程指定地址的页 return 0; }5.2 关键实现细节
- 地址映射:SPI Flash通常映射到0x90000000开始的地址空间
- 缓冲管理:由于SPI Flash需要按页编程,需要合理管理缓冲区
- 错误处理:完善的超时和状态检查机制
6. 算法编译与测试
6.1 编译生成FLM文件
- 在Options for Target -> Output中设置输出文件名为"STM32F4_SPI_Flash"
- 编译工程,生成STM32F4_SPI_Flash.FLM文件
6.2 在MDK中使用算法
- 将FLM文件复制到Keil安装目录的ARM/Flash目录下
- 在工程Options -> Debug -> Settings -> Flash Download中添加算法
- 设置正确的Flash地址范围(如0x90000000-0x907FFFFF)
7. 常见问题与解决方案
7.1 下载失败问题排查
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 擦除超时 | SPI时钟速度过高 | 降低SPI时钟分频系数 |
| 编程失败 | 未正确解锁Flash | 在Init函数中添加解锁命令 |
| 校验错误 | 电压不稳定 | 检查电源质量,增加去耦电容 |
7.2 J-Flash使用注意事项
- 修改JLinkDevices.xml文件时注意路径斜杠方向
- J-Flash V9版本配置文件格式有变化,需要参考新版文档
- 确保WorkRAMAddr和WorkRAMSize设置正确
8. 性能优化建议
- DMA传输:使用SPI DMA提高编程速度
// 启用SPI DMA示例 HAL_SPI_Transmit_DMA(&hspi1, pData, Size);- 双缓冲机制:实现编程和传输并行
- 多扇区擦除:支持同时擦除多个扇区
9. 扩展应用
9.1 支持其他SPI Flash型号
- 在FlashDev.c中修改设备参数
- 适配新的指令集(如W25Q系列与MX25系列指令差异)
9.2 加密下载
- 在ProgramPage函数中添加加密算法
- 使用AES等加密算法保护固件
注意:在实际项目中,建议先使用小数据量测试算法可靠性,确认无误后再进行完整编程。我曾在项目中遇到过因SPI时钟相位设置不当导致的数据错误,调试花费了大量时间。