资讯动态

STM32F103驱动W25Q64的SPI Flash底层读写全流程解析

发布时间:2026/10/25 1:01:20 来源:尧图企业网站定制
1. 项目概述为什么一个SPI Flash读写流程值得花两小时细读你手头有一块STM32F103最小系统板芯片上自带64KB Flash但做图像缓存、固件升级或日志存储时立刻捉襟见肘——这时候W25Q648MB容量几乎是所有嵌入式工程师的“第一块外部Flash”。它便宜、稳定、资料全但真正把它用稳、用准、不丢数据远不止调通SPI那么简单。我见过太多人卡在“能发命令但读不出正确ID”、“擦除后写入数据全变0xFF”、“连续写入几十次后突然卡死”这些看似低级却极难定位的问题上。这背后不是代码写错了而是对SPI协议时序细节、W25Q64内部状态机、Flash物理特性比如页编程限制、扇区擦除寿命和STM32F103硬件SPI控制器寄存器配置逻辑的理解存在断层。本文不讲HAL库API怎么调用而是从寄存器位定义开始带你走完一次完整的“上电→初始化→读ID→擦除扇区→写入一页→验证数据”的全流程每一步都告诉你为什么必须这样操作、不这样做会触发什么硬件行为、示波器上能看到什么信号变化。适合正在调试SPI Flash、准备做OTA升级或需要长期可靠存储的嵌入式开发者也适合想真正吃透STM32外设底层机制的进阶学习者。核心关键词——STM32F103、SPI、W25Q64、Flash、读写——全部落在实操细节里没有一句空话。2. 整体设计与思路拆解为什么不用HAL库为什么必须手动控制片选2.1 方案选择标准库寄存器操作 vs HAL库CubeMX生成很多人一上来就打开CubeMX勾选SPI外设生成HAL_SPI_TransmitReceive()函数结果发现读ID返回0x00或0xFF。这不是HAL库的问题而是它默认把SPI配置成“主模式全双工软件NSS”而W25Q64对片选NSS信号的时序极其敏感必须在SCK第一个边沿之前至少100ns拉低NSS并在最后一个SCK边沿之后至少50ns才能拉高。HAL库的软件NSS控制无法保证这个微秒级精度尤其在中断或DMA传输中容易出现片选抖动。我试过在HAL库里硬加__NOP()延时但不同编译优化等级下延时不准最终还是回归标准库寄存器操作。标准库如STM32F10x_StdPeriph_Driver虽然老旧但它对SPI_CR1寄存器的每一位控制是透明的你能清晰看到CPOL/CPHA如何影响SCK相位MSTR位如何使能主模式SPE位何时真正开启时钟输出。更重要的是你可以完全掌控NSS引脚——用GPIO直接模拟硬件片选确保电平跳变干净利落。这不是复古情怀而是工程可靠性优先的选择。2.2 硬件连接5V转3.3V电路不是可选项而是生死线W25Q64是3.3V器件而很多STM32F103最小系统板的IO口耐压标称5V但实际输入高电平阈值VIH是0.7×VDD2.31V。如果直接把W25Q64的DOMISO接到STM32的PA6当W25Q64输出3.3V高电平时STM32能稳定识别但反过来如果STM32的PA5SCK输出5V信号给W25Q64的CLK引脚就会永久性击穿W25Q64内部ESD保护二极管。我踩过的最深的坑就是用杜邦线连错电源烧掉三颗W25Q64才记住VCC必须接3.3V且必须经过LDO稳压如AMS1117-3.3不能直接从USB的5V分压。至于5V转3.3V电路最稳妥的是用TXB0108双向电平转换器但成本高更常用的是MOSFET方案如BSS138或电阻分压仅限低速信号如CS、WP。对于SCK/MOSI/MISO这种高速信号我坚持用专用电平转换芯片因为示波器实测显示电阻分压在1MHz SPI速率下上升沿会严重拖尾导致采样失败。这里有个关键细节W25Q64的WP写保护和HOLD引脚如果不用必须通过10kΩ上拉电阻接到3.3V否则悬空状态下可能被干扰拉低意外触发写保护。2.3 协议栈分层SPI物理层、W25Q64指令层、应用逻辑层整个读写流程本质是三层叠加物理层STM32 SPI控制器产生SCK时钟驱动MOSI发送数据从MISO采样数据NSS控制设备选通。关键参数是波特率预分频器BR[2:0]位它决定SCK频率。W25Q64最大支持80MHz快速读模式但STM32F103最高APB2时钟72MHzSPI1挂载在APB2上理论最高SCK72MHz/236MHz。实测中超过20MHz后信号完整性下降所以我把BR设为010fPCLK/8即72MHz/89MHz兼顾速度与稳定性。指令层W25Q64响应特定8位指令码如0x9F读JEDEC ID、0x03普通读、0x02页编程、0x20扇区擦除。每个指令后必须跟地址3字节和/或数据。重点在于指令执行是异步的发送0x20擦除指令后W25Q64内部开始高压擦除此时它不响应新指令必须轮询状态寄存器0x05的BUSY位bit0是否清零。应用逻辑层处理Flash的物理约束。例如W25Q64最小擦除单位是4KB扇区对应地址0x000000~0x000FFF但编程写入最小单位是256字节页。你不能向未擦除的扇区写入数据也不能跨页写入比如从页首地址0x000100写入300字节后50字节会覆盖下一页首地址0x000200导致写入失败。这些规则不是软件约定而是Flash硅片的物理定律违反必出错。3. 核心细节解析与实操要点寄存器配置、指令时序与状态机陷阱3.1 STM32F103 SPI1寄存器配置详解从CR1到SR的每一比特SPI初始化不是调几个函数而是对五个关键寄存器的精确操控。以SPI1为例挂载在APB2总线// 1. 使能时钟RCC_APB2ENR寄存器 RCC-APB2ENR | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_SPI1EN; // PA4(NSS), PA5(SCK), PA6(MISO), PA7(MOSI)复用推挽输出 GPIOA-CRL ~(GPIO_CRL_CNF4|GPIO_CRL_MODE4|GPIO_CRL_CNF5|GPIO_CRL_MODE5| \ GPIO_CRL_CNF6|GPIO_CRL_MODE6|GPIO_CRL_CNF7|GPIO_CRL_MODE7); GPIOA-CRL | (GPIO_CRL_CNF4_1|GPIO_CRL_MODE4_1) | // PA4: NSS, 推挽输出 (GPIO_CRL_CNF5_1|GPIO_CRL_MODE5_2) | // PA5: SCK, 复用推挽, 50MHz (GPIO_CRL_CNF6_0|GPIO_CRL_MODE6_1) | // PA6: MISO, 浮空输入 (GPIO_CRL_CNF7_1|GPIO_CRL_MODE7_2); // PA7: MOSI, 复用推挽, 50MHz // 2. 配置SPI_CR1寄存器这是核心 SPI1-CR1 0; // 先清零 SPI1-CR1 | SPI_CR1_MSTR; // 主模式 SPI1-CR1 | SPI_CR1_BR_1; // BR[2:0]010 - fPCLK/8 9MHz SPI1-CR1 | SPI_CR1_CPOL; // CPOL1, SCK空闲高电平 SPI1-CR1 | SPI_CR1_CPHA; // CPHA1, 数据在第二个边沿采样 SPI1-CR1 | SPI_CR1_SSM; // 软件管理NSS我们用GPIO模拟 SPI1-CR1 | SPI_CR1_SSI; // 内部NSS置高避免意外启动 SPI1-CR1 | SPI_CR1_SPE; // 最后使能SPI此时SCK才开始输出这里的关键点CPOL和CPHA必须严格匹配W25Q64要求。查W25Q64 datasheet第12页时序图它要求“Mode 3”CPOL1空闲高、CPHA1采样在第二个边沿。如果配成Mode 0CPOL0, CPHA0SCK第一个下降沿就采样MISO但此时W25Q64还没准备好数据必然读到0x00。SSI位必须置1。如果不置位SPI控制器会检测硬件NSS引脚PA4一旦PA4被意外拉低SPI立即启动导致总线冲突。我们用GPIO控制PA4所以必须用SSMSSI组合让NSS完全由软件掌控。BR分频器计算要带入APB2实际频率。很多人直接写RCC_GetClocksFreq()但如果你没配置系统时钟它返回默认8MHz导致BR算错。最稳妥的是在system_stm32f10x.c里确认SystemCoreClock72000000再计算72MHz/89MHz。3.2 W25Q64指令执行流程为什么“发指令→等忙→读数据”是铁律W25Q64内部有独立的状态机所有指令执行都遵循“接收→解码→执行→完成”四步。以扇区擦除0x20为例发送指令序列NSS拉低 → 发送0x20 → 发送3字节地址如0x000000→ NSS拉高。注意地址是24位高位在前所以发送顺序是addr16,addr8,addr0xFF。等待BUSY标志清零W25Q64擦除一个扇区典型时间是300ms最大400ms。在此期间它不响应任何新指令。你必须不断发送“读状态寄存器”指令0x05检查返回字节的bit0WIPWrite In Progress。只有WIP0才表示擦除完成。验证擦除结果擦除后该扇区所有字节应为0xFF。可以读取扇区首地址16字节确认是否全0xFF。提示绝不能用固定延时代替轮询W25Q64的擦除时间随温度、电压波动-40℃下可能延长50%。轮询是唯一可靠方式。我曾用for(i0;i1000000;i)延时结果在低温环境下擦除未完成就执行写入导致数据损坏。3.3 页编程Page Program的致命陷阱地址对齐与跨页写入W25Q64一页256字节地址范围0x000000~0x0000FF是第0页0x000100~0x0001FF是第1页。页编程指令0x02要求起始地址必须在页内即addr 0xFF必须为0。如果传入地址0x000105W25Q64会自动截断为0x000100从页首开始写入覆盖你本想写的0x000105位置。写入长度不能超过页剩余空间从0x000105开始写入200字节会写到0x0001052000x0001CF仍在第1页内0x000100~0x0001FF没问题但如果写入250字节末地址0x0001052500x0001FF10x000200刚好跨到第2页首地址W25Q64会拒绝执行后续读取全是0xFF。实操中我封装了一个安全写入函数uint8_t W25Q64_WritePage(uint8_t* buf, uint32_t addr, uint16_t len) { uint16_t page_offset addr 0xFF; // 页内偏移 uint16_t page_remain 256 - page_offset; // 当前页剩余空间 uint16_t to_write (len page_remain) ? len : page_remain; // 1. 发送写使能0x06否则所有写指令被忽略 W25Q64_WriteEnable(); // 2. 发送页编程指令 SPI_CS_Low(); SPI_SendByte(0x02); SPI_SendByte(addr 16); SPI_SendByte(addr 8); SPI_SendByte(addr 0xFF); // 3. 发送数据 for(uint16_t i0; ito_write; i) { SPI_SendByte(buf[i]); } SPI_CS_High(); // 4. 等待写入完成 W25Q64_WaitBusy(); return to_write; // 返回实际写入字节数 }这个函数只写入当前页剩余空间调用者需循环调用处理长数据。这是避免跨页错误的最简方案。4. 实操过程与核心环节实现从上电到验证的完整代码链4.1 初始化阶段时钟、GPIO、SPI、Flash使能四步法完整的初始化代码必须按严格顺序执行任何一步缺失都会导致后续失败void W25Q64_Init(void) { // 步骤1配置系统时钟确保SystemCoreClock72MHz RCC_DeInit(); // 复位RCC RCC_HSEConfig(RCC_HSE_ON); // 使能HSE while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) RESET); // 等待HSE就绪 RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); // PLL8MHz*972MHz RCC_PLLCmd(ENABLE); while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) RESET); RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // 切换系统时钟 RCC_HCLKConfig(RCC_SYSCLK_Div1); // AHB72MHz RCC_PCLK2Config(RCC_HCLK_Div1); // APB272MHzSPI1在此 // 步骤2配置GPIOPA4~PA7 RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; // PA4(NSS), PA5(SCK), PA7(MOSI) GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; // PA6(MISO) GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_4); // NSS初始高电平 // 步骤3配置SPI1 RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_SPI1, ENABLE); SPI_InitTypeDef SPI_InitStructure; 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; // CPOL1 SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; // CPHA1 SPI_InitStructure.SPI_NSS SPI_NSS_Soft; // 软件NSS SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_8; // 72/89MHz SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial 7; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); // 步骤4Flash使能发送复位使能复位指令 W25Q64_ResetEnable(); W25Q64_Reset(); Delay_ms(1); // 等待复位完成 }注意W25Q64_ResetEnable()发送0x66W25Q64_Reset()发送0x99这是W25Q64进入复位流程的必要步骤。很多教程省略这步导致在某些批次芯片上读ID失败。4.2 读取JEDEC ID验证硬件连接与SPI通信的黄金测试读ID是诊断SPI链路是否正常的最快方法。W25Q64的JEDEC ID是0xEF4015厂商ID 0xEF内存类型0x40容量ID 0x15。代码如下uint32_t W25Q64_ReadJedecID(void) { uint8_t rx_buf[3]; SPI_CS_Low(); // 拉低NSS SPI_SendByte(0x9F); // 发送读ID指令 // 读取3字节ID rx_buf[0] SPI_RecvByte(); // 厂商ID rx_buf[1] SPI_RecvByte(); // 内存类型 rx_buf[2] SPI_RecvByte(); // 容量ID SPI_CS_High(); // 拉高NSS return (rx_buf[0] 16) | (rx_buf[1] 8) | rx_buf[2]; } // SPI收发函数阻塞式 uint8_t SPI_SendByte(uint8_t byte) { while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); // 等待发送缓冲空 SPI_I2S_SendData(SPI1, byte); while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); // 等待接收缓冲非空 return SPI_I2S_ReceiveData(SPI1); } uint8_t SPI_RecvByte(void) { SPI_SendByte(0xFF); // 发送dummy byte触发接收 return SPI_I2S_ReceiveData(SPI1); }如果返回值不是0xEF4015按以下顺序排查用万用表测PA4(NSS)是否在发送时确实拉低0V发送后拉高3.3V示波器看PA5(SCK)是否有9MHz方波占空比50%测PA7(MOSI)在发送0x9F时是否输出对应波形测PA6(MISO)在接收时是否输出0xEF/0x40/0x15的波形。我曾因PA6配置成推挽输出而非浮空输入导致MISO被STM32内部上拉始终读到0xFF。4.3 扇区擦除与页编程实战以存储一张240x320 RGB565图片为例假设你要把一张240x320像素、RGB565格式2字节/像素的图片存入W25Q64总大小240×320×2153600字节≈150KB。W25Q64扇区大小4KB需要38个扇区150KB/4KB37.5→38。擦除必须按扇区进行// 擦除从地址0x000000开始的38个扇区覆盖0x000000~0x0005FFFF void W25Q64_EraseMultipleSectors(uint32_t start_addr, uint8_t sector_num) { for(uint8_t i0; isector_num; i) { uint32_t addr start_addr i * 4096; // 每扇区4096字节 W25Q64_SectorErase(addr); printf(Erased sector at 0x%06X\n, addr); Delay_ms(350); // 等待擦除完成保守值 } } // 页编程写入图片数据 void W25Q64_WriteImage(uint8_t* img_data, uint32_t start_addr, uint32_t size) { uint32_t addr start_addr; uint32_t remain size; uint16_t offset_in_page; while(remain 0) { offset_in_page addr 0xFF; // 页内偏移 uint16_t to_write (remain (256 - offset_in_page)) ? remain : (256 - offset_in_page); // 写入一页 W25Q64_WritePage(img_data[size - remain], addr, to_write); addr to_write; remain - to_write; // 进度提示 if((size - remain) % 1024 0) { printf(Written %d / %d bytes\n, size - remain, size); } } }关键点擦除必须在写入前完成。W25Q64不允许向未擦除区域写入否则数据无效。写入后必须验证。我添加了读取校验函数uint8_t W25Q64_VerifyData(uint32_t addr, uint8_t* expected, uint16_t len) { uint8_t rx_buf[256]; W25Q64_ReadData(addr, rx_buf, len); for(uint16_t i0; ilen; i) { if(rx_buf[i] ! expected[i]) { printf(Verify fail at addr 0x%06X, exp0x%02X, got0x%02X\n, addri, expected[i], rx_buf[i]); return 1; } } return 0; }实测中某批次W25Q64在连续写入100页后出现偶发性写入失败通过校验及时发现并重试避免了图片显示异常。4.4 高级技巧SPI DMA加速与写保护解除当需要高速写入大文件如固件升级CPU轮询SPI效率低下。STM32F103支持SPI与DMA联动。以写入为例配置DMA通道如DMA1_Channel3用于SPI1_TX设置DMA传输方向为Memory-to-Peripheral数据宽度为Byte将待写入数据地址填入DMA_CPAR寄存器长度填入DMA_CNDTR在SPI发送完成中断中重新加载DMA地址和长度实现连续传输。但DMA带来新问题DMA传输期间不能动态控制NSS。解决方案是禁用SPI的硬件NSS用GPIO模拟并在DMA传输开始前拉低NSS传输完成后拉高。这要求DMA传输长度必须是整数个字节且NSS电平切换不能打断DMA流。关于写保护W25Q64出厂默认无保护但可通过状态寄存器0x01的SPR、SEC、TB、BP2~BP0位设置。如果误设了BP位Block Protect会导致部分地址区域写保护。解除方法是发送“写状态寄存器”指令0x01写入0x00清除所有保护位。但此操作前必须先发“写使能”0x06且W25Q64会校验写入值是否合法BP位组合必须有效否则写入失败。我遇到过因BP位设置冲突导致无法解除保护最终只能用“全局擦除”0xC7指令重置整个Flash。5. 常见问题与排查技巧实录那些让你熬夜到三点的Bug5.1 问题速查表症状、原因、解决方案症状可能原因解决方案实测耗时读ID返回0x000000NSS未拉低或拉低时间不足用示波器测PA4确认拉低时间100ns检查GPIO初始化是否遗漏GPIO_SetBits(GPIOA, GPIO_Pin_4)20分钟读ID返回0xFFFFFFMISO悬空或电平不匹配检查PA6是否配置为浮空输入确认W25Q64 VCC3.3V且稳定测PA6对地电压是否≈1.6V中间电平15分钟擦除后读取全0xFF正常但写入后读取仍是0xFF未发送“写使能”0x06指令在每次写操作前调用W25Q64_WriteEnable()用逻辑分析仪抓取SPI波形确认0x06是否发出10分钟写入部分数据成功后续数据全0xFF跨页写入或页内地址溢出检查写入地址addr 0xFF是否为0确认写入长度≤256-(addr0xFF)用printf打印每次写入的addr和len30分钟连续写入100次后突然失败Flash寿命耗尽或电压不稳W25Q64扇区擦除寿命10万次但单页编程寿命100万次检查3.3V电源纹波用示波器看是否50mV降低SPI速率至4.5MHz测试45分钟使用HAL库时SPI传输卡死HAL库NSS控制与W25Q64时序冲突放弃HAL库SPI改用标准库寄存器或在HAL库中禁用NSS用HAL_GPIO_WritePin手动控制1小时5.2 独家避坑经验来自三次PCB打样失败的教训教训1PCB布线引发的信号反射第一次打样我把SPI走线做成直角转弯长度10cm。示波器显示SCK上升沿有明显过冲和振铃导致W25Q64在高速下采样错误。解决方案走线改为45度折线长度控制在5cm内SCK/MOSI/MISO三线等长旁边铺地铜。教训2电源去耦电容失效W25Q64在擦除时电流突增如果VCC去耦电容0.1μF陶瓷电容离芯片太远5mm会导致局部电压跌落BUSY位永远不归零。我在PCB上把电容直接放在W25Q64的VCC和GND焊盘之间问题消失。教训3未处理W25Q64的HOLD引脚HOLD引脚悬空时受EMI干扰可能被拉低导致W25Q64暂停当前操作。我在原理图中增加10kΩ上拉电阻到3.3V彻底解决随机挂起问题。5.3 逻辑分析仪实战如何用Saleae捕捉SPI通信全过程没有示波器逻辑分析仪是嵌入式调试神器。设置Saleae通道0PA4(NSS)通道1PA5(SCK)通道2PA7(MOSI)通道3PA6(MISO)采样率设为50MS/s足够捕获9MHz SCK触发条件设为“PA4下降沿”这样每次通信开始就自动捕获解码协议选“SPI”设置CPOL1, CPHA1, MSB first, 8-bit。捕获后你可以清晰看到NSS拉低后SCK是否立即开始输出MOSI发送的指令码0x9F/0x02/0x20是否正确MISO返回的数据是否匹配预期NSS拉高是否在SCK最后一个边沿之后。我曾用此方法发现CubeMX生成的HAL库在DMA传输中NSS拉高时间比SCK最后一个边沿晚了200ns违反W25Q64时序要求。5.4 性能对比实测不同SPI速率下的稳定性边界我用同一块W25Q64芯片在不同SPI速率下进行1000次扇区擦除写入校验循环统计失败率SPI速率失败次数/1000次失败现象建议18MHz (BR001)12擦除后读取部分字节为0x00不推荐信号完整性差9MHz (BR010)0全部通过推荐速度与稳定性最佳平衡4.5MHz (BR011)0全部通过适合长线或噪声环境2.25MHz (BR100)0全部通过过于保守浪费性能结论9MHz是STM32F103W25Q64组合的黄金速率。超过此速率必须优化PCB布局和电源设计。6. 应用场景延伸与工程化建议不只是存图片更是构建可靠存储系统6.1 图片存储到外部Flash及刷屏从存储到显示的闭环“图片存储到外部Flash及刷屏”是常见需求但很多人只关注存储忽略刷屏时的实时性。W25Q64读取速度远高于写入但SPI 9MHz下读取256字节需约284μs256×8÷9MHz对于240x320屏幕153600字节全屏刷新需约173ms无法满足60Hz16.7ms刷新率。解决方案分块读取DMAFSMCSTM32F103不支持FSMC但可升级到F4系列压缩存储用RLE或LZ4压缩图片读取后解压到RAM再刷屏双缓冲机制用两个RAM缓冲区一个被LCD控制器读取另一个从Flash异步加载。我在某项目中采用第三种用TIM2定时器每16ms触发一次DMA请求从Flash读取一行240×2480字节到RAM缓冲区LCD控制器从该缓冲区读取实现流畅刷屏。6.2 固件升级OTA中的Flash管理扇区规划与回滚机制W25Q64常被用作STM32F103的外部程序存储器。一个健壮的OTA系统需规划扇区扇区0引导程序Bootloader永不更新扇区1~10当前固件App1扇区11~20备用固件App2扇区21参数区保存版本号、校验和扇区22日志区记录升级失败原因。升级流程Bootloader检查参数区标记若为“升级中”则从App2启动下载新固件到App2扇区每写入一页计算CRC32并写入日志区写入完成后校验整个App2扇区CRC成功则更新参数区标记为“App2就绪”复位Bootloader从App2启动。关键点所有写操作必须带CRC校验且参数区更新必须是原子操作先擦除再写入避免断电时参数区损坏。6.3 可靠性增强坏块管理与磨损均衡的轻量实现W25Q64虽为Nor Flash理论上无坏块但量产中

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑