随着我们的代码功能完善,在一个文件夹下放置所有文件已经不太合适了,针对我们上一章使用NXP提供的库来说,简简单单一个点亮LED的试验,目录结构就很乱了
在做复杂功能项目的时候,需要对目录结构进行优化,这里引入一个新概念——BSP(Board Support Package 板级支持包),目录的结构先这样做
先按照下面结构建立目录
其中,bsp用来放驱动文件,imx6u放和芯片有关的文件,obj编译以后生成的.o文件,project放C代码和汇编代码。先把前一章的几个头文件放在imx6u文件夹下,把start.s和main.c放在project里,然后有些代码,比方LED初始化,时钟初始化什么的,可以拆解出来写成独立的代码放在bsp根据功能不同可以放在bsp下几个按照功能建立的文件夹中。
这次的试验还和前面一样,点亮LED。
工程分解
为了以后的驱动开发方便,我们把各个功能模块分开放,
/bsp文件夹
bsp文件夹下放下面的文件
clk为时钟管理,delay为定时,led为led的驱动,代码直接放在下面
clk文件夹下文件:
#ifndef __BSP_CLK_H #define __BSP_CLK_H #include "imx6ul.h" void clk_enable(void); #endifbsp_clk.h
#include "bsp_clk.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; }bsp_clk.c
delay文件夹下文件
#ifndef __BSP_DELAY_H #define __BSP_DELAY_H #include "imx6ul.h" void delay(volatile unsigned int n); #endifbsp_delay.h
#include "bsp_delay.h" // 空操作,演示大约1ms void delay_short(volatile unsigned int n) { while(n--){} } void delay(volatile unsigned int n) { while(n--){delay_short(0x7ff);} }bsp_delay.c
led文件夹下文件:
#ifndef __BSP_LED_H #define __BSP_LED_H #include "imx6ul.h" #define LED0 0 void led_init(void); void led_on(void); void led_off(void); void led_switch(int led, int status); #endifbsp_led.h
#include "bsp_led.h" /*初始化LED*/ void led_init(void) { // 复用、电气属性寄存器初始化 IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03,0); IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03,0x10B0); // GPIO1方向寄存器, GPIO1->GDIR = 0x8; } // 点亮LED void led_on(void) { GPIO1->DR &= ~(1<<3); //bit3清零 } // 关闭LED void led_off(void) { GPIO1->DR |=(1<<3); //bit3置一 } void led_switch(int led, int status) { switch(led) { case LED0: if(status == ON) GPIO1->DR &= ~(1<<3); /* 打开LED0 */ else if(status == OFF) GPIO1->DR |= (1<<3); /* 关闭LED0 */ break; } }bsp_led.c
/project文件夹
上面是基础的驱动,然后是project文件夹
里面是主函数和汇编的环境配置
.global _start _start: MRS R0,CPSR BIC R0,R0,#0x1f ORR R0,R0,#0x13 MSR CPSR,R0 ldr sp,=0x80200000 b mainstart.s
#include "bsp_led.h" #include "bsp_delay.h" #include "bsp_clk.h" int main(void) { clk_enable(); led_init(); while(1) { led_switch(LED0,ON); delay(200); led_switch(LED0,OFF); delay(200); } return 0; }main.c
/imx6ul文件夹
imx6ul是芯片的库文件
其中,cc.h是我们定义的数据类型,imx6ul.h是自己定义的头文件,这里定义这个头文件是简化了驱动库里导入头文件的方法,因为这里的几个库文件几乎在驱动的.h文件里几乎都要导入,定义了这个文件以后只用导入这个头文件就等于导入所有库文件了。
#ifndef __CC_H #define __CC_H #define __I volatile #define __O volatile #define __IO volatile #define ON 1 #define OFF 0 typedef signed char int8_t; typedef signed short int16_t; typedef signed int int32_t; typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; typedef unsigned long long uint64_t; typedef signed char s8; typedef signed short s16; typedef signed int s32; typedef long long s64; typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; typedef unsigned long long u64; #endifcc.h
#ifndef __IMX6UL_H #define __IMX6UL_H #include "cc.h" #include "MCIMX6Y2.h" #include "fsl_common.h" #include "fsl_iomuxc.h" #endifimx6ul.h
Makefile文件
这里的Makefile文件是个重要点,因为在工程目录里所有的文件是按照路径管理的,在根目录下单Makefile就找不到需要编译的文件了。先把Makefile放下来
CROSS_COMPILE ?= arm-linux-gnueabihf- TARGET ?= bsp CC := $(CROSS_COMPILE)gcc LD := $(CROSS_COMPILE)ld OBJCOPY := $(CROSS_COMPILE)objcopy OBJDUMP := $(CROSS_COMPILE)objdump INCLUDIRS := imx6ul \ bsp/clk \ bsp/led \ bsp/delay SRCDIRS := project \ bsp/clk \ bsp/led \ bsp/delay # 利用patsubst进行字符串修改,在INCLUDIRS每个路径前加-I \ $(patsubst <pattern>,<replacement>,<text>) \ 名称:模式字符串替换函数——patsubst。\ 功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符\ 合模式<pattern>,如果匹配的话,则以<replacement>替换。这里,<pattern>可以包括通\ 配符“%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么,<replacement>\ 中的这个“%”将是<pattern>中的那个“%”所代表的字串。\ (可以用“\”来转义,以“\%”来表示真实含义的“%”字符)\ 返回:函数返回被替换过后的字符串 INCLUDE :=$(patsubst %, -I % ,$(INCLUDIRS)) #获取项目中.c、.s文件 \ foreach 类似BASH里的for循环,用法是 $(foreach <var>,<list>,<text>) \ 就是把list里的单词逐一取出放在变量var中,然后执行text的表达式内,每次text会返回一个字符串。 \ 最后循环结束时,<text>返回值为每个字符串所组成的整个字符串,之间以空格分格, \ wildcard为展开指定对象下文件集合 SFILES := $(foreach dir ,$(SRCDIRS),$(wildcard $(dir)/*.s)) CFILES := $(foreach dir ,$(SRCDIRS),$(wildcard $(dir)/*.c)) #获取不带路径的文件名,用来修改后缀生成.o文件的文件名 SFILENDIR := $(notdir $(SFILES)) # 不带路径的文件名, CFILENDIR := $(notdir $(CFILES)) # 不带路径的文件名 # 将不带路径的文件名转换成.o后缀名,对应编译后文件名 SOBJS := $(patsubst %, obj/%,$(SFILENDIR:.s=.o)) #.s=.o是把.s用.o替换 COBJS := $(patsubst %, obj/%,$(CFILENDIR:.c=.o)) #.c=.o是把.c用.o替换 OBJS := $(SOBJS) $(COBJS) VPATH := $(SRCDIRS) .PHONY: clean $(TARGET).bin : $(OBJS) $(LD) -Timx6ul.lds -o $(TARGET).elf $^ #将所有依赖文件链接,生成.elf文件 $(OBJCOPY) -O binary -S $(TARGET).elf $@ #将elf转换为依赖的目标集合 $(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis #将elf文件反汇编 # 静态模式 <Targets...>:<tatgets-pattern>:<prereq-patterns...>下面两天为自写 $(SOBJS) : obj/%.o : %.s #将所有的.s文件编译成.o文件放在obj文件夹内 $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $< $(COBJS) : obj/%.o : %.c $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $< clean: rm -rf $(TARGET).elf $(TARGET).bin $(TARGET).dis $(OBJS) print: @echo INCLUDE = $(INCLUDE) @echo OBJS = $(OBJS)
大体的说明我都注释出来了,就是用了好几种函数
包括但不限于替换字符串的patsubst,循环结构的foreach,静态模式等,这个Makefile文件就作为后期驱动开发用到的通用Makefile了。整个文件结构要比以前用到的Makefile复杂了好多,但是按照注释看应该好理解。调试的时候在一个地方卡了很久:
SOBJS := $(patsubst %, obj/%,$(SFILENDIR:.s=.o)) #.s=.o是把.s用.o替换
patsubst后面第一个百分号后多了个空格,编译的时候一直报错
并且后面用打印的伪目标调试也能打印出来对应的变量,一定要注意。
链接脚本
注意看一下Makefile里的链接,我们使用了一个叫做链接脚本的文件来替换链接地址0x87800000。这个脚本就是根目录下单一个文件,拷贝在路径下就可以了,大致作用就是用于规定如何把输入文件内的section放入输出文件内, 并控制输出文件内各部分在程序地址空间内的布局. 但也可以用连接命令做一些其他事情,具体作用以后有机会再讲。
SECTIONS{ . = 0X87800000; .text : { obj/start.o *(.text) } .rodata ALIGN(4) : {*(.rodata*)} .data ALIGN(4) : { *(.data) } __bss_start = .; .bss ALIGN(4) : { *(.bss) *(COMMON) } __bss_end = .; }imx6ul.lds