第一个部分中,完全使用ARM汇编来控制LED,完全使用汇编来编写大的系统存在理论可行性(当然现实中也有完全使用汇编实现的操作系统),但是汇编理解起来太困难,编写起来很复杂,太琐碎,所以肯定会想要使用理解性更好的C语言来编写大部分代码,那么哪些代码用汇编编写?哪些用C语言编写呢?
从工程实践中可以得出一些结论:
1.跟具体硬件紧密相关的功能,对这部分功能,无需保持可移植性。
2.对C语言编译后的代码没有信心,或者必须进行特别优化时,必须依靠人工优化代码时。
3.相对的是,如果需要保持可移植性,那么必须把平台无关的功能通过底层的部分汇编代码进行隔绝,其他代码通过C语言来编写。
功能原理和电路图见第一篇。
代码:
汇编代码完成硬件初始化,包括内存基地址和大小设定,WatchDog关闭,设置栈,然后跳转到C代码中。
start.S
1 // 启动代码 2 .global _start 3 4 _start: 5 6 // 把外设的基地址告诉CPU 7 ldr r0, =0x70000000 //对于6410来说,内存(0x00000000~0x60000000),外设(0x70000000-0x7fffffff) 8 orr r0, r0, #0x13 //外设大小:256M 9 mcr p15,0,r0,c15,c2,4 //把r0的值(包括了外设基地址+外设大小)写给cpu 10 11 // 关看门狗 12 ldr r0, =0x7E004000 13 mov r1, #0 14 str r1, [r0] 15 16 // 设置栈,0x0c002000内存地址映射到SoC内部的SRAM中,速度较快 17 ldr sp, =0x0c002000 18 19 // 调用C函数点灯 20 bl main 21 22 halt: 23 b halt
C语言用来实现具体的流水灯功能:
main.c
1 // 功能:c实现流水灯 2 3 // 延时 4 void delay() 5 { 6 volatile int i = 0x100000; 7 while (i--); 8 } 9 10 int main() 11 { 12 int i = 0x01; 13 14 // 配置引脚 15 volatile unsigned long *gpkcon0 = (volatile unsigned long *)0x7F008820; 16 volatile unsigned long *gpkdat = (volatile unsigned long *)0x7F008824; 17 18 *gpkcon0 = 0x00001111; 19 20 // 跑马灯 21 while (1) 22 { 23 *gpkdat = i; 24 i = i << 1; 25 if (i == 0x100) 26 i = 0x01; 27 delay(); 28 } 29 30 return 0; 31 }
1 led.bin: start.o main.o 2 arm-linux-ld -Ttext 0x50000000 -o led.elf $^ 3 arm-linux-objcopy -O binary led.elf led.bin 4 arm-linux-objdump -D led.elf > led_elf.dis 5 %.o : %.S 6 arm-linux-gcc -nostdlib -o $@ $< -c 7 8 %.o : %.c 9 arm-linux-gcc -nostdlib -o $@ $< -c 10 11 clean: 12 rm *.o *.elf *.bin *.dis -rf
编译后下载到开发板中,运行,具体运行方法见第一篇。运行效果可以看到四个LED灯依次熄灭,然后点亮。
总结:
1.汇编代码完成必要的硬件初始化功能,从C语言也会被编译为汇编来说,C语言理论上也能完成这项工作,但是却没有这样做的必要,因为这部分代码无需可移植性,并且汇编比C代码更可控,效率更高。
2.在上层功能上来说,C代码可读性优秀,比汇编代码可理解性要好,而且可移植性也更好,如果要把本文代码移植到另外一个SoC上去,只需修改汇编部分,C基本无需修改。
3.汇编代码中设置了栈,这部分是为C代码的运行准备好条件,因为C语言的代码编译后的局部变量,参数等都依赖栈才能够工作。这也是和第一篇纯汇编代码有所区别的地方。