十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

MX25L128/MX25L256 SPI NOR Flash驱动移植与调试实战指南

MX25L128/MX25L256 SPI NOR Flash驱动移植与调试实战指南 简介嵌入式系统中SPI NOR Flash凭借接口简单、存储可靠等特性被广泛应用于固件存储、日志记录与OTA升级等场景。其工作原理是通过SPI总线发送指令、地址和数据完成读、写、擦除操作。掌握驱动移植方法理解页编程的跨页边界、扇区擦除的时序要求以及状态寄存器轮询机制是保障系统稳定运行的关键技术基础。在工程实践中从GPIO配置、SPI模式选择到读写擦除流程的完整链路都需要结合实际芯片特性进行适配与验证。当遇到读ID正常但读写失败、写入后读出0xFF等典型问题时合理的排查思路与逻辑分析仪等调试手段可以大幅提升效率。本文以MX25L128和MX25L256兼容驱动包为载体深入讲解SPI NOR Flash的指令细节、分层设计、移植步骤与常见陷阱为开发者提供一套可复用的驱动开发和调试方法论。1. 拿到驱动包后先搞清楚芯片和代码的匹配关系做嵌入式这几年SPI NOR Flash 基本是绕不开的东西尤其是 MX25L 系列在路由器、工控板、物联网模组里到处都能看到。这个压缩包名字里同时带了 MX25L128 和 MX25L256说明它是一套兼容两颗芯片的驱动代码这点在实际工程里非常实用因为硬件升级容量时往往不需要改 PCB只需要换一颗料、改几个宏定义就能搞定。1.1 MX25L128 和 MX25L256 的差别不仅仅是容量翻倍很多新手拿到这颗芯片第一反应就是“128 和 256 不就是容量差一倍吗代码不都一样”实际上没那么简单。MX25L128 容量是 128Mbit也就是 16MB而 MX25L256 是 256Mbit即 32MB。容量翻倍后地址位宽就必须跟着变。MX25L128 需要 24 位地址就够了而 MX25L256 虽然标准寻址也是 24 位但它支持 4 字节地址模式来访问高地址区域这块如果不处理写数据超过 16MB 后就全乱套了。具体到驱动层面你需要关注三个关键参数芯片容量宏定义#define MX25L256_CAPACITY (256 * 1024 * 1024 / 8)单位是字节也就是 32MB。地址字节数MX25L128 用 3 字节地址MX25L256 如果用 4 字节模式则需要把地址指令从 0x03 换成 0x13或者先发0xB7进入 4 字节地址模式。我一般更推荐直接切 4 字节模式让驱动统一走 32 位地址省得在传输层搞一堆判断。扇区数量MX25L128 有 256 个 64KB 扇区MX25L256 有 512 个这个值会直接影响擦除循环的上限写死的话换芯片就会出越界问题。我见过不止一个项目因为直接拿了 128 的驱动配到 256 的片子结果后面 16MB 的区域一读写就异常。所以拿到这个驱动包第一步先检查这类型的容量宏和地址位宽配置确认它是不是真正兼容两颗芯片而不是只起了个兼容的名字。1.2 这套 SPI 驱动代码的分层结构和设计思路这套代码典型的做法是分三层MCU 底层 SPI 适配层、Flash 核心驱动层、应用接口层。我在看代码的时候特别关注它的分层是否清晰因为这直接决定了你换平台时的工作量。底层 SPI 适配层负责和具体 MCU 的 SPI 外设打交道通常就是封装了spi_transfer_byte()、spi_transfer_buffer()这类函数有的做得好一点会直接用HAL_SPI_TransmitReceive()走 DMA。核心驱动层是对 Flash 指令的封装比如flash_read()、flash_write()、flash_erase()这里会处理写使能、状态寄存器轮询等工作。应用接口层则是面向业务的比如固件升级模块调用的storage_write_firmware()日志系统调用的log_flush()。这个分层思路是通用的而且不是这个压缩包独有的套路。我之所以强调要先看懂分层是因为很多驱动包代码文件不多有些人直接拿过来把spi_transfer_byte()改成自己的HAL_SPI_Transmit()就完事了一旦出了问题根本没法排查。正确做法是确认每一层的边界知道哪一层出了问题应该去改哪里。另外我注意到这套代码在设计的时候把 MX25L128 和 MX25L256 的差异点收敛到了一个flash_cfg.h头文件里应用层代码完全不用关心底层是 128 还是 256。这个做法非常推荐我自己写驱动也是同样的思路所有差异化的参数容量、地址位宽、扇区数、擦除时间全部用宏定义或者结构体配置表隔离而不是散落在各个 C 文件里到处是#ifdef。2. SPI 通信时序和指令细节是驱动跑通的核心SPI 驱动 NOR Flash本质就是主机通过 SPI 总线向 Flash 发指令、送地址、读写数据的全过程。虽然代码写起来就那么几十行但每一条指令背后都有对应的时序要求我建议你至少在调试的时候把逻辑分析仪挂上去看一眼波形能省掉很多玄学问题。2.1 指令集读、写、擦除这些基础指令怎么用MX25L 系列的指令集和市面上大多数 SPI NOR Flash 是兼容的基本是业界标准指令集这算是 Macronix 的一个优点替换其他品牌芯片的成本很低。核心指令大概这么几张表指令名称指令码地址长度说明Read Data0x033字节普通读速度较慢Fast Read0x0B3字节带 dummy 周期支持更高时钟Fast Read 4-Byte0x0C4字节4字节地址模式下的快速读Page Program0x023字节页编程一次最多 256 字节Sector Erase0x203字节4KB 扇区擦除Block Erase0xD83字节64KB 块擦除Read Status Register0x05无读取状态寄存器Write Enable0x06无写使能所有写操作前必须发Read ID0x9F无读取 JEDEC ID这里有一个最常见的坑页编程一次最多只能写 256 字节而且这 256 字节不能跨页。什么意思呢假如你当前要写的地址是 0x0000FF00如果一次性写 256 字节地址范围正好落在 0x0000FF00 ~ 0x0000FFFF没问题。但如果起始地址是 0x0000FF80你直接写 200 字节就会从 0xFF80 写到 0x10047跨越了页边界0xFF00 是起始、0xFFFF 是结束、0x10000 是下一页这时候数据会回卷写到 0x0000FF00 的位置造成数据错乱。驱动代码里对这个处理一般是先算remain 256 - (addr % 256)然后分两次写。这套代码我看到它也是这么处理的不过我还是建议你自己再仔细读一遍这个部分看看边界条件有没有写错。我平时调试的时候甚至刻意用地址刚好落在页边界附近的测试用例去跑专门验证这个逻辑。2.2 状态寄存器轮询和写使能为什么不能省SPI NOR Flash 有一个写保护机制每次进行写操作页编程、扇区擦除、块擦除之前必须先发送Write Enable (0x06)指令把状态寄存器里的 WEL 位Write Enable Latch置 1。如果没发这条指令就直接写Flash 会直接忽略操作数据写不进去。写完指令后Flash 内部开始执行编程或擦除操作这个过程需要时间短的几百微秒长的能到几秒64KB 块擦除最坏情况可能到 2 秒左右。你必须在发送下一个操作之前轮询状态寄存器的 BUSY 位bit 0直到它变成 0 才能继续下一步。实际驱动流程是这样uint8_t flash_wait_busy(void) { uint8_t status; uint32_t timeout 0xFFFFFF; do { status flash_read_status(); timeout--; } while ((status 0x01) timeout); if (timeout 0) return TIMEOUT_ERROR; return OK; }这个函数看起来简单但实际上有个容易忽略的问题每次轮询之间最好插入一点点延时不然 SPI 总线会一直处于高负载占用状态尤其在使用 RTOS 的场景下可能会导致低优先级任务饿死。我一般在轮询循环里加一个delay_us(10)或者taskYIELD()实测下来系统整体流畅度明显更好。除了状态寄存器还有写保护位SRWD、BP0~BP3如果硬件上 WP 引脚拉低了状态寄存器里的块保护位也没解除你会发现所有写操作都静默失败——指令发了状态寄存器也不报 BUSY但是读出来全是 0xFF。排查这种问题最快的方法是先把0x01Write Status Register指令发下去把状态寄存器清成 0x00同时确认 WP 引脚电平。2.3 SPI 模式选择CPOL/CPHA 必须对应MX25L 系列支持 SPI Mode 0 和 Mode 3也就是 CPOL0/CPHA0 和 CPOL1/CPHA1。这俩的区别简单理解就是时钟空闲时是高还是低、数据在时钟上升沿还是下降沿采样。在实际工程里我强烈建议把 SPI 初始化配置写成宏定义集中管理不要散落在代码里。比如#define FLASH_SPI_MODE SPI_MODE_0 #define FLASH_SPI_PRESCALER SPI_BAUDRATEPRESCALER_4 #define FLASH_SPI_CPOL SPI_CPOL_LOW #define FLASH_SPI_CPHA SPI_CPHA_1EDGE配置错模式最典型的症状是读 ID 能读到偶尔也能读写但数据会出现偶发错位比如读出来的数据每隔几个字节就跳一个位。这是因为模式不匹配时主机采样和数据变化边缘错开了一个相位导致部分位被采错。如果你是第一次把代码移植到新平台强烈建议先用逻辑分析仪抓一下 MISO/MOSI/CLK/CS 四根线的波形对照芯片手册里的时序图确认模式完全一致再往下走这一步能省掉后面大量的排查时间。3. 驱动移植与实操步骤从初始化到读写擦除这部分我用一段典型的 STM32 平台上基于标准库或 LL 库的代码做例子讲一下完整流程。这套代码我在多个项目里用过硬件平台换成别的 MCU 时核心逻辑是不变的只需要改底层传输函数。3.1 初始化流程GPIO、SPI 外设和 Flash 配置初始化阶段主要做三件事配置 SPI 引脚为复用功能、配置 SPI 外设参数、读取 Flash ID 校验通信链路。void flash_init(void) { // 1. 使能 SPI 时钟和 GPIO 时钟 __HAL_RCC_SPI1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); // 2. 配置引脚SCK、MOSI、MISO 复用为 SPI1CS 用普通 GPIO 输出 GPIO_InitTypeDef gpio {0}; gpio.Pin GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; // SCK/MISO/MOSI gpio.Mode GPIO_MODE_AF_PP; gpio.Pull GPIO_PULLUP; gpio.Speed GPIO_SPEED_FREQ_HIGH; gpio.Alternate GPIO_AF5_SPI1; HAL_GPIO_Init(GPIOA, gpio); gpio.Pin GPIO_PIN_4; // CS gpio.Mode GPIO_MODE_OUTPUT_PP; HAL_GPIO_Init(GPIOA, gpio); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // 3. 配置 SPI 外设 SPI_HandleTypeDef hspi; hspi.Instance SPI1; hspi.Init.Mode SPI_MODE_MASTER; hspi.Init.Direction SPI_DIRECTION_2LINES; hspi.Init.DataSize SPI_DATASIZE_8BIT; hspi.Init.CLKPolarity SPI_POLARITY_LOW; hspi.Init.CLKPhase SPI_PHASE_1EDGE; hspi.Init.NSS SPI_NSS_SOFT; hspi.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_4; hspi.Init.FirstBit SPI_FIRSTBIT_MSB; HAL_SPI_Init(hspi); // 4. 读取 ID 验证通信 uint8_t id[3]; flash_read_id(id); if (id[0] ! 0xC2) { // 错误处理不是 Macronix 芯片检查接线或 SPI 配置 } }这里有个细节需要注意CS 引脚务必用普通 GPIO 控制不要用 SPI 外设的硬件 NSS。硬件 NSS 在某些 MCU 上自动拉低拉高的时机和 Flash 期望的时序不完全一致容易在连续传输时出现 CS 提前释放的问题。软件 CS 虽然多几行代码但完全可控读取速度快了也不会因为 CS 释放太早导致数据错位。网上那些“3线 SPI 也能驱动 Flash”的帖子实际上就是靠软件 CS 才做到的。初始化完成后读 ID 是必须的JEDEC ID 第一个字节 0xC2 是 Macronix 的厂商代码MX25L128 的 Device ID 是 0x18MX25L256 是 0x19。如果你读到的 ID 不对先不要怀疑 Flash 坏了先检查初始化配置。3.2 读操作实现和缓冲区管理Flash 的读操作是最简单的基本上就是 CS 拉低 - 发读指令 - 发 3 字节或 4 字节地址 - 连续读数据 - CS 拉高。int flash_read(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd[4]; if (!buf || len 0) return PARAM_ERROR; flash_cs_low(); // 如果支持 4 字节地址模式这里直接走 FAST_READ_4B cmd[0] 0x0B; // Fast Read cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; spi_transmit(cmd, 4); uint8_t dummy 0xFF; spi_transmit_receive(dummy, dummy, 1); // dummy cycle spi_receive(buf, len); flash_cs_high(); return OK; }这里的 dummy cycle 是 Fast Read 和普通 Read 的差别所在。普通读0x03发出地址后可以直接读数据但 Fast Read0x0B需要在地址之后插入 8 个 dummy 时钟周期让 Flash 有足够的准备时间。这个细节很容易被忽略如果你用了 Fast Read 却把 dummy 省了读出来的第一个字节永远是错的。缓冲区管理是另一个容易出问题的地方。如果你跑的是裸机程序buf 直接指向一个全局数组就行如果跑 RTOS需要注意 buf 必须保证在传输期间不被其他任务占用。我建议把 flash 的读写接口设计成同步阻塞模式不要让 SPI DMA 和 CPU 同时访问同一块 buffer否则数据一致性很难保证。当然如果数据量特别大需要异步传输那就必须用双缓冲或者环形缓冲的机制但这部分复杂度会直线上升不是必须的话不建议一上来就搞。另外读取速度方面Fast Read 配合高 SPI 时钟能做到几 MB/s 甚至十几 MB/s但这取决于 Flash 支持的最大时钟频率。MX25L128 和 MX25L256 都支持最高 133MHz 的 Fast Read但这里有一个列外如果你的 PCB 走线长度比较长或者使用了杜邦线/面包板连接133MHz 是不现实的建议先按 40MHz 以下跑稳定后再往上提。3.3 页编程与扇区擦除的完整流程写操作比读操作复杂得多因为它涉及“写使能 - 拉低 CS - 发指令/地址/数据 - 拉高 CS - 轮询 BUSY - 检查结果”这个完整链路。这里以页编程为例完整代码我写出来你看看int flash_page_program(uint32_t addr, const uint8_t *buf, uint32_t len) { uint8_t cmd[4]; uint32_t i; if (!buf || len 0 || len 256) return PARAM_ERROR; // 检查是否跨页 uint32_t page_remain 256 - (addr % 256); if (len page_remain) return CROSS_PAGE_ERROR; flash_write_enable(); flash_cs_low(); cmd[0] 0x02; // Page Program cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; spi_transmit(cmd, 4); spi_transmit((uint8_t *)buf, len); flash_cs_high(); return flash_wait_busy(); }这段代码里最关键的就是那个“跨页检查”。如果上层调用者没有做过页对齐处理驱动层必须主动拦截。我习惯在驱动层直接返回错误码而不是默默处理因为如果驱动层偷偷帮你分两次写上层可能根本意识不到数据在 Flash 里是分两段存的万一中间掉电数据的完整性就没法保证了。让上层调用者明确知道跨页的存在反而能促进他们从逻辑上做优化。擦除操作和页编程流程基本类似只是指令不同且不需要传数据。扇区擦除0x20擦除 4KB块擦除0xD8擦除 64KB。擦除的时间远大于页编程最坏情况下扇区擦除要 400ms块擦除可能到 2 秒。这个时间在代码里要预留足够的超时阈值我之前见过有人把超时设成 0xFFFF结果在 SPI 时钟比较高的时候这个计数很快就数完了直接误报超时错误。擦除后 Flash 内容变成全 0xFF这是 NOR Flash 的特性也是它和 EEPROM 的重要区别。NOR Flash 只能把 1 写成 0不能把 0 写成 1所以要想更新某个字节必须先擦除整个扇区把扇区全部恢复成 0xFF然后再写入新值。这个特性对上层文件系统的设计影响很大如果你要做一个日志存储系统建议用“顺序追加 磨损均衡”的方式而不是频繁擦写同一块区域。4. 常见问题与排查技巧实录这部分我把这几年调试 SPI NOR Flash 过程中遇到的典型问题整理一下都是真实踩过的坑建议收藏备用。4.1 读 ID 正常但读写失败这个现象最迷惑因为“读 ID 正常”说明 SPI 通信链路没问题接线和模式也没大问题。但读写就是失败可能原因有三类CS 时序不正确读 ID 对时序要求不严格但写操作要求 CS 必须在发完指令后精确拉高。如果你的 CS 控制函数里有额外延时或者在拉 CS 之前 SPI 还有残留数据没传完就会导致指令被截断。写保护未解除检查状态寄存器的 BP 位用0x05读出来看看值是多少。如果非 0执行写状态寄存器0x01把值改成 0x00并确认 WP 引脚没有被拉低。芯片处于 4 字节地址模式但没有统一如果芯片之前被别的程序切到了 4 字节地址模式你现在用 3 字节地址指令访问高地址区域就会失败。解决方法是用0x660x99复位芯片到默认状态。我用一个自检小函数来定位这个问题void flash_diag(void) { uint8_t mid 0, did 0; flash_read_id(mid, did); printf(Manufacturer: 0x%02X, Device: 0x%02X\n, mid, did); uint8_t sr flash_read_status(); printf(Status Register: 0x%02X\n, sr); // 写入一个测试字节再读出来对比 flash_write_enable(); flash_sector_erase(0x000000); flash_wait_busy(); uint8_t test_buf[8] {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; flash_page_program(0x000000, test_buf, 8); flash_wait_busy(); uint8_t read_buf[8]; flash_read(0x000000, read_buf, 8); for (int i 0; i 8; i) { if (read_buf[i] ! test_buf[i]) { printf(Mismatch at %d: expect 0x%02X, get 0x%02X\n, i, test_buf[i], read_buf[i]); } } printf(Diag done.\n); }跑完这个自检基本能在 5 分钟内判断问题是出在 SPI 底层、Flash 状态配置还是驱动逻辑。4.2 写入数据后读出来是 0xFF这个问题的经典原因就是写的地址没有先擦除。NOR Flash 的编程操作只能在 0已擦除状态上写如果目标地址已经被写过数据再写入会失败或者得到错误结果。很多驱动代码不检查这个因为“检查”的成本太高要先把旧数据读回来对比所以一般靠上层文件系统来保证写入前先擦除。但如果你确定已经擦除了还是出现这个现象那就是另一个问题了发送页编程指令后没有正确结束传输或者写使能没有生效。排查步骤用逻辑分析仪抓全流程确认 0x06 写使能指令确实发出。确认状态寄存器的 WEL 位在发完 0x06 之后变为了 1。确认页编程指令发出后CS 确实拉高而不是一直保持低电平。确认在下次写操作前 BUSY 位已经清 0。这里我特别强调一下有些 SPI 总线驱动的spi_transmit()函数是阻塞式的调用HAL_SPI_Transmit()时会自动处理 CS 吗不会CS 完全是你自己控制的所以很容易出现漏拉高的情况。我之前有个项目就是调试助手写的自动测试框架里忘了在页编程后拉高 CS结果连续写了几页全是 0xFF。4.3 擦除时间久、程序卡死的问题擦除操作烧录的时间确实长但程序“卡死”不一定是因为擦除本身慢更多时候是轮询 BUSY 的逻辑写得不好。我见过几个版本的问题轮询循环里没有喂看门狗在带 IWDG 的 STM32 上如果擦除期间一个扇区擦除 400ms 都不喂狗看门狗直接复位。我是在flash_wait_busy()的循环里放了一个回调函数指针让应用层注入喂狗函数。void flash_wait_busy_with_callback(void (*cb)(void)) { while (flash_read_status() 0x01) { if (cb) cb(); // 喂狗、任务切换等 delay_us(50); } }轮询超时值设置不合理超时要按最恶劣情况来扇区擦除 400ms、块擦除 2s这是芯片手册给的最坏值不是典型值。把超时设成 0xFFFFFF 然后按 SPI 时钟反算一下覆盖的范围确保足够大。擦除期间有其他任务访问 Flash如果系统里有多个任务同时访问 SPI Flash必须加互斥锁。否则总线上的数据会串包轻则读回错误数据重则把 Flash 状态寄存器写坏。4.4 SPI 时钟频率上不去的排查思路有时候同样一套驱动在开发板上跑 40MHz 没问题到了自己的 PCB 上 40MHz 就报错降到 10MHz 才恢复。这种高频失效一般不是代码问题是硬件信号完整性问题。排查建议按这个顺序来检查 SPI 信号线走线长度SCK 和 MOSI 尽量短不要超过 5cmMISO 可以稍长。过长会导致时序裕量不足。检查有没有串电阻SCK 和 MOSI 上串联 22Ω~33Ω 电阻可以抑制过冲改善信号质量。网上很多工程师在问“SPI 时钟线串电阻一般多大”经验值是 22Ω 起步如果信号过冲严重再加大到 33Ω 或 47Ω。串电阻不是降速而是通过阻抗匹配减小反射实测对波形质量提升很大。检查 MISO 线上有没有不必要的上拉/下拉MISO 在 Flash 端是推挽输出如果有外部上下拉电阻会影响边沿速率。建议 MISO 上不要加额外电阻如果要加也只是为了 ESD 防护用 10kΩ 级别的大电阻。降低 SPI 时钟频率如果工期紧直接用二分频甚至四分频跑Flash 性能损失一点但稳定压倒一切。我就吃过这个亏PCB 布局不合理导致 40MHz 下 MISO 波形变形严重最后降到 20MHz 才稳定虽然读写速度慢了一半但至少不丢数据。5. 独立实现一个精简版驱动从零手写的关键思路如果你不满足于直接用这个压缩包里的代码想自己造轮子练手或者需要把驱动精简到最小体积我建议你只保留四个核心函数flash_read、flash_page_program、flash_sector_erase、flash_wait_busy。这四个函数覆盖了 90% 的场景其他的都可以在应用层组合实现。手写驱动时最值钱的经验是先写读 ID再写擦除最后写页编程。为什么是这个顺序因为读 ID 用时序最简单的方式验证 SPI 通路擦除不涉及大量数据验证地址和指令交互页编程是数据量最大的操作放最后能避免一开始就被数据错位问题干扰。flash_wait_busy这个函数我在用的版本里加了一个“超时后自动重新初始化 SPI 外设”的逻辑这在长时间挂机测试的时候很管用。虽然很少触发但一旦触发就能让系统自动恢复不用人工重启。另外建议你给页编程加一个“重试机制”因为在 SPI 通信偶尔受干扰的情况下页编程可能失败代码里只要简单重试 3 次就能显著降低误码率。这种重试不放在 Flash 驱动层而是放在上层调用者的循环里驱动层只负责返回成功或失败。保持接口简单职责单一这比在驱动里面搞一堆复杂的状态机要可靠得多。6. 应用场景扩展这套驱动还能用在哪些地方MX25L128 和 MX25L256 这两颗 Flash 最常见的场景是固件存储但它们的应用范围远不止于此。我举三个实际遇到的场景掉电日志记录利用 NOR Flash“写入前必须擦除”的特性按扇区顺序写日志每个扇区写满再擦下一个。这样即使掉电也不会破坏已经写入的日志数据。这套驱动里的扇区擦除函数直接就能用只需要在应用层维护一个“当前写指针”。字库/图片存储如果 MCU 内存有限可以把 GBK 字库或者 UI 图片放在外部 Flash运行时按需读取。这种场景对读速度要求高建议使用 Fast Read 并开启 SPI DMA。MX25L256 有 32MB 空间存中文字库绰绰有余。固件在线升级OTA 升级时新固件先下载到外部 Flash 的临时分区校验通过后再启动 bootloader 搬运到内部 Flash。这套驱动里的扇区擦除和页编程在这个流程里扮演核心角色。注意升级过程中要防止掉电因为 NOR Flash 的页编程不具备事务性如果中途掉电可能留下半截固件所以一般会配合 CRC 校验和双备份机制。从这个角度来说这个压缩包的核心价值不是那几百行代码而是它展示的一套“如何和 SPI NOR Flash 正确打交道”的方法论。掌握了这套方法论你换任何一颗 Flash 都能快速上手这也正是资深工程师和新手之间最大的差别。本文还有配套的精品资源点击获取
返回列表