LED闪烁灯 象棋小子 1048272975 嵌入式教程中LED灯以及程序教程中的”Hello world”都有其特殊的意义,意味着入门。笔者此处也不例外,分别以汇编、c语言在交叉编译环境下点LED灯作为NanoPi-NEO2的入门程序。点LED灯之前必须对芯片有基本的认识,包括其指令集、流水线等内核架构,基本的启动流程,基本的编译器开发特性等,只有这样点亮的LED灯才算实现其意义。 1. 指令集NapoPi-NEO2采用了Allwinner H5处理器,该处理器是基于64位、四核Cortex-A53的 ARMv8-A内核架构。支持两个最主要的指令集:32位指令集A32/T32(ARM/Thumb指令集)和64位指令集A64。A64为ARMv8-A新增的指令集,用于支持64位的ARM核,A32/T32用于向前兼容ARMv7架构,使之支持现存的32位指令集。其定义了AArch64和AArch32两套运行环境,分别执行64位和32位指令集,软件可以在需要的时候,进行运行环境的切换。目前ARM指令集是向前兼容的,即ARMv8-A的处理器几乎可以直接执行ARMv4架构的ARM指令集代码(如ARM7的应用代码)。因此,只需要支持ARMv4以上指令集的编译器即可编译出Cortex-A53可运行的二进制代码。 2. 流水线Allwinner H5内核Cortex-A53配置了先进的超标量体系结构管线,能够同时执行多条指令,提供2.3 MIPS/MHz的运算性能,集成了32k/32k的指令/数据一级缓存以及512k的二级缓存,从而达到最快的读取速度和最大的吞吐量,使用了先进的分支预测技术,并且具有专用的NEON整形和浮点型管线进行媒体和信号处理。 Cortex-A53流水线架构基于双对称、顺序发射的8级流水线,硬件上具有I/D Cache、分支预测结构,因此指令在流水线的流入流出过程变得不明确,但仍可以通过统计分析其大概的过程。 Allwinner H5在上电启动后,最先启动其中之一的CPU核,该CPU核尝试从各个储存设备加载代码启动,当从某一储存设备找到正确的启动代码,会从该储存设备加载代码到内部的SRAM,并启动执行。Allwinner H5在启动阶段是处于默认的CPU状态,其 I/D Cache,L2 Cache、分支预测等均是关闭的,运行时钟采用的是外部晶体时钟,针对于NanoPi-NEO2,为24M。我们可以根据以上信息,开启I Cache,设计一个较精确的软件延时函数,每次访问I Cache均会命中,每次访问D Cache均从主存读取,需要相应周期的等待延时,每次跳转均会分支预测失败,清流水线需额外8个CPU时钟。在一个实用的系统中,I/D Cache、分支预测等硬件功能必须开启,不然CPU性能大打折扣。 3. 汇编实现汇编代码中有两点需要注意: 1) CPU启动时需要验证启动代码,只有验证通过才会加载执行,因此启动代码需要加入相应的启动头。 2) 此处避开链接器功能,不使用链接文件,编写的闪烁灯代码应该是位置无关的,即代码加载进任意RAM位置都是可以正确执行的。 #define PA_BASE (0x01c20800) #define CFG0_REG_OFS 0x00 #define CFG1_REG_OFS 0x04 #define CFG2_REG_OFS 0x08 #define CFG3_REG_OFS 0x0c #define DAT_REG_OFS 0x10 .text .global_start _start: B reset // one intruc tion jumping to realcode .ascii"eGON.BT0" // magic="eGON.BT0" .word 0 // check_sum generated by PC .word 0x2000 // length generated by PC .word 48 // the size of boot_file_head_t .word 0 // the version of boot_file_head_t .word 0x10000 // the return value .word 0x10000 // run addr .word 0 // eGON version .space8 // platform information reset: //开启i-cache MRC P15, 0, R0, C1, C0, 0 ORR R0, R0, #0x00001000 MCR P15, 0, R0, C1, C0, 0 BLGpio_Init Loop: LDR R0, =PA_BASE LDR R1, [R0, #DAT_REG_OFS] ORR R1, R1, #(1<<10) STR R1, [R0, #DAT_REG_OFS] // LED Blue on BL Delay_s // delay 1s BIC R1, R1, #(1<<10) STR R1, [R0, #DAT_REG_OFS] // LED Blue off BL Delay_s // delay 1s B Loop Gpio_Init: LDR R0, =PA_BASE LDR R1, [R0, #CFG1_REG_OFS] BIC R1, R1, #(0x7<<8) // pin 10 ORR R1, R1, #(0x1<<8) // output STR R1, [R0, #CFG1_REG_OFS] BX LR // CPU CLOCK 24M Delay_s: LDR R6, =1200000; // 延时1s // 跳转清流水线,以下指令均只用作填充流水线 Delay2: SUBS R6, R6, #1 // 双发射 MOV R0, R0 // 单发射, cycle 1 MOV R0, R0 // 单发射, cycle 2 MOV R0, R0 // 单发射, cycle 3 MOV R0, R0 // 单发射, cycle 4 MOV R0, R0 // 单发射, cycle 5 MOV R0, R0 // 单发射, cycle 6 MOV R0, R0 // 单发射, cycle 7 MOV R0, R0 // 单发射, cycle 8 MOV R0, R0 // 单发射, cycle 9 MOV R0, R0 // 单发射, cycle 10 MOV R0, R0 // 单发射, cycle 11 MOV R0, R0 // 单发射, cycle 12 BNE Delay2 // 跳转会清流水线,8个ARMCLOCK,cycle 20 BX LR 编译汇编文件led.S arm-linux-gcc -c led.S -o led.o 链接生成elf可执行文件 arm-linux-ld led.o -o led.elf 生成二进制可执行文件 arm-linux-objcopy -O binary -S led.elf led.bin 注:arm-linux-gcc为32位arm交叉编译器,可以编译ARMv4指令集即可。针对NanoPi-NEO2,相应可用的交叉编译器位于licheebrandytoolchaingcc-armbin arm-linux-gnueabi-gcc,用arm-linux-gnueabi-gcc相应的位置路径替换掉arm-linux-gcc即可。 4. C实现C代码中需要注意两点: 1) 需要汇编指令跳转到c函数,链接器默认链接_start符号做为代码的开头,除了相应的启动头,需一条跳转汇编指令链接到代码起启位置,用来跳转到c入口。 .text .global _start _start: B reset// one intruction jumping to real code .ascii "eGON.BT0" //magic="eGON.BT0" .word 0// check_sum generated by PC .word 0x2000// length generated by PC .word 48// the size of boot_file_head_t .word 0// the version of boot_file_head_t .word 0x10000// the return value .word 0x10000// run addr .word 0// eGON version .space 8 // platform information reset: // 开启i-cache MRC P15,0, R0, C1, C0, 0 ORR R0,R0, #0x00001000 MCR P15,0, R0, C1, C0, 0 LDR SP,=0x17000 .extern main BL main 2)c文件中不要尝试使用c库以及使用全局变量、静态变量等,因为此处避开链接器功能,不使用链接文件,编写的闪烁灯代码c运行环境是位置无关的,只有栈是有效的。 #define PA_BASE (0x01c20800) #define CFG0_REG_OFS0x00 #define CFG1_REG_OFS0x04 #define CFG2_REG_OFS0x08 #define CFG3_REG_OFS0x0c #define DAT_REG_OFS 0x10 #define writel(value,reg) *(volatile unsigned int*)(reg)=value #define readl(reg) (*(volatile unsigned int *)(reg)) void Delay_s(void) { // CPU CLOCK 24M, 循环体每次20个Arm clock, 延时一秒 unsigned int temp1 = 1200000; unsigned int temp2 = 0; asm volatile ( "1:n" "subs %0,%0, #1n" // 单发射 cycle 1 // 跳转清流水线,以下指令均只用作填充流水线 "mov %1, %1n" // 双发射 cycle 1 "mov %1, %1n" // 单发射 cycle 2 "mov %1, %1n" // 单发射 cycle 3 "mov %1, %1n" // 单发射 cycle 4 "mov %1, %1n" // 单发射 cycle 5 "mov %1, %1n" // 单发射 cycle 6 "mov %1, %1n" // 单发射 cycle 7 "mov %1, %1n" // 单发射 cycle 8 "mov %1, %1n" // 单发射 cycle 9 "mov %1, %1n" // 单发射 cycle 10 "mov %1, %1n" // 单发射 cycle 11 "mov %1, %1n" // 单发射 cycle 12 "bne 1bn" // 跳转会清流水线,8级流水线,cycle20 : "+r"(temp1):"r"(temp2): "cc" ); } void Gpio_Init(void) { unsigned int value; value = readl(PA_BASE+CFG1_REG_OFS); value &= ~(7<<8); // pin10 value |= (1<<8); writel(value, PA_BASE+CFG1_REG_OFS); } void main(void) { unsigned int value; Gpio_Init(); while (1) { value = readl(PA_BASE+DAT_REG_OFS); value |= (1<<10); // LED Blue on writel(value, PA_BASE+DAT_REG_OFS); Delay_s(); value &= ~(1<<10); // LED Blueoff writel(value, PA_BASE+DAT_REG_OFS); Delay_s(); } } 编译汇编文件start.S arm-linux-gcc -c start.S -o start.o 编译c文件led.c arm-linux-gcc -c led.c -nostdlib -o led.o 链接生成elf可执行文件 arm-linux-ld start.o led.o -o led.elf 生成二进制可执行文件 arm-linux-objcopy -O binary -S led.elf led.bin 注:arm-linux-gcc为32位arm交叉编译器,可以编译ARMv4指令集即可。针对NanoPi-NEO2,相应可用的交叉编译器位于licheebrandytoolchaingcc-armbin arm-linux-gnueabi-gcc,用arm-linux-gnueabi-gcc相应的位置路径替换掉arm-linux-gcc即可。 5. 闪烁灯烧写运行编译器直接编译生成的二进制代码是不满足相应的启动格式的,需要通过Allwinner工具gen_check_sum添加代码校验和等等。gen_check_sum工具在uboot目录树中licheebrandyu-boot-2014.07tools。 把gen_check_sum工具拷贝到源码目录,制作启动代码。 ./gen_check_sum led.bin led_boot.bin 把二进制启动代码led_boot.bin烧写进储存设备,对于sd/mmc卡,CPU从设备8k位置处加载启动代码。 用dd命令烧写进sd/mmc卡,/dev/sdb为sd/mmc卡设备文件。 dd bs=1k seek=8 if=./led_boot.bin of=/dev/sdb 6. 结语在交叉编译环境下,可以编译相应的c库如newlib、uclibc等等,加入第三方中间件,如uCOS、FreeRTOS等RTOS,emWin等GUI,Fatfs、yaffs等文件系统,lwip等tcp/ip协议等等,可以构建最基本裸机开发架构。 交叉编译环境下汇编闪烁灯工程例程以及C闪烁灯工程例程,gen_check_sum相关工具。 http://pan.baidu.com/s/1dFvh83n
|