title: NorFlash 学习
tags: ARM
date: 2018-10-19 18:31:59
NorFlash 学习
NOR/NAND Flash的差别
NOR | NAND | |
---|---|---|
容量 | 1~32MB | 16~512MB |
XIP 可执行程序 | Yes(因为是内存接口), 可以随机访问任意地址的数据 | No |
擦除 | 非常慢(5s) | 快(3ms) |
写 | 慢 | 快 |
读 | 快 | 快 |
可靠性 | 比较高,位反转的比例小于NAND Flash 的10% | 比较低:,位反转比较常见,必需有校验措施,比如“1-4 bit EDC/ECC”;必须有坏块管理措施 |
可擦除次数 | 10000~100000 | 100000~1000000 |
生命周期 | 低于NAND Flash的10% | 是NOR Flash的10倍以上 |
接口 | 与RAM接口相同 | I/O接口 |
访问方法 | 随机访问 | 顺序访问 |
易用性 | 容易 | 复杂 |
主要用途 | 常用于保存代码和关键数据 | 用于保存数据 |
价格 | 高 | 低 |
- Nor Flash的块大小范围为64kB、128kB:NAND Flash的块大小范围为8kB,64kB,擦/写一个Nor Flash块需4s,而擦/写一个NAND Flash块仅需2ms。
- NAND Flash一般以512字节为单位进行读写。这使得Nor Flash适合于运行程序,而NAND Flash更适合于存储数据
- 容量相同的情况下,NAND Flash的体积更小.Nor Flash的容量通常为IMB~4MB(也有32MB的Nor Flash),NAND Flash的容量为8MB~512MB。
- 在Nor Flash上常用jffs2文 件系统,而在NAND Flash常用yaffs文件系统。在更底层,有MTD驱动程序实现对它们的读、写、擦除操仵,它也实现了EDC/ECC校验。
- 这里用的nor里面有4个bank,每个bank里面有若干block(擦除大小),不同bank里面的block可能不一样的
引脚接口
这里NorFlash是16位的,所以地址线忽略A0
命令
所谓命令是什么意思呢?
-
Read MODE
,就是先发送地址,然后就有数据得到,和正常的读内存是一样的 -
Reset Mode
,往任意地址写0xF0
都退出特殊模式 -
读ID
,NOR Flash 分为1字节和2字节模式,所以有两种情况的操作指令.板载是2字节的.操作如下:- 发送地址555,发送数据AA, 0x555*2=0xaaa
- 发送地址2AA,发送数据55 0x2AA*2=0x554
- 发送地址555,发送数据90 0x555*2=0xaaa
- 发送地址00,收到数据C2
但是板载的MCU的A1接到Nor的A0,所以也就是Nor获得地址X,实际MCU需要发送地址X<<1
Nor Flash的操作 | 2440的操作 | 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 2249 | md.w 2 1 (1:表示读一次) |
退出读ID状态:给任意地址写F0H就可以了 | 退出读ID状态:给任意地址写F0H就可以了 | mw.w 0 f0 |
注意,这个uboot应该在NOR上启动,因为NAND启动的时候,看不到NORFLASH
Device ID : MX29LV160DT: 22C4; MX29LV160DB: 2249.
OpenJTAG> mw.w 0 f0
OpenJTAG> mw.w aaa aa
OpenJTAG> mw.w 554 55
OpenJTAG> mw.w aaa 90
OpenJTAG> md.w 0 1
00000000: 00c2 ..
OpenJTAG> md.w 2 1
00000002: 2249 I"
参数识别(CFI)
CFI(common flash interface)探测,就是直接发各种命令来读取芯片的信息,比如 ID、容量等,芯片本身就包含了电压有多大,容量有有多少等信息.其实就是先用55地址写98
,然后读参数.
一般情况下先去读取是否支持CFI
,也就是
- 写98H到55H
- 查询
0x10,0x11,0x12
是否为QRY
字符串 - 如果是,那么支持CFI接口
Nor Flash上操作cfi | 2440上操作cfi | U-BOOT上操作cfi |
---|---|---|
往55H地址写入98H | 往AAH地址写入98H | mw.w aa 98 |
读地址10H得到0051 | 读地址20H得到0051 | md.w 20 1 |
读地址11H得到0052 | 读地址22H得到0052 | md.w 22 1 |
读地址12H得到0059 | 读地址24H得到0059 | md.w 24 1 |
读地址27H得到容量 | 读地址4EH得到容量 | md.w 4e 1 |
下图中读取地址10
,读取到0x0051='Q'
,比如读设备大小地址0x27
,会读到0x15=2**15/1024/1024=2M
,具体的一些参数参考标准文档CFI100
识别存储信息,其中具体的信息要搜索文档CFI100
可以看到有4个bank reggion,region里又若干sector.这个是擦除最小单位了.根据计算如下.其中2D为低位,2E为高位.block=[2E:2D]+1,
blocks | sector size | |
---|---|---|
bank1 | 1 | 256*0x0040/1024=16k |
bank2 | 2 | 256*0x0020/1024=8k |
bank3 | 1 | 256*0x0080/1024=32k |
bank3 | 0x1E+1=31 | 256*0x0100/1024=64k |
总计 | 35个block | 空间大小=16+2*8+32+64*31=2048k=2M |
CFI描述如下
备注手册里另一张图Table 1-1. MX29LV160DT SECTOR ARCHITECTURE
好像是倒着来的扇区排序
写数据
NorFlash也是一种FLASH,需要先擦除才能写数据.对于已经擦除的单元,同样需要命令才能写入
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 |
命令参考,往1M的地方写,地址需要*2,也就是0x20,0000
OpenJTAG> mw.w aaa aa
OpenJTAG> mw.w 554 55
OpenJTAG> mw.w aaa 80
OpenJTAG> mw.w aaa aa
OpenJTAG> mw.w 554 55
OpenJTAG> mw.w 200000 30
OpenJTAG> mw.w 0 f0
OpenJTAG> md.w 200000 1
00200000: ffff ..
//读取
OpenJTAG> mw.w aaa aa
OpenJTAG> mw.w 554 55
OpenJTAG> mw.w aaa a0
OpenJTAG> mw.w 200000 1234
OpenJTAG> md.w 200000 1
00200000: 1234 4.
//接下来不能直接写了 又需要重新来执行命令
OpenJTAG> mw.w 200004 5555
OpenJTAG> md.w 200004
00200004: ffff ..
程序设计
注意 板载的是16位位宽,地址线是CPU的A1接到NOR的A0,所以MCU地址=NOR指令地址*2.
调试代码 的时候发现工具MobaXterm_Personal_10.4
有时候无法接受串口输入,重启下软件串口
-
时序设置: 参考内存管理.md
-
基本的读写操作
#define NOR_BASE_BANK 0 /** * @brief 在基于Nor cmd_addr的位置写入 val * 比如在55H的地方写入x,也就是地址信号发出55, * 对应实际的地址线为55<<1 地址线 加上bank的基地址, * 所以cpu实际的地址是 base+(55<<1) * @param base * @param addr * @param val */ void NorWriteU16(uint16_t base,uint16_t addr, uint16_t val) { volatile uint16_t* p= (volatile uint16_t* )((addr<<1)+base); *p=val; } /** * @brief 读取2字节 * * @param base * @param addr * @param val */ uint16_t NorReadU16(uint16_t base,uint16_t addr) { volatile uint16_t* p= (volatile uint16_t* )((addr<<1)+base); return *p; } /** * @brief CMD写入16位数据 * @param addr cmd地址 * @param val 写入的值 */ void NorFlashCMDPut(uint16_t addr,uint16_t val ) { NorWriteU16(NOR_BASE_BANK,addr,val); } /** * @brief CMD读取Nor的16位数据 * * @param addr * @param val * @return uint16_t */ uint16_t NorFlashCMDGet(uint16_t addr) { return NorReadU16(NOR_BASE_BANK,addr); }
-
识别CFI
void NorFlashInfo(void) { // 进入CFI模式,INTERFACE (CFI) MODE NorFlashCMDPut(0x55,0x98); //开始读取数据 uint8_t buf[10]={0,0,0,0,0,0,0,0,0}; //尝试读取“QRY” buf[0]=(uint8_t)NorFlashCMDGet(0x10); buf[1]=(uint8_t)NorFlashCMDGet(0x11); buf[2]=(uint8_t)NorFlashCMDGet(0x12); buf[3]=(uint8_t)' '; printf("get 0x20,0x22,0x24=%s ",buf); printf("end "); NorFlashCMDPut(0, 0xf0); }
-
NOR的擦除与写
注意: NOR的物理地址是需要CPU地址
>>1
的,所以操作地址的时候之前的底层函数已经使用了cpu地址>>1
但是,Nor是16位位宽的,也就是如下排列的
16位宽 8位宽 00 00,01 01 02,03 02 04,05 03 06,17 我们需要将8位位宽的地址转换为16位位宽的地址,也就是说addr>>1.
void do_write_nor_flash(void) { unsigned int addr; unsigned char str[100]; int i, j; unsigned int val; /* 获得地址 */ printf("Enter the address of sector to write: "); addr = get_uint(); printf("Enter the string to write: "); gets(str); printf("writing ... "); /* str[0],str[1]==>16bit * str[2],str[3]==>16bit */ i = 0; j = 1; while (str[i] && str[j]) { val = str[i] + (str[j]<<8); /* 烧写 */ nor_cmd(0x555, 0xaa); /* 解锁 */ nor_cmd(0x2aa, 0x55); nor_cmd(0x555, 0xa0); /* program */ //这个是重点------------------------------------------------- nor_cmd(addr>>1, val); //这个是重点------------------------------------------------- /* 等待烧写完成 : 读数据, Q6无变化时表示结束 */ wait_ready(addr); i += 2; j += 2; addr += 2; } val = str[i]; /* 烧写 */ nor_cmd(0x555, 0xaa); /* 解锁 */ nor_cmd(0x2aa, 0x55); nor_cmd(0x555, 0xa0); /* program */ nor_cmd(addr>>1, val); /* 等待烧写完成 : 读数据, Q6无变化时表示结束 */ wait_ready(addr); }
FAQ:
在老师的视频中,写NOR的具体地址时,nor_cmd(addr>>1, val); 这里地址右移1位,老师的解释是地址线A1->A0的缘故.但是我觉得不对.
因为nor_cmd
函数已经将地址转换为nor视角的地址了,也就是说我想写nor中的x地址,我们应该直接操作地址x即可.但是测试之后发现代码确实跑的没问题?那么我哪里理解错了呢?
后来想到 Nor是16位位宽的,也就是说Nor中的0,其实对应了存储单元[0,1],结论: 所以这里的地址>>1,并不是因为地址线偏移(其实也是因为地址线,因为是16位,所以偏移1位),但是从函数的角度来说,实际是因为nor的地址是16位宽的,我们需要转换为8位位宽的 addr>>1 变成16位位宽的地址
后面的测试了下往 addr=1里面写
12
,实际是在0,1里面写,往addr=2里面写12
,实际是往2里面写,往addr=3里面写,也是在往2里面写 -
判断是否忙(DQ6)
-
DQ7:Data# Polling bit,DQ7在编程时的状态变化.
在编程过程中从正在编程的地址中读出的数据的DQ7为要写进数据的补码.比如写进的数据为0x0000,及输进的DQ7为'0',则在编程中读出的数据为'1';当编程完成时读出的数据又变回输进的数据即'0'. 在擦除过程中DQ7输出为'0';擦除完成后输出为'1';留意读取的地址必须是擦除范围内的地址.
RY/BY#:高电平表示'停当',低电平表示'忙'. -
DQ6:轮转位1(Toggle Bit 1).
在编程和擦除期间,读任意地址都会导致DQ6的轮转(0,1间相互变换)当操纵完成后,DQ6停止转换.
DQ2:轮转位2(Toggle Bit 2).当某个扇区被选中擦除时,读有效地址(地址都在擦除的扇区范围内)会导致DQ2的轮转.
留意:DQ2只能判定一个特定的扇区是否被选中擦除.但不能区分这个快是否正在擦除中或者正处于擦除暂停状态.相比之下,DQ6可以区分NorFLASH是否处于擦除中或者擦除状态,但不能区分哪个快被选中擦除.因此需要这2个位来确定扇区和模式状态信息. -
DQ5: 超时位(Exceeded Timing Limits),当编程或擦除操纵超过了一个特定内部脉冲计数是DQ5=1;这表明操纵失败.当编程时把'0'改为'1'就会导致DQ5=1,由于只有擦除擦做才能把'0'改为'1'.当错误发生后需要执行复位命令(见图1-1)才能返回到读数据状态.
-
DQ3: (扇区擦除计时位)Sector Erase Timer,只在扇区擦除指令时起作用.当擦除指令真正开始工作是DQ3=1,此时输进的命令(除擦除暂停命令外)都被忽略.DQ3=0,是可以添加附加的扇区用于多扇区擦除.
void wait_ready(unsigned int addr) { unsigned int val; unsigned int pre; pre = nor_dat(addr>>1); val = nor_dat(addr>>1); while ((val & (1<<6)) != (pre & (1<<6))) { pre = val; val = nor_dat(addr>>1); } }
-
程序优化
- 老师的写操作并没有去判断地址是否是高低地址,也就是写0地址和写1地址都是写在0地址2个字节,实际上如果写1地址应该先读取0地址,再写1地址,这里会涉及到擦除? 如果都先擦除就没有这个问题
其他设计点
-
读写NORFLASH的时候需要关闭中断
因为我们使用NOR启动的时候,cpu执行中断的时候会跳回到0地址,也就是NOR中去读取指令,这个时候如果在操作NOR,很明显取址有问题了,会死机等.之前串口有时候没输出死机可能就是这个原因了.
-
nor是16位宽的,所以在擦除和写nor的时候,实际地址需要
>>1
才是nor要接受的实际地址,具体分析见程序设计章节中的写操作 -
Makefile中加入选项
-march=armv4
否则在操作下面函数时,16位变量被处理为2个8位变量.网上搜索是可能gcc 考虑地址对齐的问题volatile unsigned short *p = (volatile unsigned short *)(base + (offset << 1)); *p = val; 30000f3c: e5c23000 strb r3, [r2]
%.o : %.c arm-linux-gcc -march=armv4 -c -o $@ $< %.o : %.S arm-linux-gcc -march=armv4 -c -o $@ $<
-
程序应该烧录在NOR上,因为nand启动的时候,片内ram是0地址,mcu无法访问nor