1. NOR Flash 到底是什么先搞清楚它在存储世界中的位置嵌入式系统里有一类芯片平时存在感极低但每次上电都靠它才能把 CPU 拉起来。这个芯片就是 NOR Flash很多人都把它叫做 Nor Flash甚至 NorFLash都是一个东西。严格来说NOR 指的是存储单元内部晶体管的一种排列逻辑它的名字来源于“或非门”的英文缩写跟“不是某个品牌”没有关系。这一点很多刚接触底层硬件的同学容易搞混以为 Nor 是某个厂商缩写其实不是。这一课的内容主要解决三个问题NOR Flash 为什么能存数据、它跟外部电路怎么接、硬件上挂接时有哪些坑。适合正在做嵌入式驱动开发、准备画板子选型或者刚开始读 Flash 芯片手册的工程师阅读。如果你只是写纯应用层软件可能用不太到但一旦涉及产品启动、固件升级、配置保存那你迟早要跟它打交道。NOR Flash 的定位非常有意思。它属于非易失性存储器掉电不丢数据这一点跟 EEPROM 很像。但它的容量通常比 EEPROM 大得多读取速度也快很多而且支持片内执行XIP也就是说 CPU 可以直接在 Flash 里取指运行代码不需要先拷贝到 RAM。这一条特性直接决定了它在嵌入式系统中最重要的用途——启动代码的载体。和 NAND Flash 相比NOR Flash 的容量通常偏小成本相对偏高但它的随机读取性能极好按字节寻址所有地址都可见非常适合存放代码和关键参数。我见过很多第一次做硬件的人把 16MB 的 NAND 和 8MB 的 NOR 放在一个选项里纠结其实它们两个根本不是一个路子NAND 是块设备思维按页/块读写适合大容量数据存储NOR 是内存地址映射思维直接挂在总线或 SPI 上适合代码执行和小数据存储。选错方向后续整个软件栈都会别扭。那“第十五课”这个位置放在整个嵌入式学习链路里其实很典型。前面学完 GPIO、UART、中断后面要接触 Bootloader、驱动和系统启动NOR Flash 正好是桥接底层硬件的关键一环。这一课先讲原理和硬件怎么接下一课大概率就是写驱动读写它。如果你能把 NOR Flash 这片东西吃透后面看 Bootloader 源码、分析 Flash 分区、定位启动失败都会顺畅很多。2. 存储原理浮栅晶体管是怎么记住数据的2.1 一个晶体管就是一个存储单元NOR Flash 的存储核心是一种叫做浮栅晶体管的特殊结构。普通 MOS 管只有控制栅极而浮栅晶体管在控制栅和沟道之间多了一层由多晶硅制作的浮栅层这层浮栅被绝缘介质完全包裹与外界没有直接电连接。电荷一旦进入浮栅就被“关”在里面即使断电也能保持数年甚至更长时间这个特性就是非易失性的物理根源。你可以把浮栅想象成一个密封的口袋。口袋外面有一扇可以通过电压控制的“门”当电压足够高电子就能越过绝缘层进入口袋口袋里的电子数量决定了晶体管导通阈值电压的高低。读取的时候我们并不直接触摸口袋而是感应它的状态口袋里有电荷晶体管开启需要的阈值变高给同样的读电压管子不通口袋里没电荷阈值低管子导通。这个通/断两种稳定状态就对应数字世界的 0 和 1。这里有一个容易混淆的概念NOR 和 NAND 都叫 Flash存储单元用的都是浮栅晶体管但单元之间的连接方式不同。NOR 结构中每个存储单元的一端都直接连接到 Bit Line另一端连接到 Ground形成一种“或非”逻辑关系NAND 则是把几十个存储单元串联起来像一串珠子一样接到 Bit Line。正是因为 NOR 每个单元都能独立和 Bit Line 相连它才能做到按字节随机读取而 NAND 要读某个字节得先把整串单元的状态全部读出再筛选所以天生按页访问。2.2 编程写入动作是怎么发生的NOR Flash 的写入动作不叫 write规范叫 Program。原理是利用量子隧穿效应让电子穿过绝缘层注入浮栅。具体过程是这样的在控制栅极上施加一个较高的正电压通常十几伏同时让源极接地漏极也接到一个中间电平。这时候沟道里的电子在强电场作用下获得足够能量一部分高能电子会越过绝缘层势垒被浮栅捕获。这个过程叫热电子注入。注入完成后撤掉高压电子就被困在浮栅里无法逃出单元状态从 1 变成 0。有一点必须注意闪存的编程只能把 1 变成 0不能把 0 变成 1。所有 Flash 都是这个特性包括 NAND。要让一个单元从 0 变回 1只能执行擦除操作。所以如果你在应用层对 Flash 做类似 RAM 的“覆盖写”比如原来地址存的是 0x5A你想改成 0x5B那是做不到的。正确做法是先把整个扇区擦除成 0xFF再写入新数据。这个逻辑是所有 Flash 驱动开发的基础。2.3 擦除动作是怎么发生的擦除是把浮栅里的电子全部清空让整个存储区域恢复成全 1 状态。这个过程刚好和编程相反它利用的是 F-N 隧穿效应。在擦除时控制栅极接地而衬底或者源极施加一个高压让浮栅里的电子在电场作用下以隧穿方式穿过绝缘层全部被抽出去。因为擦除不能只对单个单元操作必须按块或扇区进行所以一个扇区里的所有单元会同时被擦除变成一个整块的 0xFF。不同厂商的 NOR Flash扇区划分略有不同但主流的分法已经形成一种默契4KB 小扇区、32KB 块、64KB 块还有整片擦除。设计驱动的时候凡是代码要存参数、存日志、存升级镜像都必须结合扇区大小做好磨损均衡规划。这里有个细节很多刚入行的朋友容易忽略擦除虽然是按扇区但编程却可以按字节或者按页页大小一般是 256 字节写的时候一个时序最多写一页。2.4 为什么 NOR Flash 的读速度远高于写速度这是 NOR Flash 最容易被初学者忽略的特性。因为每个存储单元都能独立经过译码电路访问读操作只需要选中地址把对应单元的导通状态通过灵敏放大器转换成电信号就行这个路径短、时序快所以读速度很高通常能够支持工作在几十 MHz 频率的 SPI 总线上。而写操作要先擦后写擦除本身要经历高电压充到浮栅、等待隧穿完成、再放电整个过程耗时毫秒级编程同样需要维持高电压注入电子一个字节或者一个字结构不同典型时间是几十微秒。所以说 NOR 的读快、写慢这不算缺点而是 Flash 这个技术方案的固有物理特征。如果产品需要频繁写数据你得考虑是不是选 NAND 或者用外部 EEPROM 做数据冗余而不是硬扛在 NOR 上。3. 硬件接口与外围电路串行 SPI 和并行两种阵营3.1 串行 SPI NOR Flash 是当前绝对主力现在绝大多数嵌入式系统用的 NOR Flash都是串行 SPI 接口。芯片引出四根线CS 片选、CLK 时钟、MOSI 主出从入、MISO 主入从出某些型号还带 WP 写保护脚和 HOLD 保持脚。引脚少、封装小、PCB 布线简单一颗 8Mbit 的芯片SOP-8 封装就能搞定这在小体积物联网设备里非常有吸引力。SPI 接口又有几种模式常见的是标准 SPI四线、双倍速率Dual SPI和四线Quad SPI。Quad SPI 模式下把 WP 和 HOLD 脚复用成 IO2 和 IO3数据线由一根变成四根读速度能提升好几倍。很多需要快速启动的产品比如带 GUI 的嵌入式设备、需要从 Flash 加载大量资源文件的系统都会用 Quad SPI 模式。如果你做的是跑裸机或者 RTOS 的小系统标准 SPI 就够了但如果要跑 Linux尤其涉及核心启动镜像从 SPI NOR 加载我建议优先选支持 Quad 读并且带 4 字节地址模式的芯片这样后续性能余量更大。我在实际项目中就遇到过一个板子前期选了只支持标准 SPI 的片子后期想在 Flash 里放开机画面和字库读速度直接成为瓶颈最后不得不换型号折腾了一轮。3.2 并行 NOR Flash 还有哪些场景在坚守并行 NOR Flash 是早年 PC BIOS 和嵌入式系统的主力数据线 8 位或 16 位地址线一大把还带片选、读使能、写使能、输出使能等一堆控制信号。它的好处很明显直接映射到 CPU 地址空间读操作就像读 SRAM 一样不需要额外切换状态非常适合 FPGA 配置、高端 DSP 引导以及需要极高读取实时性的场合。不过现在新设计里并行 NOR 越来越少原因很直接占引脚、占 PCB 面积、良率成本都比 SPI 高。只有在一些工业控制板、老平台维护、或者对启动时间有极端要求的专用系统里你还能看到它。作为一个工程师了解并行 NOR 的引脚定义和读写时序是有价值的因为你要能读懂老的原理图、维护存量项目但新设计直接用 SPI NOR 基本够用。3.3 引脚定义与最小硬件电路设计我以一颗最常见的 8Mbit SPI NOR Flash 为例梳理最小硬件电路需要哪些东西电源大多数型号支持 1.8V 或 3.3V 供电两个电压版本不能混用。电源引脚旁边必须放置两个去耦电容一个 100nF一个 10μF靠近芯片电源引脚摆放。很多小批量样板画得太随意电容离芯片两三厘米高速 SPI 读的时候波形畸变严重会出现偶发读错数据。CS 片选必须接 MCU 的 GPIO 或者 SPI 片选。如果片选脚悬空芯片可能会误进入非预期的状态尤其是上电瞬间噪声可能导致 Flash 被误选中。建议在硬件上对 CS 做上拉保证默认高电平。CLK 时钟SPI 时钟脚MCU 侧配置成输出。时钟频率上限看芯片手册不同厂商、不同电压域差别很大从 20MHz 到 133MHz 都有。新手设计时容易犯的错是不看芯片的最高时钟能力直接往 SPI 控制器配置里填一个很高的分频导致数据采样时序不满足。MOSI/MISO数据线。MOSI 走 MCU 到 FlashMISO 走 Flash 到 MCU。这两根线一般不需要额外上拉但如果你用的是 Quad SPI 模式IO2/IO3 必须注意它们复用自 WP 和 HOLD 引脚这两个脚如果悬空读取 ID 没问题但一旦要读数据时芯片检测到 WP/HOLD 电平不确定可能会锁死操作。设计时要把这两个脚通过电阻上拉或下拉固定电平。WP 写保护低电平有效。如果这个脚被拉低则受保护区域的写操作会被拒绝。设计上建议默认拉高让芯片处于允许编程状态。如果要搞代码保护才通过软件控制它拉低。HOLD 保持低电平有效。HOLD 拉低后芯片暂停通信时钟变化不影响内部状态。这个脚如果不使用必须接高电平很多随机读失败的板子问题就出在 HOLD 悬空。还有一个非常容易忽略的引脚是 RESET 复位脚部分型号带硬件复位引脚。如果你的系统里 Flash 挂了 RESET要确保上电时它能正常拉高设备树里也要配置对应 GPIO。我在一个产品调试里就踩过这个坑Flash 带 RESET但驱动没有主动释放它导致每次开机 Flash 都处于复位未完成状态驱动读取 ID 超时。3.4 硬件设计常见踩坑点做 NOR Flash 相关硬件设计有几个高频问题我基本每次评审原理图都要提醒一遍第一电源稳压器选型时只看了电压没看峰值电流。Flash 在擦除瞬间电流会有一个脉冲尖峰如果稳压器输出能力不足或者去耦不够擦除状态下电源电压会跌落导致擦除失败或擦除结果不完整。这类问题非常难排查因为你看示波器时故障是偶发的而且擦除失败后 Flash 里的旧数据已经被半抹掉非常尴尬。第二SPI 信号串联电阻的使用。对于短距离板内走线一般不需要串电阻但如果 Flash 离 MCU 比较远或者走线经过带状线、过孔数多反射会导致眼图恶化。常规做法是在 MOSI、CLK 上各串联 22Ω 或 33Ω 电阻MISO 上视情况加。别小看这几个电阻它能有效抑制过冲降低信号完整性风险。第三多片 Flash 或 Flash 与其他 SPI 器件共享总线。两条 SPI 设备的片选不能同时有效这要求驱动层严格管理片选时序否则总线数据出现不确定状态。硬件设计上最好给每个 SPI 设备独立的片选 GPIO并且不要共用 CS 信号。第四需要仔细阅读芯片手册里的上电时序要求。大多数 NOR Flash 对 VCC 上升时间有要求如果太慢芯片内部上电复位电路工作异常可能导致第一次读 ID 失败必须做二次读取或者软件复位。硬件上如果电源上升时间可控这个问题一般能规避。4. 芯片选型与关键参数容量、块大小、电压和速度怎么定4.1 容量和地址模式市面上常见的 SPI NOR Flash容量从 512Kbit 到 256Mbit 都有但嵌入式产品用得最多的是 4Mbit 到 32Mbit。选容量不是拍脑袋而是看用途精确算出来的。比如一个物联网网关Bootloader 放 64KB内核镜像放 2MB文件系统放 6MB再留一部分做参数备份和升级缓存那总容量至少 10MB。有人可能会问为什么不干脆选 16MB省心。理论可以但要注意容量超过 16MB也就是 2 字节地址无法覆盖时芯片必须切换到 4 字节地址模式。有些老驱动只写死了 3 字节地址导致读取后半段地址空间时数据错乱。所以选容量的时候驱动方案要同步评估别只盯价格。厂家手册里的容量常常写的是 bit 单位比如 8Mbit对应的实际容量是 1MB。很多新手在老工程师面前说“我的 Flash 是 8MB”其实别人心里在换算成 1MB。为了减少这种误会我在团队里统一要求方案阶段全部用 Byte 单位沟通。4.2 块大小与磨损均衡SPI NOR Flash 的扇区大小直接决定驱动复杂度。最常见的扇区是 4KB这也就是说你每次改一个小参数最小单位也得擦掉 4KB 的一片区域。如果你的程序每次写一个字节都先擦一个扇区那这个扇区很快就会被磨损坏。所以做参数存储的时候常见的做法是分配两个或多个扇区轮转写入每次写新值之前先把旧值统一标记失效写完一个扇区再切换下一个。这种方法能把寿命放大很多倍代价是驱动逻辑稍微复杂一点。擦除次数上限一般标称 10 万次但实际芯片在多次擦写后阈值漂移数据保持时间会显著缩短。如果是产品样机可能感觉不到真正量产几年后老批次芯片出问题的概率就会显现。4.3 电压域和电平匹配很多国产 MCU 使用 1.8V 或 3.3V 供电Flash 的输入电平必须匹配。如果 MCU 是 3.3VFlash 是 1.8V直接连接会击穿 Flash 内部电路反过来 MCU 1.8V Flash 3.3V那 Flash 就无法正确识别高电平。需要做电平转换时我一般倾向选择支持宽电压域的 Flash比如 1.65V 到 3.6V 全域支持这样一颗芯片在不同板卡之间复用更方便。另外SPI 信号的电平阈值并不只看标称电压还要参考 VIH/VIL 参数。如果总线上有其他器件把高电平拉到 2.5V而 Flash 的 VIH 最低是 0.7×VCC约 2.31V 在 3.3V 供电下那 2.5V 其实是可以被正确识别的。但这些边界余量小高速状态不稳定稳妥设计还是保证高电平接近电源电压。4.4 时钟频率和温度等级SPI 时钟频率不能一味追高。芯片标称 133MHz那是读操作在一个特定电压范围和特定电容负载条件下的能力实际 PCB 寄生电容、走线长度都会影响上限。我通常预留 30% 裕量比如芯片标称 100MHz驱动里配置成 70MHz 左右牺牲一点性能换取稳定性。工业级温宽-40 到 85 度是基本要求如果在户外设备或者高温环境得选 AEC-Q100 等级或者指定工业级版本。消费级芯片在高温下写入时间会变长、数据保持能力下降这是物理特性决定的不是玄学。5. 实操记录从原理图到驱动点亮一颗 NOR Flash5.1 硬件连接示例我最近调试的一块板子MCU 主控用的是某国产 Cortex-M 系列Flash 是一颗 16Mbit 的 SPI NOR3.3V 供电原理图连接如下CS 接 MCU PB12默认上拉 10K 到 3.3VCLK 接 MCU PB13MOSI 接 MCU PB15MISO 接 MCU PB14WP 引脚接 10K 上拉到 3.3VHOLD 引脚接 10K 上拉到 3.3VVCC 接 3.3V旁边放 100nF 和 4.7μF 去耦电容GND 直连地平面这几个引脚的上拉电阻非常关键。我之前调试时WP 和 HOLD 直接用飞线悬空芯片读 ID 一切正常但执行写入命令时状态寄存器里的 WIP 位一直不翻转卡死在那里。量了半天电压发现 HOLD 引脚在噪声干扰下低于阈值芯片被拉进保持状态数据全被忽略。5.2 初始化与读取 JEDEC ID上电后第一步不是读任何数据而是发一个软件复位命令确保芯片从任何异常状态中恢复。然后读 JEDEC ID这个命令固定是 0x9F芯片会回传厂商 ID、设备 ID 和额外 ID。我用示波器抓过时序确保 SPI 模式配置正确。大部分 SPI NOR 支持 Mode 0 或 Mode 3两者区别是时钟极性 CPOL 和相位 CPHA 的不同。配置错了读回来的数据全是 0xFF看起来像芯片没焊接好。排查这类问题示波器看 CLK 空闲电平和采样沿比反复改代码更高效。初始化代码的思路是拉低 CS发送 0x9F连续读 3 个字节最后拉高 CS。返回的第一个字节是厂商 ID比如某厂商的 ID 是 0xEF某国产厂商是 0xC8。读不到合法 ID说明要么接线错误、要么芯片没进入正常状态、要么 SPI 外设配置不对先别急着调驱动程序把物理层搞定再说。5.3 读数据的标准流程读数据的核心命令是 0x03普通读命令。地址可以是 3 字节也可以是 4 字节取决于容量和是否进入 4 字节模式。发送命令加地址后主控连续发送时钟Flash 从 MISO 串出数据直到片选拉高。这里有个性能优化点。普通读命令 0x03 没有最大频率限制但每次读都需要先把地址发送出去对于大块数据读取地址发送时间占比很高。快速读命令 0x0B 增加了 dummy byte让高频信号有时间稳定同时支持更高的时钟频率。如果内核镜像比较大建议用快速读命令做批量读取。实际项目里我遇到过读大文件时数据随机错误的情况。定位过程是先看波形发现 MISO 波形上冲明显再测 MISO 信号到 MCU 引脚的走线长度发现因为布局问题绕了一大圈串联电阻没加。后来在 MISO 上加 33Ω 串联电阻并且把 SPI 时钟从 80MHz 降到 50MHz问题立刻消失整包固件校验通过。5.4 写入和擦除的标准流程写入前必须先擦除。擦除命令 0x20 擦除 4KB 扇区0xD8 擦除 64KB 块0xC7 是整片擦除。发擦除命令之前先发 0x06 写使能命令把状态寄存器里的 WEL 位置为 1。如果 WEL 位是 0任何写和擦除操作都会被忽略。擦除命令完成后状态寄存器里的 WIP 位为 1表示芯片正忙。软件需要轮询读 0x05 命令直到 WIP 位变成 0。注意这期间不能对同一个芯片执行其他操作片选时序和状态轮询的节奏必须配合好。写入命令 0x02 是页编程一次最多写 256 字节。如果数据跨越页边界必须拆分成多次页编程。很多人第一次写 Flash 驱动误以为可以连续发一长串数据结果写到页尾就停了后面的数据直接丢。因为 Flash 内部只能把数据先锁存在页缓冲区然后一次性写入跨页数据必须分两次写。正确的写流程是写使能发页编程命令发地址发不超过 256 字节的数据拉高片选轮询 WIP 位直到为 0再继续下一页。这个流程做完读回校验数据一致才算写入成功。6. 常见问题与排查技巧实录启动时读 ID 超时先确认电源电压到达芯片引脚的波形看有没有跌落然后确认 CS 信号默认高电平CLK 极性配置正确最后检查 HOLD 和 WP 引脚是否固定电平。这四步能解决 90% 的读 ID 问题。读数据全是 0xFF大概率是片选没拉低或者 SPI 模式配置错。0xFF 全高说明 Flash 没有被正确选中或者数据线没有驱动。小概率是芯片本身没焊接好用万用表测引脚对地阻值辅助判断。写使能后 WIP 位不翻转典型原因是 WP 引脚被拉低了写保护生效。有的芯片写保护是块级配置即使 WP 引脚拉高如果状态寄存器里的保护位被设过也需要先解除保护再执行擦写。具体要看芯片手册的 BP 位配置逻辑。擦除后数据还是旧的检查擦除地址是否对齐。擦除命令的地址必须落在扇区起始地址上如果地址给的是扇区中间某个值擦除操作不会生效这是很多驱动刚开始调试时都会犯的错误。擦除之前先做地址掩码对齐把低 12 位清零4KB 扇区再发命令。数据写入后读回不一致优先检查是否跨页。写入地址加数据长度如果超过页边界后面的数据不会写进去。其次是电源稳定性尤其是同时擦写多页时VCC 跌落会导致编程电压不足单元没有正确注入电荷读回时表现为随机跳变。时间长了数据丢失或启动不稳定考虑是不是频繁操作同一扇区磨损导致数据保持能力下降。我见过一块板子日志系统每 10 秒写一次同一个扇区一年不到就出问题。后来改成双扇区轮转存活周期增长了十倍以上。7. 最后分享一点个人体会NOR Flash 这个器件表面看起来非常简单无非就是读、写、擦三个命令但实际项目中牵扯到的物理特性、时序约束、硬件电路、驱动状态管理是一整套体系。很多人学习时只看驱动源码忽略硬件信号完整性和芯片手册出了问题之后只能反复盲调效率很低。我自己的建议是拿到一块新板子第一步先不要急着写业务代码用逻辑分析仪或者示波器抓一遍读 ID 的时序亲眼确认 CLK、CS、MOSI、MISO 的波形关系这比任何驱动框架都更能帮你建立直觉。接着做一次扇区擦除和页写入验证 Flash 的物理操作正常再往上叠抽象层。另外做产品化设计时别把 Flash 当成无限耐用的大仓库。它写次数有限、擦除时间较长、读写速度不对称这些物理特性要在项目早期就纳入设计考量。我见过太多方案代码写得很漂亮但存储规划一团糟最后量产阶段频繁返工。先把这些底层细节吃透后续做 Bootloader、OTA 升级、文件系统你都会感觉顺手很多。这一课先把原理和硬件讲透下一课自然会进入驱动代码实现和状态机控制。如果你也在调试 SPI NOR Flash遇到硬件或者驱动上的问题按照前面排查列表的顺序逐项确认基本上都能定位到根因。