从零建立独立的MSP432 KEIL工程
很多网友喜欢使用原子的那种一个工程所有的文件都在一个总的目录下的开发方式,这种开发方式的弊端在于多工程开发的时候会导致文件重复拷贝,部分文件发生修改的时候版本不好管理,当然优点也是明显的,就是部署实例代码比较方便,直接整个拷贝过去基本就能编译使用,所以这个教程就和大家一起用KEIL建立一个独立的工程。
Cloud喜欢用通用的思维去考虑问题,希望本章节给大家解释完了MSP432的建立方式,在建立其他SDK开发环境的时候也能举一反三自己搞定工程的建立。
如果不想学习这个分析的过程,也可以直接跳过这部分,直接到第5节按步骤进行。
所以开篇我们先要讲解一下一个MCU的工程到底需要哪些东西,以下内容支持任何平台下的基于C的主流开发方式:
在我们学习C语言的过程中,书上会告诉我们C语言需要一个main函数,然后我们用软件点一下编译按钮就完成编译了。实际上这个过程要复杂很多,有太多的细节被屏蔽在了IDE(集成开发环境,比如Visual Studio、Keil、IAR等)中。那么我们需要先从C工程的编译开始讲起。由于我们使用的是KEIL for ARM这个开发环境,其使用的内置工具链为ARMCC,因此我们下面主要以ARMCC的特性来讲解,其他编译工具链例如GCC等其实也是类似的。
【小知识】:工具链,简单理解就是用来编译代码的一套程序集,以ARMCC为例,其可执行目录下有ARMAR、ARMASM、ARMCC、ARMLINK、FROMELF等程序,分别用来实现打包、汇编、编译、链接、转换等功能
事实上C代码的编译过程是分步骤的,简单概括步骤分为下面的3步:
预处理 → 编译 → 链接
而详细的编译过程我们可以以下面这个图来描述:

如图所示:
1、首先是预处理过程:C文件先经过预处理产生.i文件,这一步主要是将C文件所包含的所有文件(一般是以.h为后缀的头文件,当然实际上也可以包含.c在内的任何格式的文件)进行一个展开,所谓的#include其实本质上就是把被包含的文件原样插入到这一行中(有嵌套包含的文件同样也会嵌套被插入进来);
2、然后是编译过程:这个过程有两种情况,一种是对.c文件也就是C源码进行编译,另一种是对.s文件也就是汇编代码进行编译。通过编译器将C文件或者通过汇编器将汇编文件编译为object文件(ARMCC中用.o后缀)。另外,实际上C源码也是先会编译成汇编的形式然后再生成object文件,只是编译器一般不对外输出这些汇编文件;
3、最后是链接过程:通过链接器将前面编译好的object文件和其他的库文件(包含静态库或动态库,在MCU中往往不使用动态库)按照链接脚本的指示链接起来得到最终的可执行文件(ARMCC中用.axf后缀)。
【小知识】:我们在这里提到了库文件,这个库文件一般是通过打包工具(ARMCC使用ARMAR命令)将object文件打包到一个库文件中(ARMCC使用.lib后缀)
另外,axf文件可以通过转换工具得到其他各种类型的其他输出文件,例如用于存储实际程序的二进制代码的.bin文件、以十六进制格式存储实际程序的.hex文件等。
1、工程的管理文件,这个文件通过IDE工具新建工程来建立,KEIL的工程文件后缀为.uvprojx;
2、存放main函数的应用.c文件和其他应用代码;
3、存放驱动库的.c文件或者已经编译好的静态库文件(ARMCC使用.lib后缀);
4、上面代码文件或库文件所需要的.h头文件,由于编译过程中编译器会通过#include语句自动寻找这些文件,所以一般我们只需要关心这些文件所在的路径;
5、用于启动C环境所需的启动汇编代码,该文件用于MCU基本环境的初始化,最后直接或者间接跳转到main函数中;
6、用于存放中断向量表的文件,该文件往往和启动汇编代码在同一个文件中;
7、用于指导链接器进行链接的链接文件;
【小知识】:编译过程中的其他文件:
d文件(又称depend文件、依赖文件),用于描述C文件所依赖的文件列表,通过文件的依赖关系我们就可以在重新编译工程的时候选择只编译那些与修改有关的文件,而忽略无关的文件,这样可以加快程序的编译过程。
所谓依赖就是该文件的编译是否会受到其他文件的影响,比如一个a.c包含了a.h文件,那么即使a.c这个文件没有变化而a.h这个文件被修改的时候,a.c依然需要重新被编译。
需要注意的是,当我们修改了全局的编译参数时,整个工程都会被重新编译,例如我们在编译选项中新增了一个头文件的路径,那么有可能这个动作会影响工程里面所有的代码行为,所以整个工程都会重新编译一次。
【小知识】:在KEIL或其他各种IDE中,一般都会提供两种编译目标的方式,一种是build,另一种是rebuild,前者只编译必要的文件而后者每次编译的时候都会对所有的文件进行编译动作。在KEIL中,两种编译方式的按钮如下图所示,左边是build右边是rebuild:

我们在SDK的examples\nortos\MSP_EXP432P401R\driverlib\目录下随便找一个工程,比如timer32_periodic_mode_led_toggle这个工程。为了避免打开文件对原工程文件的改动,我们先将这个目录备份,然后再打开。
我们进入timer32_periodic_mode_led_toggle目录下的keil目录,然后打开该目录下的timer32_periodic_mode_led_toggle.uvprojx文件,这个文件就是KEIL的工程文件。
进入KEIL环境后,我们点击下面这个按钮进入工程的配置页面:

那么结合原工程的配置,这里为大家讲解一下这个工程配置页面的相关配置和含义:
Device:这个页面用来选择所开发的芯片型号,选择MSP432P401R

Target:该页面主要配置目标的编译器、可用的内存地址等信息。这里可以看到IROM1为从0x0开始的大小为0x40000的空间,IRAM1起始地址为0x20000000,大小0x10000

Output:该页面描述与工程输出文件相关的配置,这里的输出文件包含了可执行文件、调试信息、hex文件、代码浏览信息等。页面可以选择输入文件的路径,用来保存产生的中间文件,还可以选择是否生产其他信息,也可以选择是生成可执行文件还是库文件

Listing:生成lst文件配置和map文件,可以方便用户在编译完成后查看相关的符号信息

User:该页面用于配置用户自定义的命令,比如可以在After Build/Rebuild选项中添加如下命令用来生成bin文件:
fromelf --bin --output [bin文件名] [axf文件名]
当然KEIL支持我们可以用统一的写法来生成bin文件:
fromelf --bin -o “$L@L.bin” “$L@L.axf”
其中,$L指代axf文件的路径,不包含文件名;@L指代axf的文件名,不含.axf的后缀。利用这种特殊的变量关系可以自动替换命令中的相关文件名。

C/C++:用来控制C/C++源码的编译过程,在Define栏中添加全局宏定义:__MSP432P401R__,可以在Optimization中选择优化等级(O-0为不优化、O-1为初步优化、O-2为较多优化、O-3为完整优化、另外GCC中O-s为大小优化),为了删除多余不用的C函数接口我们可以勾选“One ELF Section per Function”这个功能来支持GC以优化最终的程序体积,推荐勾选C99模式。另外在Include Paths中可以添加头文件路径。最终的编译命令参数将在最底下的“Compiler control string”中体现出来,也就是最终实际传递给编译器的参数命令

Asm:用来控制汇编过程,暂时可以不配置

Linker:用来控制链接过程,这里有两种链接的方式:一种是勾选“Use Memory Layout from Target Dialog”,使用Target页面的内存布局配置来自动生成链接脚本;还有一种是不勾选,使用“Scatter File”所选择的sct文件(在KEIL中被称为分散加载文件scatter)进行链接

Debug:用于调试功能设置,勾选右上角的“Use”按钮并选择“TI XDS Debugger”,勾选“Load application at Startup”,勾选“Run to main()”可以方便调试的时候直接到main函数,“Initialization File”配置为..\..\..\..\..\..\source\ti\devices\msp432p4xx\rom\keil\keil_rom_load_msp432p401r.ini

Utilities:用于和镜像下载等相关的配置,建议勾选“Use Debug Driver”来直接使用调试器作为程序的下载器

然后我们关闭这个配置界面,看他工程都包含了什么文件:

可以看到,一个基本的工程中一般有几个文件:
1、system_msp432p401r.c:这个是系统启动配置相关的C源码
2、startup_msp432p401r_uvision.s:这个是启动汇编代码
3、msp432p4xx_driverlib.lib:这个文件是SDK提供的库文件,使用lib文件可以加快编译速度,缺点是无法进入具体驱动函数进行调试和学习,推荐不需要进入驱动函数调试的同学使用这个库来来参与编译,这个lib文件在:
../../../../../../source/ti/devices/msp432p4xx/driverlib/keil/msp432p4xx_driverlib.lib
另外右键这个lib文件点击第一个菜单可以打开它的配置选项:

默认是全部链接这个库的,如果只需要链接部分库内容,可以勾选“Select Modules”然后在下面的o文件列表中选择所需的库(可以看到lib文件里面也是由很多o文件组成的)

如果需要独立驱动文件的同学可以手动添加
4、最后一个就是和实际工程应用相关的C文件。
以上就是我们对SDK中的实例工程的一个简单分析,接下去就可以做实际的工程构建了。
事实上,TI已经为我们准备好所有的文件,其主要的文件都在下面这个目录中:
[SDK安装路径]\source\ti\devices\msp432p4xx这个目录下面如下图所示:

我们可以看到:
driverlib 显然是存放驱动库的;
inc 其实就是include的缩写,应该是存放头文件的目录;
linker_files 翻译过来就是链接文件,也就是链接脚本,当然在KEIL里面我们可以使用这里的链接脚本也可以直接通过界面来配置;
rom 目录显然是和固话在芯片中的ROM代码相关;
startup_system_files 目录显然是存放system_xx.c和startup_xx.s文件的地方
那么我们一个一个来看这些目录:
首先我们打开driverlib,里面可以看到很多驱动文件和4个目录,如下图所示:

可以看到驱动的.c文件和.h文件都在这个目录下。同时有4个目录,分别是ccs、GCc、iar和keil,显然这个keil就是和我们使用KEIL平台是相关的。另外的ccs是TI官方的开发环境,GCc是使用GCC编译的环境支持,iar是另一个老牌集成开发环境IAR的支持。
我们打开keil目录看一下里面有什么:

可以看到有三个文件,可以看到有一个lib文件,这个其实就是TI官方已经为我们编译好的、在KEIL环境下使用的完整驱动库,而另外两个文件应该是TI用来编译这个工程所使用的KEIL工程及其配置文件,一般我们是不需要的。事实上,我们可以打开那个后缀为uvprojx的文件,里面可以看到里面其实就是上级目录中的那些驱动:

那么我们使用ti提供的驱动库的时候就有了两种方法:
一种是使用上一级目录中的那些独立的.c文件,也就是添加我们需要的.c文件到KEIL工程下;
另一种就是直接使用lib文件,将lib文件添加到KEIL工程中。
(拓展学习:我们可以看一眼GCc目录下的文件,可以看到GCc下提供的编译好的库文件的后缀就是.a)
然后我们进入inc的目录,可以看到如下头文件:

这里有些头文件名称中带有芯片型号的信息,而另一些没有,显然带有型号信息的头文件是给具体芯片使用的,而没有带型号信息的属于通用文件,大家都可以用。那么以我们使用的MSP432P401R这个芯片为例,我们最终包含的文件应该就是如下这些:
msp.h、msp_compatibility.h、msp432.h、msp432p4xx.h、msp432p401r.h、msp432p401r_classic.h、system_msp432p401r.h
其中,msp432p401r_classic.h这个文件是用来兼容以前430代码的头文件,我们可能用不到;而system_msp432p401r.h这个文件件显然是给system_xxx.c这个c文件使用的。
我们打开msp.h或者msp432.h这个通用头文件可以看到它其实是通过宏来控制包含了其他的特定芯片头文件:

而具体包含哪个芯片头文件是通过判断例如“__MSP432P401R__”这些宏来实现的,这一点很重要,这意味着我们需要在工程的全局宏定义中去添加这个宏(还记得前面我们在KEIL的C/C++选项卡中添加的那个宏么?)
linker_files这个目录里面也是一样有4个文件夹,同样是GCc、iar、keil、ccs的分类,我们进入keil目录可以看到里面全部都是后缀为.sct的文件,其中我们对应芯片所使用的应该是msp432p401r.sct这个文件。
我们可以打开这个文件来看看里面的内容:

可以看到里面出现了ROM、RAM的字样。在MCU中,往往会将用于存储程序的空间定义为ROM空间(物理上的形式一般多使用FLASH存储器这种介质),而将用于存储数据的空间定义为RAM空间。
另外, 我们看到ER_IROM1 0x00000000 0x00040000这段代码,可以知道代码是存在0x0开始的地址,大小是0x40000也就是256KB,而RAM是在0x20000000开始的地址,大小是0x10000也就是64KB,这个和我们在MSP432P401R的芯片手册上看到的信息是一致的:


这个目录下是ti在MSP432芯片内所固话的ROM的代码工程,我们暂时不需要。
可以看到这个目录中的文件也是类似的风格,4个目录下是不同开发环境的文件,而system_xxx.c是根据不同的芯片型号来划分的:

同样进入keil目录,可以看到很多芯片对应的汇编启动文件,显然我们也是选择我们所需的文件即可。

那么我们现在总结一下为建立一个基于KEIL的MSP432P401R工程所需的文件(这里为了节省字数,我们使用SDK的相对路径来表示):
1、启动汇编文件:
source\ti\devices\msp432p4xx\startup_system_files\keil\startup_msp432p401r_uvision.s
2、system文件:
source\ti\devices\msp432p4xx\startup_system_files\system_msp432p401r.c
3、链接文件:
source\ti\devices\msp432p4xx\linker_files\keil\MSP432P401R.sct
4、驱动库:
可以直接使用ti已经编译好的lib文件:
source\ti\devices\msp432p4xx\driverlib\keil\msp432p4xx_driverlib.lib
也可以使用ti提供的C源码文件:
source\ti\devices\msp432p4xx\driverlib\adc14.c
source\ti\devices\msp432p4xx\driverlib\aes256.c
source\ti\devices\msp432p4xx\driverlib\comp_e.c
source\ti\devices\msp432p4xx\driverlib\cpu.c
source\ti\devices\msp432p4xx\driverlib\crc32.c
source\ti\devices\msp432p4xx\driverlib\cs.c
source\ti\devices\msp432p4xx\driverlib\dma.c
source\ti\devices\msp432p4xx\driverlib\flash.c
source\ti\devices\msp432p4xx\driverlib\flash_a.c
source\ti\devices\msp432p4xx\driverlib\fpu.c
source\ti\devices\msp432p4xx\driverlib\gpio.c
source\ti\devices\msp432p4xx\driverlib\i2c.c
source\ti\devices\msp432p4xx\driverlib\interrupt.c
source\ti\devices\msp432p4xx\driverlib\mpu.c
source\ti\devices\msp432p4xx\driverlib\pcm.c
source\ti\devices\msp432p4xx\driverlib\pmap.c
source\ti\devices\msp432p4xx\driverlib\pss.c
source\ti\devices\msp432p4xx\driverlib\ref_a.c
source\ti\devices\msp432p4xx\driverlib\reset.c
source\ti\devices\msp432p4xx\driverlib\rtc_c.c
source\ti\devices\msp432p4xx\driverlib\spi.c
source\ti\devices\msp432p4xx\driverlib\sysctl.c
source\ti\devices\msp432p4xx\driverlib\sysctl_a.c
source\ti\devices\msp432p4xx\driverlib\systick.c
source\ti\devices\msp432p4xx\driverlib\timer_a.c
source\ti\devices\msp432p4xx\driverlib\timer32.c
source\ti\devices\msp432p4xx\driverlib\uart.c
source\ti\devices\msp432p4xx\driverlib\wdt_a.c
5、头文件:
驱动的头文件路径就是:
source\ti\devices\msp432p4xx\driverlib
msp芯片头文件和system文件路径在:
source\ti\devices\msp432p4xx\inc
另外实际上,还有一些头文件我们一时没有确定,但是没关系,后面我们在实际建立工程的过程当中会逐步确定。
6、编译所需要的宏:
__MSP432P401R__
由于cloud已经走过一遍弯路,所以在正式开始之前,让我们先看一下驱动文件的#include代码,这里我们打开adc.c文件:

可以看到,它包含了三个头文件:adc14.h、debug.h、interrupt.h,但是这三个文件的包含方式不是使用直接的文件名而是前面有路径。这意味着编译器将会在所有的头文件搜索路径中加上这样一长串的路径去找文件。因此我们在对应添加头文件路径的时候应该要添加这个“ti/devices/msp432p4xx/driverlib/xxx”所在的路径,同时我们在拷贝文件的时候也要保持原有的路径结构。
那么有没有别的方法呢?当然有,但是我们就需要修改这些驱动文件里面的#include代码,缺点就是修改的文件比较多,而且一旦更新驱动源码的话,还需要重新去修改。所以综合考虑,这里我们就按原工程目录结构来建立比较稳妥。
好的,现在我们找一个空的目录,然后我们先建立如下的目录结构:

其中source/ti/devices/msp432p4xx/这个目录是沿袭了官方SDK的目录结构,这样建立目录的原因前面我们已经提到过了。然后我们点开原SDK的source/ti/devices/msp432p4xx目录,按照一一对应的原则拷贝如下文件或目录:
1、driverlib下需要拷贝所有的.c和.h文件,同时拷贝该目录下的keil目录下的msp432p4xx_driverlib.lib这个库文件:


2、inc下拷贝MSP432P401R芯片所相关的头文件:

3、linker_files下拷贝keil目录下的MSP432P401R.sct文件(实际上这个文件可以不用,但是我们还是把它拷贝过来,以防不时之需):

4、startup_system_files下拷贝system_msp432p401r.c以及keil目录下的startup_msp432p401r_uvision.s:


然后我们可以打开KEIL,新建一个MSP432的工程:

跳出的对话框我们选中刚刚我们建立的目录:

在跳出的Device器件选择对话框中选择Texas Instruments、MSP432P4xx Series、MSP432P401R:

然后会跳出KEIL的RTE配置界面,我们暂时不需要使用RTE功能,所以点击“Cancel”:

此时可以看到工程列表是空的:

然后我们点击这个按钮进行目录的配置:

然后在Groups里面添加三个组:driver_lib、app、system:

然后我们选择driver_lib,在右边你的Files里面添加驱动库文件:

添加后的效果如下图所示:

app我们先暂时放一边,一会儿再添加。我们先添加system文件:

然后添加启动汇编文件startup_msp432p401r_uvision.s,这里需要修改文件选择对话框中的“文件类型”,不然是无法显示.s后缀的文件的:

于是我们的工程目录就变成了这样:

现在我们来配置工程的选项,由于ARMV6的编译器可能支持并不是很好,所以首先我们要在Target标签页中修改ARM编译器为V5编译器:

然后我们看到Output标签页其实已经帮我们自动配置好了,如果想要修改生成的文件名及文件夹可以自行修改:

然后我们配置C/C++的选项卡,添加全局宏定义“__MSP432P401R__”,推荐优化等级设置为O2,然后一定要勾选C99 Mode否则有些文件可能无法正常编译(因为ti的SDK源码使用了一些C99的语法特性)

另外我们要点击“...”的按钮,在弹出的目录选择器中添加头文件的路径,如下图所示:

然后我们的Debug选项卡中选择合适的调试器,TI官方的板子是支持CMSIS-DAP和TI XDS两种调试支持的。

其他的选项卡暂时不需要做额外设置。
我们先编译一下,可以看到会提示错误,我们找到第一个错误:

显然,这是系统无法找到core_cm4.h这个头文件而报错。那么这个文件在哪里呢,不要慌,我们去ti的SDK目录下搜索一下这个文件:

然后我们右键,选择“打开文件所在的位置”去找到这个文件所在的目录:

可以发现是在SDK的source\third_party\CMSIS\Include目录下。那么我们把这整个目录也拷贝到我们的工程目录中,同样是按照目录结构来复制,在我们的source目录下建立third_party\CMSIS\Include目录,然后把文件都复制进去(其实并不是全部文件都需要,但为了描述简便,这里就统一复制处理了,有兴趣的同学可以自行研究)。
然后我们需要再修改C/C++编译选项中的头文件路径,将这个Include文件夹的路径也添加到路径中:

然后再编译,可以看到,驱动库已经正常编译了。但是最后linking的时候报了错误:

提示我们main函数没有,显然,目前我们还没有加main函数,所以解决方法也很简单,我们在app下面新建一个main.c,然后实现一个main函数即可:

这里要注意的是main函数的返回值建议用int而不是void,否则编译器可能会报错或者报警告。
然后我们将这个文件添加到工程中:

然后再点击编译,可以看到,程序已经正常编译了:

实际上我们上面的main函数所在的main.c还是太简单了,虽然能保证我们代码能够运行到main函数中,但是还是缺少了一些东西。这里我们可以参考前面分析过的实例工程的应用C文件来进行:

首先我们可以看到,需要使用完整的头文件路径来使用driverlib.h,同时main函数的第一条必须要先调用WDT_A_holdTimer()函数来关看门狗(实例中是带MAP前缀的,我们可以去掉前缀)

然后我们可以看到它还写了一些终端服务函数在这个C文件中,那么我们目前是不需要的,这部分以后再做介绍。
至此,本教程就基本结束了,希望能够帮助大家完成MSP432的KEIL工程的创建