历史上的今天
今天是:2025年04月19日(星期六)
2020年04月19日 | S3C2440 代码重定位实验(三)
2020-04-19 来源:eefocus
代码重定位
我们现在来解决代码重定位实验(一)所引入的代码重定位问题。
对于S3C2440来说:
BIN文件小于4KB时:
若是Nand方式启动,则不存在任何问题
若是Nor方式启动,则我们可以只重定位.data段即可
当BIN文件大于4KB时:
若是Nand方式启动,则需要重定位整个程序,包括代码段和数据段
若是以Nor方式启动,则依然只重定位.data段即可
只重定位.data段和清零.bss段
对于重定位.data段的代码,正常情况下应该是使用汇编来编写的,我为了简便起见,使用了C语言来编写。由于此时尚未重定位数据段和清零BSS段,是不应该调用C函数的。但是我保证了这两个函数不访问全局变量,所以只要正确设置了栈指针,调用也可以正常工作。
使用的链接脚本relocate.lds如下:
SECTIONS {
.text 0 : {*(.text)}
.rodata : {*(.rodata)}
_data_offset = .;
.data 0x30000000 : AT(_data_offset) {
_data_LMA = LOADADDR(.data);
_data_start = .;
*(.data)
_data_end = .;
}
_bss_start = .;
.bss : { *(.bss) }
_bss_end = .;
}
relocate.c
extern unsigned char _data_offset;
extern unsigned char _data_start;
extern unsigned char _data_end;
extern unsigned char _bss_start;
extern unsigned char _bss_end;
void copyDataSection(void){
volatile unsigned char *dataLMA = &_data_offset;
volatile unsigned char *start = &_data_start;
volatile unsigned char * end = &_data_end;
while(start <= end){
*start = *dataLMA;
start++;
dataLMA++;
}
}
void clearBSS(void){
volatile unsigned char* start = &_bss_start;
volatile unsigned char* end = &_bss_end;
while(start < end){
*start = 0;
start++;
}
}
CRT0.S
.text
.global _start
_start:
/* 1.关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 2.设置时钟 */
/* 2.1 设置LOCKTIME(0x4C000000)=0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* 2.2 设置CLKDIVN(0x4C000014) = 0x5 FCLK : HCLK : PCLK = 400m : 100m : 50m*/
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 2.3 设置CPU处于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 /* #R1_nF:OR:R1_iA */
mcr p15,0,r0,c1,c0,0
/* 2.4 设置MPLLCON(0x4C000004)=(92<<12) | (1 << 4) | (1 << 0)
* m = MDIV + 8 = 100
* p = PDIV + 2 = 3
* s = SDIV = 1
* Mpll = (2 * m * Fin) / (p * 2 ^ s)= (2 * 100 * 12) / (3 * 2 ^ 1) = 400MHZ
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12) | (1 << 4) | (1 << 0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 3.设置栈
* 自动分辨NOR启动或者NAND启动
* 向0地址写入0,在读出来,如果写入则是NAND,否则是NOR
*/
ldr r0, =0
ldr r1, [r0] /* 读出原来的值备份 */
str r0, [r0] /* 向0地址写入0 */
ldr r2, [r0] /* 再次读出来 */
cmp r1, r2
ldr sp, =0x40000000 + 4096 /* nor启动 */
movne sp, #4096 /* nand启动 */
strne r1, [r0] /* 恢复原来的值 */
//初始化SDRAM内存控制器
bl sdram_init
bl copyDataSection
bl clearBSS
bl main
halt:
b halt
main.c:
#include "myprintf.h"
#include "uart.h"
#include "util.h"
char gCh = 'A';
char gCh1;
int main(void) {
uart0_init();
printf("%snr", "NorFlash Relocate Test.");
while(1) {
gCh++;
printf("%c(0x%x)", gCh, gCh);
wait(800000);
}
return 0;
}
将编译生成的BIN文件烧写至NorFlash中,启动开发板,发现可以main函数正常修改全局变量了,如下图所示:

上述代码效率改进
从拷贝函数中可以看出,我们每次只拷贝一个字节。而JZ2440的SDRAM是32位的,这样拷贝的话效率就很低。为此,我们一次拷贝4个字节的数据。我们修改relocate.c文件如下所示:
relocate.c
extern unsigned int _data_offset;
extern unsigned int _data_start;
extern unsigned int _data_end;
extern unsigned int _bss_start;
extern unsigned int _bss_end;
void copyDataSection(void){
volatile unsigned int *dataLMA = &_data_offset;
volatile unsigned int *start = &_data_start;
volatile unsigned int * end = &_data_end;
while(start <= end){
*start = *dataLMA;
start++;
dataLMA++;
}
}
void clearBSS(void){
volatile unsigned int* start = &_bss_start;
volatile unsigned int* end = &_bss_end;
while(start < end){
*start = 0;
start++;
}
}
再次编译烧写至开发板,以Nor方式启动,并上电观察。

通过上述动态图可以看出,全局变量gCh本来是等于‘A’,对应0x41。但是现在却是0了,被清零了。这是为什么呢?为什么清零.bss段的时候,会把.data段的值给清掉呢?
我们观察清零.bss段的反汇编,如下所示:
Disassembly of section .data:
30000000 <_data_start>:
30000000: 41 .byte 0x41
Disassembly of section .bss:
30000001 ... 00000d2c d2c: e52db004 push {fp} ; (str fp, [sp, #-4]!) d30: e28db000 add fp, sp, #0 ; 0x0 d34: e24dd00c sub sp, sp, #12 ; 0xc d38: e59f3040 ldr r3, [pc, #64] ; d80 d3c: e50b300c str r3, [fp, #-12] d40: e59f303c ldr r3, [pc, #60] ; d84 d44: e50b3008 str r3, [fp, #-8] d48: ea000005 b d64 d4c: e51b200c ldr r2, [fp, #-12] d50: e3a03000 mov r3, #0 ; 0x0 d54: e5c23000 strb r3, [r2] d58: e51b300c ldr r3, [fp, #-12] d5c: e2833001 add r3, r3, #1 ; 0x1 d60: e50b300c str r3, [fp, #-12] d64: e51b200c ldr r2, [fp, #-12] d68: e51b3008 ldr r3, [fp, #-8] d6c: e1520003 cmp r2, r3 d70: 3afffff5 bcc d4c d74: e28bd000 add sp, fp, #0 ; 0x0 d78: e8bd0800 pop {fp} d7c: e12fff1e bx lr d80: 30000001 .word 0x30000001 d84: 30000002 .word 0x30000002 由该段汇编代码可知,它对0x30000001地址开始,到0x30000002地址前一个字节为止,进行了清零操作。而数据段.data的地址位于0x30000000处,所以理论上好像不会被清掉。 我们再观察SDRAM的电路图可知: CPU发出的地址线最低两位没有接到SDRAM芯片上,因为两片SDRAM组成了32位的数据总线,CPU对于内存是按照4字节寻址的,地址线的最低两位被忽略,默认为0。 所以若是单次访问4个字节的数据,无论是访问地址0x30000001、0x30000002或者0x30000003,CPU最终访问的都是0x30000000开始的四字节数据。所以数据段的数据就在清零.bss段时被清理掉了。 问题原因找到了,解决方法就简单了。我们在链接脚本中主动让数据段和BSS段都按4字节先对齐好,就不会有问题了。修改链接脚本如下所示: relocate.lds: SECTIONS { .text 0 : {*(.text)} .rodata : {*(.rodata)} /*按4字节对齐*/ . = ALIGN(4); _data_offset = .; .data 0x30000000 : AT(_data_offset) { _data_LMA = LOADADDR(.data); _data_start = .; *(.data) _data_end = .; } /*按4字节对齐*/ . = ALIGN(4); _bss_start = .; .bss : { *(.bss) } _bss_end = .; } 重新编译,以Nor方式启动,上电观察,又恢复正常了。 重定位整个程序 当我们的开发板是以Nand方式启动,并且BIN文件超过4KB时,则需要重定位整个程序,包括代码段和数据段。为此,我们需要引入一个概念:位置无关代码。位置无关代码(PIC:Position Independent Code),就是无论被加载到任何地址空间都可以正常工作的代码。 那么怎么写位置无关的程序呢? 调用程序时使用b或者bl相对跳转指令 重定位之前,不能使用绝对地址,不可访问全局变量或者静态变量 不可访问有初始值的数组(因为这些初始值放置在.rodata段内,而.rodata段是位置相关的,不在栈中) 重定位之后,需要使用绝对跳转指令跳转到运行时地址(链接地址)处开始执行,比如ldr pc, =main 由于我们还没有涉及到Nand Flash的操作实验(下一篇文章),而Nand Flash不能像访问内存一样地读取数据,所以我们还是先将BIN文件烧写至Nor Flash,并以Nor Flash方式启动,进行整个程序的重定位。 跳转指令 在汇编文件中调用main函数时,注意必须使用绝对跳转指令: ldr pc, =main 而不能使用bl main这种位置无关指令,否则仍旧是在Nor Flash或者是SRAM中运行。 又因为ldr pc, =main指令并不改变lr寄存器存储的返回地址,所以在跳转到main函数之前,要修改lr,使其指向halt,否则从main函数返回,会重新再一次执行ldr pc, =main指令,如下所示: ldr lr, =halt ldr pc, =main 全部文件如下所示: 我们列出最主要的几个源码文件: CRT0.S .text .global _start _start: /* 1.关闭看门狗 */ ldr r0, =0x53000000 ldr r1, =0 str r1, [r0] /* 2.设置时钟 */ /* 2.1 设置LOCKTIME(0x4C000000)=0xFFFFFFFF */ ldr r0, =0x4C000000 ldr r1, =0xFFFFFFFF str r1, [r0] /* 2.2 设置CLKDIVN(0x4C000014) = 0x5 FCLK : HCLK : PCLK = 400m : 100m : 50m*/ ldr r0, =0x4C000014 ldr r1, =0x5 str r1, [r0] /* 2.3 设置CPU处于异步模式 */ mrc p15,0,r0,c1,c0,0 orr r0,r0,#0xc0000000 /* #R1_nF:OR:R1_iA */ mcr p15,0,r0,c1,c0,0 /* 2.4 设置MPLLCON(0x4C000004)=(92<<12) | (1 << 4) | (1 << 0) * m = MDIV + 8 = 100 * p = PDIV + 2 = 3 * s = SDIV = 1 * Mpll = (2 * m * Fin) / (p * 2 ^ s)= (2 * 100 * 12) / (3 * 2 ^ 1) = 400MHZ */ ldr r0, =0x4C000004 ldr r1, =(92<<12) | (1 << 4) | (1 << 0) str r1, [r0] /* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定 * 然后CPU工作于新的频率FCLK */ /* 3.设置栈 * 自动分辨NOR启动或者NAND启动 * 向0地址写入0,在读出来,如果写入则是NAND,否则是NOR */ ldr r0, =0 ldr r1, [r0] /* 读出原来的值备份 */ str r0, [r0] /* 向0地址写入0 */ ldr r2, [r0] /* 再次读出来 */ cmp r1, r2 ldr sp, =0x40000000 + 4096 /* nor启动 */ movne sp, #4096 /* nand启动 */ strne r1, [r0] /* 恢复原来的值 */ //初始化SDRAM内存控制器 bl sdram_init //bl copyDataSection bl copyBIN2SDRAM bl clearBSS //bl main ldr lr, =halt ldr pc, =main //这才是真正跳转到SDRAM中执行代码 halt: b halt relocate.c extern unsigned int _text_start; extern unsigned int _bss_start; extern unsigned int _bss_end; void copyBIN2SDRAM(void){ volatile unsigned int* srcStart = 0x0; volatile unsigned int* destStart = &_text_start; volatile unsigned int* destEnd = &_bss_start; while(destStart < destEnd){ *destStart = *srcStart; destStart++; srcStart++; } } void clearBSS(void){ volatile unsigned int* start = &_bss_start; volatile unsigned int* end = &_bss_end; while(start < end){ *start = 0; start++; } } relocate.lds SECTIONS { . = 0x30000000; _text_start = .; .text : {*(.text)} .rodata : {*(.rodata)} /*按4字节对齐*/ . = ALIGN(4); .data : { _data_start = .; *(.data) _data_end = .; } /*按4字节对齐*/ . = ALIGN(4); _bss_start = .; .bss : { *(.bss) } _bss_end = .; } Makefile CROSS_COMPILE_PREFIX=arm-linux- CFLAGS=-g TARGET=relocate $(TARGET).bin : CRT0.o uart.o myprintf.o Ctype.o sdram.o util.o $(TARGET).o main.o @#$(CROSS_COMPILE_PREFIX)ld -Ttext 0x0000000 -Tdata 0x30000000 -o $(TARGET).elf $^ $(CROSS_COMPILE_PREFIX)ld -T $(TARGET).lds -o $(TARGET).elf $^ $(CROSS_COMPILE_PREFIX)objcopy -O binary -S $(TARGET).elf $@ $(CROSS_COMPILE_PREFIX)objdump -D $(TARGET).elf > $(TARGET).dis CRT0.o : CRT0.S $(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -c -o $@ $^ myprintf.o : myprintf.c stdarg.h $(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $< uart.o : uart.c stdarg.h s3c2440_soc.h $(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $< $(TARGET).o : $(TARGET).c $(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $< main.o : main.c $(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $< Ctype.o : Ctype.c Ctype.h $(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $< util.o : util.c util.h $(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $< sdram.o : sdram.c sdram.h $(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $< clean: rm -f *.elf *.dis *.bin *.o 编译并烧写至开发板,选择Nor Flash方式启动,观察如下所示: 与本文上面的在Nor Flash中运行效果动态图对比,可以发现,在SDRAM中运行的速度有了明显的提升。 



史海拾趣
|
求助各位大侠:谁有或知道有单片机控制的TT50通信模块的C语言初始化程序和收发信息程序 求助各位TT50通信模块的初始化程序和短信收发的C语言程序,由单片机控制TT50,TT50的初始化程序,在接到单片机的控制指令时怎么发送消息?谢谢各位大侠!!!!… 查看全部问答> |
|
目前正在调SD卡(2.0的协议)驱动,它的读写速度特别慢,写200KB/s左右,读大概是写的2-3倍.目前用的通信模式是SD模式,传输方式为DMA传输,时钟频率调高来试过了,可是速度还是一个样慢.我测试用的SD卡是标准卡,本身速度 ...… 查看全部问答> |
|
外部引脚的时钟400M ,2分频生成200M脚本:create_generated_clock -name clk_200 -source clk_400 -divide_by 2为何DC显示:Error:Required argument \'source_objects\' was not found (CMD-007)… 查看全部问答> |
|
1 AMBA AHB字节选通的问题 比如我一个memory是32位宽的。ARM是字节对齐操作,通过地址低两位来选择哪个字节,也就是说我的控制器在接收ARM地址后先要右移两位来确定地址,因为最低两们并不代表实际地址。读的时候可以理解,不管你ARM要哪个字节, ...… 查看全部问答> |
|
用PADS 把器件的封装做好了,想打印出来实物比较下。 只会用CAM一层一层的打印,怎么才可以把焊盘和丝印打印在一起形成一个完整的封装 ...… 查看全部问答> |




