1 内存接口概念#
1.1 不同类型的控制器#
S3C2440是个片上系统,有GPIO控制器(接有GPIO管脚(GPA-GPH)),有串口控制器 (接有TXD RXD引脚),有memory controller内存控制器,有Nand控制器等…
1 | (1)GPIO控制器属于门电路,不涉及到时序,相对简单。 |
1.2 如何访问控制器地址#
GPIO/门电路接口、协议类接口、内存类接口都属于CPU的统一编址。但对于Nand Flash,它没有独立的地址线和cpu的地址总线相连接,因此它不参与CPU的统一编址。
1.2.1 门电路/协议类控制器#
对于门电路接口、协议类接口,直接访问寄存器即可。
1.2.2 内存类控制器#
对于内存类接口,交给内存控制器去处理。下面详细分析:
CPU只管发出一个地址,内存控制器根据该地址范围选择不同的模块,然后从模块中得到数据或者发送数据到模块中。
如下图,SDRAM、DM9000网卡、Nor Flash都接在s3c2440的数据总线和地址总线上,CPU把数据和地址发送出去,然后内存控制器根据地址范围确定要拉低选中哪个片选信号(nCS),再根据片选信号(nCS)选择相应的设备,进行收发地址和数据,互不干扰。
1 | (1)当CPU发出的指令的地址范围处于0x00000000 - 0x08000000, |
内存控制器根据不同的地址地址范围,发出不同的片选引脚,只有被片选引脚选中的芯片才能正常工作,不被选中的芯片就像不存在一样,不工作。
从s3c2440 datasheet中我们得知内存控制器可访问的地址范围有1G(0x0000,0000-0x4000,0000),8个bank,每个bank_size为128M。理论上需要2^30(30条地址线)来确定是哪个bank,哪个地址。但是实际上只用到了27条,那么是怎么确定是哪个bank被选中了呢?
cpu每次发给内存控制器的地址都是Addr[31:0],但是内存控制器发给外设(sdram,nor,dm9000)却只用到了A[26:0]。第[29:27]被用来确定要拉低哪个nGCS,即要选中哪个bank:
1 | bit[29:27] bit[26:0] |
那么对于大容量的nandflash,理论上需要更多的地址线来确认访问地址,那既然没有地址线,cpu是如何访问nand的呢?当然是通过nand控制器,nand是地址、命令、数据都共用数据总线。这里只是引入一个话题,具体见s3c2440裸机-nand控制器。
2 不同位宽内存与CPU地址连接关系#
s3c2440芯片手册上外设rom与CPU地址总线连接如下:
2.1 8bit x1 rom与CPU地址线连接#
2.2 8bit x2 rom与CPU地址线连接#
2.3 8bit x4 rom与CPU地址线连接#
2.4 16bit x1 rom与CPU地址线连接#
2.5 16bit x2 rom与CPU地址线连接#
从上面的图中,我们知道可以对2片位宽为8bit的内存扩展级联成1个16bit的内存,同理可用4片位宽为8bit的内存进行级联成1个32bit的内存。
3 不同位宽内存与CPU为什么要错位相连#
从上面的图中,我们还看见一个规律:
1 | 当外设总线位宽为8bit时, 外设A0接CPU的地址总线ADDR[0], |
为什么要这样设计呢?先看一个例子:
1 | MOV R0, #3 |
如图有8bitROM、16bitROM、32bitROM:
1 | (1)对于8bitROM ,8bit是一次读写的最小单位,即0地址是第一个8bit,1地址是第二个8bit; |
用表格更好理解:
ROM/bit | CPU发出地址 | ROM收到地址 | ROM返回数据 | 内存控制器挑选出数据给CPU |
---|---|---|---|---|
8bit(ROM) | 0b000011 | 0b000011 | 编号3的存储单元中的8数据 | 编号3的存储单元中的8数据 |
16bit(ROM) | 0b000011 | 0b000001 | 编号1的存储单元中的16数据 | 根据”A0=1”,挑出低8bit数据 |
32bit(ROM) | 0b000011 | 0b000000 | 编号0的存储单元中的32数据 | 根据”A1A0=11”,挑出最低8bit数据 |
对上图的数据再次整理:
ROM/bit | CPU发出地址 | ROM收到地址(内存控制器转发给rom) | ROM返回数据 | 内存控制器组装数据给CPU |
---|---|---|---|---|
8bit(ROM) | 0b000100 | 0b000100 | 地址4的一个1byte数据 | 组装地址7、6、5、4数据成4字节数据 |
0b000101 | 地址5的一个1byte数据 | |||
0b000110 | 地址6的一个1byte数据 | |||
0b000111 | 地址7的一个1byte数据 | |||
16bit(ROM) | 0b000100 | 0b00010 | 地址2的一个2byte数据 | 组装地址3、2的数据成4字节数据 |
0b00011 | 地址3的一个2byte数据 | |||
32bit(ROM) | 0b000100 | 0b00001 | 地址1的一个4byte数据 | 直接返回4字节数据 |
这里牵扯到地址、内存中数据的排列存储,有点深入,如果实在无法理解,记住怎么去错位相连就好了。
结论:
1 | 1.和cpu地址总线相连的外设地址线确定了要访问外设的地址,即哪个存储单元; |
再举一个例子, 假如读取一个32位的数据时,前面读的是8位数据:
1 | MOV R0, #4 |
我们知道CPU发出的是32bit地址,那么
1 | 对于32bit Rom,内存控制器会给它发1次,rom也会相应的接收1次; |
3.1 配置内存控制器位宽#
接不同的rom外设,s3c2440内存控制器总线位宽要配置成不一样。位宽和等待控制寄存器如下:
BWSCON(BUSWIDTH&WAITCONTROLREGISTER):
我们SDRAM的位宽为32,DW6[25:24]设置成10, 没有使用等待信号,所以WS6[26]=0。 bank7跟随bank6的配置, 因此BWSCON寄存器的值为:0x22000000
4 内存控制器地址映射范围#
怎样确定芯片的访问地址?
1 | 1. 根据片选信号确定基地址 |
外设类型 | 接内存控制器的哪个片选 | 基地址 | 占用CPU的地址总线 | 地址范围(offset + size) |
---|---|---|---|---|
nor | nGCS0 | 0x0000,0000 | ADDR0-ADDR20 | 0x0000,0000 ~ 0x001f,ffff(2M) |
dm9000网卡 | nGCS4 | 0x2000,0000 | ADDR0和ADDR2 | 0x2000,0000 ~ 0x2000,0005(5byte) |
sdram | nGCS6 | 0x3000,0000 | ADDR0-ADDR25 | 0x3000,0000 ~ 0x3000,0000 + RAM_SIZE |
这里再次提醒一下: 有人发现上图中nor没有和CPU的ADDR0相连接,sdram没有和CPU的ADDR0、ADDR1相连接。不要觉得ADDR0、ADDR1没用到,由于nor数据位宽是16bit,ADDR0是给内存控制器拆分数据用的,同样sdram数据位宽32bit,ADDR0、ADDR1也是给内存控制器拆分数据用的。这个上面已分析过,这也是什么要错位连接的原因。
5 SDRAM访问实例#
以EM63A165TS-6G这款外接SRAM存储来展开介绍。
5.1 SDRAM存储结构#
5.2 SDRAM引脚接线#
这里采用2片 EM63A165TS-6G 级联作为外接内存,关于EM63A165TS-6G的规则描述参考datasheet。可以看到该sdram是16bit 的,从接线可以看出第一片存储低16位数据,第二片存储高16位数据。
引脚说明:
1 | A0-A12:地址总线 |
5.3 SDRAM地址范围#
前面提到片选接了nGCS6,地址映射的base_addr=0x3000,0000,那么size是多大呢?
容量为:4M word x 16-bit x 4-bank = 32M,再看原理图我们是两片级联,所以容量为4M word x 32-bit x 4-bank=64M。所以地址范围是**[0x3000_0000 ~ 0x33ff_ffff]**
在对比另一款W9825G6KH SDRAM为例,地址总线A0-A12,数据总线D0-D15,因此内存大小:2^13 * 2^9 = = 4194304,等于4M,加上有4个bank,数据为宽16位,因此内存大小4Mx4x2字节。
框图翻译成中文形式:
1 | CLK:时钟线,SDRAM 是同步动态随机存储器,“同步”的意思就是时钟,因此需要一根额外的时钟线,这是和 SRAM 最大的不同,SRAM 没有时钟线。 |
5.4 SDRAM数据访问过程#
我们知道64M=2^20*2^6=2^26,那么需要26条地址线,再看看原理图,我们发现SDRAM的地址线A[12:0]只有13条,那么最多只能访问2^13=8K的数据,地址线明显配不上这么大的容量,那么它是如何解决的呢?
答:当然是拆分地址了,多次传输。
我们从SDRAM的内部存储结构得知要确定SDRAM的一个存储单元,先确定是哪个bank,然后再确定在哪一行、哪一列即可。SDRAM有4个bank,由BA0、BA1决定选中哪个bank,查看SDRAM手册见下图:
通过选中nSRAS选中行地址,从而发送行地址;最后通过选中nSCAS选中列地址,从而发送列地址。例如:
1 | ldr r0, =0x30000000; |
过程如下:
1 | 1.发出片选信号nGCS6,选中SDRAM |
然后从sdram规格书确定行列地址的数目:
1 | 3.发出行地址信号nSRAS,使能行地址传输。传输行地址,确定是哪一行(看SDRAM手册确定行地址数(A12-A0)13条) |
从而发送完整的0x30000000地址到了SDRAM,SDRAM返回4byte数据给CPU。
5.5 SDRAM驱动实例#
s3c2440内存控制器共有13个寄存器。我们要设置内存控制器参数,适配外接SDRAM。
1 | BANK0--BANK5只需要设置BWSCON和BANKCONx(x为0~5)两个寄存器; |
5.5.1 位宽寄存器#
BWSCON(BUSWIDTH&WAITCONTROLREGISTER)
我们SDRAM的位宽为32,DW6[25:24]设置成10, 没有使用等待信号,所以WS6[26]=0。 bank7跟随bank6的配置, 因此BWSCON寄存器的值为:0x22000000。
5.5.2 BANK控制寄存器#
BANKCONTROLREGISTER:
在8个BANK中,只有BANK6和BANK7可以外接SRAM或SDRAM。BANKCON6设置参数如下:
1 | MT[16:15]:设置BANK是ROM/SRAM还是DRAM,我们用的SDRAM,属于DRAM。 |
5.5.3 刷新控制寄存器#
REFRESH(REFRESHCONTROLREGISTER)
1 | REFEN[23]:设置开启SDRAM的刷新功能。 |
5.5.4 BANKSIZE寄存器#
BANKSIZEREG ISTER
1 | BURST_EN[7]:0=ARM核禁上突发传输,1=ARM核支持突发传输(推荐); |
5.5.5 模式设置寄存器#
SDRAM MODE REGISTER SET REGISTER(MRSR)
1 | CL[6:4]:表示发出行、列地址后,等多久才返回收到数据, 看SDRAM手册发现Tcas >=18ns,所以配置成2 clocks即可。 |
5.5.6 测试代码#
1 | void sdram_init(void) { |
当进行sdram_init后可已访问0x3000_0000地址的内容,led流水灯闪烁。
不初始化sdram_init,sdram_test执行会导致程序卡死。
6 NorFlash访问实例#
6.1 Flash种类特性介绍#
flash一般分为nand flash和nor flash,各自特性如下:
Nor | NAND | |
---|---|---|
XIP(片上执行) | yes | no |
性能(擦除) | 非常慢(5s,块太大) | 快(3ms) |
性能(写) | 慢 | 快 |
性能(读) | 快 | 快 |
可靠性 | 高 | 一般(容易出现位反转) |
可擦除次数 | 10000 ~ 100000 | 100000 ~ 1000000 |
接口 | 与ram类似,可直接访问任意地址 | I/O接口(无地址线,必须串行访问,命令、地址、数据共用8位IO) |
易用性 | 容易 | 复杂 |
主要用途 | 常用于保存代码和关键数据 | 用于保存数据 |
价格 | 高 | 低 |
容量 | 小 | 大 |
常用文件系统类型 | jffs | yaffs |
nor有以下优缺点相对nand:
1 | 优点: |
6.2 NorFlash地址范围#
前面介绍内存控制器地址映射范围说了,得知nor接了bank 0,地址范围是0x0000,0000 ~ 0x001f,ffff。
6.3 NorFlash引脚描述#
下面是一款典型的nor flash原理图MX29LV800BBTC。
引脚信息:
1 | 地址线(A0-A20) |
Nor Flash可以像内存一样读,但是不能像内存一样写,需要做一些特殊的操作才能进行写操作,这是因为nor是属于rom(只读存储器),不能像ram一样可以任意的写0写1,只能将存储介质中的电平由1变成0,不能将0变成1,所以要向nor中写入数据,必须先进行擦除动作。
6.4 NorFlash硬件连接#
6.5 NorFlash数据访问过程#
下图是S3C2440的内存控制器的可编程访问周期读写时序,里面的时间参数要根据外部norflash的性能进行配置。
时序含义:
1 | Tacs: Address set-up time before nGCSn(表示地址信号A发出多久后才能发出nGCS片选) |
下面我们根据此款norflash MX29LV160D手册中的访问时序图来分析,如下图:
从上面MX29LV160D手册的时序图中我们看见:
1 | (1)先发送地址信号A |
1 | 发出地址数据(Addresses)后,要等待Taa(要求大于等于70ns)时间,地址数据才有效; |
Tas(地址建立时间,也就是地址发送多久后才能继续发后面的片选信号)最小可以为0,那么说明地址信号(A)、片选(CE)、读(OE)使能信号可以一起发出。
为了简单我们把地址(Addresses),片选信号(CE#),读信号(OE#),同时发出,然后让它们都等待70ns即可(等待地址信号,片选信号,读写使能信号有效)。
我们再看看上面的nor访问时序图,释放地址、片选、读使能信号都没有时间差值dt要求,那么说明地址、片选、读使能信号可以同时释放。
6.6 NorFlash时序初始化#
打开s3c2440内存控制器。
6.6.1 BANK控制寄存器设置#
6.6.1.1 内存控制器时序设置#
6.6.1.1.1 Tacc#
Tacc表示数据访问周期:
从上图可以看到Tacc的默认值是111,对应14个clocks。s3c2440系统上电采用12MHz的晶振,HCLK=OSC=12MHz,那么Tacc=(1/(12*10^6)) * 14≈1166 ns,这个值很大,远超过了我们的nor手册上的Trc=70ns,几乎可以满足所有NorFlash的要求,这也是为什么我们不做初始化也能访问norflash的原因。
启动后,由于我们的时钟HCLK设置成了100MHz,T=1000/100=10ns,Tacc= 10ns*14 >70ns, 所以内存控制器不配置Tacc也是能访问该flash的。为了让访问速率加快,因此设置Tacc>70ns即可,配置成101,8个clocks即可。
6.6.1.1.2 Tacs/Tcos/Tcoh/Tcah#
从nor的分析中,我们得知地址、片选、读使能同时发出和同时释放,所以配置Tacs,Tcos,Tcoh,Tcah皆为0。
1 | BANKCON0 = (*(volatile unsigned long *)(0x48000004)); |
6.6.2 测试代码#
1 | int main(void) { |
6.6.2.1 测试结果#
输入0~4,Tacc小于70ns,无法读取Nor Flash上数据,LED不能闪烁。
输入5~7,Tacc大于70ns,可以读取Nor Flash上数据,LED不断闪烁,且值越小越快。
结论:我们的内存控制器默认配置的tacc一般都能兼容大多数市面上的norflash,一般都是可以访问的,无需进行对内存控制器进行多余的配置。
7 u-boot命令访问NorFlash#
前提:
norflash初始化正常,能够正常从nor上执行。
对s3c2440而言,cpu总是从0地址读取指令执行程序。当cpu设置成nor启动时,0地址对应nor。cpu从nand启动时,0地址对应sram。
7.1 操作NorFlash#
将板子设为nor启动,那么0地址对应nor,我们先将uboot烧写到nor中,启动uboot。
打开这款MX29LV800BBTC norflash手册,找到操作flash的命令表:
7.1.1 reset#
往任何一个地址写入F0即可。
7.1.2 读ID#
很多的Nor Flash可以配置成位宽16bit(Word),位宽8bit(Byte),我们这款norflash数据位宽为16bit。下面我们按照nor手册上的命令表尝试一下:
1 | 往地址555H写入AAH(解锁) |
上面的地址是对于norflash的,那么我们CPU要怎么发送地址呢?从原理图接线我们知道CPU和nor的地址是错位相连的。
cpu地址 | nor地址 |
---|---|
A15~A1 | A14~A0 |
那么可以看到cpu的地址实际相当于是nor地址左移了一位,那么比如要想给nor上的555H地址写入AAH,那么CPU要发出的地址应该为0x555<<1,也就是nor地址的2倍。
下面对在Nor Flash的操作,cpu的操作,U-BOOT上的操作进行比较,如下表:
Nor Flash的操作 | cpu的操作 | U-BOOT上的操作 |
---|---|---|
往地址555H写入AAH(解锁) | 往地址AAAH写入AAH(解锁) | mw.w aaa aa |
往地址2AAH写入55H(解锁) | 往地址554H写入55H(解锁) | mw.w 554 55 |
往地址555H写入90H(命令) | 往地址AAAH写入90H(命令) | mw.w aaa 90 |
读0地址得到厂家ID(C2H) | 读0地址得到厂家ID(C2H) | md.w 0 1 (1:表示读一次) |
读1地址得到设备ID(22DAH或225BH) | 读2地址得到设备ID(22DAH或225BH) | md.w 2 1 |
退出读ID状态(给任意地址写F0H) | 退出读ID状态(给任意地址写F0H) | mw.w 0 f0 |
我们读出厂家id为c2,设备id为2249,和我们的nor手册上是一致的。我们发出f0命令,进行复位,这时读取的数据就不再是厂家id和设备id了,而是我们norflash中的实际的数据17 00 00 ea。
7.1.3 读数据#
前面说了,nor属于rom, 有独立地址线,可以像ram一样的读,只要做好内存控制器的初始化工作就可以直接读了。
我们再用二进制编辑器打开我们烧进去的uboot.bin,发现内容一样,说明我们从norflash中读出来的数据是正确的。
7.1.4 读属性#
通常Linux内核里面要识别一个 Nor Flash 有两种方法:
一种是 jedec 探测,就是在内核里面事先定义一个数组,该数组里面放有不同厂家各个芯片的一些参数,探测的时候将 flash 的 ID 和数组里面的 ID 一一比较,如果发现相同的,就使用该数组的参数。 jedec 探测的优点就是简单,只要通过flash的数组编号,即可访问该款flash属性,缺点是如果内核要支持的 flash 种类很多,这个数组就会很庞大。
一种是 CFI(common flash interface)探测,就是直接发各种命令来读取芯片的信息,比如 ID、容量等,芯片本身就包含了电压有多大,容量有有多少等信息。
我们的这款norflash属于cfi探测,下面对在Nor Flash上操作,s3c2440上操作,U-BOOT上进行cfi 探测(读取芯片信息)。
下图是从datasheet中检索出进入cfi模式后的一些flash属性查找表,可以按照表格命令查询norflash的一些属性(容量、电压、block信息等):
1 | 1.根据命令表往55H地址写入98H进入cfi模式 |
Nor Flash上操作cfi | 2440上操作cfi | U-BOOT上操作cfi |
---|---|---|
往55H地址写入98H(进入cfi模式) | 往AAH地址写入98H | mw.w aa 98 |
读地址10H得到0051(’q’) | 读地址20H得到0051 | md.w 20 1 |
读地址11H得到0052(‘r’) | 读地址22H得到0052 | md.w 22 1 |
读地址12H得到0059(‘y’) | 读地址24H得到0059 | md.w 24 1 |
读地址27H得到容量 | 读地址4EH得到容量 | md.w 4e 1 |
读地址1BH得到VCCmin | 读地址36H得到VCCmin | md.w 36 1 |
从测试结果我们看到容量为2^21=2M,Vcc最小提供电压是2.7v。
7.1.5 写数据#
前面说了,nor属于rom, 有独立地址线,可以像ram一样的读,用md命令直接读取,不能像内存一样直接写,不信我们试试:
我们在Nor Flash地址0x10000读数据
1
由于我们的uboot只有162k,烧录到norflash后,norflash上的的0x100000地址还没有被写入数据,norflash的容量为2M(0~0x200000),所以读取NorFlash的0x10000的地址数据是0xffff...
在Nor flash的0x10000地址写数据0x1234,然后在这个地址读出数据:
可以看到0x1234无法写进去,读出来还是0xfffff。为什么呢?要怎么才能将0x1234写进去。找到命令表:
Nor Flash上写操作 2440上写操作 U-BOOT上写操作 往地址555H写AAH(解锁) 往地址AAAH写AAH(解锁) mw.w aaa aa 往地址2AAH写55H(解锁) 往地址554H写55H(解锁) mw.w 554 55 往地址555H写A0H 往地址AAAH写A0H mw.w aaa a0 往地址PA写PD 往地址0x100000写1234h mw.w 100000 1234
可以看到0x1234已被写入到地址0x100000。再次往0x100000地址处,写入0x5678:
这时我们发现0x100000地址处的数据不是0x5678,而是0x1230,为什么?
1 | 原因:flash有无法位反转的特性。 |
所以得到就是0x1230, 因此flash写入前一定要先擦除。
7.1.5.1 擦除#
从datasheet找到擦除命令表:
Nor Flash擦操作 | u-boot擦操作 |
---|---|
往地址555H写AAH | mw.w aaa aa |
往地址2AAH写55H | mw.w 554 55 |
往地址555H写80H | mw.w aaa 80 |
往地址555H写AAH | mw.w aaa aa |
往地址2AAH写55H | mw.w 554 55 |
往地址PA写30H | mw.w 100000 30 |
擦除后再读取发现数据就已经变成了0xffff,后面就可以进行写操作了。
7.1.5.2 写入#
找到写入命令表,进行写入:
现在数据就变成我们的0x5678了。
注意:在写norflash时,要注意不要写0地址或者是uboot所在的地址,这样写入后norflash上的uboot程序就被破坏了。比如本测试就是写了0x100000地址,这个地址在uboot之外。
7.2 操作NorFlash-拓展#
7.2.1 地址位宽不对齐导致死机分析#
uboot发送md.w 0, md.w 2, md.w 4等偶地址命令能够读取norflash,但使用md.w 1, md.w 3,md.w 5就会出现死机,为什么?
1 | 由于我们的norflash是16bit数据位宽的,访问时要2byte对齐。如果不想以2byte为单位进行访问,那么要用uboot中用md.b 1,md.b 3这种单字节读取命令。 |
7.2.2 每次写都要先擦除#
操作norflash进行擦写的时候能够解锁一次,擦写多次吗?
1 | 不能,每次擦写都要进行解锁动作。 |
7.2.3 擦除单位-块#
擦除那么是以块(block)为单位的,那么当进行擦除时发送的地址并不是以块对齐的,会有什么结果?
也能擦除成功,会根据地址范围确定在哪一个块中。
填入的地址是0x100009,也是擦除0x100000地址对应的块。
8 NorFlash驱动实例#
8.1 识别NorFlash#
我们知道要识别norflash属性,要让norflash进入cfi模式,然后按照手册上的表格发送一系列的命令就能获取norflash属性。
8.1.1 发命令#
实现一个cpu向nor发命令的一个函数nor_cmd()。我们的norflash是16bit位宽的,所以访问nor是以16位为单位访问的。
1 |
|
nor_cmd(0x55, 0x90);
即可往norflash的0x55写入了0x98。
8.1.2 读一次数据#
1 | unsigned short nor_read_word(unsigned int base, unsigned int offset) { |
调用nor_dat(0x100000)
即可得到该地址的数据。
8.1.3 识别函数#
有了发命令函数nor_cmd和读一次数据函数nor_dat,那么就就可以参考nor芯片手册的命令表进行操作norflash了。
1 | /* 进入NOR FLASH的CFI模式 |
从测试结果来看每个region的block个数和block_size不一定一样,像region[0]只有一个block,block_size为4*64K;
region[1]有2个block,block_size=2*64K。
8.2 读数据#
由于NOR Flash是内存类接口,可以像内存一样读取,那么do_read_nor_flash函数代码如下:
1 | void do_read_nor_flash(void){ |
8.3 擦数据#
norflash擦写都是需要一定时间的,那么当我执行擦除或者写入动作后什么时候代表一次擦写动作已经完成了呢?
芯片手册提供了一个方法,每次擦除或者烧写过程中都可以查询数据总线上的第6位(Q6),当它保持稳定的时候表示一次擦除或者烧写动作完成,如下图:
1 | void wait_ready(unsigned int addr) { |
可以看到擦除后这个block就是全0xffff了。
8.3 写数据#
1 | void do_write_nor_flash(void){ |
由于我的norflash是位宽为16bit的,所以我们上面代码do_write_nor_flash进行写入时是以2byte(wold)为单位进行写入的。
总结:只要从spec中拿到了命令操作表,读写擦,识别就可以很轻松应对实现。