对于2440而言,nand启动,nand的前4k内容由硬件复制到sram。
nor flash,可以像内存一样读,但是不能像内存一样写,执行写操作需要特殊的操作。
程序中包含有需要写的全局或者静态变量,它们在bin文件中,写在nor flash上,直接修改这样的变量是无效的。
到底什么意思呢?还是看例子比较有说服力。
在学习C语言的过程中,我们或多或少知道一些东西,c/c++可执行文件需要预处理,编译,汇编,连接。
程序有text段,data段,bss段,rodata段等等,今天,就和它们来个亲密接触吧。
还是先说上面的问题吧,看例子:
在之前的程序代码基础上,启动代码增加自动识别是nand还是nor启动:
/* 设置内存: sp 栈 */ /* 分辨是nor/nand启动 * 写0到0地址, 再读出来 * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动 * 否则就是nor启动 */ mov r1, #0 ldr r0, [r1] /* 读出原来的值备份 */ str r1, [r1] /* 0->[0] */ ldr r2, [r1] /* r2=[0] */ cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */ ldr sp, =0x40000000+4096 /* 先假设是nor启动 */ ldreq sp, =4096 /* nand启动 */ streq r0, [r1] /* 恢复原来的值 */
nor flash启动,写入和读出不会相等,即执行ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
而nand启动,由于写入读出都相等,会执行
ldreq sp, =4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
上面也说了,由于nor flash 的特性,导致像内存一样读可以,可是写操作需要特殊处理,后面的例子你将会看到按内存方式直接写nor flash是无效的。
start.S:
.text .global _start _start: /* 关闭看门狗 */ ldr r0, =0x53000000 ldr r1, =0 str r1, [r0] /* 设置内存: sp 栈 */ /* 分辨是nor/nand启动 * 写0到0地址, 再读出来 * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动 * 否则就是nor启动 */ mov r1, #0 ldr r0, [r1] /* 读出原来的值备份 */ str r1, [r1] /* 0->[0] */ ldr r2, [r1] /* r2=[0] */ cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */ ldr sp, =0x40000000+4096 /* 先假设是nor启动 */ ldreq sp, =4096 /* nand启动 */ streq r0, [r1] /* 恢复原来的值 */ bl SystemInit //调用main函数 bl main halt: b halt
main.c:
#include "s3c2440_gpio.h" #include "s3c2440_soc.h" #include "uart.h" #include "init.h" unsigned char glob_a='a'; unsigned char glob_b='b'; const char p=1; char *q="char *q"; static int golb_c; static int golb_d; int glob_e=1; int glob_f; void SystemInit(void) { //配置LOCKTIME(0x4C000000) = 0xFFFFFFFF *(volatile unsigned int *)0x4C000000=0xFFFFFFFF; //CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 *(volatile unsigned int *)0x4C000014=0x5; //协处理指仿 __asm__( "mrc p15, 0, r1, c1, c0, 0 " /* 读出控制寄存噿*/ "orr r1, r1, #0xc0000000 " /* 设置为“asynchronous bus mode‿*/ "mcr p15, 0, r1, c1, c0, 0 " /* 写入控制寄存噿*/ ); /* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) * m = MDIV+8 = 92+8=100 * p = PDIV+2 = 1+2 = 3 * s = SDIV = 1 * FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M */ *(volatile unsigned int *)0x4C000004=(92<<12)|(1<<4)|(1<<0); } void Delay(uint32_t count) { while(count--); } int main(void) { uart_init(); puts("Hello, world! "); //sdram_init(); while (1) { putchar(glob_a); glob_a++; Delay(100000); } return 0; }
注意,上面的红色部分,此前的代码我们都没有使用全局变量,这样加上之后,我们编译生成bin文件,其他文件夹都是前面例子的,其实这里只是使用了配置时钟,区分是nand还是nor启动以及前面的串口程序。把这个程序烧写在nand flash上运行,和我们想象中的一致,但是,烧写在nor flash 上,代码却和我们预想的不一致了。
在nand 上 运行,串口会打印abcde。。。。
在nor上运行,串口一直打印a!
这是为什么呢?
我们程序不是做了++处理吗?
查看反汇编:
Disassembly of section .data: 00000800 <__data_start>: 800: 00006261 andeq r6, r0, r1, ror #4 00000801 <glob_b>: 801: d0000062 andle r0, r0, r2, rrx 00000804 <q>: 804: 000005d0 ldreqd r0, [r0], -r0 00000808 <glob_e>: 808: 00000001 andeq r0, r0, r1 Disassembly of section .rodata: 000005cc <p>: 5cc: 00000001 andeq r0, r0, r1 5d0: 72616863 rsbvc r6, r1, #6488064 ; 0x630000 5d4: 00712a20 rsbeqs r2, r1, r0, lsr #20 5d8: 6c6c6548 cfstr64vs mvdx6, [ip], #-288 5dc: 77202c6f strvc r2, [r0, -pc, ror #24]! 5e0: 646c726f strvsbt r7, [ip], #-623 5e4: 000d0a21 andeq r0, sp, r1, lsr #20 Disassembly of section .bss: 0000080c <golb_c>: 80c: 00000000 andeq r0, r0, r0 00000810 <golb_d>: 810: 00000000 andeq r0, r0, r0 00000814 <glob_f>: 814: 00000000 andeq r0, r0, r0 Disassembly of section .comment: 00000000 <.comment>: 0: 43434700 cmpmi r3, #0 ; 0x0 4: 4728203a undefined 8: 2029554e eorcs r5, r9, lr, asr #10 c: 2e342e33 mrccs 14, 1, r2, cr4, cr3, {1} 10: 47000035 smladxmi r0, r5, r0, r0 14: 203a4343 eorcss r4, sl, r3, asr #6 18: 554e4728 strplb r4, [lr, #-1832] 1c: 2e332029 cdpcs 0, 3, cr2, cr3, cr9, {1} 20: 00352e34 eoreqs r2, r5, r4, lsr lr 24: 43434700 cmpmi r3, #0 ; 0x0 28: 4728203a undefined 2c: 2029554e eorcs r5, r9, lr, asr #10 30: 2e342e33 mrccs 14, 1, r2, cr4, cr3, {1} 34: 47000035 smladxmi r0, r5, r0, r0 38: 203a4343 eorcss r4, sl, r3, asr #6 3c: 554e4728 strplb r4, [lr, #-1832] 40: 2e332029 cdpcs 0, 3, cr2, cr3, cr9, {1} 44: 00352e34 eoreqs r2, r5, r4, lsr lr
这个我们知道了一点(上面例子的第一个static给个非零初始值就更好了,但我不想开虚拟机了,没动手的你可要试试哦),全局或者静态变量初始化为0或者不初始化的都放在.bss段,初始化了的全局或者静态变量放在.data段,const修饰的变量放在.rodata段,.comment段是注释段,比如上面的注释前面几个机器码,我们可以看出它注释的是编译器是gnu gcc。(如果你感兴趣可以全部打出来看看注释信息,采用的UE查看的)
小端模式,所以是反着输入的。
然后就应该是Makefile了,上面的代码需要连接一个data段,否则生成的bin文件会非常大。
.PHONY:clean objcts :=start.o main.o init.o uart.o s3c2440_gpio.o sdram.bin:$(objcts) arm-linux-ld -Ttext 0 -Tdata 0x800 $^ -o sdram.elf arm-linux-objcopy -O binary -S sdram.elf $@ arm-linux-objdump -S -D sdram.elf > sdram.dis %.o:%.c arm-linux-gcc -o $@ -c -g $< %.o:%.S arm-linux-gcc -o $@ -c -g $< clean: -rm *.bin *.o *.elf *.dis
是不是发现好像makefile变了,是的,是时候加进一步了,之前为了不附加难度,都是使用的最基本的方式书写Makefile,现在熟悉了2440大部分裸机代码了,就该写出点其他的make了。这个Makefile还不是最终版,有潜藏bug,后面再说怎么进一步优化。其中增加了一个连接地址
-Tdata 0x800
这是把数据段从0x800开始存储。为什么要从0x800开始连接数据段呢?
先看看我们没有指定这个选项的时候:
可以看到这个bin文件的大小居然有30多k,而我们的代码是非常小的,这显然是不正常的,我们加上连接时指定数据段地址之后:
这样之后,我们发现文件的大小就比较好接受了。这个数据段地址,需要根据实际情况调整,这里只是演示作用。
好的,现在回到Makefile:
Makefile命令中的带有-(减号)时,表示忽略错误,继续执行make。
@:使命令在被执行前不被回显。
这里主要说明-(减号):
.PHONY:clean
app:main.o
gcc -o app main.o
main.o:main.c
gcc -c main.c
clean:
rm *.o
rm app
main函数只有一个printf函数,此时执行make
.PHONY:clean app:main.o gcc -o app main.o main.o:main.c gcc -c main.c clean: -rm *.o rm app
关于2440上面的那个Makefile,在我之前的随笔中是有说明的,
Makefile学习之路——2
Makefile 7——自动生成依赖关系 三颗星
看了这两篇之后,你就会知道上面的Makefile存在什么潜在bug。
我们的2440依赖了一个头文件(s3c2440_soc.h),而这个头文件是没有对应源文件的,这样更改这个头文件之后,必须make clean之后,再make才能确保是最新的,如果更改了这个头文件,还是直接make,那么make是不会响应你最新改动的,所以需要我们使用sed指令来进行Makefile的书写,也就是上面的Makefile7所说明的东西。
以前学过了的东西,一定要试着拿在实验或者项目上应用,否则,学那么不使用又有什么意义呢?
Question:
main函数中的
char *q="char *q";
q变量倒是存在.data段中,那么“char *q”这个字符串存放在哪里呢?后面的随笔会给出答案,其实这也是C语言中接触过的知识点。