这里要解释一个地址连接很多新手的疑惑,就是连接8bitSDRAM是:
CPU_ADDR0 –> SDRAM_ADDR0
CPU_ADDR1 –> SDRAM_ADDR1
CPU_ADDR2 –> SDRAM_ADDR2
而连接32bitSDRAM却是:
CPU_ADDR2 –> SDRAM_ADDR0
CPU_ADDR3 –> SDRAM_ADDR1
CPU_ADDR4 –> SDRAM_ADDR2
前面说过,对于S3C2440来说,一个地址对应一个字节,那么对于32bit的SDRAM来说,其一个地址对应4个字节,那处理器要访问的字节映射到32bit的SDRAM就是要除以4,举个例子:
在表格1里,若处理器要访问地址CPU_ADDR = 0x04, 即访问偏移4的字节数据时,映射到8bitSDRAM就是SDRAM_ADDR = 0x04,和处理器是一一对应的,但是在32bitSDRAM中是在SDRAM_ADDR=0x01的首个字节处,若CPU_ADDR = 0x09,则8bit_SDRAM_ADDR = 0x09,32bit_SDRAM_ADDR = 0x2,就是原本一组一个字节现在变成4个字节那么偏移量自然要除以4了,除以4正是偏移2个bit位!!!
3.需要注意的是:对于S3C2440是32bit处理器来说,是以字对齐移动的,即会忽略bit[1:0],比如访问地址0b00001111,其实处理器会忽略最低两位向外发出地址0b00001100,然后一口气read、write4个字节!(是连续4个字节不是连续4个地址上的值,如果是8bit位宽SDRAM那确实是等效于4个地址,但对于32bit还是那个地址)而转到CPU就会认为是地址0b00001100,0b00001101,0b00001110,0b00001111都会被read、write,然后再偏移0b00001111 - 0b00001100 = 0b11处获取所需字节,上面所说的一口气read、write4个字节只是大部分情况下,其实汇编有3中read/write指令,字节(8bit)read/write, 半字(16bit)read/write, 字(32bit)read/write,而字节,半字操作只当是对齐下才使用,并且SDRAM是8bit才体现高效,比如读地址0x04 char 类型数值,则直接汇编后是读取一个字节即可,剩下的高24bit用0填充构成32it数据,就不用读剩下的3个字节从而节省访问时间,而32bit的SDRAM都是直接填充CPU的32bit,不管是读一个字节还是两个字节,但是!如果不是地址对齐的话,则只能连续读取4个字节,不管你要的是一个还是两个字节,然后偏移到你要的字节处,比如读取0b00001101,一口气读取0b00001100的4个字节后偏移0b00001101-0b00001100=1处赋值,如果是char类型就复赋值0b00001101,如果short类型就赋值0b00001101 0b00001110,当然如果是int的类型话就会将0b00001101 0b00001110 0b00001111赋值,不过还少一个字节(0b00010000处)怎么办?没错再次发出读取命令读0b00010000的连续4个字节,然后将偏移0b00010000 - 0b00010000=0出再赋值一个字节即可,从这里可以看出若是读int类型最好地址对齐,这样读一次就可以了,而char类型虽然对不对齐都是读一次,但是如果不对齐还得偏移(移位),也要耗CPU时间,所以在编写code声明变量最好char放一块,int放一块,不要交叉,举个例子:
char a; char b; int c; flash布局如下(耗8字节):
char a; int c; char b; flash 布局如下:
无论哪种布局都有问题,左边的虽然也耗8个字节,但由于int c不是字地址对齐,要读取两次!! 右边虽然字地址对齐,但是却多耗一个字节地址存储,浪费flash空间(明明前面有空间不填充~)
以下分别讨论6个case以增强理解:
a.在8bit/32bitSDRAM, 地址对齐下read/write一个char
b.在8bit/32bitSDRAM, 地址对齐下read/write一个short
c.在8bit/32bitSDRAM, 地址对齐下read/write一个int
d.在8bit/32bitSDRAM, 地址非对齐下read/write一个char
e.在8bit/32bitSDRAM, 地址非对齐下read/write一个short
f.在8bit/32bitSDRAM, 地址非对齐下read/write一个int
case1:
CPU_ADDR=0x04,对齐后CPU_ADDR=0x04,在8bit SDRAM_ADDR=0x04,read 一个字CPU(char val = (char )0x04)(字节命令),最终高24bit用0填充后送入register处理,在32bit SDRAM_ADDR=0x01,read 4个字节到CPU(char val = (char )0x01)后直接送到register处理。
PS:为何read一个字节时要高24bit填充0,虽然register处理最终只会用到一个字节低8bit,但是不允许bit出现高阻态!要么0,要么1,而read 8bit的SDRAM时,其高24bit处于高阻态!而32bitSDRAM高24bit本来就有值。
再来看看write,假设写的是0xff 全称是写0x000000ff,8bit SDRAM也是直接write到0x04地址一个字节即可,高24bit反正没连接!但是对于32bit SDRAM就有问题了,我只是想write一个char类型,在32bit SDRAM的 0x01地址高3个字节并不想覆盖,怎么办呢?其实很简单,查看SDRAM 引脚分布会发现有LDQM UDQM 两pin ,是数据掩码,在读数据时可以通过控制将低8bit或者高8bit用0与,在write中可以将相应的8bit置为无效,这个当使能低字节时可以被写入,而高3字节不受影响!! 对应连接ICnWBE[3:0]
case2:
CPU_ADDR=0x04,对齐后CPU_ADDR=0x04,在8bit SDRAM_ADDR=0x04,read 一个字节到CPU(char val = (char )0x04),CPU 进行保存移位后发现SDRAM位宽是8bit还不够,于是,还差short - SDRAM位宽 = 16bit - 8bit = 8bit,需要 剩余8bit / 位宽 = 8bit/8bit =1个地址,即地址+1,从而再read 一个字节到CPU(char val = (char )0x05)这两步等价于(short val = (short )0x04),(半字命令)然后高16bit用0填充,而32bit SDRAM就直接读取后送到register处理了。对于write,8bitSDRAM,发现不能一次写16bit,就先写低8bit 然后地址++,移位再将高8bit写入,而32bit就简单多了,直接写入 不过只是使能低16bit,也即使能nWBE[1:0]。
case3:
这个就不用多说了吧………………
case4:
CPU_ADDR=0x05不对齐,对齐后CPU_ADDR=0x04,在8bit SDRAM_ADDR=0x04,read 一个字节到CPU(char val = (char )0x04),(当然这里由于是非对齐导致的,所以采用的是字命令,即)然后移位再读取直到读取4个字节,等效于 (int val = (int )0x04)(字命令)然后右移8bit将低8bit除去(即不用0x04这个地址的值),接着低8bit就是0x05地址上的值了,然后再将高24bit填充0(虽然bit[23:8]有值)送入register,而对于32bitSDRAM也类似,不过8bitSDRAM是读取4个字节4次,而32bitSDRAM只用一次读取4个字节,从而节约时间,其他的移位填充就都一样了对于write来说,假设要写0xff,全称是0x000000ff,因为对齐地址是0x04,所以最终值为0x0000ff00,当然,由于只是写0x05,不能覆盖其他地址上的值,所以直到写的地址是0x05时才使能nwbe,这也是为何8bit的SDRAM也有该pin,而32bit就简单了,直接写,只是使能bit[15:8]可以写入!
case5: 请读者自己根据case4思考
case6: 请读者自己根据case4思考
///////////// 以上只是个人分析 ////////////////////////
4.现在编写code,实现的功能是设置SDRAM,将code copy到SDRAM,然后跳到SDRAM的main()函数继续执行,先用汇编实现,最后再用C实现,汇编有如下文件:
head.S led_main.c Makefile
head.S code 如下:
1 .equ MEM_CTL_BASE, 0x48000000
2 .equ SDRAM_BASE, 0x30000000
3
4 .text
5 .global _start
6 _start:
7 bl disable_watch_dog
8 bl memsetup
9 bl copy2sdram
10 ldr pc, =on_sdram
11
12 on_sdram:
13 ldr sp, =0x34000000
14 bl main
15 loop:
16 b loop
17
18 disable_watch_dog:
19 ldr r0, =0x53000000
20 ldr r1, =0x0
21 str r1, [r0]
22 mov pc, lr
23
24 memsetup:
25 ldr r0, =MEM_CTL_BASE
26 adrl r1, mem_cfg_val
27 add r2, r0, #52
28 copy_loop:
29 ldr r3, [r1], #4
30 str r3, [r0], #4
31 cmp r0, r2
32 bne copy_loop
33 mov pc, lr
34
35 copy2sdram:
36 ldr r0, =0
37 ldr r1, =SDRAM_BASE
38 ldr r2, =4096
39 cp_loop:
40 ldr r3, [r0], #4
41 str r3, [r1], #4
42 cmp r0, r2
43 bne cp_loop
44 mov pc, lr
45
46 .align 4
47 mem_cfg_val:
48 .long 0x22011110 @BWSCON
49 .long 0x00000700 @BANKCON0
50 .long 0x00000700 @BANKCON1
51 .long 0x00000700 @BANKCON2
52 .long 0x00000700 @BANKCON3
53 .long 0x00000700 @BANKCON4
54 .long 0x00000700 @BANKCON5
55 .long 0x00018005 @BANKCON6
56 .long 0x00018005 @BANKCON7
57 .long 0x008c07a3 @REFRESH
58 .long 0x000000b1 @BANKSIZE
59 .long 0x00000030 @MRSRB6
60 .long 0x00000030 @MRSRB7
在makefile中链接是会指定其运行地址为 0x30000000,所以这些code的基址是_start=0x30000000,但是CPU刚开始上电复位时期PC赋值是0,然后每执行一条指令PC+4,所以PC依次为0 4 8 ………根本无法将这个_start=0x30000000装载到PC指针中,所以这code依旧在处理器的内部4k中运行,关系运行地址(链接地址)和加载地址(烧入地址)我会单独写篇博文,这里不祥讲。由于code的调用是通过bl即相对调用,范围是相对PC偏差32M以内,同时与地址无关,使得我们的函数调用可以依旧在内部的4K执行,当code被copy到SDRAM后,执行ldr pc, =on_sdram,就实现了从内部4kSRAM到外部SDRAM的跳转了,原因很简单,就是刚上电PC值为0,依次 0 4 8 加4(相对运行),但是这些指令地址其实是 0x30000000+0 ; 0x30000000 +4 ; 0x30000000 + 8; …………….,直到汇编指令ldr将on_sdram(0x30000000+相对_start偏移地址)的地址装载到PC就实现跳转了,因为ldr就是加载绝对值得!需要注意的还有adrl r1, mem_cfg_val,因为标识mem_cfg_val也是0x30000000+(相对_start偏移地址),所以不能直接将这个地址赋值到r1,还没执行copy2sdram,SDRAM还没有值,所以采用相对地址指令,总之code里面都要用地址无关指令,举个例子,如果_start=0x30000000 第二条指令为0x30000004,当_start=0x33000000 第二条指令为0x33000004,这样第二条指令老是会随着基址_start的不同老改变,若采用相对地址概念,即第二条指令为(_start+4),这样在操作相对地址就不用考虑其绝对地址在哪了,直到函数跳转到SDRAM时再使用其绝对地址!
led_main.c
1 #define GPBCON (*(volatile unsigned long *)0x56000010)
2 #define GPBDAT (*(volatile unsigned long *)0x56000014)
3
4 int main()
5 { 6 GPBCON |= ((1<<10) | (1<<16) | (1<<12)); // set led1 led4 led2output 7 GPBDAT &= ~((1<<5) | (1<<8) | (1<<6)); // enable led1 led4 led2 8 9 return 0; 10 }
11
Makefile:
1 sdram.bin : head.S led_main.c
2 arm-linux-gcc -c head.S -o head.o
3 arm-linux-gcc -c led_main.c -o led_main.o
4 arm-linux-ld -Ttext 0x30000000 head.o led_main.o -o sdram.elf
5 arm-linux-objcopy -O binary -S sdram.elf sdram.bin
6 arm-linux-objdump -D -S sdram.elf > sdram.dis
7
8 clean:
9 rm -f *.o sdram.elf sdram.bin sdram.dis
make 后可以查看其反汇编code:
sdram.dis:
1
2 sdram.elf: file format elf32-littlearm
3
4 Disassembly of section .text:
5
6 30000000 <_start>:
7 30000000: eb000005 bl 3000001c <disable_watch_dog>
8 30000004: eb000008 bl 3000002c <memsetup>
9 30000008: eb000010 bl 30000050 <copy2sdram>
10 3000000c: e59ff090 ldr pc, [pc, #144] ; 300000a4 <mem_cfg_val+0x34>
11
12 30000010 <on_sdram>:
13 30000010: e3a0d30d mov sp, #872415232 ; 0x34000000
14 30000014: eb000025 bl 300000b0 <main>
15
16 30000018 <loop>:
17 30000018: eafffffe b 30000018 <loop>
18
19 3000001c <disable_watch_dog>:
20 3000001c: e3a00453 mov r0, #1392508928 ; 0x53000000
21 30000020: e3a01000 mov r1, #0 ; 0x0
22 30000024: e5801000 str r1, [r0]
23 30000028: e1a0f00e mov pc, lr
24
25 3000002c <memsetup>:
26 3000002c: e3a00312 mov r0, #1207959552 ; 0x48000000
27 30000030: e28f1038 add r1, pc, #56 ; 0x38
28 30000034: e1a00000 nop (mov r0,r0)
29 30000038: e2802034 add r2, r0, #52 ; 0x34
基址都是从0x30000000开始的,而一开始code是在内部4kSRAM基址为0出,这就是为何要使用地址无关指令了(采用相对偏移地址)。
5.再来看看C,要注意的是调用C必须先设置好栈,所以汇编的第一条指令就是ldr sp, =4096,
也是3个文件,只不过大部分的code由C实现:
head.S led_main.c Makefile
head.S
1 .text
2 .global _start
3 _start:
4 ldr sp, =4096
5 bl disable_watch_dog
6 bl memsetup
7 ldr r0, =0
8 ldr r1, =0x30000000
9 ldr r2, =4096
10 bl copy2sdram
11 ldr pc, =on_sdram
12
13 on_sdram:
14 ldr sp, =0x34000000
15 bl main
16 loop:
17 b loop
18
led_main.c:
1 #define GPBCON (*(volatile unsigned long *)0x56000010)
2 #define GPBDAT (*(volatile unsigned long *)0x56000014)
3
4 struct mem_reg{
5 unsigned long BWSCON;
6 unsigned long BANKCON0;
7 unsigned long BANKCON1;
8 unsigned long BANKCON2;
9 unsigned long BANKCON3;
10 unsigned long BANKCON4;
11 unsigned long BANKCON5;
12 unsigned long BANKCON6;
13 unsigned long BANKCON7;
14 unsigned long REFRESH;
15 unsigned long BANKSIZE;
16 unsigned long MRSRB6;
17 unsigned long MRSRB7;
18 } ;
19
20 int disable_watch_dog()
21 {
22 (*(volatile unsigned long *)0x53000000) = 0;
23
24 return 0;
25 }
26
27 /*int memsetup() 28 { 29 unsigned long *p_mem_reg_cfg =(unsigned long* )0x48000000; 30 *p_mem_reg_cfg++ = 0x22011110; 31 *p_mem_reg_cfg++ = 0x00000700; 32 *p_mem_reg_cfg++ = 0x00000700; 33 *p_mem_reg_cfg++ = 0x00000700; 34 *p_mem_reg_cfg++ = 0x00000700; 35 *p_mem_reg_cfg++ = 0x00000700; 36 *p_mem_reg_cfg++ = 0x00000700; 37 *p_mem_reg_cfg++ = 0x00018005; 38 *p_mem_reg_cfg++ = 0x00018005; 39 *p_mem_reg_cfg++ = 0x008c07a3; 40 *p_mem_reg_cfg++ = 0x000000b1; 41 *p_mem_reg_cfg++ = 0x00000030; 42 *p_mem_reg_cfg++ = 0x00000030; 43 44 return 0; 45 }*/
46
47 int memsetup()
48 {
49 struct mem_reg *p_mem_reg_cfg =(struct mem_reg * )0x48000000; //memery registers base address
50 p_mem_reg_cfg->BWSCON = 0x22011110;
51 p_mem_reg_cfg->BANKCON0 = 0x00000700;
52 p_mem_reg_cfg->BANKCON1 = 0x00000700;
53 p_mem_reg_cfg->BANKCON2 = 0x00000700;
54 p_mem_reg_cfg->BANKCON3 = 0x00000700;
55 p_mem_reg_cfg->BANKCON4 = 0x00000700;
56 p_mem_reg_cfg->BANKCON5 = 0x00000700;
57 p_mem_reg_cfg->BANKCON6 = 0x00018005;
58 p_mem_reg_cfg->BANKCON7 = 0x00018005;
59 p_mem_reg_cfg->REFRESH = 0x008c07a3;
60 p_mem_reg_cfg->BANKSIZE = 0x000000b1;
61 p_mem_reg_cfg->MRSRB6 = 0x00000030;
62 p_mem_reg_cfg->MRSRB7 = 0x00000030;
63
64 return 0;
65 }
66
67 int copy2sdram(unsigned long *src, unsigned long *des, unsigned long size)
68 {
69 unsigned long i;
70 for(i=0; i<size; i +=4)
71 {
72 *des++ = *src++;
73 }
74
75 return 0;
76 }
77
78 int main()
79 {
80 GPBCON |= ((1<<14)); // set led3 output
81 GPBDAT &= ~((1<<7) ); // enable led3
82
83 return 0;
84 }
85
Makefile 跟汇编的一样,需要注意的是copy2sdram的3个参数是通过r0,r1,r2传入的,这也是汇编调用C传参的格式第一个参数放r0,第二个参数放r1………..不过最多只能4个,所以当要传多参数可以传个指针下去或者结构体。