嵌入式学习笔记004-裸奔篇之SDRAM

  1. 前提:针对S3C2440来说,一个地址对应一个字节!

  2. 以下是SDRAM物理存储map,同样的地址,但对应的数据data也即位宽8bit、32bit作为分析
    嵌入式学习笔记004-裸奔篇之SDRAM_第1张图片

这里要解释一个地址连接很多新手的疑惑,就是连接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字节):
嵌入式学习笔记004-裸奔篇之SDRAM_第2张图片

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个,所以当要传多参数可以传个指针下去或者结构体。

你可能感兴趣的:(嵌入式,sdram,s3c2440,TQ2440)