news 2026/10/15 17:59:28

JZ2440裸板开发:NOR Flash从硬件连接到读写擦除实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JZ2440裸板开发:NOR Flash从硬件连接到读写擦除实战

1. 从零拆解JZ2440裸板开发中的NOR Flash

搞嵌入式裸板开发的朋友,尤其是玩过ARM9那一代经典开发板的人,对“NOR Flash”这个词一定不陌生。我最初接触JZ2440这块板子的时候,满脑子都是“裸板”两个字——没有操作系统、没有文件系统、没有驱动框架,所有东西都得自己从寄存器层面一点点抠出来。而NOR Flash恰恰是这块板子上最值得深挖的存储器件之一,因为它直接关系到你的程序能不能被正确烧录、能不能在断电后依然保存、能不能在启动时被CPU正确读取并执行。

这篇文章要聊的就是JZ2440裸板开发中关于NOR Flash的完整实践。我会从NOR Flash的基本特性讲起,拆解它在JZ2440这块板子上的硬件连接方式、控制器配置逻辑、读写擦除操作的底层实现,以及在实际调试中容易踩的坑。适合已经有一定ARM裸板基础、正在学习存储器件驱动开发、或者想搞清楚“程序到底是怎么从Flash跑到内存里执行”的嵌入式学习者。如果你之前只会在IDE里点“下载”按钮,对底层发生了什么一无所知,那这篇内容应该能帮你把这块知识补上。

我个人的习惯是,学任何一块外设之前,先搞清楚三件事:它是什么、它接在哪里、CPU怎么跟它说话。NOR Flash也不例外。下面我就按这个思路,把JZ2440上NOR Flash的开发过程完整梳理一遍。

2. NOR Flash的核心特性与选型逻辑

2.1 NOR Flash为什么适合存启动代码

NOR Flash和NAND Flash虽然都叫Flash,但两者的脾气完全不同。NOR Flash最大的特点是支持随机访问,你可以像读内存一样直接读取任意地址的数据,不需要先发命令再读页。这个特性决定了它非常适合存储启动代码,因为CPU上电后需要立刻从某个固定地址取指令执行,如果存储器件不支持随机访问,CPU就没法直接运行里面的代码。

JZ2440上用的NOR Flash型号是SST39VF1601或者兼容型号,容量通常是2MB。这块芯片的接口是并行接口,地址线和数据线分开,CPU通过总线直接访问。你可以把它理解成一块“只读的内存”,读操作几乎和读SDRAM一样简单,但写操作就麻烦得多,需要先解锁、再发命令序列、然后才能写入数据。

注意:NOR Flash的“写”和“擦除”是两个不同的操作。写入只能把1变成0,擦除才能把0变回1。所以每次写入新数据之前,必须先擦除对应扇区。

2.2 JZ2440上NOR Flash的硬件连接

JZ2440的NOR Flash挂在Bank0上,也就是片选信号nGCS0对应的地址空间。S3C2440这颗CPU的Bank0比较特殊,它是启动Bank,上电后CPU会从Bank0的0地址开始取第一条指令。如果从NOR Flash启动,那么NOR Flash的基地址就是0x00000000;如果从NAND Flash启动,NOR Flash会被映射到0x40000000。

硬件连接上,NOR Flash的地址线A0~A20接到CPU的地址总线,数据线D0~D15接到CPU的数据总线,片选nGCS0接到NOR Flash的nCE,读信号nOE和写信号nWE分别接到NOR Flash的nOE和nWE。这些连接方式决定了我们在写代码时,访问NOR Flash就像访问内存一样,直接用指针操作即可。

这里有个细节值得注意:JZ2440的NOR Flash是16位宽的,也就是说每次读写操作是2个字节。这意味着地址线的最低位A0在16位模式下实际上对应的是字节偏移的bit1。很多新手在计算地址偏移时容易搞错,把字节地址直接当成16位字的地址用,结果读写的数据总是错位。

2.3 为什么不用NAND Flash替代NOR Flash

有人可能会问,NAND Flash容量更大、价格更便宜,为什么还要用NOR Flash?答案就在“启动”这两个字上。NAND Flash不支持随机访问,CPU没法直接从NAND Flash取指令执行。S3C2440虽然支持NAND启动,但它的做法是先把NAND Flash前4KB的内容复制到片内SRAM(Steppingstone)里,然后从SRAM执行。这4KB的代码必须负责把整个程序搬到SDRAM里,过程比较复杂。

而NOR Flash可以直接在片内执行代码(XIP,eXecute In Place),启动流程简单直接。对于裸板开发来说,用NOR Flash启动能省去很多搬运代码的麻烦,调试起来也更直观。当然,代价就是容量小、成本高。所以实际产品中常见的是NOR Flash存启动代码、NAND Flash存大容量数据,两者配合使用。

3. NOR Flash控制器配置与底层操作原理

3.1 S3C2440的Bank0时序配置

虽然NOR Flash的访问看起来像内存,但CPU和Flash之间的时序并不是天生就匹配的。S3C2440的每个Bank都有对应的BWSCON(总线宽度和等待控制寄存器)和BANKCON(Bank控制寄存器),需要根据NOR Flash的时序参数来配置。

BWSCON中Bank0对应的位是[2:0],需要设置数据宽度为16位。BANKCON0中需要设置Tacs(片选建立时间)、Tcos(片选到读/写建立时间)、Tacc(访问周期)、Tcoh(片选保持时间)、Tcah(地址保持时间)等参数。这些参数的单位是HCLK周期,需要查NOR Flash的数据手册来确定。

以SST39VF1601为例,它的读取访问时间通常是70ns或90ns。假设HCLK是100MHz,一个周期是10ns,那么Tacc至少需要设置为7个周期才能满足70ns的访问时间。实际配置时通常会留一些余量,设置成8或9个周期。如果Tacc设置得太小,读出来的数据会不稳定;设置得太大,虽然能正常工作,但会降低访问速度。

实操心得:调试NOR Flash时,如果读出来的数据总是随机错误,优先检查BANKCON0的Tacc参数。我遇到过好几次因为Tacc设小了导致读取数据偶尔出错的情况,用示波器看时序才发现访问周期不够。

3.2 NOR Flash的命令序列机制

NOR Flash的读操作很简单,直接读地址就行。但写操作和擦除操作需要按照特定的命令序列来执行。以SST39VF1601为例,它的命令序列是这样的:

  • 写入0xAA到地址0x5555
  • 写入0x55到地址0x2AAA
  • 写入0xA0到地址0x5555
  • 写入数据到目标地址

这套命令序列看起来很奇怪,为什么要往特定地址写特定数据?这其实是NOR Flash内部的状态机设计。Flash内部有一个命令寄存器,只有按照特定顺序写入特定数据,才能让状态机进入编程模式。这种设计是为了防止误操作,毕竟Flash的擦写次数有限,不能随便乱写。

擦除操作也是类似的命令序列,只是最后一步写入的是0x80表示擦除,然后再发一次命令序列加上0x10表示擦除整个芯片,或者加上扇区地址表示擦除特定扇区。

3.3 扇区擦除与整片擦除的取舍

NOR Flash的擦除操作有两种:扇区擦除和整片擦除。扇区擦除一次擦除一个扇区,SST39VF1601的扇区大小通常是2KB或4KB;整片擦除则是一次把整个芯片擦干净。

选择哪种擦除方式取决于你的应用场景。如果只是更新一小部分数据,用扇区擦除更合适,因为擦除次数少、速度快。如果需要重新烧录整个程序,整片擦除更省事。但要注意,整片擦除的时间比较长,通常需要几百毫秒甚至几秒,期间CPU不能访问NOR Flash,否则会读到无效数据。

注意:擦除操作执行期间,NOR Flash会进入忙状态,此时读取任何地址都会返回状态寄存器的值,而不是实际数据。需要通过读取状态寄存器的DQ7位或DQ6位来判断擦除是否完成。

4. 裸板环境下NOR Flash读写擦除的完整实现

4.1 地址映射与指针操作

在裸板环境下,我们没有文件系统、没有驱动框架,所有操作都要通过指针直接访问物理地址。JZ2440从NOR Flash启动时,NOR Flash的基地址是0x00000000。我们可以定义一个宏来表示这个基地址:

#define NOR_FLASH_BASE 0x00000000

然后通过volatile指针来读写:

typedef volatile unsigned short nor_data_t; nor_data_t *nor_addr = (nor_data_t *)NOR_FLASH_BASE;

这里用unsigned short是因为NOR Flash是16位宽的,每次读写2个字节。用volatile关键字是为了防止编译器优化掉看似“无用”的读写操作,因为Flash的读写有副作用,不能被优化。

4.2 解锁与命令写入函数

根据前面讲的命令序列,我们可以封装几个基础函数:

#define NOR_CMD_ADDR1 0x5555 #define NOR_CMD_ADDR2 0x2AAA #define NOR_CMD_UNLOCK1 0xAA #define NOR_CMD_UNLOCK2 0x55 #define NOR_CMD_PROGRAM 0xA0 #define NOR_CMD_ERASE 0x80 #define NOR_CMD_CHIP_ERASE 0x10 #define NOR_CMD_SECTOR_ERASE 0x30 void nor_write_cmd(unsigned int addr, unsigned short cmd) { *(volatile unsigned short *)(NOR_FLASH_BASE + (addr << 1)) = cmd; }

注意这里的addr << 1,因为NOR Flash是16位宽,地址线的最低位对应字节偏移的bit1。比如要访问字节地址0x5555,实际上要往16位地址0x2AAA写入数据。这个移位操作是很多新手容易忽略的地方。

4.3 扇区擦除的完整流程

扇区擦除的流程可以拆解为以下步骤:

  1. 发送解锁命令序列
  2. 发送擦除命令0x80
  3. 再次发送解锁命令序列
  4. 发送扇区擦除命令0x30和目标扇区地址
  5. 轮询状态寄存器等待擦除完成

对应的代码实现:

int nor_erase_sector(unsigned int sector_addr) { nor_write_cmd(NOR_CMD_ADDR1, NOR_CMD_UNLOCK1); nor_write_cmd(NOR_CMD_ADDR2, NOR_CMD_UNLOCK2); nor_write_cmd(NOR_CMD_ADDR1, NOR_CMD_ERASE); nor_write_cmd(NOR_CMD_ADDR1, NOR_CMD_UNLOCK1); nor_write_cmd(NOR_CMD_ADDR2, NOR_CMD_UNLOCK2); nor_write_cmd(sector_addr, NOR_CMD_SECTOR_ERASE); return nor_wait_ready(); }

nor_wait_ready函数通过读取状态寄存器的DQ7位来判断操作是否完成。DQ7位在擦除期间会翻转,擦除完成后会稳定为1。也可以通过DQ6位判断,DQ6在操作期间会不断翻转,完成后停止翻转。

4.4 数据写入与校验

写入数据之前必须先擦除对应扇区,因为写入只能把1变成0,如果目标地址已经有数据(0),写入新数据时那些位会保持0,导致写入结果错误。

写入流程:

  1. 发送解锁命令序列
  2. 发送编程命令0xA0
  3. 写入目标地址和数据
  4. 轮询状态寄存器等待写入完成
int nor_program_word(unsigned int addr, unsigned short data) { nor_write_cmd(NOR_CMD_ADDR1, NOR_CMD_UNLOCK1); nor_write_cmd(NOR_CMD_ADDR2, NOR_CMD_UNLOCK2); nor_write_cmd(NOR_CMD_ADDR1, NOR_CMD_PROGRAM); *(volatile unsigned short *)(NOR_FLASH_BASE + (addr << 1)) = data; return nor_wait_ready(); }

写入完成后,建议读回数据做校验。虽然Flash内部有校验机制,但在裸板环境下,读回校验是最直接的验证手段。

4.5 读取操作的实现与优化

读取操作最简单,直接读指针即可:

unsigned short nor_read_word(unsigned int addr) { return *(volatile unsigned short *)(NOR_FLASH_BASE + (addr << 1)); }

但要注意,如果刚刚执行过擦除或写入操作,需要先等待操作完成再读取,否则读到的可能是状态寄存器的值而不是实际数据。

对于需要读取大量数据的场景,可以用循环批量读取,或者用DMA来搬运数据。不过NOR Flash的读取速度本身就不快,DMA的收益有限,通常直接循环读取就够了。

5. 调试NOR Flash时的常见问题与排查技巧

5.1 读取数据全为0xFF或随机值

这是最常见的问题。如果读出来的数据全是0xFF,说明NOR Flash可能没有被正确擦除,或者地址映射有问题。如果读出来是随机值,多半是时序配置不对。

排查步骤:

  • 检查BWSCON和BANKCON0的配置是否正确
  • 用示波器测量nGCS0、nOE、地址线和数据线的波形
  • 确认NOR Flash的基地址是否正确(从NOR启动是0x00000000,从NAND启动是0x40000000)
  • 检查地址移位是否正确(16位模式下要左移1位)

5.2 擦除操作一直不完成

如果nor_wait_ready函数一直返回忙状态,可能是以下原因:

  • 命令序列写错了,Flash没有进入擦除模式
  • 地址移位错误,命令写到了错误的位置
  • NOR Flash的写保护引脚被拉低,禁止了擦写操作
  • 硬件连接有问题,写信号没有正确到达Flash

实操心得:我遇到过因为写保护引脚悬空导致擦除一直失败的情况。JZ2440的NOR Flash有一个WP引脚,如果悬空或者被拉低,擦写操作会被禁止。检查硬件原理图确认这个引脚的状态。

5.3 写入数据后读回不一致

写入数据后读回不一致,通常是因为:

  • 目标扇区没有先擦除
  • 写入过程中被打断(比如中断触发)
  • 时序参数不满足Flash的写入要求
  • 数据宽度设置错误,比如把16位数据当成8位写

排查时可以先擦除整个扇区,然后写入一个已知数据,再读回对比。如果单个数据能正确写入,说明基本流程没问题,问题可能出在批量写入时的时序或中断处理上。

5.4 NOR Flash与NAND Flash启动的切换

JZ2440可以通过拨码开关选择从NOR Flash还是NAND Flash启动。如果拨码开关设置错误,会出现程序跑不起来或者跑起来但行为异常的情况。

从NOR Flash启动时,CPU直接从0x00000000取指令,NOR Flash的基地址就是0x00000000。从NAND Flash启动时,CPU先把NAND前4KB搬到片内SRAM,NOR Flash被映射到0x40000000。如果你的程序里硬编码了NOR Flash的地址,切换启动方式后地址就错了。

注意:在写代码时,尽量用宏定义来表示NOR Flash的基地址,不要硬编码。这样切换启动方式时只需要改一个宏定义。

5.5 常见问题速查表

问题现象可能原因排查方法
读取全为0xFF未擦除、地址错误、片选未使能检查BANKCON0、测量nGCS0波形
读取随机值时序配置错误增大Tacc参数、示波器看时序
擦除不完成命令序列错误、写保护检查命令序列、检查WP引脚
写入后读回不一致未擦除、中断打断、时序不足先擦除再写、关中断、增大时序余量
程序跑飞启动方式错误、地址映射错误检查拨码开关、确认基地址

6. 从NOR Flash开发延伸出的存储器件学习思路

搞完NOR Flash的裸板开发之后,我对存储器件的学习有了一个比较清晰的套路。不管是NOR Flash、NAND Flash、EEPROM还是SD卡,本质上都是“CPU通过某种接口跟存储介质对话”。区别只在于接口协议、命令格式和操作时序不同。

以NOR Flash为例,它的核心知识点可以归纳为三条:地址映射方式、命令序列机制、状态轮询方法。掌握了这三条,换一块NOR Flash芯片,只需要查数据手册确认命令序列和时序参数,代码框架基本不用大改。

再往深了说,NOR Flash的XIP特性让它成为启动代码的理想载体,但它的擦写寿命有限(通常10万次左右),所以不能频繁写入。如果需要频繁记录数据,应该用EEPROM或者带磨损均衡算法的NAND Flash。这些选型上的考量,在裸板开发阶段可能感受不深,但到了实际产品设计时就会变得非常重要。

我个人在实际操作中的体会是,裸板开发NOR Flash最大的价值不在于学会操作这一块芯片,而在于通过它理解CPU如何与外部存储器件交互这个通用问题。你把NOR Flash的时序配置、命令序列、状态轮询这三块搞透了,后面学NAND Flash、SD卡、甚至PCIe设备,都会觉得有章可循。最后再分享一个小技巧:调试Flash操作时,先把擦除、写入、读取三个操作分别单独测试通过,再组合起来跑完整流程。这样出问题时能快速定位是哪个环节的毛病,比一上来就跑完整流程然后抓瞎要高效得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 17:59:12

数据分析项目分型:描述、诊断、预测、规范四类方法与场景指南

1. 为什么接需求前要先分型&#xff1a;数据分析项目的地基问题在数据运营的日常工作里&#xff0c;我见过太多类似的情况&#xff1a;业务方跑过来丢一句"帮我看看这个数据怎么回事"&#xff0c;等你折腾两天把数提出来&#xff0c;对方又说"这不是我要的&…

作者头像 李华
网站建设 2026/10/15 17:58:00

JsonCpp源码直用:C++项目不依赖预编译库的集成方案

简介&#xff1a;JsonCpp是C中常用的JSON解析与生成库&#xff0c;这份资源提供了不经过编译库、直接将源码嵌入项目的方案&#xff0c;适合需要在Visual Studio等C环境中快速集成JSON功能的开发者。作者结合实际项目经验&#xff0c;整理出只需复制json目录下的头文件以及src/…

作者头像 李华
网站建设 2026/10/15 17:56:40

嵌入式Linux驱动开发:距离传感器实战解析

简介&#xff1a;这份代码是 STK3310 距离传感器的完整可移植驱动&#xff0c;基于 MTK 平台实测可用&#xff0c;通过 GPIO 模拟 I2C 与传感器通信&#xff0c;面向嵌入式驱动工程师、移动设备 BSP 开发者&#xff0c;以及想掌握 I2C 时序和传感器驱动框架的读者。资源共包含 …

作者头像 李华
网站建设 2026/10/15 17:56:26

共享储能场景微网运营商与用户聚合商的主从博弈建模与仿真

共享储能作为一个这两年很火的话题&#xff0c;真正落到工程和商业模式层面时&#xff0c;最让人头疼的问题往往是&#xff1a;储能建在谁的地盘上、由谁运营、服务谁、收益怎么分。你去看那些实际落地的共享储能试点项目&#xff0c;如果只靠峰谷套利&#xff0c;几乎没有一个…

作者头像 李华
网站建设 2026/10/15 17:55:29

Qt5嵌入式代码编辑器QCodeEditor集成与定制指南

简介&#xff1a;QCodeEditor 是一份基于 Qt5 的代码编辑器小部件资源&#xff0c;主要面向需要为桌面应用快速嵌入轻量级编辑能力的 Qt 开发者。该小部件支持自动括号匹配、自动缩进、空格替换制表符&#xff0c;并内置 C、GLSL、XML、JSON 等多语言的高亮规则与补全规则&…

作者头像 李华