本实验对应的例程在保存在光盘资料的:i.MX6UL终结者光盘资料\04_裸机例程源码\4_led_bsp 目录下,我们在9.1章节创建的“core”文件夹下新建“imx6ul.h”文件,在该文件输入下面的代码:
1 #ifndef __IMX6UL_H 2 #define __IMX6UL_H
3
4 #include "cc.h"
5 #include "MCIMX6Y2.h"
6 #include "fsl_common.h"
7 #include "fsl_iomuxc.h"
8
9 #endif
10
“imx6ul.h”文件很简单,主要是引用了一些头文件,我们在其他文件中可以引用使用“imx6ul.h”文件,然后保存并退出。
然后我们进入到“drivers/led”目录,在该目录下新建led.h和led.c两个文件,然后在led.h文件中输入下面的代码:
1 #ifndef __BSP_LED_H
2 #define __BSP_LED_H
3 #include "imx6ul.h"
4
5 #define LED0 0
6
7 /* 函数声明 */
8 void led_init(void);
9 void led_switch(int led, int status);
10 #endif
我们在led.h文件主要是声明了led_init和led_switch两个函数。然后我们打开led.c文件,在里面输入下面的代码:
1 #include "led.h"
2
3 /*
4 * @description : 初始化LED对应的GPIO
5 * @param : 无
6 * @return : 无
7 */
8 void led_init(void)
9 {
10 /* 1、初始化IO复用 */
11 IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03,0); /* > 复用为GPIO1_IO03 */
12
13
14 /* 2、、配置GPIO1_IO03的IO属性
15 *bit 16:0 HYS关闭
16 *bit [15:14]: 00 默认下拉
17 *bit [13]: 0 kepper功能
18 *bit [12]: 1 pull/keeper使能
19 *bit [11]: 0 关闭开路输出
20 *bit [7:6]: 10 速度100Mhz
21 *bit [5:3]: 110 R0/6驱动能力
22 *bit [0]: 0 低转换率
23 */
24 IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03,0X10B0);
25
26 /* 3、初始化GPIO,GPIO1_IO03设置为输出*/
27 GPIO1->GDIR |= (1 << 3);
28
29 /* 4、设置GPIO1_IO03输出低电平,打开LED0*/
30 GPIO1->DR &= ~(1 << 3);
31 }
32
33
34 /*
35 * @description : LED控制函数,控制LED打开还是关闭
36 * @param - led : 要控制的LED灯编号
37 * @param - status : 0,关闭LED0,1 打开LED0
38 * @return : 无
39 */
40 void led_switch(int led, int status)
41 {
42 switch(led)
43 {
44 case LED0:
45 if(status == ON)
46 GPIO1->DR &= ~(1<<3); /* 打开LED0 */
47 else if(status == OFF)
48 GPIO1->DR |= (1<<3); /* 关闭LED0 */
49 break;
50 }
51 }
led.c文件里面有两个函数led_init 和 led_switch, led_init函数用来初始化LED用到的IO引脚。led_switch函数用来控制LED的亮和灭。然后我们保存并退出。
然后我们在“drivers/clk”文件夹下面新建clk.h和clk.c两个文件,在clk.h中输入下面的代码:
1 #ifndef __BSP_CLK_H
2 #define __BSP_CLK_H
3
4 #include "imx6ul.h"
5
6 /* 函数声明 */
7 void clk_enable(void);
8
9 #endif
文件“clk.h”里面声明了函数“void clk_enable(void)”,然后保存退出。我们在打开clk.c文件,在里面输入下面的代码:
1 #include "clk.h"
2
3 /*
4 * @description : 使能I.MX6U所有外设时钟
5 * @param : 无
6 * @return : 无
7 */
8 void clk_enable(void)
9 {
10 CCM->CCGR0 = 0XFFFFFFFF;
11 CCM->CCGR1 = 0XFFFFFFFF;
12 CCM->CCGR2 = 0XFFFFFFFF;
13 CCM->CCGR3 = 0XFFFFFFFF;
14 CCM->CCGR4 = 0XFFFFFFFF;
15 CCM->CCGR5 = 0XFFFFFFFF;
16 CCM->CCGR6 = 0XFFFFFFFF;
17 }
clk.c文件中我们定义了函数clk_enable,用来使能所有的外设时钟。
然后我们在“drivers/delay”文件夹中,新建delay.h和delay.c两个文件,在delay.h中输入下面的代码:
1 #ifndef __BSP_DELAY_H
2 #define __BSP_DELAY_H
3
4 #include "imx6ul.h"
5
6
7 /* 函数声明 */
8 void delay(volatile unsigned int n);
9
10 #endif
我们在delay.h文件中声明了delay延时函数,保存并退出。然后我们打开delay.c文件,在里面输入下面的代码:
1 #include "delay.h"
2
3 /*
4 * @description : 短时间延时函数
5 * @param - n : 要延时循环次数(空操作循环次数,模式延时)
6 * @return : 无
7 */
8 void delay_short(volatile unsigned int n)
9 {
10 while(n--){
}
11 }
12
13 /*
14 * @description : 延时函数,在396Mhz的主频下
15 * 延时时间大约为1ms
16 * @param - n : 要延时的ms数
17 * @return : 无
18 */
19 void delay(volatile unsigned int n)
20 {
21 while(n--)
22 {
23 delay_short(0x7ff);
24 }
25 }
我们在delay.c文件中定义了两个函数delay_short和delay,这两个函数是第八章main.c里面的,我们把它提取出来放到专门的延时目录里面了。
然后我们在工程的根目录下分别创建start.S和main.c两个文件。我们打开main.c文件,在里面输入下面的代码:
1 #include "clk.h"
2 #include "delay.h"
3 #include "led.h"
4
5 /*
6 * @description : mian函数
7 * @param : 无
8 * @return : 无
9 */
10 int main(void)
11 {
12 clk_enable(); /* 使能所有的时钟 */
13 led_init(); /* 初始化led */
14
15 while(1)
16 {
17 /* 打开LED0 */
18 led_switch(LED0,ON);
19 delay(300);
20
21 /* 关闭LED0 */
22 led_switch(LED0,OFF);
23 delay(300);
24 }
25
26 return 0;
27 }
在main.c文件我们只定义了main函数,在里面首先调用clk_enable();函数使能所有外设的时钟,然后调用led_init();函数初始化LED对应的GPIO,最后程序进入死循环,在死循环里面程序调用led_switch函数,来设置LED的状态,然后在调用delay延时函数,延时300毫秒,然后在切换LED的状态。
然后我们打开start.S,在里面输入里面的代码:
1
2 .global _start /* 全局标号 */
3
4 /*
5 * 描述: _start函数,程序从此函数开始执行,此函数主要功能是设置C
6 * 运行环境。
7 */
8 _start:
9
10 /* 进入SVC模式 */
11 mrs r0, cpsr
12 bic r0, r0, #0x1f /* 将r0寄存器低5位清零,cpsr的M0~M4*/
13 orr r0, r0, #0x13 /* r0或上0x13,表示使用SVC模式 */
14 msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
15
16 /* 设置栈指针,
17 * 注意:IMX6UL的堆栈是向下增长的!
18 * 堆栈指针地址一定要是4字节地址对齐的!!!
19 * DDR范围:0X80000000~0X9FFFFFFF
20 */
21
22 ldr sp,=0X80200000 /* 用户模式栈首地址为0X80200000大小2MB */
23 b main /* 跳转到main函数 */
Start.S里面的内容和我们在第八章的一样,首先是汇编设置栈指针,cpu工作在SVC模式等操作,最后跳转到我们C程序的main函数。
然后我们在工程目录下创建文件“Makefile”,然后在该文件里面输入下面的内容:
1 CROSS_COMPILE ?= arm-linux-gnueabihf-
2 TARGET ?= drivers
3
4 CC := $(CROSS_COMPILE)gcc
5 LD := $(CROSS_COMPILE)ld
6 OBJCOPY := $(CROSS_COMPILE)objcopy
7 OBJDUMP := $(CROSS_COMPILE)objdump
8
9 INCDIRS := core \
10 drivers/clk \
11 drivers/led \
12 drivers/delay
13
14 SRCDIRS := ./ \
15 drivers/clk \
16 drivers/led \
17 drivers/delay
18
19
20 INCLUDE := $(patsubst %, -I %, $(INCDIRS))
21
22 SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S) )
23 CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c) )
24
25 SFILENDIR := $(notdir $(SFILES))
26 CFILENDIR := $(notdir $(CFILES))
27
28 SOBJS := $(patsubst %, output/%, $(SFILENDIR:.S=.o))
29 COBJS := $(patsubst %, output/%, $(CFILENDIR:.c=.o))
30 OBJS := $(SOBJS) $(COBJS)
31
32 VPATH := $(SRCDIRS)
33
34 .PHONY: clean
35
36 $(TARGET).bin : $(OBJS)
37 $(LD) -Timx6ul.lds -o $(TARGET).elf $^
38 $(OBJCOPY) -O binary -S $(TARGET).elf $@
39
40 $(SOBJS) : output/%.o : %.S
41 $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
42
43 $(COBJS) : output/%.o : %.c
44 $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
45
46 clean:
47 rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
大家可以看到我们现在编写的Makefile文件比之前的要复杂很多了,我们的这个Makefile是一个通用的Makefile,在后面裸机的实验中,我们都是把需要编译的源文件所在的目录添加到这个Makefile中就可以直接使用。下面我们详细的分析下这个Makefile文件:
第1行到第7行我们定义了一些变量,第2行的变量target是目标文件的名字,我们这里是drivers,最后编译生成的.bin文件就是drivers.bin,其它的几行都是和编译器相关的。
第9行定义了变量INCDIRS,它的赋值是工程里面用到的所有含有“.h”头文件的目录,比如本例程中用到的目录有:core、drivers/clk、drivers/delay、drivers/led,所以我们在INCDIRS中添加这些目录,如下:
INCDIRS := core drivers/clk drivers/led drivers/delay
我们看到第9行到第11行最后面都有一个“\”符号,他表示的意思是换行,一般在一行写不完的情况下,可以使用“\符号”来换行。在后面的例程中我们会根据需要在INCDIRS变量后面填对对应的头文件。
第14行我们定义了变量SRCDIRS,它是包含整个工程用到的所有.c和.S文件,比如本例程用到的含有.c和.S文件的目录有“./”(当前工程的根目录下)、“drivers/clk”、“drivers/delay”、“drivers/led”,所以SRCDIRS的赋值如下:
SRCDIRS := ./ drivers/clk drivers/led drivers/delay
我们在后面的例程中会根据需要在SRCDIRS中添加对应的.c和.S文件。
第20行的变量INCLUDE用到了函数patsubst,通过该函数给变量INCLUDE添加了一个“-I”,展开如下:
INCLUDE := -I core -I drivers/clk -I drivers/led -I drivers/delay
加“-I”的目的是因为Makefile语法要求指明头文件目录的时候需要加上“-I”。
第22行的变量SFILES保存工程中所有用到的.S汇编文件,因为SRCDIRS中保存了.c和.S文件,我们使用Makefile的foreach和wildcard两个函数从里面挑选出.S汇编文件,最终的SFILES赋值如下:
SFILES := ./start.S
第23行变量CFILES保存工程中用到的所有.c文件,最终CFILES的赋值如下:
CFILES = ./main.c drivers/clk/clk.c drivers/led/led.c drivers/delay/delay.c
第25行和第26行的变量SFILENDIR和CFILENDIR包含所有的.S和.c文件,它们使用notdir函数将SFILES和CFILES中的路径去掉,所以SFILENDIR和CFILENDIR的赋值如下:
SFILENDIR = start.S
CFILENDIR = main.c clk.c led.c delay.c
第28行和第29行的变量SOBJS和COBJS是.S和.c文件编译以后对应的.o文件目录,默认编译出来的.o文件和源文件在同一个目录下,这里我们将所有的.o文件都保存到output目录下,所以:
SOBJS = output/start.o
COBJS = output/main.o output/clk.o output/led.o output/delay.o
第30行的变量OBJS是SOBJS和COBJS的集合,如下:
OBJS=output/start.o output/main.o output/clk.o output/led.o output/delay.o
第32行的VPATH是指定搜索目录的,这里指定的搜索目录是SRCDIRS所保存的目录,这样当编译的时候,索要的.S和.c文件就会在SRCDIRS中指定的目录里面找到。
第34行指定了一个伪目标clean。
第36行到第47行就很熟悉了,前面我们已经详细讲解过了。
我们现在总结下Makfile完成的工作是找到需要编译的文件,然后按照设置的.o文件存放的位置,编译出.o文件,并输出到设置的目录里面,最终生成目标文件,使用的命令和前面一样。
下面我们在工程目录下创建imx6ul.lds文件,输入下面的内容:
1 SECTIONS{
2 . = 0X87800000;
3 .text :
4 {
5 output/start.o
6 *(.text)
7 }
8 .rodata ALIGN(4) : {
*(.rodata*)}
9 .data ALIGN(4) : {
*(.data) }
10 __bss_start = .;
11 .bss ALIGN(4) : {
*(.bss) *(COMMON) }
12 __bss_end = .;
13 }
它的内容和上一章节的基本一样,主要是start.o文件路径不同(第5行)。