本实验对应的例程在光盘资料的:i.MX6UL终结者光盘资料\04_裸机例程源码\2_led_C program目录下,我们在Ubuntu系统建立“1_Led_C program”文件夹,然后在“Led_C program”文件夹下建立文件:start.S、main.c、main.h。其中start.S是汇编文件,main.c和main.h是C语言文件。
我们在前面新建的“strart.S”文件中输入下面的代码:
1 .global _start /* 全局标号 */
2 /*
3 * 描述: _start函数,程序从此函数开始执行,此函数主要功能是设置C
4 * 运行环境。
5 */
6 _start:
7 /* 进入SVC模式 */
8 mrs r0, cpsr
9 bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
10 orr r0, r0, #0x13 /* r0或上0x13,表示使用SVC模式 */
11 msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
12 ldr sp, =0X80200000 /* 设置栈指针 */
13 b main /* 跳转到main函数 */
第1行定义了一个全局标号_start
第6行是程序的入口、
第8行到第11行是设置处理器进入SVC模式
第12行通过ldr指令设置SVC模式下的sp指针(0x80200000),i.MX6 ULL终结者开发板上的内存地址范围是0X800000000XA0000000(512MB)或0X800000000X90000000(256MB),所以不论是512MB版本还是256MB版本的,其内存起始地址都是0X80000000,由于i.MX6ULL的堆栈是向下增长的,所以SP指针设置成0X80200000(0X80200000-0X80000000,是2MB的栈空间,足够我们使用了)。
第13行是跳转到main函数(C语言的入口函数)。
至此汇编部分的程序我们就完成了,主要用来设置处理器在SVC模式下运行,然后初始化SP指针,最后跳转到C程序的main入口函数。如果大家有接触过三星的S3C2440,S3c6410或者S5PV210的处理器,我们在使用内存之前必须先初始化CPU的内存控制器,所以在他们的汇编文件中一定有内存控制器的初始化代码(比如Uboot的汇编中)。大家可能会发现我们上面编写的start.S文件中并没有发现初始化内存控制器的代码,但是却将SVC模式下的SP指针设置到了内存的地址范围里面,这样不是有问题吗?大家还记得在第六章我们在编译生成“led.bin”文件以后,通过create_imx工具在“led.bin”文件添加了一些数据包头吗,也就是DCD数据,在第六章我们已经讲过DCD数据里面包含了内存控制器的参数配置了,i.MX6ULL内存固化的Boot ROM程序会读取DCD数据中的内存控制器参数,bin完成内存控制器的初始化配置。
接下来我们开始实现C语言部分,首先我们打开前面建立的“main.h”文件,然后输入下面的代码:
1 #ifndef __MAIN_H
2 #define __MAIN_H
3 /*
4 * CCM相关寄存器地址
5 */
6 #define CCM_CCGR0 *((volatile unsigned int *)0X020C4068)
7 #define CCM_CCGR1 *((volatile unsigned int *)0X020C406C)
8 #define CCM_CCGR2 *((volatile unsigned int *)0X020C4070)
9 #define CCM_CCGR3 *((volatile unsigned int *)0X020C4074)
10 #define CCM_CCGR4 *((volatile unsigned int *)0X020C4078)
11 #define CCM_CCGR5 *((volatile unsigned int *)0X020C407C)
12 #define CCM_CCGR6 *((volatile unsigned int *)0X020C4080)
13 /*
14 * IOMUX相关寄存器地址
15 */
16 #define SW_MUX_GPIO1_IO03 *((volatile unsigned int *)0X020E0068)
17 #define SW_PAD_GPIO1_IO03 *((volatile unsigned int *)0X020E02F4)
18 /*
19 * GPIO1相关寄存器地址
20 */
21 #define GPIO1_DR *((volatile unsigned int *)0X0209C000)
22 #define GPIO1_GDIR *((volatile unsigned int *)0X0209C004)
23 #define GPIO1_PSR *((volatile unsigned int *)0X0209C008)
24 #define GPIO1_ICR1 *((volatile unsigned int *)0X0209C00C)
25 #define GPIO1_ICR2 *((volatile unsigned int *)0X0209C010)
26 #define GPIO1_IMR *((volatile unsigned int *)0X0209C014)
27 #define GPIO1_ISR *((volatile unsigned int *)0X0209C018)
28 #define GPIO1_EDGE_SEL *((volatile unsigned int *)0X0209C01C)
29 #endif
在main.h中,我们通过宏的方式定义了要使用到的所有寄存器名称,后面的十六进制数就是寄存器对应的地址,比如GPIO1_DR寄存器的地址是0X0209C000。然后保存并退出“main.h”。
接下来我们打开“main.c”文件,在里面输入下面的代码:
1 #include "main.h"
2 /*
3 * @description : 使能I.MX6U所有外设时钟
4 * @param : 无
5 * @return : 无
6 */
7 void clk_enable(void)
8 {
9 CCM_CCGR0 = 0xffffffff;
10 CCM_CCGR1 = 0xffffffff;
11 CCM_CCGR2 = 0xffffffff;
12 CCM_CCGR3 = 0xffffffff;
13 CCM_CCGR4 = 0xffffffff;
14 CCM_CCGR5 = 0xffffffff;
15 CCM_CCGR6 = 0xffffffff;
16 }
17 /*
18 * @description : 初始化LED对应的GPIO
19 * @param : 无
20 * @return : 无
21 */
22 void led_init(void)
23 {
24 /* 1、初始化IO复用 */
25 SW_MUX_GPIO1_IO03 = 0x5; /* 复用为GPIO1_IO03 */
26 /* 2、、配置GPIO1_IO03的IO属性
27 *bit 16:0 HYS关闭
28 *bit [15:14]: 00 默认下拉
29 *bit [13]: 0 kepper功能
30 *bit [12]: 1 pull/keeper使能
31 *bit [11]: 0 关闭开路输出
32 *bit [7:6]: 10 速度100Mhz
33 *bit [5:3]: 110 R0/6驱动能力
34 *bit [0]: 0 低转换率
35 */
36 SW_PAD_GPIO1_IO03 = 0X10B0;
37 /* 3、初始化GPIO */
38 GPIO1_GDIR = 0X0000008; /* GPIO1_IO03设置为输出 */
39 /* 4、设置GPIO1_IO03输出低电平,打开LED0 */
40 GPIO1_DR = 0X0;
41 }
42 /*
43 * @description : 打开LED灯
44 * @param : 无
45 * @return : 无
46 */
47 void led_on(void)
48 {
49 /*
50 * 将GPIO1_DR的bit3清零
51 */
52 GPIO1_DR &= ~(1<<3);
53 }
54 /*
55 * @description : 关闭LED灯
56 * @param : 无
57 * @return : 无
58 */
59 void led_off(void)
60 {
61 /*
62 * 将GPIO1_DR的bit3置1
63 */
64 GPIO1_DR |= (1<<3);
65 }
66 /*
67 * @description : 短时间延时函数
68 * @param - n : 要延时循环次数(空操作循环次数,模式延时)
69 * @return : 无
70 */
71 void delay(volatile unsigned int n)
72 {
73 while(n--){}
74 }
75 /*
76 * @description : 延时函数,在396Mhz的主频下
77 * 延时时间大约为1ms
78 * @param - n : 要延时的ms数
79 * @return : 无
80 */
81 void mdelay(volatile unsigned int n)
82 {
83 while(n--)
84 {
85 delay(0x7ff);
86 }
87 }
88 /*
89 * @description : mian函数
90 * @param : 无
91 * @return : 无
92 */
93 int main(void)
94 {
95 clk_enable(); /* 使能所有的时钟 */
96 led_init(); /* 初始化led */
97 while(1) /* 死循环 */
98 {
99 led_off(); /* 关闭LED */
100 mdelay(300); /* 延时大约500ms */
101 led_on(); /* 打开LED */
102 mdelay(300); /* 延时大约500ms */
103 }
104 return 0;
105 }
main.c文件里面一共有7个函数,下面一个个的看下每个函数的具体功能:
-
void clk_enable(void)函数使能CCM_CCGR0-CCM_CCGR6的时钟(使能所有外设时钟)。
-
void led_init(void)函数初始化LED对应的IO(复用成GPIO,默认下拉,输出低电平)。
-
void led_on(void)函数点亮LED(对应GPIO输出低电平)。
-
void led_off(void)函数关闭LED(对应GPIO输出高电平)。
-
void delay_short(volatile unsigned int n)延时函数。
-
void mdelay(volatile unsigned int n)毫秒级延时函数。
-
int main(void),C程序主函数。先调用clk_enable()函数完成外设时钟的使能,然后调用led_init()函数完成LED对应的IO的初始化,最后进入while(1)死循环,实现LED每隔300毫秒亮灭的状态切换。
然后我们新建Makfile文件,在里面输入下面的内容:
1 objs := start.o main.o
2 led.bin:$(objs)
3 arm-linux-gnueabihf-ld -Ttext 0X87800000 -o led.elf $^
4 arm-linux-gnueabihf-objcopy -O binary -S led.elf $@
5 %.o:%.s
6 arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
7 %.o:%.S
8 arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
9 %.o:%.c
10 arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
11 clean:
12 rm -rf *.o led.bin led.elf
在本Makefile文件中我们使用到了变量和自动变量,关于Makefile的变量和自动变量可以参考《跟我一起写Makefile.pdf》文档(光盘资料的:i.MX6UL终结者光盘资料\10_其它参考资料 目录下)
第1行我们定义了变量objs,该变量包含要生成的led.bin文件所需要的start.o和main.o,也就是我们工程的start.S和main.c编译生成的“.o”文件。需要我们注意的是start.o一定要放到最前面,因为后面链接的时候start.o要在最前面,这样才能保证start.o在链接完成以后,运行的时候最先执行。
第2行就是默认最终生成的可执行文件“led.bin”,“led.bin”文件依赖于“start.o”和“main.o”两个文件,如果当前工程目录下没有这两个文件,Make命令就会在Makefile中找到相应的规则生成“start.o”和“main.o”。比如“start.s”文件根据第5行的规则生成,“start.S”根据规则中的第7行生成,“main.o”根据规则中的第9行生成。
第3行使用arm-linux-gnueabihf-ld进行连接,连接的起始地址是0x87800000,这一行用到了自动变量“$^”,它是指所有依赖文件的集合,也就是变量“objs”,即“start.o”和“main.o”,连接的时候汇编的start.o要放在最前面,因为程序首先从汇编的“_start”标号处开始执行,因此这一行展开就是:
arm-linux-gnueabihf-ld -Ttext 0X87800000 -o led.elf start.o main.o
第4行使用arm-linux-gnueabihf-objcopy -O binary -S ledc.elf
@
把
“
l
e
d
.
e
l
f
”
文
件
转
换
成
“
l
e
d
.
b
i
n
”
文
件
,
这
里
也
用
到
了
自
动
变
量
“
@把“led.elf”文件转换成“led.bin”文件,这里也用到了自动变量“
@把“led.elf”文件转换成“led.bin”文件,这里也用到了自动变量“@”,它的意思是目标集合,也就是“led.elf”,那么本行展开相当于:
arm-linux-gnueabihf-objcopy -O binary -S led.elf led.bin
第5行到第10行是针对不同后缀名的文件,将其编译成对应的“.o”文件。比如start.s就会使用第5行的规则生成对应的“start.o”文件,第6行就是具体执行的命令,这里用到了自动变量“ @ ” 和 “ @”和“ @”和“<”,“$<”的意思是依赖目标集合的第一个文件,比如“start.s”编译成“start.o”,第5行和第6行的代码相当于:
start.o:start.s
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o start.o start.s
第11行是清理规则,通过“make clean”可以删除编译生成的中间文件以及目标文件。
Makefile文件我们就分析到这里。