首先我们看到nor flash的电路图:
可以看到,nor flash有数据线,有地址线,同时也要明白nor flash可以像内存一样读,但是不能像内存一样写
我们下面通过uboot来演示nor flash的操作,涉及到很多命令
首先看nor 和 nand的区别:
解释一下坏块:即在使用过程中,用着用着书籍就被破坏了
一般用nor来存储关键性的程序,用nand flash来存储海量文件,允许出错的文件,比如存储视频监控的文件,还有就是手机一般都是使用的nand flash
使用UBOOT体验NOR FLASH的操作(开发板设为NOR启动,进入UBOOT)
先使用OpenJTAG
烧写UBOOT
到NOR FLASH
1. 读数据
md.b 0
看到nor的命令手册
NOR手册上:
往地址555H
写AAH
往地址2AAH
写55H
往地址555H
写90H
读0地址得到厂家ID: C2H
读1地址得到设备ID: 22DAH
或225BH
退出
读ID状态: 给任意地址写F0H
又要注意到:
2440的A1接到NOR的A0,并不是一一对应,相差一位,所以2440发出(555h<<1), 左移1位即乘以2,NOR才能收到555h这个地址
注:555<<1即AAAH,后续同理
那么UBOOT怎么操作?
往地址AAAH
写AAH
mw.w aaa aa
往地址554
写55H
mw.w 554 55
往地址AAAH
写90H
mw.w aaa 90
读0地址得到厂家ID: C2H md.w 0 1
读2地址得到设备ID: 22DAH或225BH md.w 2
1(1表示读一次)
退出
读ID状态: mw.w 0 f0
mw.w这里的w就是十六位操作,可以将前两条命令当做解锁,后面的一条为命令
如果没有退出读状态的话,再执行md.b 0
就读不出来了,因此要退出相应的状态。
3.读取CFI信息
NOR有两种规范, jedec, cfi(common flash interface)
NOR手册:
进入CFI模式 往55H写入98H
读数据:
读10H得到0051
读11H得到0052
读12H得到0059
读27H得到容量
2440的A1接到NOR的A0,所以2440发出(555h<<1), NOR才能收到555h这个地址
UBOOT怎么操作?
进入CFI模式 往AAH写入98H mw.w aa 98
读数据:
读20H得到0051 md.w 20 1
读22H得到0052 md.w 22 1
读24H得到0059 md.w 24 1
读4EH得到容量 md.w 4e 1
退出CFI模式 mw.w 0 f0
分析:执行md.w 4e 1
得到的结果为15(十进制),转换为16进制即为21,即容量为2^21 ,再/1024/1024 = 2M,即是nor的大小
4. 写数据: 在地址0x100000写入0x1234
md.w 100000 1 // 得到ffff
mw.w 100000 1234
md.w 100000 1 // 还是ffff
会发现写不进去
NOR手册:
往地址555H写AAH
往地址2AAH写55H
往地址555H写A0H
往地址PA写PD
2440的A1接到NOR的A0,所以2440发出(555h<<1), NOR才能收到555h这个地址
UBOOT怎么操作?
往地址AAAH写AAH mw.w aaa aa
往地址554H写55H mw.w 554 55
往地址AAAH写A0H mw.w aaa a0
往地址0x100000写1234h mw.w 100000 1234
5. 再次往0x100000写入0x5678
因为原来0x100000上的数据不是0xffff,再次烧写失败(需要先擦除,再烧写)
往地址AAAH写AAH
往地址554H写55H
往地址AAAH写A0H
往地址0x100000写5678h mw.w 100000 5678
5.1 先擦除
mw.w aaa aa
mw.w 554 55
mw.w aaa 80
mw.w aaa aa
mw.w 554 55
mw.w 100000 30
5.2 再烧写
mw.w aaa aa
mw.w 554 55
mw.w aaa a0
mw.w 100000 5678
总结:我们烧写时,如果上面的数据,不是0ffff,没有被擦除过,我们就要先擦出,擦除完后,才可以烧写,擦除烧写的命令可以从芯片手册里面获得。
下面进行nor flash的编程
目的:识别nor flash
大致步骤:
1.打印菜单, 供我们选择测试内容
如下代码(放在一个while(1)中):
printf("[s] Scan nor flash\n\r");
printf("[e] Erase nor flash\n\r");
printf("[w] Write nor flash\n\r");
printf("[r] Read nor flash\n\r");
printf("[q] quit\n\r");
printf("Enter selection: ");
c = getchar();
printf("%c\n\r", c);
2.选择测试内容
switch (c)
{
case 'q':
case 'Q':
return;
break;
case 's':
case 'S':
do_scan_nor_flash();
break;
case 'e':
case 'E':
do_erase_nor_flash();
break;
case 'w':
case 'W':
do_write_nor_flash();
break;
case 'r':
case 'R':
do_read_nor_flash();
break;
default:
break;
}
下面对每个函数进行编写
发送命令函数
nor_cmd
函数代码如下,往NOR Flash某个地址发送指令,
/* offset是基于NOR的角度看到 */
void nor_cmd(unsigned int offset, unsigned int cmd)
{
nor_write_word(NOR_FLASH_BASE, offset, cmd);
}
写命令函数
/* 比如: 55H 98
* 本意是: 往(0 + (0x55)<<1)写入0x98
*/
void nor_write_word(unsigned int base, unsigned int offset, unsigned int val)
{
volatile unsigned short *p = (volatile unsigned short *)(base + (offset << 1));
*p = val;
}
读取函数
nor_read_word
函数是从NOR Flash 读取两个字节(本开发板位宽16bit),读取数据的地址,是基于2440,所以读取NOR Flash某个地址上的数据时,需要把NOR Flash对应的地址左移一位(地址乘以2)。
unsigned int nor_read_word(unsigned int base, unsigned int offset)
{
volatile unsigned short *p = (volatile unsigned short *)(base + (offset << 1));
return *p;
}
读取地址中的数据
向nor_dat
函数中写入NOR Flash某个地址,返回该NOR Flash地址上的数据。
unsigned int nor_dat(unsigned int offset)
{
return nor_read_word(NOR_FLASH_BASE, offset);
}
进入NOR FLASH的CFI
模式,读取各类信息
do_scan_nor_flash
函数代码如下,该函数的功能:进入CFI模式读取NOR Flash中的厂家ID,设备ID,容量等信息。
50/* 进入NOR FLASH的CFI模式
51 * 读取各类信息
52 */
53 void do_scan_nor_flash(void)
54 {
55 char str[4];
56 unsigned int size;
57 int regions, i;
58 int region_info_base;
59 int block_addr, blocks, block_size, j;
60 int cnt;
61
62 int vendor, device;
63
64 /* 打印厂家ID、设备ID */
65 nor_cmd(0x555, 0xaa); /* 解锁 */
66 nor_cmd(0x2aa, 0x55);
67 nor_cmd(0x555, 0x90); /* read id */
68 vendor = nor_dat(0);
69 device = nor_dat(1);
70 nor_cmd(0, 0xf0); /* reset */
71
72 nor_cmd(0x55, 0x98); /* 进入cfi模式 */
073
74 str[0] = nor_dat(0x10);
75 str[1] = nor_dat(0x11);
76 str[2] = nor_dat(0x12);
77 str[3] = '\0';
78 printf("str = %s", str);
79
80 /* 打印容量 */
81 size = 1<<(nor_dat(0x27));
82 printf("v=0x%x,d=0x%x,s=0x%x,%dM",vendor,device,size,size/(1024*1024));
83
84 /* 打印各个扇区的起始地址 */
85 /* 名词解释:
86 * erase block region : 里面含有1个或多个block, 它们的大小一样
87 * 一个nor flash含有1个或多个region
88 * 一个region含有1个或多个block(扇区)
89
90 * Erase block region information:
91 * 前2字节+1 : 表示该region有多少个block
92 * 后2字节*256 : 表示block的大小
93 */
94
95 regions = nor_dat(0x2c);
96 region_info_base = 0x2d;
97 block_addr = 0;
98 printf("Block/Sector start Address:");
99 cnt = 0;
100 for (i = 0; i < regions; i++)
101 {
102 blocks = 1 + nor_dat(region_info_base) + (nor_dat(region_info_base+1)<<8);
103 block_size=256*(nor_dat(region_info_base+2)+(nor_dat(region_info_base+3)<<8));
104 region_info_base += 4;
105
106 //printf("…………");
107
108 for (j = 0; j < blocks; j++)
109 {
110 /* 打印每个block的起始地址 */
111 //printf("0x%08x ", block_addr);
112 printHex(block_addr);
113 putchar(' ');
114 cnt++;
115 block_addr += block_size;
116 if (cnt % 5 == 0)
117 printf("\n\r");
118 }
119 }
120 printf("\n\r");
121 /* 退出CFI模式 */
122 nor_cmd(0, 0xf0);
123 }
分析:
第65,66行 这两步是解锁
,解锁之后就进入读ID状态
,就可以读取厂家和设备ID了。
第68行 是把读取到的厂家ID
的值,复制给vendor变量。
第69行 是把读取到的设备ID
的值,复制给device变量。
第70行 退出读ID状态
: 给任意地址写F0H。
第72行,往地址0x55地址写入数据0x98,是进入cfi模式
。
第74,75,76行是读取NOR Flash地址0x10,0x11,x012中的字符,赋值给字符串str。
第81行,根据芯片手册可知道,读取NOR Flash地址0x27
处的数据,得到的是NOR Flash容量大小2的幂数,所以把1左移读取到的数据,就可得到NOR Flash的容量。
第95行读取NOR Flash地址0x2c地址中的数据,可以得到NOR Flash中有多少region
。
第102行根据Erase block region information:的信息可以知道读取[2E,2D]这两个字节的地址+1,可以得到一个region有多少block(参考芯片手册)。代码中的region_info_base变量的值是0x2d,0x2d是前两个字节中的低字节,0x2e是前两个字节中的高字节,所以需要左移8位,然后加上1就得到了一个region有多少block.。
第103行参考芯片手册,读取[30,2F]这两个字节地址,然后乘上256就可以得到一个块的大小。
第104行,地址加4,读取下一个region有多少block和每个block的大小。
第112,115行,由于NOR Flash的基地址是0
,所以第一个block的首地址是0,下一个block的首地址,就是上一个block的首地址加上block的大小。
第112行往0地址写入0xf0,退出CFI模式
。
主函数
main函数代码如下所示。把timer
中断去掉,否则: 测试NOR Flash时进入CFI等模式时, 如果发生了中断,cpu必定读NOR Flash,那么读不到正确的指令
,导致程序崩溃。
#include "s3c2440_soc.h"
#include "uart.h"
#include "init.h"
char g_Char = 'A';
char g_Char3 = 'a';
const char g_Char2 = 'B';
int g_A = 0;
int g_B;
int main(void)
{
led_init();
//interrupt_init(); /* 初始化中断控制器 */
key_eint_init(); /* 初始化按键, 设为中断源 */
//timer_init();
puts("\n\rg_A = ");
printHex(g_A);
puts("\n\r");
nor_flash_test();
return 0;
}
同时注意 编译程序时加上: -march=armv4
, 否则
volatile unsigned short *p = xxx;
*p = val; // 会被拆分成2个strb操作
Makefile
如下:
all: start.o led.o uart.o init.o main.o exception.o interrupt.o timer.o nor_flash.o my_printf.o string_utils.o lib1funcs.o
#arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-ld -T sdram.lds $^ -o sdram.elf
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D sdram.elf > sdram.dis
clean:
rm *.bin *.o *.elf *.dis
%.o : %.c
arm-linux-gcc -march=armv4 -c -o $@ $<
%.o : %.S
arm-linux-gcc -march=armv4 -c -o $@ $<
可以看到上面的测试函数中有三个函数还没有实现,分别是:
do_erase_nor_flash
、do_write_nor_flash
、do_read_nor_flash
目的:擦除nor flash某个扇区
,编写某个地址
,读某个地址
。
等待烧写wait_ready
等待烧写完成 : 读数据, Q6无变化时表示结束 (参考芯片手册),
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);
}
}
擦除NOR Flash 某个扇区
do_erase_nor_flash
函数的代码如下。参考芯片手册,就可以知道擦除某个扇区,还是相对比较简单的。
125 void do_erase_nor_flash(void)
126 {
127 unsigned int addr;
128
129 /* 获得地址 */
130 printf("Enter the address of sector to erase: ");
131 addr = get_uint();
132
133 printf("erasing ...");
134 nor_cmd(0x555, 0xaa); /* 解锁 */
135 nor_cmd(0x2aa, 0x55);
136 nor_cmd(0x555, 0x80); /* erase sector */
137
138 nor_cmd(0x555, 0xaa); /* 解锁 */
139 nor_cmd(0x2aa, 0x55);
140 nor_cmd(addr>>1, 0x30); /* 发出扇区地址 */
141 wait_ready(addr);
142 }
第131行,get_uint
函数用于获取输入的地址。
第134,135这两行是解锁
。
第136行是erase sector。
第138,139行是再次解锁
。
第140行是对发出的扇区地址。
第 141行等待擦除完成。
写NOR Flash
do_write_nor_flash
的代码如下所示,开发板上的NOR Flash的位宽是16bit,所以可以把要写的数据构造出16bit然后在写进NOR Flash中。
144 void do_write_nor_flash(void)
145 {
146 unsigned int addr;
147 unsigned char str[100];
148 int i, j;
149 unsigned int val;
150
151 /* 获得地址 */
152 printf("Enter the address of sector to write: ");
153 addr = get_uint();
154
155 printf("Enter the string to write: ");
156 gets(str);
157
158 printf("writing ...\n\r");
159
160 /* str[0],str[1]==>16bit
161 * str[2],str[3]==>16bit
162 */
163 i = 0;
164 j = 1;
165 while (str[i] && str[j])
166 {
167 val = str[i] + (str[j]<<8);
168
169 /* 烧写 */
170 nor_cmd(0x555, 0xaa); /* 解锁 */
171 nor_cmd(0x2aa, 0x55);
172 nor_cmd(0x555, 0xa0); /* program */
173 nor_cmd(addr>>1, val);
174 /* 等待烧写完成 : 读数据, Q6无变化时表示结束 */
175 wait_ready(addr);
176
177 i += 2;
178 j += 2;
179 addr += 2;
180 }
181
182 val = str[i];
183 /* 烧写 */
184 nor_cmd(0x555, 0xaa); /* 解锁 */
185 nor_cmd(0x2aa, 0x55);
186 nor_cmd(0x555, 0xa0); /* program */
187 nor_cmd(addr>>1, val);
188 /* 等待烧写完成 : 读数据, Q6无变化时表示结束 */
189 wait_ready(addr);
190 }
第153行把通过get_uint
获得的地址赋值给addr变量,
第156行通过gets函数获得输入的字符串
。
第168行两个8位的数据,组合成一个16位的数据
赋值给变量val。
读NOR Flash
do_read_nor_flash
函数代码如下,由于NOR Flash是内存类接口,可以像内存一样读取。
191 void do_read_nor_flash(void)
192 {
193 unsigned int addr;
194 volatile unsigned char *p;
195 int i, j;
196 unsigned char c;
197 unsigned char str[16];
198
199 /* 获得地址 */
200 printf("Enter the address to read: ");
201 addr = get_uint();
202
203 p = (volatile unsigned char *)addr;
204
205 printf("Data : \n\r");
206 /* 长度固定为64 */
207 for (i = 0; i < 4; i++)
208 {
209 /* 每行打印16个数据 */
210 for (j = 0; j < 16; j++)
211 {
212 /* 先打印数值 */
213 c = *p++;
214 str[j] = c;
215 printf("%02x ", c);
216 }
217
218 printf(" ; ");
219
220 for (j = 0; j < 16; j++)
221 {
222 /* 后打印字符 */
223 if (str[j] < 0x20 || str[j] > 0x7e) /* 不可视字符 */
224 putchar('.');
225 else
226 putchar(str[j]);
227 }
228 printf("\n\r");
229 }
第201行中的get_uint函数,从串口中获得输入的地址。
第203行,强制类型转化。
第207行~216行是对NOR Flash内容的读取,输出的内容为16进制的数据,由于NOR Flash是内存类接口,可以像内存一样读取。
第220行~227输出NOR Flash的内容为字符型数据,其中的第223行用来判断,输出的字符是否为不可视字符,要是为不可视字符输出点’.’,要是可视字符输出字符。