i.MX6ULL终结者创建裸机工程程序设计

本实验对应的例程在保存在光盘资料的: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行)。在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_46635880/article/details/108679856