韦东山开发手册阅读笔记(一)

35 阅读21分钟

1、Windows/Linux基础与工具

1.1 GCC 编译

一个 C/C++文件要经过

  1. 预处理(preprocessing)
  2. 编译(compilation)
  3. 汇编(assembly)
  4. 和连接(linking)

等 4 步才能变成可执行文件,如在日常交流中通常使用“编译” 统称这 4 个步骤,

假设有一个源文件 hello.c

1.1.1 预处理 (Pre-processing)

  • 做了什么: “大扫除”。处理所有以 # 开头的指令。

    • 展开宏定义:#define PI 3.14 全部替换成 3.14
    • 包含头文件:#include <stdio.h> 的内容直接“复制粘贴”到你的代码里。
    • 处理条件编译: 比如 #ifdef DEBUG
    • 去掉注释: 让代码变得干干净净。
  • 指令: gcc -E hello.c -o hello.i

  • 产物: .i 文件(依然是文本,但体积变大了很多)。

1.1.2. 编译 (Compilation)

  • 做了什么: “翻译官”。这是最核心的一步。GCC 把你写的 C 语言翻译成 汇编语言。这时候编译器会检查你的语法错误(比如少个分号)。
  • 指令: gcc -S hello.i -o hello.s
  • 产物: .s 文件(汇编代码,你能看懂一点点指令,如 mov, push)。

1.1.3. 汇编 (Assembly)

  • 做了什么: “降维打击”。把汇编代码翻译成机器能听懂的 二进制指令(机器码)
  • 指令: gcc -c hello.s -o hello.o
  • 产物: .o 文件(目标文件/Object file)。它是二进制的,你直接打开会看到一堆乱码。

1.1.4. 链接 (Linking)

  • 做了什么: “组装车间”。你的程序可能调用了别人的库(比如 printf 函数是在 C 标准库里的)。链接器把你的多个 .o 文件和系统提供的库文件(.so.a)“粘”在一起,生成最终的可执行文件。
  • 指令: gcc hello.o -o hello
  • 产物: 可执行文件(Linux 下通常没有后缀,或者叫 a.out)。

1.2 gcc vs arm-linux-gcc 的本质区别

在 Windows下进行开发时,只需要单击几个按钮即可编译,集成开发环境(比如 Visual studio)已经将各种编译工具的使用封装好了。

Linux 下也有很优秀的集成开发工具,但是更多的时候是直接使用编译工具;即使使用集成 开发工具,也需要掌握一些编译选项。

  • PC 上的编译工具链为 gcc、ld、objcopy、objdump 等,它们编译出来的程序在 x86 平台上运行。
  • 要编译出能在 ARM 平台上运行的程序,必须使用交叉编译工具 arm-linux-gcc、

它们就像是两个翻译官。虽然他们都叫 GCC(GNU Compiler Collection),但他们的“母语”和“目标语言”不同。

我们可以用 “宿主机(Host)”“目标机(Target)” 的概念来理解:

特性gcc (本地编译器)arm-linux-gcc (交叉编译器)
运行在哪里本地电脑 (x86)本地电脑 (x86)
产出的程序跑在哪里本地电脑 (x86)嵌入式开发板 (ARM)
链接的库标准的 x86 版 Glibc 库针对 ARM 指令集优化的库

1.3 Makefile

在 Linux 中使用 make 命令来编译程序,特别是大程序;而 make 命令所执行的动作依赖于 Makefile 文件。 Makefile 的存在只为了解决两件事:

  1. 自动化编译: 敲一个 make 命令,全部搞定。
  2. 增量编译(核心价值): 它会检查哪个文件改动过,只重新编译改动过的部分,没动过的直接链接,极大地节省时间。

2、 GPIO 接口

GPIO(General Purpose I/O Ports)意思为通用输入/输出端口,通俗地说,就是一些引脚, 可以通过它们输出高低电平或者通过它们读入引脚的状态——是高电平还是低电平。

2.1 硬件核心:三个“控制面板”(寄存器)

芯片内部为每一组引脚都准备了三个 32 位的“控制面板”。通过 C 语言往这些地址写数据,就能改变引脚的物理特性。

寄存器名称核心功能理解视角
GPxCON配置 (Configuration)决定引脚的身份。是当“输出”(发信号控制激光),还是“输入”(读传感器信号)?
GPxDAT数据 (Data)真正的开关。配置为输出时,写 1 引脚变高电平(3.3V),写 0 变低电平(0V)。
GPxUP上拉 (Pull-up)决定引脚悬空时的状态。开启上拉能防止引脚因为电磁干扰而产生随机信号(这对读取稳定信号至关重要)。

2.2 怎样使用软件来访问硬件

单个引脚的操作无外乎 3 种:输出高低电平、检测引脚状态、中断。对某个引脚的操作 一般通过读、写寄存器来完成。 在 Linux C 编程中,看到的地址只是内存;但在嵌入式底层,地址就是硬件。

// 1. 定义地址:假设 0x56000050 是 GPFCON 寄存器的物理地址
#define GPFCON (*(volatile unsigned long *)0x56000050)

// 2. 操作硬件
GPFCON = 0x100; // 这行代码执行后,芯片某个引脚的物理电压可能瞬间改变

2.3. 位运算

  • | (或): 有 1 则 1。常用于 “置 1” (打开开关)。

  • & (与): 全 1 才 1。常用于 “清 0”“检测”

  • ~ (取反): 1 变 0,0 变 1。常用于 “构造掩码”

  • << (左移): 移动 1 的位置。 例

  1. 公式: Register |= (1 << n)(将某一位“置 1”)
  2. 公式: Register &= ~(1 << n)(将某一位“清 0”)

3、内存管理单元 MMU

内存管理单元(Memory Management Unit)简称 MMU,它负责虚拟地址到物理地址的 映射,并提供硬件机制的内存访问权限检查。现代的多用户多进程操作系统通过 MMU 使得 各个用户进程都拥有自己独立的地址空间:地址映射功能使得各进程拥有“看起来”一样的 地址空间,而内存访问权限的检查可以保护每个进程所用的内存不会被其他进程破坏。

  • 虚拟地址 (Virtual Address): 程序员看到的地址。在 32 位系统里,每个进程都以为自己拥有从 0x000000000xFFFFFFFF 的完整 4GB 空间。

  • 物理地址 (Physical Address): 内存条上实打实的电子存储单元地址。

虚拟地址最终需要转换为物理地址才能读写实际的数据,这通过将虚拟地址空间、物理 地址空间划分为同样大小的一块块小空间(称为段或页),然后为这两类小空间建立映射关系。 由于虚拟地址空间远大于物理空间,有可能多块虚拟地址空间映射到同一块物理地址空间, 或者有些虚拟地址空间没有映射到具体的物理地址空间上去(可以在使用到时再映射)。如图 7.1 所示为这些映射关系。 image.png

虚拟地址是一个索引组合。MMU 像翻字典一样,根据 TTB 指向的首页,一页页翻下去,最后加上偏移量,找到物理内存。

3.1 虚拟地址到物理地址的转换过程

将一个虚拟地址转换为物理地址,一般有两个办法:用一个确定的数学公式进行转换或用 表格存储虚拟地址对应的物理地址。这类表格称为页表(Page table),页表由一个个条目(Entry) 组成;每个条目存储了一段虚拟地址对应的物理地址及其访问权限,或者下一级页表的地址。 在 ARM CPU 中使用第二种方法。S3C2410/S3C2440 最多会用到两级页表:

  • 以段(Section, 1MB)的方式进行转换时只用到一级页表,
  • 以页(Page)的方式进行转换时用到两级页表。

页的大小有 3 种:大页(64KB)、小页(4KB)、极小页(1KB)。 条目也称为“描述符”(Descriptor), 有:段描述符、大页描述符、小页描述符、极小页描述符——它们保存段、大页、小页或极小页的起始物理地址;粗页表描述符、细页表描述符——它们保存二级页表的物理地址。 “TTB base”代表一级页表的地址

大概的转换过程如下,请参考图 7.3(通用的转换过程):

  • (1)根据给定的虚拟地址找到一级页表中的条目;
  • (2)如果此条目是段描述符,则返回物理地址,转换结束;
  • (3)否则如果此条目是二级页表描述符,继续利用虚拟地址在此二级页表中找到下一个条目;
  • (4)如果这第二个条目是页描述符,则返回物理地址,转换结束;
  • (5)其他情况出错

image.png

image.png

1. TTB (Translation Table Base)

一句话解释:TTB 是 CPU 内部的一个硬件寄存器,里面只存一样东西——“第一级页表(大字典)在物理内存中的起始地址”。

  • 它的作用: MMU 是个瞎子分拣机器,当它拿到一个虚拟地址准备查表时,它根本不知道那张“表(字典)”放在物理内存的哪个角落。TTB 寄存器就是 MMU 的“导航仪”。
  • 工作机制: 当 Linux 准备运行你的 C 程序时,它会在内存里帮你建好一堆页表,然后把这堆页表最开头那个字节的物理地址,强行塞进 CPU 的 TTB 寄存器里。MMU 拿到这个基址,才能开始后面的查表工作。
2. 描述符 (Descriptor)

一句话解释:描述符就是页表(字典)里的“一行数据”。在 32 位系统中,它刚好是一个 32 位(4 字节)的整数。

当你查字典查到某一行时,这一行(32 个 bit)不仅仅告诉你“物理地址在哪”,它还包含了非常多控制芯片硬件的“附加指令”。

一个典型的描述符(32 位)被切分成了几个区域:

  • [31:20] 物理基址: 告诉你最终的物理地址前缀(或者下一级字典的地址)。
  • [11:10] AP (访问权限): 规定这段内存是“只读”、“读写”还是“禁止用户态访问”。
  • [3] C 位 (Cacheable): 告诉 CPU,读取这块内存时,要不要放进高速缓存(Cache)里。
  • [1:0] 类型位: 告诉 MMU,我现在这条记录,是一个 1MB 的段描述符,还是一个指向二级页表的粗页表描述符?
3. MMU 怎么“加上虚拟地址后面的偏移量”?

这是一个纯粹的二进制拼接魔术。我们以 1MB 的“段映射”为例(查一次表)。

1MB 的空间需要 20 位二进制来表示地址(220=1 MB2^{20} = 1\text{ MB})。

假设你的程序要访问虚拟地址:0x30001234

  • 切分虚拟地址: MMU 自动把这个 32 位的地址切成两半。

    • 前 12 位(高位): 0x300。这是 “目录索引” 。MMU 拿着它去页表里找第 0x300 行的描述符。
    • 后 20 位(低位): 0x001234。这就是 “偏移量” (页内地址)。它代表你要找的数据,在那个 1MB 厂房里的具体相对位置。
  • 拼接物理地址:

    • 假设 MMU 查表发现,第 0x300 行的描述符写着:物理首地址是 0x40000000
    • MMU 直接把物理首地址(前缀)和偏移量(后缀)拼在一起(在底层其实就是做加法或位或运算)。
    • 最终物理地址 = 0x40000000 + 0x001234 = 0x40001234
4. 为什么 4KB 小页要搞“两级”?为什么说两级反而省内存?

你的直觉非常敏锐:一本字典变成两本字典,怎么反而省空间了?

核心破局点在于:在单级页表下,字典必须“全量印刷”;但在多级页表下,字典可以“按需印刷”。

我们来算一笔硬核的账:

前提: 32 位系统有 4GB(2322^{32} 字节)的虚拟地址空间。每个描述符占 4 字节。我们希望以 4KB 为粒度来管理内存。

方案 A:只用一级页表(一本超级大字典)

如果我们硬要用一级页表来管理 4KB 的页,那么 4GB 空间会被切成 100 万个页4 GB/4 KB10000004\text{ GB} / 4\text{ KB} \approx 1000000)。

  • 那么这本“大字典”必须要有 100 万行。
  • 字典体积 = 100 万 ×\times 4 字节 = 4 MB4\text{ MB}
  • 致命缺陷: 哪怕你的 C 程序只写了一句 printf("hello");,只用了几 KB 的内存,MMU 的硬件机制也要求这本 4 MB4\text{ MB} 的大字典必须完整、连续地存放在物理内存中!系统里如果有 100 个进程,光存字典就要吃掉 400 MB400\text{ MB} 的物理内存。这在当年内存只有 64 MB64\text{ MB} 的板子上是不可接受的。
方案 B:用两级页表(一本薄总目录 + 若干本薄分册)
  • 第一级字典(总目录): 按照 1MB 来划分,一共只有 4096 行。

    • 总目录体积 = 4096 ×\times 4 字节 = 16 KB16\text{ KB}
    • 这本总目录是必须常驻内存的。
  • 第二级字典(分册): 每一本分册负责管理那 1MB 里的 256 个 4KB 小页。

    • 单本分册体积 = 256 ×\times 4 字节 = 1 KB1\text{ KB}
  • 奇迹时刻(按需分配): 如果你的程序只用了 0 到 1MB 的地址,Linux 内核只会为你创建 1 本“分册” 。剩下的 4095 本分册,因为你没用到那部分地址,内核根本就不会在内存里创建它们!

  • 实际字典体积: 16 KB16\text{ KB}(总目录)+ 1 KB1\text{ KB}(一本分册)= 17 KB17\text{ KB}

结论:

  • 一级页表:消耗 4 MB4\text{ MB}

  • 两级页表:最低仅消耗 17 KB17\text{ KB}

3.2 TLB (Translation Lookaside Buffer)

从虚拟地址到物理地址的转换过程可知:使用一级页表进行地址转换时,每次读/写数据 需要访问两次内存,第一次访问一级页表获得物理地址,第二次才是真正的读/写数据;使用 两级页表时,每次读/写数据需要访问 3 次内存,访问两次页表(一级页表和二级页表)获得 物理地址,第三次才是真正的读/写数据,太慢了!

  • TLB 是什么: 它是 MMU 内部一块极其昂贵、速度极快的硬件缓存,专门用来记录“刚刚查过的字典条目”。
  • 工作机制当 MMU 拿到虚拟地址时,先不查内存里的页表,而是先看一眼 TLB:“我之前有没有查过这个地址?”如果查过(TLB Hit),直接拿到物理地址,瞬间完成转换。只有没查过(TLB Miss),才会老老实实去内存里翻字典,翻完把结果再记到 TLB 里

3.3内存访问权限检查 (AP & Domain)

内存的访问权限检查是 MMU 的主要功能之一,简单地说,它就是决定一块内存是否允 许读、是否允许写。

3.4 Cache 的作用

同样基于程序访问的局部性,在主存和 CPU 通用寄存器之间设置一个高速的、容量相对 较小的存储器,把正在执行的指令地址附近的一部分指令或数据从主存调入这个存储器,供 CPU 在一段时间内使用,这对提高程序的运行速度有很大的作用。这个介于主存和 CPU 之 间的高速小容量存储器称作高速缓冲存储器(Cache)

启用 Cache 后,CPU 读取数据时,如果 Cache 中有这个数据的复本则直接返回,否则从主存 中读入数据,并存入 Cache 中,下次再使用(读/写)这个数据时,可以直接使用 Cache 中的复本。

4、存储控制器

在嵌入式系统中,存储器按掉电后数据是否丢失,严格划分为两大阵营:

  1. RAM (Random Access Memory, 随机存取存储器) :易失性(Volatile),掉电数据丢失,速度极快,作为系统的运行内存
  2. ROM / Flash (Read-Only Memory / Flash Memory) :非易失性(Non-Volatile),掉电数据不丢失,作为系统的程序与数据仓库

4.1、 RAM

4.1.1. SRAM (Static RAM - 静态随机存储器)

  • 物理原理:利用交叉耦合触发器(通常是 6 个晶体管/6T)锁住状态。只要不断电,数据永远保持。

  • 核心特点

    • 极速:读写速度纳秒级,几乎可以和 CPU 同频。
    • 简单:不需要外部复杂的控制器,不需要定时刷新。
    • 昂贵且占面积:容量极小。
  • 工程应用:CPU 内部的 L1/L2 Cache(高速缓存);单片机(MCU,如 STM32)内部的微小运行内存(通常几十到几百 KB)。

4.1.2. DRAM / SDRAM (Dynamic RAM - 动态随机存储器)

  • 物理原理:利用极其微小的电容充放电来存储数据(1个晶体管+1个电容/1T1C)。有电荷为 1,无电荷为 0。

  • 致命弱点 (漏电) :电容存在天然漏电效应。必须依靠专门的内存控制器周期性地给电容“充电”,这个动作称为刷新 (Refresh)

  • 核心特点:集成度极高,容量大,成本低,但控制逻辑极其复杂。

  • 工程应用:开发板、电脑、手机的系统主存

    注:SDRAM 中的 "S" 代表 Synchronous(同步),即它的读写受统一的系统时钟(HCLK)控制。

4.1.3 SRAM 与 SDRAM 核心对比

特性SRAM (静态内存)SDRAM (动态内存)
存储介质触发器 (晶体管锁存)电容 (充放电)
是否需刷新 (极其依赖硬件控制器)
读写速度极快 (纳秒级)较快
集成度/容量低 / 小 (KB 级)高 / 大 (MB/GB 级)
成本极其昂贵相对便宜

4.2 ROM / Flash

4.2.1. 早期 ROM 家族

  • Mask ROM (掩膜 ROM) :出厂时数据直接被光刻在硅片上,绝对无法修改。
  • PROM / EPROM:可编程/紫外线可擦除,操作繁琐,已被淘汰。
  • EEPROM (电可擦除可编程 ROM) :可以按字节 (Byte) 进行修改,但容量极小且速度慢,常用于保存少量的系统配置参数(如 MAC 地址)。

4.2.2. 现代主流:Flash (闪存)

Flash 是 EEPROM 的演进,它是现代嵌入式固件存储的绝对主力,分为:

A:NOR Flash (主打“直接运行”)
  • 物理特性:地址线和数据线独立分开,总线接口与 SRAM 类似。
  • 杀手锏 (XIP) :支持 eXecute In Place (片内执行) 。CPU 可以直接通过地址总线给出指针,按字节从 NOR Flash 里读取机器码并直接执行,无需搬运到 RAM 中。
  • 缺点:写入和擦除速度慢,容量小。
  • 工程应用:存放微控制器 (MCU) 的业务代码;存放高级 ARM 处理器的 Bootloader (如 U-Boot);电脑主板的 BIOS。
B:NAND Flash (主打“海量存储”)
  • 物理特性:地址、数据、命令复用同一组 I/O 引脚(如 8 根线)。
  • 操作铁律:不能按字节随机读写。只能按“页 (Page, 如 2KB/4KB)”读写,按“块 (Block, 如 128KB)”擦除。
  • 核心限制绝对不支持 XIP。代码不能直接在 NAND Flash 上运行,必须先由硬件或 Bootloader 搬运到 SDRAM 中,CPU 才能执行。
  • 工程应用:存放 Linux 内核镜像、根文件系统、用户数据;U盘、SSD、eMMC 的底层物理介质。

4.2.3 NOR Flash 与 NAND Flash 核心对比

特性NOR FlashNAND Flash
总线结构独立地址/数据总线地址/数据/命令复用总线
XIP (片内执行)完全支持绝对不支持
读写单位按字节读 / 按块擦除读写 / 按擦除
读取速度非常快较快
写入/擦除速度非常快
坏块管理出厂几乎无坏块出厂可能有坏块,需软件/硬件 ECC 校验
主要用途运行引导代码 (Bootloader/MCU 固件)海量数据存储 (OS 镜像/文件系统/视频算法模型)

5、NAND Flash 控制器

NAND Flash 在嵌入式系统中的地位与 PC 上的硬盘类似,用于保存系统运行所必需的操 作系统、应用程序、用户数据、运行过程中产生的各类数据。与内存掉电后数据丢失不同, NAND Flash 中的数据在掉电后仍可永久保存。

5.1. NAND Flash 与 NOR Flash 的本质区别

image.png

  • NOR:支持 XIP(片上执行),随机读取快,但写入/擦除慢,容量小。
  • NAND:不支持 XIP,顺序访问快,容量大,成本低,但可靠性低(需要 ECC 和坏块管理)。

5.2 NAND Flash 的物理结构

以 NAND Flash K9F1208U0M 为例

宏观结构 —— 页(Page)和块(Block)

NAND Flash 存储结构(自底向上):

┌─────────────────┐
│   整个芯片       │  ← 64MB = 4096 个 Block
├─────────────────┤
│   Block #4095   │  ← 1 个 Block = 32 个 Page
│   Block #4094   │     擦除的最小单位(16KB)
│       ...       │
│   Block #0      │
├─────────────────┤
│   Page #31      │  ← 1 个 Page = 512B 数据 + 16B OOB
│   Page #30      │     读/写的最小单位
│       ...       │
│   Page #0       │
└─────────────────┘

核心概念:

概念大小作用类比
Page(页)512B + 16B OOB读写的最小单位书本的「一页」
Block(块)32 页 = 16KB擦除的最小单位书本的「一章」
OOB16 字节/页存 ECC、坏块标记等「元数据」页边的「批注区」

⚠️ 关键约束:写之前必须先擦除整个 Block,且只能把 1→0,不能直接 0→1。

5.3 引脚与通信方式 —— 为什么地址要「分 4 次发」?

5.3.1. 引脚极简设计

传统 RAM:地址线 26 根 + 数据线 8 根 = 34 根引脚 ❌ 太贵

NAND Flash:
┌─────────────────┐
│ IO0~IO7  │ 8 根 │ ← 地址/命令/数据 复用这 8 根
│ CLE      │ 1 根 │ ← 告诉 Flash:现在传的是「命令」
│ ALE      │ 1 根 │ ← 告诉 Flash:现在传的是「地址」
│ nWE/nRE  │ 2 根 │ ← 写/读使能
│ nCE      │ 1 根 │ ← 片选,选中这个芯片
│ R/nB     │ 1 根 │ ← 忙/就绪状态
└─────────────────┘
总共才 14 根引脚 ✅ 省钱省空间
本质:用**时间换空间**——串行传 26 位地址(4 个周期),换取引脚减少 70%

5.3.2. 26 位地址怎么通过 8 根线传?→ 分时复用 + 4 个周期

64MB = 2^26 字节 → 需要 26 位地址

传输方式(每次传 8 位):
┌────┬────┬────┬────┐
│周期1│周期2│周期3│周期4│
├────┼────┼────┼────┤
│A0~A7│A9~A16│A17~A24│A25+保留│
│列地址│ 行地址低 │ 行地址中 │ 行地址高 │
└────┴────┴────┴────┘

时间轴:
┌────────┬────────┬────────┬────────┐
│ 传命令 │ 传地址 │ 传数据 │ 传状态 │
│ 0~10ns │10~20ns │20~50ns │50~60ns │
└────────┴────────┴────────┴────────┘
     ↑        ↑        ↑        ↑
  CLE=1    ALE=1   CLE=0    读 NFDATA
  ALE=0    CLE=0   ALE=0
  
一页数据区 = 512 字节 = 2^9 → 需要 9 位地址 (A0~A8)
但列地址寄存器只有 8 位 (A0~A7) → 最多寻址 256 字节

💡 解决方案:用命令字"免费"携带第 9 位地址 (A8)
┌──────────┬────────┬────────┐
│ 命令字   │ 隐含 A8 │ 访问区域 │
├──────────┼────────┼────────┤
│ 00h      │ A8=0   │ A 区 (0~255)  │
│ 01h      │ A8=1   │ B 区 (256~511)│
└──────────┴────────┴────────┘

📌 注意:没有 A8! A8 由读命令隐含决定(命令 00h → A8=0,命令 01h → A8=1),这是设计巧思。

5.4 一页 528 字节寻址

一页 528 字节布局:
┌─────────────────────────────┐
│ 0    255256   511512 527 │
│   A区    │   B区    │  C区   │
│ (数据)   │  (数据)  │ (OOB)  │
└─────────────────────────────┘
     ↑           ↑          ↑
  命令 00h     命令 01h    命令 50h
  指针指向    指针指向    指针指向

三区访问规则表:

目标区域起始命令列地址范围地址指针行为典型用途
A 区 (0~255)00hA0-A7 (0~255)指针锁定 A 区,持续有效读/写前半页数据
B 区 (256~511)01hA0-A7 (0~255)指针仅本次有效,操作完自动回 A 区读/写后半页数据
C 区 (512~527)50hA0-A3 (0~15)指针锁定 C 区,持续有效读/写 ECC、坏块标记

💡 为什么这么设计?

  • 列地址只有 8 位(A0~A7) ,最多寻址 256 字节
  • 但一页有 512 字节数据 + 16 字节 OOB = 528 字节
  • 所以用命令字隐含高位地址(相当于 A8、A9),实现「8 位地址线访问 528 字节」

页寄存器(Page Register)

读操作流程:
1. CPU 发「读命令 + 地址」
2. Flash 把目标页 528 字节 → 整个拷贝到「页寄存器」
3. CPU 再通过 IO 引脚,**逐字节**从页寄存器读数据

写操作流程:
1. CPU 逐字节写数据 → 先填满「页寄存器」
2. CPU 发「确认命令 10h」
3. Flash 把页寄存器 528 字节 → 一次性写入存储阵列

✅ 好处:外部只需 8 根数据线,内部批量操作,速度&成本双赢
存储阵列 (Array)          页寄存器 (Register)        外部 IO 引脚
     │                          │                        │
     ▼                          ▼                        ▼
┌─────────┐            ┌─────────┐            ┌─────────┐
│ 慢速    │ ──批量──► │ 中速    │ ──逐字节──► │ 快速    │
│ 25μs/页 │            │ 528B缓存 │            │ 50ns/字节│
└─────────┘            └─────────┘            └─────────┘
       ↑_________________内部总线________________↑
                    (并行高速传输)
                    
存储阵列(NAND Array) :存数据的地方,速度极慢。读取一个页需要 **25μs**(微秒)。
IO 接口(IO Pins) :对外传数据的地方,速度很快。传输一个字节只需 **50ns**(纳秒)。