利用汇编编写LED灯点亮程序我们已经实现过了,所以这里不再着重讲解基本原理,直接上C语言版实现过程了。
一、利用汇编初始化C环境
1、确定Cortex-A 处理器运行模式 我们说过 Cortex-A 有九个运行模型,这里我们设置处理器运行在 SVC 模式下。处理器模式的设置是通过修改 CPSR(程序状态)寄存器来完成的,其中 M[4:0](CPSR 的 bit[4:0])就是设置处理器运行模式的如果要将处理器设置为 SVC 模式,那么 M[4:0]就要等于 0X13。11~13 行代码就是先使用指MRS 将 CPSR寄存器的值读取到 R0 中,然后修改 R0 中的值,设置 R0 bit[4:0]为 0X13,然后再使用指令MSR 将修改后的 R0 重新写入到 CPSR 中。 2、确定堆栈指针初始地址 行通过 ldr 指令设置 SVC 模式下的 SP 指针=0X80200000,因为 I.MX6U-ALPHA 开发 板 上 的 DDR3 地 址 范 围 是0X80000000~0XA0000000(512MB) 或 者0X80000000~0X90000000(256MB),不管是 512MB 版本还是 256MB 版本的,其 DDR3 起始地址都是 0X80000000。由于 Cortex-A7 的堆栈是向下增长的,所以将 SP 指针设置为 0X80200000,因此 SVC 模式的栈大小 0X80200000-0X80000000=0X200000=2MB,2MB 的栈空间已经很大了, 如果做裸机开发的话绰绰有余。 3、汇编启动代码实现
.global _start
_start:
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0x13
msr cpsr, r0
ldr sp, =0X80200000
b main
二、C语言部分实验程序编写
1、main.h的编写 各个寄存器的用途以及寻找方法在上一个博客中已经介绍到了,这里就不再进行赘诉。重点关注一下“volatile”的用法: volatile的本意是“易变的” 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。精确地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问;如果不使用valatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)
#ifndef __MAIN_H
#define __MAIN_H
#define CCM_CCGR0 *((volatile unsigned int *)0X020C4068)
#define CCM_CCGR1 *((volatile unsigned int *)0X020C406C)
#define CCM_CCGR2 *((volatile unsigned int *)0X020C4070)
#define CCM_CCGR3 *((volatile unsigned int *)0X020C4074)
#define CCM_CCGR4 *((volatile unsigned int *)0X020C4078)
#define CCM_CCGR5 *((volatile unsigned int *)0X020C407C)
#define CCM_CCGR6 *((volatile unsigned int *)0X020C4080)
#define SW_MUX_GPIO1_IO03 *((volatile unsigned int *)0X020E0068)
#define SW_PAD_GPIO1_IO03 *((volatile unsigned int *)0X020E02F4)
#define GPIO1_DR *((volatile unsigned int *)0X0209C000)
#define GPIO1_GDIR *((volatile unsigned int *)0X0209C004)
#define GPIO1_PSR *((volatile unsigned int *)0X0209C008)
#define GPIO1_ICR1 *((volatile unsigned int *)0X0209C00C)
#define GPIO1_ICR2 *((volatile unsigned int *)0X0209C010)
#define GPIO1_IMR *((volatile unsigned int *)0X0209C014)
#define GPIO1_ISR *((volatile unsigned int *)0X0209C018)
#define GPIO1_EDGE_SEL *((volatile unsigned int *)0X0209C01C)
#endif
2、main.c的编写 这部分直接给出如下:
#include "main.h"
void clk_enable(void)
{
CCM_CCGR0 = 0xffffffff;
CCM_CCGR1 = 0xffffffff;
CCM_CCGR2 = 0xffffffff;
CCM_CCGR3 = 0xffffffff;
CCM_CCGR4 = 0xffffffff;
CCM_CCGR5 = 0xffffffff;
CCM_CCGR6 = 0xffffffff;
}
void led_init(void)
{
SW_MUX_GPIO1_IO03 = 0x5;
SW_PAD_GPIO1_IO03 = 0X10B0;
GPIO1_GDIR = 0X0000008;
GPIO1_DR = 0X0;
}
void led_on(void)
{
GPIO1_DR &= ~(1<<3);
}
void led_off(void)
{
GPIO1_DR |= (1<<3);
}
void delay_short(volatile unsigned int n)
{
while(n--){}
}
void delay(volatile unsigned int n)
{
while(n--)
{
delay_short(0x7ff);
}
}
int main(void)
{
clk_enable();
led_init();
while(1)
{
led_off();
delay(500);
led_on();
delay(500);
}
return 0;
}
三、Makefile程序编写
先补充一下基本的makefile语法知识: $<的意思是依赖目标集合的第一个文件; $@的意思是目标集合。 $^的意思是所有依赖文件的集合
objs := start.o main.o
ledc.bin:$(objs)
arm-linux-gnueabihf-ld -Timx6ul.lds -o ledc.elf $^
arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis
%.o:%.s
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
%.o:%.S
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
%.o:%.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
clean:
rm -rf *.o ledc.bin ledc.elf ledc.dis
特别注意我们的代码是:
arm-linux-gnueabihf-ld -Timx6ul.lds -o ledc.elf $^
而不是上一章内容的
arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf $^
四、链接脚本程序编写
前一博客中我们是通过“-Ttext”来指定链接地址是 0X87800000 的,这样的话所有的文件都会链接到以 0X87800000 为起始地址的区域。但是有时候我们很多文件需要链接到指定的区域,或者叫做段里面,比如在 Linux 里面初始化函数就会放到 init 段里面。因此我们需要能够自定义一些段,这些段的起始地址我们可以自由指定,同样的我们也可以指定一个文件或者函数应该存放到哪个段里面去。要完成这个功能我们就需要使用到链接脚本,看名字就知道链脚本主要用于链接的,用于描述文件应该如何被链接在一起形成最终的可执行文件。其主要目的是描述输入文件中的段如何被映射到输出文件中,并且控制输出文件中的内存排布。比如我们编译生成的文件一般都包含 text 段、data 段等等。链接脚本的语法很简单,就是编写一系列的命令,这些命令组成了链接脚本,每个命令是一个带有参数的关键字或者一个对符号的赋值,可以使用分号分隔命令。像文件名之类的字符串可以直接键入,也可以使用通配符“*”。最简单的链接脚本可以只包含一个“SECTIONS”, 我们可以在这一个“SECTIONS”里面来描述输出文件的内存布局。我们一般编译出来的代码都包含在 text、data、bss 和 rodata 这四个段内。 我们本试验的链接脚本要求如下: ①、链接起始地址为 0X87800000。 ②、start.o 要被链接到最开始的地方,因为 start.o 里面包含这第一个要执行的命令。 根据要求,在 Makefile 同目录下新建一个名为“imx6ul.lds”的文件,然后在此文件里面输入如下所示代码:
SECTIONS{
. = 0X87800000;
.text :
{
start.o
main.o
*(.text)
}
.rodata ALIGN(4) : {*(.rodata*)}
.data ALIGN(4) : { *(.data) }
__bss_start = .;
.bss ALIGN(4) : { *(.bss) *(COMMON) }
__bss_end = .;
}
五、编译及烧录
最终实验现象为闪烁灯。
|