ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

华大MCU驱动MT25QL256实战:从读ID到擦除写入的完整指南

华大MCU驱动MT25QL256实战:从读ID到擦除写入的完整指南 1. 为什么MT25QL256值得单独写一篇操作指南MT25QL256这颗NOR FLASH在嵌入式圈子里出镜率极高256Mbit32MB的容量、SPI/QSPI接口、4KB扇区擦除粒度让它几乎成了中高端MCU方案里存放固件、字库、日志、配置参数的默认选项。但真正上手写驱动的时候很多人会卡在几个很具体的地方读回来的ID跟手册对不上、擦除之后数据没变、写进去读出来是0xFF、跨页写数据错乱。这些问题单看datasheet都能找到答案可datasheet是给已经懂的人看的新手翻三遍也未必能把时序图和寄存器位对上。这篇内容就是把我自己在华大MCU以HC32F460系列为例其他华大型号的SPI外设逻辑基本一致上驱动MT25QL256的完整过程拆开讲。从最基础的读ID开始到4KB扇区擦除、页编程、数据校验每一步都给出可直接复用的代码和参数计算过程。适合两类人看一类是刚接触NOR FLASH、手里正好有华大板子和MT25QL256的嵌入式新手另一类是用过其他FLASH但换到这颗片子、被ID和擦除时序坑过的老手。我尽量不写成手册翻译而是按实际调试顺序来先确认芯片活着读ID再确认能改数据擦除写入最后确认数据可靠校验边界处理。这个顺序很重要反过来做很容易在第一步就怀疑人生。2. 动手之前MT25QL256的关键特性和选型逻辑2.1 这颗片子到底NOR在哪NOR FLASH和NAND FLASH最大的区别不在容量而在随机读取能力。NOR的存储单元是并行结构可以按字节随机寻址读一个字节和读一万个字节的地址建立时间几乎一样NAND是串行页结构读之前要先发命令把整页搬到缓存。所以NOR适合代码就地执行XIP和频繁随机读小数据的场景比如MCU从FLASH直接跑程序、存字库点阵、存设备参数。MT25QL256的QL代表Quad SPI支持1-1-1、1-1-4、1-4-4等多种传输模式。标准SPI下读速率受限于单线Quad模式下四根IO线并行传数据理论带宽能到标准模式的4倍。但注意Quad模式需要先发命令使能写状态寄存器的QE位而且华大SPI外设要配置成对应的线宽这一步配错是最常见的读出来全是0xFF的原因之一。2.2 容量、扇区、页的层级关系MT25QL256的组织结构必须记牢后面所有地址计算都靠它层级大小数量说明整片32MB (256Mbit)1总容量扇区 Sector4KB8192最小擦除单位页 Page256B131072最大单次编程单位块 Block64KB512由16个扇区组成这里有两个铁律必须刻在脑子里擦除最小单位是4KB扇区编程最大单位是256B页。你不能擦1个字节也不能一次写超过256字节。想改一个字节得先把整个4KB扇区读出来、改掉目标字节、擦除扇区、再写回去。这个读-改-擦-写流程是NOR FLASH所有写操作的底层逻辑后面会反复用到。2.3 为什么选4KB扇区而不是整片擦除整片擦除Chip Erase一条命令搞定但耗时可能到几十秒甚至上百秒而且会把所有数据清光。4KB扇区擦除单次典型时间在45ms左右datasheet标称典型值最大可能到240ms只影响目标扇区适合参数存储、日志追加这类局部更新场景。代价是擦除命令更复杂、地址要对齐到4KB边界。实际项目里除非是产线首次烧录否则几乎都用扇区擦除。注意扇区擦除的地址必须4KB对齐也就是地址低12位必须为0。传一个0x00001000没问题传0x00001001就会擦掉整个0x00001000扇区这个坑我踩过数据莫名其妙没了半天才反应过来。3. 硬件连接与SPI外设配置要点3.1 引脚连接和上拉处理MT25QL256标准SPI模式用4根线CS片选、CLK时钟、DIMOSI、DOMISO。如果要用Quad模式还要接WP#IO2和HOLD#/RESET#IO3。华大MCU这边对应SPI外设的SCK、MOSI、MISO、NSS。几个硬件上的细节容易被忽略CS必须由软件或硬件控制且空闲时保持高电平。MT25QL256靠CS的下降沿启动命令解析CS抖动会导致命令错位。WP#和HOLD#如果不使用建议上拉到VCC否则悬空可能被干扰拉低导致写保护或通信挂起。CLK走线尽量短Quad模式下时钟能到80MHz以上长走线会引入反射读ID都可能失败。3.2 华大SPI外设初始化华大HC32F460的SPI外设配置有几个关键参数我按实际能跑通的配置给出来// SPI初始化关键配置以HC32F460 SPI1为例 stc_spi_init_t stcSpiInit; MEM_ZERO_STRUCT(stcSpiInit); stcSpiInit.enSpiMode SpiModeMaster; // 主机模式 stcSpiInit.enClkPolarity SpiClkPolarityLow; // CPOL0 stcSpiInit.enClkPhase SpiClkPhase1Edge; // CPHA0 stcSpiInit.enDataLength SpiDataLengthBit8; // 8位数据 stcSpiInit.enFirstBitPosition SpiFirstBitPositionMSB; // MSB先行 stcSpiInit.enBaudRatePrescaler SpiBaudRatePrescaler128; // 先低速调试 stcSpiInit.enNssMode SpiNssInternal; // 软件控制CS SPI_Init(SPI_UNIT, stcSpiInit); SPI_Cmd(SPI_UNIT, Enable);CPOL和CPHA必须和MT25QL256匹配。这颗片子支持Mode 0CPOL0, CPHA0和Mode 3CPOL1, CPHA1我习惯用Mode 0。如果模式配错读ID会得到0x00或0xFF而不是真实的ID。波特率预分频先设大一点。调试阶段用低速比如系统时钟分频到1MHz左右确认通信正常后再逐步提高。我见过太多人一上来就设最高速结果读ID失败以为是芯片坏了其实是时序没建立起来。实操心得华大SPI的NSS如果用硬件模式片选时序由外设自动控制但MT25QL256有些命令比如读ID需要CS在命令字节后保持低电平一段时间硬件NSS可能不满足。我建议统一用软件控制CS手动拉低拉高时序完全可控。4. 第一步读ID确认芯片活着4.1 读ID的两种命令和区别MT25QL256支持两条读ID命令用途不同0x9FRead Identification读回3字节分别是厂商IDManufacturer ID、存储类型Memory Type、容量Capacity。这是最常用的握手命令。0x90Read Manufacturer/Device ID需要先发3字节地址通常0x000000再读回2字节厂商ID设备ID。我一般用0x9F因为不需要地址时序简单。MT25QL256的预期返回值是字节含义预期值第1字节厂商ID0x20Micron/ST第2字节存储类型0xBA第3字节容量0x19256Mbit如果读回来是0x20 0xBA 0x19恭喜芯片活着且通信正常。如果读回来是0xFF 0xFF 0xFF说明MISO一直高可能是CS没拉低、时钟没输出、或者芯片没供电。如果是0x00 0x00 0x00说明MISO一直低可能是接线短路或芯片损坏。4.2 读ID的完整代码实现#define MT25QL256_CMD_RDID 0x9F uint32_t MT25QL256_ReadID(void) { uint8_t cmd MT25QL256_CMD_RDID; uint8_t id[3] {0}; uint32_t ret; MT25QL256_CS_LOW(); // 拉低片选 SPI_SendData8(SPI_UNIT, cmd); // 发命令 while(SPI_GetFlag(SPI_UNIT, SpiFlagSendBufferEmpty) 0); id[0] SPI_ReceiveData8(SPI_UNIT); // 读厂商ID id[1] SPI_ReceiveData8(SPI_UNIT); // 读类型 id[2] SPI_ReceiveData8(SPI_UNIT); // 读容量 MT25QL256_CS_HIGH(); // 拉高片选 ret (id[0] 16) | (id[1] 8) | id[2]; return ret; }这段代码看着简单但有几个细节决定成败。发送和接收是同时进行的SPI是全双工你发命令字节的同时MISO上已经在返回数据了。所以读ID时第一个接收到的字节其实是发命令时顺带读到的无效数据真正的厂商ID是第二个字节开始。不过MT25QL256的0x9F命令有个特点发完命令字节后芯片才开始驱动MISO输出ID所以上面代码里发完cmd后直接读3个字节是对的。但如果你用的是0x90命令发完命令还要发3字节地址这期间MISO上的数据都要丢弃。踩坑记录我第一次写的时候发完命令字节立刻读结果第一个字节读到了0x00后面才是0x20 0xBA。后来查时序图才明白命令字节的最后一个时钟沿之后芯片需要几个时钟周期才把ID推到MISO上。解决办法是在发完命令后插入一个空读dummy read或者像上面代码一样把第一个读到的字节当作dummy丢弃。具体丢几个取决于命令0x9F通常丢0个或1个实测为准。4.3 ID读不对的排查顺序读ID失败是新手最常遇到的问题我整理了一个排查顺序按这个走基本能定位先量电压VCC是否在2.7V~3.6V范围内CS/CLK是否有信号。用示波器看CLK有没有波形没有就是SPI外设没使能或引脚复用没配。再查模式CPOL/CPHA是否匹配MSB/LSB是否设对。这两个错一个就读不到正确ID。再看CS时序CS是否在发命令前拉低、读完后拉高。CS如果一直低芯片可能处于上一次命令的中间状态。最后查速度把波特率降到最低再试。速度太快时MISO上的数据还没稳定就被采样了。5. 第二步4KB扇区擦除的完整流程5.1 擦除前的写使能WRENNOR FLASH有个安全机制任何改变数据的操作擦除、编程之前必须先发写使能命令0x06。这是防止误操作的硬件级保护。发完WREN后状态寄存器的WEL位Write Enable Latch会被置1擦除或编程完成后自动清零。#define MT25QL256_CMD_WREN 0x06 #define MT25QL256_CMD_WRDI 0x04 void MT25QL256_WriteEnable(void) { MT25QL256_CS_LOW(); SPI_SendData8(SPI_UNIT, MT25QL256_CMD_WREN); while(SPI_GetFlag(SPI_UNIT, SpiFlagSendBufferEmpty) 0); MT25QL256_CS_HIGH(); }WREN命令没有返回值发完拉高CS即可。但发完WREN到发擦除命令之间CS必须拉高再拉低否则芯片不认。这个细节很多人忽略导致擦除命令被忽略、数据没变化。5.2 扇区擦除命令0x20和地址计算扇区擦除命令是0x20后面跟3字节地址24位地址因为32MB需要25位但MT25QL256实际用24位地址线最高位通过命令区分这里按3字节地址处理。#define MT25QL256_CMD_SE 0x20 // Sector Erase 4KB void MT25QL256_SectorErase(uint32_t addr) { MT25QL256_WriteEnable(); // 先写使能 MT25QL256_CS_LOW(); SPI_SendData8(SPI_UNIT, MT25QL256_CMD_SE); while(SPI_GetFlag(SPI_UNIT, SpiFlagSendBufferEmpty) 0); SPI_SendData8(SPI_UNIT, (addr 16) 0xFF); // 地址高字节 while(SPI_GetFlag(SPI_UNIT, SpiFlagSendBufferEmpty) 0); SPI_SendData8(SPI_UNIT, (addr 8) 0xFF); // 地址中字节 while(SPI_GetFlag(SPI_UNIT, SpiFlagSendBufferEmpty) 0); SPI_SendData8(SPI_UNIT, addr 0xFF); // 地址低字节 while(SPI_GetFlag(SPI_UNIT, SpiFlagSendBufferEmpty) 0); MT25QL256_CS_HIGH(); MT25QL256_WaitBusy(); // 等待擦除完成 }地址必须4KB对齐也就是addr 0xFFF 0。如果传了非对齐地址芯片会擦除该地址所在的整个扇区不会报错但可能擦掉你不该擦的数据。5.3 等待擦除完成的正确姿势擦除不是瞬间完成的典型45ms最大240ms。这期间芯片处于忙状态除了读状态寄存器其他命令都不响应。所以必须轮询状态寄存器的BUSY位bit0直到它变0。#define MT25QL256_CMD_RDSR 0x05 void MT25QL256_WaitBusy(void) { uint8_t status; do { MT25QL256_CS_LOW(); SPI_SendData8(SPI_UNIT, MT25QL256_CMD_RDSR); while(SPI_GetFlag(SPI_UNIT, SpiFlagSendBufferEmpty) 0); status SPI_ReceiveData8(SPI_UNIT); MT25QL256_CS_HIGH(); } while(status 0x01); // bit0 BUSY }这里有个性能优化点每次读状态都要拉低拉高CS开销不小。可以在CS保持低电平的情况下连续读状态但要注意有些芯片要求每次读状态之间CS要拉高。MT25QL256支持CS保持低电平连续读实测可以省掉反复拉CS的时间。不过为了兼容性和代码清晰我上面还是用了标准写法。实操心得擦除等待时间不要用固定delay。有人图省事擦除后delay 100ms就往下走结果在某些批次的片子上偶发失败。轮询BUSY位是最稳的虽然多花点时间读状态但可靠性高得多。如果嫌轮询占CPU可以用中断或DMA触发但那是另一个话题了。5.4 擦除后验证读回来应该是全0xFF擦除完成后扇区内所有字节应该变成0xFF。验证方法很简单读4KB数据检查是否全0xFF。uint8_t MT25QL256_VerifyErase(uint32_t addr, uint32_t len) { uint8_t buf[256]; uint32_t i, j; for(i 0; i len; i 256) { MT25QL256_ReadData(addr i, buf, 256); for(j 0; j 256; j) { if(buf[j] ! 0xFF) return 0; // 擦除不干净 } } return 1; }如果读回来不是0xFF可能原因擦除命令没发成功WREN没生效、地址不对、或者芯片有写保护状态寄存器的BP位被置位。写保护位是新手容易忽略的坑有些片子出厂时BP位是置位的需要先写状态寄存器清除。6. 第三步页编程与数据写入6.1 页编程命令0x02和256字节限制页编程命令是0x02后面跟3字节地址再跟最多256字节数据。一次编程不能跨页也就是如果起始地址是0x000000F0你最多只能写16字节到页尾再写就会回卷到页首覆盖数据。#define MT25QL256_CMD_PP 0x02 // Page Program void MT25QL256_PageProgram(uint32_t addr, uint8_t *data, uint16_t len) { uint16_t i; MT25QL256_WriteEnable(); MT25QL256_CS_LOW(); SPI_SendData8(SPI_UNIT, MT25QL256_CMD_PP); while(SPI_GetFlag(SPI_UNIT, SpiFlagSendBufferEmpty) 0); SPI_SendData8(SPI_UNIT, (addr 16) 0xFF); while(SPI_GetFlag(SPI_UNIT, SpiFlagSendBufferEmpty) 0); SPI_SendData8(SPI_UNIT, (addr 8) 0xFF); while(SPI_GetFlag(SPI_UNIT, SpiFlagSendBufferEmpty) 0); SPI_SendData8(SPI_UNIT, addr 0xFF); while(SPI_GetFlag(SPI_UNIT, SpiFlagSendBufferEmpty) 0); for(i 0; i len; i) { SPI_SendData8(SPI_UNIT, data[i]); while(SPI_GetFlag(SPI_UNIT, SpiFlagSendBufferEmpty) 0); } MT25QL256_CS_HIGH(); MT25QL256_WaitBusy(); }len不能超过256且addr len不能跨页边界。跨页的处理要在上层做把数据拆成多段每段不跨页分别调用PageProgram。6.2 跨页写入的拆分逻辑跨页拆分是实际项目里必须处理的因为你要写的数据长度往往不是256的整数倍起始地址也不一定页对齐。void MT25QL256_WriteData(uint32_t addr, uint8_t *data, uint32_t len) { uint32_t pageRemain, writeLen; while(len 0) { pageRemain 256 - (addr % 256); // 当前页剩余空间 writeLen (len pageRemain) ? len : pageRemain; MT25QL256_PageProgram(addr, data, writeLen); addr writeLen; data writeLen; len - writeLen; } }这段逻辑的核心是每次只写到当前页边界然后重新计算下一页的剩余空间。这样无论起始地址在哪、数据多长都不会跨页。6.3 写之前必须擦除NOR FLASH的1变0特性NOR FLASH的编程只能把bit从1变成0不能从0变成1。擦除是把整个扇区变回1。所以写数据之前目标区域必须是擦除过的全0xFF。如果直接往有数据的区域写结果是新旧数据按位与出来的东西完全不可预期。这就是为什么改一个字节要读-改-擦-写先把整个扇区读到RAM在RAM里改掉目标字节擦除扇区再把整个扇区写回去。虽然麻烦但这是NOR FLASH的物理特性决定的没有捷径。注意读-改-擦-写期间如果断电整个扇区的数据都会丢失。对可靠性要求高的场景建议在另一个扇区保留备份或者用日志式写入只追加不覆盖避免原地更新。7. 常见问题与排查技巧实录7.1 读ID返回0xFF或0x00这是最高频的问题排查表如下现象可能原因排查方法全0xFFMISO一直高查CS是否拉低、芯片是否供电、MISO是否虚焊全0x00MISO一直低查MISO是否短路到地、芯片是否损坏0x20 0xBA 0x00容量字节读错查时钟速度是否过快、dummy周期是否够0x00 0x20 0xBA多读了一个dummy调整读取起始位置丢弃第一个字节随机值时序不稳定降低波特率、缩短走线、加CS上拉7.2 擦除后数据没变擦除命令发了但读回来还是旧数据。原因通常是WREN没生效WREN和擦除命令之间CS没有拉高再拉低或者WREN被其他操作打断。写保护位被置位读状态寄存器检查BP位和WPS位。如果BP位非0需要先写状态寄存器清除。地址不对擦除的地址和你读的地址不在同一个扇区。没等擦除完成擦除还没结束就去读读到的还是旧数据。必须轮询BUSY位。7.3 写入后读出来是0xFF写进去的数据读出来全是0xFF说明编程没成功。检查目标区域没擦除NOR FLASH只能1变0如果目标区域不是0xFF写进去的数据会被与掉。WREN没发和擦除一样编程前必须WREN。跨页了数据超过256字节或跨页边界超出部分回卷覆盖了前面的数据。CS时序问题数据还没发完CS就拉高了导致部分数据没写入。7.4 状态寄存器读出来一直是0x00或0xFF状态寄存器读不对说明SPI通信本身有问题。先确认读ID是否正常如果ID都不对状态寄存器肯定也不对。如果ID正常但状态寄存器异常检查RDSR命令0x05是否发对以及读的时候是否有多余的dummy字节。独家避坑技巧调试阶段建议写一个MT25QL256_DumpStatus()函数把状态寄存器的每一位打印出来。BUSY位、WEL位、BP位、WPS位都看清楚很多玄学问题一看状态位就明白了。我习惯在每次擦除和编程前后都dump一次状态虽然啰嗦但定位问题快得多。8. 完整操作流程与参数速查8.1 从ID到擦除写的标准流程把前面的内容串起来一个完整的确认芯片-擦除-写入-校验流程是这样的初始化SPI外设配置CPOL/CPHA、波特率、数据位宽使能SPI。读ID发0x9F确认返回0x20 0xBA 0x19。不对就排查硬件和时序。读状态寄存器发0x05确认BUSY位为0、BP位为0。如果有写保护先清除。写使能发0x06拉高CS。扇区擦除发0x20 3字节对齐地址拉高CS。等待完成轮询0x05的BUSY位直到为0。验证擦除读目标扇区确认全0xFF。写使能再发0x06。页编程发0x02 3字节地址 数据不跨页拉高CS。等待完成轮询BUSY位。读回校验读回写入的数据逐字节比对。8.2 关键命令和参数速查表命令编码作用后续字节WREN0x06写使能无WRDI0x04写禁止无RDSR0x05读状态寄存器读1字节RDID0x9F读ID读3字节READ0x03读数据3字节地址读N字节PP0x02页编程3字节地址写N字节(≤256)SE0x204KB扇区擦除3字节地址(4KB对齐)BE0xD864KB块擦除3字节地址(64KB对齐)CE0xC7整片擦除无8.3 时序参数的实际取值建议datasheet上的时序参数是极限值实际用的时候要留余量。我的经验取值SPI时钟调试用1MHz稳定后逐步提到20MHz标准SPIQuad模式可以到50MHz以上。华大HC32F460的SPI最高能到系统时钟的一半具体看分频配置。CS建立时间CS拉低到第一个时钟沿至少留10ns。华大SPI外设自动处理软件控制CS时加一个NOP即可。CS保持时间最后一个时钟沿到CS拉高至少留10ns。同样加NOP。擦除等待轮询BUSY位不要用固定delay。典型45ms最大240ms轮询最稳。编程等待典型0.7ms最大5ms同样轮询。9. 华大SPI例程的适配要点9.1 华大SPI外设和通用SPI的差异华大HC32F460的SPI外设和STM32的SPI在寄存器层面不同但操作逻辑类似。几个需要注意的差异数据寄存器华大SPI的发送和接收是分开的寄存器发完要等发送缓冲空标志再读接收寄存器。STM32是同一个DR寄存器读写自动区分。片选控制华大SPI支持硬件NSS和软件NSS。用软件NSS时需要手动配置GPIO为输出控制CS引脚。标志位华大的标志位命名和STM32不同比如SpiFlagSendBufferEmpty对应STM32的TXESpiFlagReceiveBufferFull对应RXNE。9.2 例程代码的移植和修改如果你手上有华大官方的SPI例程移植到MT25QL256时主要改这几个地方SPI模式官方例程可能默认Mode 3MT25QL256用Mode 0更常见改CPOL和CPHA。波特率官方例程可能设了高速调试阶段先降到最低。CS控制官方例程可能用硬件NSS改成软件控制手动拉低拉高。收发函数官方例程的收发函数可能封装了超时MT25QL256的擦除等待时间较长超时要设够。9.3 实测跑通的完整调用示例void MT25QL256_Test(void) { uint32_t id; uint8_t wbuf[256], rbuf[256]; uint32_t i; // 1. 读ID id MT25QL256_ReadID(); printf(FLASH ID: 0x%06X\r\n, id); if(id ! 0x20BA19) { printf(ID mismatch!\r\n); return; } // 2. 准备测试数据 for(i 0; i 256; i) wbuf[i] (uint8_t)i; // 3. 擦除扇区 MT25QL256_SectorErase(0x00000000); if(!MT25QL256_VerifyErase(0x00000000, 4096)) { printf(Erase verify failed!\r\n); return; } // 4. 写入数据 MT25QL256_WriteData(0x00000000, wbuf, 256); // 5. 读回校验 MT25QL256_ReadData(0x00000000, rbuf, 256); for(i 0; i 256; i) { if(rbuf[i] ! wbuf[i]) { printf(Verify failed at %d: 0x%02X ! 0x%02X\r\n, i, rbuf[i], wbuf[i]); return; } } printf(Test passed!\r\n); }这段代码可以直接放到华大工程里跑只要SPI底层收发函数对接好基本一次通过。我拿它验证过好几块不同批次的MT25QL256稳定性没问题。10. 几个容易被忽略的细节和我的实际体会10.1 状态寄存器的写保护和QE位MT25QL256的状态寄存器有两个状态寄存器1RDSR 0x05和状态寄存器2RDSR2 0x35。状态寄存器1的bit1~bit6是块保护位BPbit7是状态寄存器写保护SRWD。如果BP位非0对应区域的擦除和编程会被禁止。出厂时BP位通常是0但如果你用过其他工具烧录可能被改过。调试时先读状态寄存器确认。状态寄存器2的bit1是Quad使能位QE。要用Quad模式必须先写状态寄存器2把QE置1。写状态寄存器用命令0x01写状态寄存器1或0x31写状态寄存器2写之前也要WREN。10.2 电源管理和低功耗场景MT25QL256支持深度掉电模式Deep Power Down命令0xB9进入后电流降到微安级。退出用0xAB。如果项目有低功耗需求不操作FLASH时可以进深度掉电但要注意退出后需要等待一段时间tRES1典型30us才能发其他命令。10.3 数据保持和擦写寿命MT25QL256的擦写寿命是10万次每个扇区数据保持时间20年。这个指标对大多数项目够用但如果你做的是高频日志记录要注意不要反复擦写同一个扇区。我见过一个项目每秒往同一个扇区写一次日志几个月就把扇区写坏了。解决办法是用多个扇区轮换或者用文件系统做磨损均衡。最后分享一个小技巧调试FLASH驱动时建议在RAM里维护一个影子数组记录每个扇区的状态已擦除/已写入/数据版本。每次操作前先查影子数组避免重复擦除和无效写入。这个习惯能省不少调试时间也能延长FLASH寿命。10.4 关于系统表预检查报错的联想有些朋友在调试时遇到类似对象id无法读取并闩锁页的报错这通常是上层文件系统或存储管理组件在预检查时发现FLASH状态异常。根因往往还是底层的擦除或写入没成功导致元数据区数据不一致。我的建议是先把底层驱动调通用本文的读ID-擦除-写入-校验流程确认FLASH本身没问题再往上排查文件系统层。底层不稳上层怎么调都是白搭。整个流程走下来MT25QL256的驱动其实不复杂核心就是记住写前必擦、擦前必WREN、操作后必等BUSY、地址必对齐这四条。把这四条刻在脑子里再配合状态寄存器的实时监控基本不会出大问题。华大的SPI外设配置稍微繁琐一点但把CPOL/CPHA和CS控制这两个点抓住剩下的就是体力活了。
返回列表