目录
- 前言
- 汇编文件编写 + 分析
- C语言文件编写 + 分析
- 编写Makefile文件
- 编写链接脚本
- 最终编译验证
前言
在上一篇文章中我们已经使用汇编语言编写LED灯
实验,但是在实际项目中我们如果全部使用汇编,未免难度太大同时也增加了开发难度,这并不是我们想看到的,但是我们也不能完全逃避使用汇编,只不过是最大化的简化汇编文件的编写,大部分是用C语言
开发,只是汇编用来完成C语言
环境的初始化,然后从汇编
跳转到C语言
代码里面去。
汇编文件编写 + 分析
汇编:完成C语言环境的初始化
先上完整汇编代码:
.global _start
_start:
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0x13
msr cpsr, r0
ldr sp, =0X80200000
b main
好!按照老样子,接下来开始详细讲解每行代码的用处,以及为什么这样写!
.global _start
//相当于声明_start
为全局变量
_start:
//代表程序开始运行入口
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0x13
msr cpsr, r0
//这一部分主要的作用是代表进入SVC
模式,至于具体怎么设置成SVC
模式的可见下面讲解:
- 以前的ARM处理器有7种运行型:
User,FIQ,IRQ,SVC,Abort,Undef,System。
其中User
是非特权模式,其余6种都是特权模式,所以本篇文章中使用SVC模式
。
所有的处理器模式都共用一个CPSR物理寄存器
,也就意味着我们需要配置该寄存器,使其功能为SVC模式
。
其中第0~4位
为设置处理器模式控制位。具体含义如下所示:
所以由上图可知我们需要将M[4:0]
设置为10011
即可,也就是低5位
。
- 因为我们需要使用汇编命令来访问存储器,所以需要提前介绍几个汇编命令,分别如下:
对应于上面命令mrs r0, cpsr
相当于是将特殊寄存器CPSR
里面的数据复制到r0
中。
对应于上面命令bic r0, r0, #0x1f
相当于是
0x1f
相当于0x00011111
, r0 = r0 & (~0x00011111)
。 ~0x00011111 = 0x11100000
,所以相当于是将r0
的低5位
置0
。
对应于上面命令orr r0, r0, #0x13
相当于是
0x13
相当于0x00010011
,r0 = r0 | 0x00010011
,所以相当于是将r0
的低5位
置10011
。
对应于上面命令msr cpsr, r0
相当于是经r0
的值赋值给CPSR
中,即至此成功配置输出模式为SVC
模式。
ldr sp, =0X80200000
因为 I.MX6U的 DDR3 地址范围是0X80000000 ~ 0XA0000000(512MB)或者0X80000000~0X90000000(256MB),不管是 512MB 版本还是 256MB 版本的,其 DDR3 起始地址都是 0X80000000。由于 Cortex-A7 的堆栈是向下增长的,所以将 SP 指针设置为 0X80200000,
因此 SVC 模式的栈大小 0X80200000-0X80000000=0X200000=2MB,2MB 的栈空间已经很大了,如果做裸机开发的话绰绰有余。
b main
//跳转到main
函数,main
函数就是C
语言代码
C语言文件编写 + 分析
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;
}
好!按照老样子,接下来开始详细讲解每行代码的用处,以及为什么这样写!
clk_enable();
//其中是使能所有的时钟
led_init();
//初始化led
led_off();
//关闭 LED
delay(500);
// 延时大约 500ms
编写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 -o $@ $<
%.o:%.S
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
%.o:%.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
clean:
rm -rf *.o ledc.bin ledc.elf ledc.dis
好!按照老样子,接下来开始详细讲解每行代码的用处,以及为什么这样写!
objs := start.o main.o
//相当于定义了一个变量objs
,只不过生成objs
需要依赖start.o
和 main.o
,但是生成start.o
和 main.o
需要之后的语句来还生成。
ledc.bin:$(objs)
//生成ledc.bin
需要依赖start.o
和 main.o
,但是生成start.o
和 main.o
需要之后的语句来还生成。
%.o:%.s
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
%.o:%.S
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
%.o:%.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
//相当于针对.s
文件类型将其编译成对应的.o
文件,其实就是汇编.s(.S)
和.c
文件。其中$@
代表是目标集合,在这里代表start.o
,$<
代表依赖目标集合的第一个文件,这里的依赖目标集合为(start.o和main.o)
,所以第一个文件为start.s
。至此应该生成了start.o
和main.o
,既然我们已经成功生成了依赖目标文件,那么下一步就是要进行链接操作。
arm-linux-gnueabihf-ld -Timx6ul.lds -o ledc.elf $^
//使用arm-linux-gnueabihf-ld
进行链接,只不过这里的链接为我们自己编写的链接脚本,至于这个链接脚本的内容是什么,之后会做详细讲解。这里先简单介绍下功能。相当于将start.o
和main.o
生成ledc.elf
,也就是链接文件。
arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
//使用arm-linux-gnueabihf-objcopy
来将ledc.elf
文件转为ledc.bin
arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis
//使用arm-linux-gnueabihf-objdump
来反汇编,生成ledc.dis
文件。
clean:
rm -rf *.o ledc.bin ledc.elf ledc.dis
//工程清理规则,通过命令make clean
就可以清理工程。
编写链接脚本
脚本文件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 = .;
}
好!按照老样子,接下来开始详细讲解每行代码的用处,以及为什么这样写!
SECTIONS
//相当于是我们C语言中的函数
. = 0X87800000;
//“.
”相当于是特殊符号,即相当于是pc指针,指向了其地址,在这个代码中相当于是将代码链接到以
0X87800000
为起始地址的地方。
.text :
{
start.o
main.o
*(.text)
}
//.text
相当于是段名,其中start.o
,main.o
代表可以链接到段名为.text
的所有文件。*(.text)
中的“*
”是通配符,表示所有输入文件的.text
段都放到.text
中。
.rodata ALIGN(4) : {*(.rodata*)}
.data ALIGN(4) : { *(.data) }
定义了一个名为“.data
”的段,然后所有文件的“.data
”段都放到这里面。但是这一行多了一个“ALIGN(4)
”,这是什么意思呢?这是用来对“.data
”这个段的起始地址做字节对齐的,ALIGN(4)
表示 4
字节对齐。也就是说段“.data
”的起始地址要能被 4
整除,一般常见的都是 ALIGN(4)
或者 ALIGN(8)
,也就是 4
字节或者 8
字节对齐。
__bss_start = .;
.bss ALIGN(4) : { *(.bss) *(COMMON) }
__bss_end = .;
//所有文件中的“.bss
”数据都会被放到这个里面,“.bss
”数据就是那些定义了但是没有被初始化的变量。就是因为有这个特性,所以我们需要手动对.bss
段进行变量清零,因此我们需要知道.bss
段的起始和结束地址,这样我们直接对这段内存赋值为0
即可完成清零。
最终编译验证
我们将编译出来的ledc.bin
烧写到SD
卡中,最终结果就是LED0
就会以500ms
的时间间隔亮灭。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)