1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
3)对正点原子Linux感兴趣的同学可以加群讨论:935446741
4)关注正点原子公众号,获取最新资料更新
第十三章BSP工程管理实验
在前面的章节中,我们都是将所有的源码文件刚到工程的根目录下,如果工程文件比较少的话这样做无可厚非,但是如果工程源文件达到几十、甚至数百个的时候,这样一股脑全部放到根目录下就会使工程显得混乱不堪。所以我们必须对工程文件做管理,将不同功能的源码文件放到不同的目录中。另外我们也需要将源码文件中,所有完成同一个功能的代码提取出来放到一个单独的文件中,也就是对程序分功能管理。本章我们就来学习一下如何对一个工程进行整理,使其美观、功能模块清晰、易于阅读。
13.1工程管理简介
打开我们上一章的工程根目录,如图13.1.1所示:
图13.1.1工程根目录
在图13.1.1中我们将所有的源码文件都放到工程根目录下,即使这个工程只是完成了一个简单的流水灯的功能,但是其工程根目录下的源码文件就已经不少了。如果在添加一些其他的功能文件,那么文档就会更大,显得很混乱,所以我们需要对这个工程进行整理,将源码文件分模块、分功能整理。我们可以打开一个STM32的例程,如图13.1.2所示:
图13.1.2 STM32F103例程工程文件
图13.1.2中的工程目录就很美观、不同的功能模块文件放到不同的文件夹中,比如驱动文件就放到HARDWARE文件夹中,ST的官方库就放到STM32F10x_FWLib文件夹中,编译产生的过程文件放到OBJ文件夹中。我们可以参考这个工程目录结构来整理第十二章的例程工程,新建名为“5_ledc_bsp”的文件夹,在里面新建bsp、imx6ul、obj和project这4个文件夹,完成以后如图13.1.3所示:
图13.1.3新建的工程根目录文件夹
其中bsp用来存放驱动文件;imx6ul用来存放跟芯片有关的文件,比如NXP官方的SDK库文件;obj用来存放编译生成的.o文件;project存放start.S和main.c文件,也就是应用文件;
将十二章实验中的cc.h、fsl_common.h、fsl_iomuxc.h和MCIMX6Y2.h这四个文件拷贝到文件夹imx6ul中;将start.S和main.c这两个文件拷贝到文件夹project中。我们前面的实验中所有的驱动相关的函数都写到了main.c文件中,比如函数clk_enable、led_init和delay,这三个函数可以分为三类:时钟驱动、LED驱动和延时驱动。因此我们可以在bsp文件夹下创建三个子文件夹:clk、delay和led,分别用来存放时钟驱动文件、延时驱动文件和LED驱动文件,这样main.c函数就会清爽很多,程序功能模块清晰。工程文件夹都创建好了,接下来就是编写代码了,其实就是将时钟驱动、LED驱动和延时驱动相关的函数从main.c中提取出来做成一个独立的驱动文件。
13.2 硬件原理分析
本章使用到的硬件资源和第八章一样,就是一个LED0。
13.3 实验程序编写
本实验对应的例程路径为:开发板光盘-> 1、裸机例程->5_ledc_bsp。
使用VScode新建工程,工程名字为“ledc_bsp”。
13.3.1 创建imx6ul.h文件
新建文件imx6ul.h,然后保存到文件夹imx6ul中,在imx6ul.h中输入如下内容:
示例代码13.3.1.1 imx6ul.h文件代码
1 #ifndef __IMX6UL_H
2 #define __IMX6UL_H
3/***************************************************************
4 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
5文件名 : imx6ul.h
6作者 : 左忠凯
7版本 : V1.0
8描述 : 包含一些常用的头文件。
9其他 : 无
10论坛 : www.openedv.com
11日志 : 初版V1.0 2019/1/3 左忠凯创建
12 ***************************************************************/
13 #include "cc.h"
14 #include "MCIMX6Y2.h"
15 #include "fsl_common.h"
16 #include "fsl_iomuxc.h"
17
18 #endif
文件imx6ul.h很简单,就是引用了一些头文件,以后我们就可以在其他文件中需要引用imx6ul.h就可以了。
13.3.2编写led驱动代码
新建bsp_led.h和bsp_led.c两个文件,将这两个文件存放到bsp/led中,在bsp_led.h中输入输入如下内容:
示例代码13.3.2.1 bsp_led.h文件代码
1 #ifndef __BSP_LED_H
2 #define __BSP_LED_H
3 #include "imx6ul.h"
4/***************************************************************
5 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
6文件名 : bsp_led.h
7作者 : 左忠凯
8版本 : V1.0
9描述 : LED驱动头文件。
10其他 : 无
11论坛 : www.openedv.com
12日志 : 初版V1.0 2019/1/4 左忠凯创建
13 ***************************************************************/
14
15 #define LED0 0
16
17/* 函数声明 */
18void led_init(void);
19void led_switch(int led,int status);
20 #endif
bsp_led.h的内容很简单,就是一些函数声明,在bsp_led.c中输入如下内容:
示例代码13.3.2.2 bsp_led.c文件代码
1 #include "bsp_led.h"
2/***************************************************************
3 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
4文件名 : bsp_led.c
5 作者 : 左忠凯
6版本 : V1.0
7描述 : LED驱动文件。
8其他 : 无
9论坛 : www.openedv.com
10日志 : 初版V1.0 2019/1/4 左忠凯创建
11 ***************************************************************/
12
13/*
14 * @description : 初始化LED对应的GPIO
15 * @param : 无
16 * @return : 无
17 */
18void led_init(void)
19{
20 /* 1、初始化IO复用 */
21 IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03,0);
22
23 /* 2、、配置GPIO1_IO03的IO属性 */
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 * @description : LED控制函数,控制LED打开还是关闭
35 * @param - led : 要控制的LED灯编号
36 * @param - status : 0,关闭LED0,1 打开LED0
37 * @return : 无
38 */
39void led_switch(int led,int status)
40{
41 switch(led)
42 {
43 case LED0:
44 if(status == ON)
45 GPIO1->DR &=~(1<<3);/* 打开LED0 */
46 elseif(status == OFF)
47 GPIO1->DR |=(1<<3);/* 关闭LED0 */
48 break;
49 }
50}
bsp_led.c里面就两个函数led_init和led_switch,led_init函数用来初始化LED所使用的IO,led_switch函数是控制LED灯的打开和关闭,这两个函数都很简单。
13.3.3编写时钟驱动代码
新建bsp_clk.h和bsp_clk.c两个文件,将这两个文件存放到bsp/clk中,在bsp_clk.h中输入输入如下内容:
示例代码13.3.3.1 bsp_clk.h文件代码
1 #ifndef __BSP_CLK_H
2 #define __BSP_CLK_H
3/***************************************************************
4 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
5文件名 : bsp_clk.h
6作者 : 左忠凯
7版本 : V1.0
8描述 : 系统时钟驱动头文件。
9其他 : 无
10论坛 : www.openedv.com
11日志 : 初版V1.0 2019/1/4 左忠凯创建
12 ***************************************************************/
13
14 #include "imx6ul.h"
15
16/* 函数声明 */
17void clk_enable(void);
18
19 #endif
bsp_clk.h很简单,在bsp_clk.c中输入内容:
示例代码13.3.3.2 bsp_clk.c文件代码
1 #include "bsp_clk.h"
2
3 /***************************************************************
4 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
5文件名 : bsp_clk.c
6作者 : 左忠凯
7版本 : V1.0
8描述 : 系统时钟驱动。
9 其他 : 无
10论坛 : www.openedv.com
11日志 : 初版V1.0 2019/1/4 左忠凯创建
12 ***************************************************************/
13
14/*
15 * @description : 使能I.MX6U所有外设时钟
16 * @param : 无
17 * @return : 无
18 */
19void clk_enable(void)
20{
21 CCM->CCGR0 =0XFFFFFFFF;
22 CCM->CCGR1 =0XFFFFFFFF;
23 CCM->CCGR2 =0XFFFFFFFF;
24 CCM->CCGR3 =0XFFFFFFFF;
25 CCM->CCGR4 =0XFFFFFFFF;
26 CCM->CCGR5 =0XFFFFFFFF;
27 CCM->CCGR6 =0XFFFFFFFF;
28}
bsp_clk.c只有一个clk_enable函数,用来使能所有的外设时钟。
13.3.4编写延时驱动代码
新建bsp_delay.h和bsp_delay.c两个文件,将这两个文件存放到bsp/delay中,在bsp_delay.h中输入输入如下内容:
示例代码13.3.4.1 bsp_delay.h文件代码
1 #ifndef __BSP_DELAY_H
2 #define __BSP_DELAY_H
3 /***************************************************************
4 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
5文件名 : bsp_delay.h
6作者 : 左忠凯
7版本 : V1.0
8描述 : 延时头文件。
9其他 : 无
10论坛 : www.openedv.com
11日志 : 初版V1.0 2019/1/4 左忠凯创建
12 ***************************************************************/
13 #include "imx6ul.h"
14
15/* 函数声明 */
16void delay(volatileunsignedint n);
17
18 #endif
在bsp_delay.c中输入内容:
示例代码13.3.4.2 bsp_delay.c文件代码
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_delay.c
作者 : 左忠凯
版本 : V1.0
描述 : 延时文件。
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/1/4 左忠凯创建
***************************************************************/
1 #include "bsp_delay.h"
2
3/*
4 * @description : 短时间延时函数
5 * @param - n : 要延时循环次数(空操作循环次数,模式延时)
6 * @return : 无
7 */
8void delay_short(volatileunsignedint n)
9{
10 while(n--){}
11}
12
13/*
14 * @description : 延时函数,在396Mhz的主频下
15 * 延时时间大约为1ms
16 * @param - n : 要延时的ms数
17 * @return : 无
18 */
19void delay(volatileunsignedint n)
20{
21 while(n--)
22 {
23 delay_short(0x7ff);
24 }
25}
bsp_delay.c里面就两个函数,delay_short和delay,这两个其实就是第十二章中main.c里面的函数。
13.3.5修改main.c文件
在第十二章中,led驱动、延时驱动和时钟驱动相关的函数全部都写到了main.c中,本章我们在前几节已经将这些驱动根据功能模块放置到相应的地方,所以main.c里面的内容就得修改,将main.c里面的内容改为如下所示代码:
示例代码13.3.5.1 main.c文件代码
/**************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : mian.c
作者 : 左忠凯
版本 : V1.0
描述 : I.MX6U开发板裸机实验5 BSP形式的LED驱动
其他 : 本实验学习目的:
1、将各个不同的文件进行分类,学习如何整理工程、就和学习STM32一样创建工程
的各个文件夹分类,实现工程文件的分类化和模块化,便于管理。
2、深入学习Makefile,学习Makefile的高级技巧,学习编写通用Makefile。
论坛 : www.openedv.com
日志 : 初版V1.0 2019/1/4 左忠凯创建
**************************************************************/
1 #include "bsp_clk.h"
2 #include "bsp_delay.h"
3 #include "bsp_led.h"
4
5/*
6 * @description : mian函数
7 * @param : 无
8 * @return : 无
9 */
10int main(void)
11{
12 clk_enable();/* 使能所有的时钟 */
13 led_init();/* 初始化led */
14
15 while(1)
16 {
17 /* 打开LED0 */
18 led_switch(LED0,ON);
19 delay(500);
20
21 /* 关闭LED0 */
22 led_switch(LED0,OFF);
23 delay(500);
24 }
25
26 return0;
27}
在main.c中我们仅仅留下了main函数,至此,本例程跟程序相关的内容就全部编写好了。
13.4编译下载验证
13.4.1编写Makefile和链接脚本
在工程根目录下新建Makefile和imx6ul.lds这两个文件,创建完成以后的工程如图13.4.1.1所示:
图13.4.1最终的工程目录
在文件Makefile中输入如下所示内容:
示例代码13.4.1.1 Makefile文件代码
1 CROSS_COMPILE ?= arm-linux-gnueabihf-
2 TARGET ?= bsp
3
4 CC :=$(CROSS_COMPILE)gcc
5 LD :=$(CROSS_COMPILE)ld
6 OBJCOPY :=$(CROSS_COMPILE)objcopy
7 OBJDUMP :=$(CROSS_COMPILE)objdump
8
9 INCDIRS := imx6ul \
10 bsp/clk \
11 bsp/led \
12 bsp/delay
13
14 SRCDIRS:= project \
15 bsp/clk \
16 bsp/led \
17 bsp/delay
18
19 INCLUDE:=$(patsubst %, -I %, $(INCDIRS))
20
21 SFILES :=$(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
22 CFILES :=$(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
23
24 SFILENDIR :=$(notdir $(SFILES))
25 CFILENDIR :=$(notdir $(CFILES))
26
27 SOBJS :=$(patsubst %, obj/%, $(SFILENDIR:.S=.o))
28 COBJS :=$(patsubst %, obj/%, $(CFILENDIR:.c=.o))
29 OBJS :=$(SOBJS)$(COBJS)
30
31 VPATH :=$(SRCDIRS)
32
33 .PHONY: clean
34
35$(TARGET).bin:$(OBJS)
36 $(LD) -Timx6ul.lds -o $(TARGET).elf $^
37 $(OBJCOPY) -O binary -S $(TARGET).elf $@
38 $(OBJDUMP) -D -m arm $(TARGET).elf >$(TARGET).dis
39
40$(SOBJS): obj/%.o : %.S
41 $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
42
43$(COBJS): obj/%.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文件要比前面的实验复杂很多,因为“示例代码13.4.1.1”中的Makefile代码是一个通用Makefile,我们以后所有的裸机例程都使用这个Makefile。使用时候只要将所需要编译的源文件所在的目录添加到Makefile中即可,我们接下来详细分析一下“示例代码13.4.1.1”中的Makefile源码:
第1~7行定义了一些变量,除了第2行以外其它的都是跟编译器有关的,如果使用其它编译器的话只需要修改第1行即可。第2行的变量TARGET目标名字,不同的例程肯定名字不一一样。
第9行的变量INCDIRS包含整个工程的.h头文件目录,文件中的所有头文件目录都要添加到变量INCDIRS中。比如本例程中包含.h头文件的目录有imx6ul、bsp/clk、bsp/delay和bsp/led,所以就需要在变量INCDIRS中添加这些目录,即:
INCDIRS := imx6ul bsp/clk bsp/led bsp/delay
仔细观察的话会发现第9~11行后面都会有一个符号“\”,这个相当于“换行符”,表示本行和下一行属于同一行,一般一行写不下的时候就用符号“\”来换行。在后面的裸机例程中我们会根据实际情况来在变量INCDIRS中添加头文件目录。
第14行是变量SRCDIRS,和变量INCDIRS一样,只是SRCDIRS包含的是整个工程的所有.c和.S文件目录。比如本例程包含有.c和.S的目录有bsp/clk、bsp/delay、bsp/led和project,即:
SRCDIRS := projectbsp/clk bsp/led bsp/delay
同样的,后面的裸机例程中我们也要根据实际情况在变量SRCDIRS中添加相应的文件目录。
第19行的变量INCLUDE是用到了函数patsubst,通过函数patsubst给变量INCDIRS添加一个“-I”,即:
INCLUDE := -I imx6ul -I bsp/clk -I bsp/led -I bsp/delay
加“-I”的目的是因为Makefile语法要求指明头文件目录的时候需要加上“-I”。
第21行变量SFILES保存工程中所有的.s汇编文件(包含绝对路径),变量SRCDIRS已经存放了工程中所有的.c和.S文件,所以我们只需要从里面挑出所有的.S汇编文件即可,这里借助了函数foreach和函数wildcard,最终SFILES如下:
SFILES := project/start.S
第22行变量CFILES和变量SFILES一样,只是CFILES保存工程中所有的.c文件(包含绝对路径),最终CFILES如下:
CFILES = project/main.c bsp/clk/bsp_clk.c bsp/led/bsp_led.c bsp/delay/bsp_delay.c
第24和25行的变量SFILENDIR和CFILENDIR包含所有的.S汇编文件和.c文件,相比变量SFILES和CFILES,SFILENDIR和CFILNDIR只是文件名,不包含文件的绝对路径。使用函数notdir将SFILES和CFILES中的路径去掉即可,SFILENDIR和CFILENDIR如下:
SFILENDIR = start.S
CFILENDIR = main.c bsp_clk.c bsp_led.c bsp_delay.c
第27和28行的变量SOBJS和COBJS是.S和.c文件编译以后对应的.o文件目录,默认所有的文件编译出来的.o文件和源文件在同一个目录中,这里我们将所有的.o文件都放到obj文件夹下,SOBJS和COBJS内容如下:
SOBJS = obj/start.o
COBJS = obj/main.o obj/bsp_clk.o obj/bsp_led.o obj/bsp_delay.o
第29行变量OBJS是变量SOBJS和COBJS的集合,如下:
OBJS = obj/start.o obj/main.o obj/bsp_clk.o obj/bsp_led.o obj/bsp_delay.o
编译完成以后所有的.o文件就全部存放到了obj目录下,如图13.4.1.1所示:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210315155532536.png#pic_center
图13.4.1.1编译完成后的obj文件夹
第31行的VPATH是指定搜索目录的,这里指定的搜素目录就是变量SRCDIRS所保存的目录,这样当编译的时候所需的.S和.c文件就会在SRCDIRS中指定的目录中查找。
第34行指定了一个伪目标clean,伪目标前面讲解Makefile的时候已经讲解过了。
第35~47行就很熟悉了,前几章都已经详细的讲解过了。
“示例代码13.4.1.1”中的Makefile文件内容重点工作是找到要编译哪些文件?编译的.o文件存放到哪里?使用到的编译命令和前面实验使用的一样,其实Makefile的重点工作就是解决“从哪里来到哪里去的”问题,也就是找到要编译的源文件、编译结果存放到哪里?真正的编译命令很简洁。
链接脚本imx6ul.lds的内容基本和上一章一样,主要是start.o文件路径不同,本章所使用的imx6ul.lds链接脚本内容如下所示:
示例代码13.4.1.2 imx6ul.lds连接脚本
1 SECTIONS{
2.=0X87800000;
3.text :
4{
5 obj/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}
注意第5行设置的start.o文件路径,这里和上一章的链接脚本不同。
13.4.2 编译下载
使用Make命令编译代码,编译成功以后使用软件imxdownload将编译完成的bsp.bin文件下载到SD卡中,命令如下:
chmod 777 imxdownload //给予imxdownload可执行权限,一次即可
./imxdownload bsp.bin /dev/sdd //烧写到SD卡中
烧写成功以后将SD卡插到开发板的SD卡槽中,然后复位开发板,如果代码运行正常的话LED0就会以500ms的时间间隔亮灭,实验现象和上一章一样。