我们的C程序通常会经过:编译,链接,最后形成可执行文件。当编译过后,文件中出现的地址称为编译地址(编译地址就是编译器为每条指令指定的地址,有部分地址不能确定),而在链接后程序中出现的称为链接地址,与此相对的还有一个运行地址(程序正在运行时所处的地址)。而在嵌入式应用中因为成本的问题,SRAM通常很小,所以一般会将程序烧写到nandflash中,系统上电后,如果是从nandflash启动6410会将nandflash中前8K(共四页,每页2K,至于为什么这样我会在后面的nandflash中详解)的代码由硬件自动拷贝到SRAM(stepping stone)中,并且会将其映射到0x0(因为ARM上电后会从0x0地址处取指令运行)地址处。因此这就造成了一个问题,当我们的程序大于8K时该怎么让其运行。一般在这前8K代码中采用与位置无关的代码编写,让这前8K代码在完成系统的一些必要的初始化(中断向量表,关看门狗,nandflash以及DRAM的初始化)后会将自身“搬移”到SRAM中,并且跳转到SRAM中继续运行。因此这就涉及到链接地址和运行地址不一致的问题,当这两者不一致时,如果正在运行的是与位置无关代码则程序可正常运行,否则会出错,下面将通过一个厂家的源程序来说明。
start.S
// 启动代码 .global _start _start: // 把外设的基地址告诉CPU ldr r0, =0x70000000 orr r0, r0, #0x13 mcr p15,0,r0,c15,c2,4 // 关看门狗 ldr r0, =0x7E004000 mov r1, #0 str r1, [r0] // 设置栈 ldr sp, =8*1024 // 开启icaches可以提高运行速度 #ifdef CONFIG_SYS_ICACHE_OFF bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache #else orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache #endif mcr p15, 0, r0, c1, c0, 0 // 设置时钟 bl clock_init //时钟的初始化 // 重定位 adr r0, _start /*adr是小范围的地址读取伪指令,ldr是大范围的读取地址伪指令。可实际上adr是将基于PC相对偏移的地址值或 *基于寄存器相对地址值读取的为指令,而ldr用于加载32为立即数或一个地址到指定的寄存器中。到这儿就会看到 *其中的区别了。如果在程序中想加载某个函数或者某个在联接时候指定的地址时请使用adr,例如在lds中需要重 *新定位的地址。当加载32为的立即数或外部地址时请用ldr。运用adr指令时标号最好和运用处在同一个代码段*/
ldr r1, =_start //r1存放的是链接地址,一般在链接脚本或者直接在链接命令里指定ldr r2, =bss_start //r2存放的是bss段的起始地址,也是一个链接地址cmp r0, r1 beq clean_bss//运行地址和链接地址不一致则代码要搬移,如果相等说明现在已经搬移完毕然后跳转去清除bss段copy_loop:ldr r3, [r0], #4 //将所有代码搬移到链接地址处,长度为r2-r1str r3, [r1], #4cmp r1, r2bne copy_loop// 搬移完后清BSS段clean_bss:ldr r0, =bss_startldr r1, =bss_endmov r2, #0cmp r0, r1beq on_ddrclean_loop:str r2, [r0], #4cmp r0, r1bne clean_loopon_ddr:// 调用main函数ldr pc, =mainhalt:b halt link.lds//r0存放的是当前地址也就是运行地址
SECTIONS { . = 0x1000; //.表示当前地址,start.S中搬移代码那个地方的_start的链接地址 .text : { start.o * (.text) } .data : { * (.data) //数据段 } bss_start = .; .bss : { * (.bss) } bss_end = .; }
clock.c
上面的始终初始化配合韦东山的一张图就很容易看了// 功能:c语言初始化时钟 #define APLL_LOCK (*((volatile unsigned long *)0x7E00F000)) #define MPLL_LOCK (*((volatile unsigned long *)0x7E00F004)) #define EPLL_LOCK (*((volatile unsigned long *)0x7E00F008)) #define OTHERS (*((volatile unsigned long *)0x7e00f900)) #define CLK_DIV0 (*((volatile unsigned long *)0x7E00F020)) #define ARM_RATIO 0 // ARMCLK = DOUTAPLL / (ARM_RATIO + 1) = 532/(0+1) = 532 MHz #define MPLL_RATIO 0 // DOUTMPLL = MOUTMPLL / (MPLL_RATIO + 1) = 532/(0+1) = 532 MHz #define HCLKX2_RATIO 1 // HCLKX2 = HCLKX2IN / (HCLKX2_RATIO + 1) = 532/(1+1) = 266 MHz #define HCLK_RATIO 1 // HCLK = HCLKX2 / (HCLK_RATIO + 1) = 266/(1+1) = 133 MHz #define PCLK_RATIO 3 // PCLK = HCLKX2 / (PCLK_RATIO + 1) = 266/(3+1) = 66.5 MHz #define APLL_CON (*((volatile unsigned long *)0x7E00F00C)) #define APLL_CON_VAL ((1<<31) | (266 << 16) | (3 << 8) | (1)) #define MPLL_CON (*((volatile unsigned long *)0x7E00F010)) #define MPLL_CON_VAL ((1<<31) | (266 << 16) | (3 << 8) | (1)) #define CLK_SRC (*((volatile unsigned long *)0x7E00F01C)) void clock_init(void) { /* 1. 设置各PLL的LOCK_TIME,使用默认值 */ APLL_LOCK = 0xffff; // APLL_LOCK,供cpu使用 MPLL_LOCK = 0xffff; // MPLL_LOCK,供AHB(存储/中断/lcd等控制器)/APB(看门狗,定时器,SD等)总线上的设备使用 EPLL_LOCK = 0xffff; // EPLL_LOCK,供UART,IIS,IIC使用 /* 2. 设置为异步模式(Asynchronous mode) */ OTHERS &= ~0xc0; //《linux installation for u-boot》3.7中:用MPLL作为HCLK和PCLK的Source是异步(ASYNC)模式,用APLL是同步(SYNC)模式 while ((OTHERS & 0xf00) != 0); /* 3. 设置分频系数 */ CLK_DIV0 = (ARM_RATIO) | (MPLL_RATIO << 4) | (HCLK_RATIO << 8) | (HCLKX2_RATIO << 9) | (PCLK_RATIO << 12); /* 4. 设置PLL,放大时钟 */ APLL_CON = APLL_CON_VAL; MPLL_CON = MPLL_CON_VAL; /* 5. 选择PLL的输出作为时钟源 */ CLK_SRC = 0x03; }
man.c
#include "uart.h" int main() { char c; init_uart(); while (1) { c = getchar(); putchar(c+1); } return 0; }
uart.c 串口初始化比较简单
// 功能:初始化串口 #include "uart.h" #define ULCON0 (*((volatile unsigned long *)0x7F005000)) #define UCON0 (*((volatile unsigned long *)0x7F005004)) #define UFCON0 (*((volatile unsigned long *)0x7F005008)) #define UMCON0 (*((volatile unsigned long *)0x7F00500C)) #define UTRSTAT0 (*((volatile unsigned long *)0x7F005010)) #define UFSTAT0 (*((volatile unsigned long *)0x7F005018)) #define UTXH0 (*((volatile unsigned char *)0x7F005020)) #define URXH0 (*((volatile unsigned char *)0x7F005024)) #define UBRDIV0 (*((volatile unsigned short *)0x7F005028)) #define UDIVSLOT0 (*((volatile unsigned short *)0x7F00502C)) #define GPACON (*((volatile unsigned long *)0x7F008000)) void init_uart(void) { /* 1. 配置引脚 */ GPACON &= ~0xff; GPACON |= 0x22; /* 2. 设置数据格式等 */ ULCON0 = 0x3; // 数据位:8, 无校验, 停止位: 1, 8n1 UCON0 = 0x5; // 时钟:PCLK,禁止中断,使能UART发送、接收 UFCON0 = 0x01; // FIFO ENABLE UMCON0 = 0; // 无流控 /* 3. 设置波特率 */ // DIV_VAL = (PCLK / (bps x 16 ) ) - 1 = (66500000/(115200x16))-1 = 35.08 // DIV_VAL = 35.08 = UBRDIVn + (num of 1’s in UDIVSLOTn)/16 UBRDIV0 = 35; UDIVSLOT0 = 0x1; } /* 接收一个字符 */ char getchar(void) { while ((UFSTAT0 & 0x7f) == 0); // 如果RX FIFO空,等待 return URXH0; // 取数据 } /* 发送一个字符 */ void putchar(char c) { while (UFSTAT0 & (1<<14)); // 如果TX FIFO满,等待 UTXH0 = c; // 写数据 }
uart.h
void putchar(char c); char getchar(void); void init_uart(void);
Makefile
link.bin: start.o main.o clock.o uart.o arm-linux-ld -T link.lds -o link.elf $^ arm-linux-objcopy -O binary link.elf link.bin arm-linux-objdump -D link.elf > link.dis %.o : %.S arm-linux-gcc -o $@ $< -c %.o : %.c arm-linux-gcc -o $@ $< -c -fno-builtin #上面的"-fno-builtin"是因为我们在这个例子中自己实现了一些内建函数(系统一些常见的函数,例如printf,putchar等,这些函数不需引用相关头文#件),此时不加该参数系统会报warning: conflicting types for built-in function 'putchar'错误 clean: rm *.o *.elf *.bin *.dis -rf