STM32F103与GD32F103通过SPI接口高效读写Nor Flash的实战解析

张开发
2026/4/12 12:29:57 15 分钟阅读

分享文章

STM32F103与GD32F103通过SPI接口高效读写Nor Flash的实战解析
1. Nor Flash基础与硬件连接实战第一次接触Nor Flash时我完全被它和Nand Flash的区别搞晕了。直到在项目中实际使用GD25Q128芯片后才发现Nor Flash最大的优势就是支持XIP就地执行特性这意味着我们可以直接从Flash中运行代码而不需要先加载到RAM。这特性在资源受限的嵌入式系统中简直是救命稻草。硬件连接是第一个容易踩坑的地方。记得我第一次画PCB时把MOSI和MISO接反了调试了半天才发现问题。以GD32F103和GD25Q128为例正确的连接方式应该是GD32F103的PB12SPI_NSS接Flash的CS片选PB13SPI_SCK接SCK时钟线PB14SPI_MISO接SO数据输出PB15SPI_MOSI接SI数据输入这里有个细节要注意SPI接口通常需要上拉电阻但GD25Q128内部已经集成了上拉所以硬件设计时可以省去外部电阻。不过如果通信距离较长超过10cm建议还是加上4.7KΩ的上拉更稳妥。2. SPI外设配置的魔鬼细节配置SPI接口时时钟极性CPOL和相位CPHA的设置是个关键。GD25Q128要求CPOL1、CPHA1也就是模式3。如果设错模式你会发现能读取ID但无法正常读写数据。void SPI_FLASH_Init(void) { SPI_InitTypeDef SPI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; // 使能时钟容易漏掉GPIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); // GPIO配置注意要设置50MHz速度 GPIO_InitStructure.GPIO_Pin GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); // CS引脚要单独配置为推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(GPIOB, GPIO_InitStructure); // SPI关键参数配置 SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_High; // 模式3 SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; // 模式3 SPI_InitStructure.SPI_NSS SPI_NSS_Soft; // 软件控制片选 SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_4; // 18MHz72MHz SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI2, SPI_InitStructure); SPI_Cmd(SPI2, ENABLE); }实测中发现SPI时钟分频系数对稳定性影响很大。当主频72MHz时SPI_BaudRatePrescaler_418MHz在面包板上还能工作但换成杜邦线就可能出错。建议产品中设置为SPI_BaudRatePrescaler_89MHz更可靠。3. 关键操作实现与性能优化读操作相对简单但要注意地址对齐。GD25Q128的快速读Fast Read命令0x0B比普通读快25%不过需要先发送一个dummy字节void SPI_FLASH_FastRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead) { SPI_FLASH_CS_LOW(); SPI_FLASH_SendByte(0x0B); // 快速读指令 SPI_FLASH_SendByte((ReadAddr 0xFF0000) 16); SPI_FLASH_SendByte((ReadAddr 0xFF00) 8); SPI_FLASH_SendByte(ReadAddr 0xFF); SPI_FLASH_SendByte(0xFF); // dummy字节 while(NumByteToRead--) { *pBuffer SPI_FLASH_SendByte(0xFF); } SPI_FLASH_CS_HIGH(); }写操作就复杂多了必须遵循擦除-写入流程。最坑的是GD25Q128的页编程只能写256字节超过会回卷到页首覆盖数据。我的优化方案是先检查写入范围是否跨页对非0xFF区域先执行扇区擦除分页写入数据void SPI_FLASH_SmartWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite) { u16 pageRemain 256 - (WriteAddr % 256); if(pageRemain NumByteToWrite) pageRemain NumByteToWrite; while(1) { SPI_FLASH_PageWrite(pBuffer, WriteAddr, pageRemain); if(NumByteToWrite pageRemain) break; pBuffer pageRemain; WriteAddr pageRemain; NumByteToWrite - pageRemain; pageRemain (NumByteToWrite 256) ? 256 : NumByteToWrite; } }擦除操作最耗时一个4KB扇区要40-200ms。建议在系统空闲时提前擦好备用区域或者使用双Bank芯片实现擦写并行。4. 稳定性提升的实战技巧电源噪声是导致Flash操作失败的主因之一。我在一个工业项目中遇到过数据偶尔出错的问题最后发现是电机启停时电源波动导致的。解决方法有三个在VCC引脚加10μF0.1μF去耦电容写操作前检查电压是否在2.7-3.6V范围内关键数据采用ECC校验软件容错也必不可少。建议实现以下保护机制u8 SPI_FLASH_CheckWrite(u32 addr, u8* data, u16 len) { u8 buf[256]; u16 i; SPI_FLASH_Read(buf, addr, len); for(i0; ilen; i) { if((buf[i] data[i]) ! data[i]) { // 检查写入是否成功 return 0; // 失败 } } return 1; // 成功 }温度影响容易被忽视。GD25Q128在-40℃时写操作可能需300ms而85℃时仅需60ms。低温环境下建议延长擦除/写入后的等待时间避免连续写入超过芯片规格书规定的最大页数必要时启用温度补偿算法在最近的一个车载项目中我们通过DMASPI双缓冲实现了12MB/s的读取速度关键是把SPI时钟提到36MHz并用内存中的预取数据减少等待时间。但要注意这种高速模式需要严格的阻抗匹配和等长走线。

更多文章