创客神器NanoPi
直播中

刘天

14年用户 132经验值
擅长:微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件
私信 关注
[经验]

【NanoPi NEO2试用体验】LED闪烁灯

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 intruction 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


更多回帖

发帖
×
20
完善资料,
赚取积分