第六章新建MDK工程
本教程所有例程都是在MDK下编写、编译和调试的,因此首先要学习的就是如何新建MDK工程,本章就来讲解一下最基本的MDK工程创建方法,工程创建成功以后用汇编语言编写一个最简单的点灯程序,测试工程创建是否正确。
本章将分为如下几个小节:
6.1、新建MDK工程;
6.2、工程测试;
6.3、程序编写与编译;
6.4、代码调试验证;
6.1 新建MDK工程
不管你以前有没有学习过STM32单片机,会不会使用MDK,此时请务必做一次本章实验,因为会有一些新的、很重要的知识点!
首先创建一个文件夹来存放例程工程,名字以及位置自定义即可,这里我在桌面上新建了一个名为“汇编LED灯实验”的文件夹。打开Keil uVision5,点击菜单Project ->New Uvision Project,如图6.1.1所示。
图6.1.1新建工程
然后弹出工程命名和保存的操作窗口,工程名字我们取:atk_mp1_m4,然后将工程保存到刚刚新建的最后点击保存即可。具体操作窗口如图6.1.2所示。
图6.1.2工程命名和保存
接下来会弹出一个选择Device的界面,就是选择我们的芯片设备型号,大家根据自己使用的芯片型号依次选择即可。STM32MP157开发板的芯片型号是:STM32MP157DAA1,所以我们选择:STMicroelectronicsàSTM32MP1 SeriesàSTM32MP157àSTM32MP157DAAxàSTM32MP157DAAx:Cortex-M4(如果使用的是其他芯片,选择相应的型号就可以了),如图6.1.3所示。
图6.1.3选择芯片型号
特别注意:一定要安装对应的器件支持包(即pack包)才会显示这些内容哦,如果没得选择,请关闭MDK,然后安装光盘:3、软件\MDK5\Keil.STM32MP1xx_DFP.1.3.0.pack这个安装包后重试。
点击OK后,弹出Manage Run-Time Environment对话框,如图6.1.4所示:
图6.1.4界面
在这个界面,我们可以添加自己需要的组件,从而方便构建开发环境,不过这里我们不需要。我们直接点击Cancel即可。这样就得到了我们的初步工程,如图6.1.5所示。
图6.1.5初步创建好的工程
这只是一个最基本的工程框架,此时的工程文件夹“汇编LED灯实验”中的内容如图6.1.6所示:
图6.1.6工程文件夹MDK-ARM 目录
这里我们说明一下,atk_mp1_m4.uvprojx是工程文件,非常关键,不能删除,MDK5.31生成的工程文件是以.uvprojx为后缀,如果要打开工程,双击此文件即可打开工程。DebugConfig,Listings和Objects三个文件夹是MDK自动生成的文件夹,其中DebugConfig文件夹用于存储一些调试配置文件,Listings和Objects文件夹用来存储MDK编译过程的一些中间文件。
6.2 新建程序和编写文件
工程创建好以后就是编写程序了,首先要创建一个代码编写文件,点击File->New,如图6.2.1所示:
图6.2.1 新建文件
点击以后就会在MDK右侧的编辑区新建一个默认名为“Text1”的代码编辑文件,如图6.2.2所示:
图6.2.2 新创建好的文件
此时“Text1”这个文件还没有保存到“汇编LED灯实验”里面,鼠标放到图6.2.2中“Text1”的代码编辑区,然后按下键盘的CTRL+C组合键来保存文件(或者点击MDK的File->Save)。此时会弹出文件保存对话框,这里我们要创建一个汇编文件,因此文件名后缀要为.s,文件名为:startup_stm32mp15xx.s,文件保存到我们前面创建的“汇编LED灯实验”文件夹里面,如图6.2.3所示:
图6.2.3 保存文件
用同样的方法创建一个名为:main.s的汇编文件,完成以后“汇编LED灯实验”文件夹如图6.2.4所示:
图6.2.4 新建文件以后的工程文件夹
此时main.s和startup_stm32mp157xx.s这两个汇编文件只是存在于工程文件夹中,但是还并没有添加到工程里面,所以接下来就是将这两个文件添加到工程里面。在MDK的Project区,鼠标放到Target1->Source Group 1上,然后点击右键,弹出如图6.2.5所示列表:
图6.2.5 添加文件到工程中
点击图6.2.5中的“Add Existing Files to Group ‘Source Group 1’…”,也就是添加现有文件到工程中,此时会弹开对话框,对话框里面的“文件类型”默认是“C Source file(*.c)”,也就是默认只能添加C文件,不能添加.s汇编文件。所以我们要设置“文件类型”为“All files (*.*)”,也就是所有文件,如图6.2.6所示:
图6.2.6 添加文件选择
选中图6.2.6中的“startup_stm32mp15xx.s”,然后点击“Add”按钮即可将此文件添加到MDK工程中。同样的方法,main.s也添加到MDK工程中,添加完成以后的MDK工程如图6.2.7所示:
图6.2.7 创建好的MDK工程
至此,MDK工程创建好了,接下来就是在相应的文件中编写代码。
6.3 程序编写与编译
6.3.1 程序编写
由于我们还没有学习过STM32MP157,现在还不能写程序,因此这里直接参考开发板的“实验0 汇编LED灯实验”,路径:开发板光盘à1、程序源码à3、M4裸机驱动例程MP157-M4 HAL库V1.1à实验0 汇编LED灯实验。在startup_stm32mp15xx.s中输入如下内容:
示例代码6.3.1.1 startup_stm32mp15xx.s文件内容
;**********************************************************************
;* @file : startup_stm32mp15xx.s
;* @author : 正点原子Linux团队
;* @version : V1.0
;* @date : 2020-05-03
;* @Description : STM32MP1设备的中断向量表
;* 本文件功能:
;* - 初始化SP
;* - STM32P1启动以后先执行Reset_Handler函数
;* - Reset_Handler函数里面跳转去运行Start函数
;* @license : Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
;**********************************************************************
1 Stack_Size EQU 0x00000400 ;栈大小为0X400,C语言运行要使用
2
3 AREA STACK, NOINIT, READWRITE, ALIGN=3
4 __stack_limit
5 Stack_Mem SPACE Stack_Size
6 __initial_sp ;初始化SP
7
8 AREA RESET, DATA, READONLY
9 EXPORT __Vectors
10 EXPORT __Vectors_End
11 EXPORT __Vectors_Size
12
13 __Vectors DCD __initial_sp ;栈顶
14 DCD Reset_Handler ;复位中断向量表
15
16
17 __Vectors_End
18 __Vectors_Size EQU __Vectors_End - __Vectors
19
20 AREA |.text|, CODE, READONLY
21
22 Reset_Handler PROC 复位中断函数
23 IMPORT Start
24
25 LDR R0, =Start ;跳转执行Start函数
26 BX R0
27 ENDP
示例代码6.3.1.1中前面的序号是为了代码阅读方便后面添加的,大家在参考写程序的时候不要将这些序号写进去!
在main.s中输入如下内容:
示例代码6.3.1.2 main.s文件内容
;**********************************************************************
;* @file : main.s
;* @author : 正点原子Linux团队
;* @version : V1.0
;* @date : 2020-05-03
;* @description : MP157开发板M4裸机例程main汇编文件
;* 本文件功能:
;* - 定义所要使用的寄存器
;* - Start函数编写,复位中断函数Reset_Handler会执行Start函数
;* - 使能GPIOI时钟,初始化GPIOI_0这个IO为推挽输出
;* - 循环里面周期性的点亮/熄灭LED0
;* @license : Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
;**********************************************************************
1 PERIPH_BASE EQU (0x40000000)
2 MCU_AHB4_PERIPH_BASE EQU (PERIPH_BASE + 0x10000000)
3 RCC_BASE EQU (MCU_AHB4_PERIPH_BASE + 0x0000)
4 RCC_MC_AHB4ENSETR EQU (RCC_BASE + 0XAA8)
5 GPIOI_BASE EQU (MCU_AHB4_PERIPH_BASE + 0xA000)
6 GPIOI_MODER EQU (GPIOI_BASE + 0x0000)
7 GPIOI_OTYPER EQU (GPIOI_BASE + 0x0004)
8 GPIOI_OSPEEDR EQU (GPIOI_BASE + 0x0008)
9 GPIOI_PUPDR EQU (GPIOI_BASE + 0x000C)
10 GPIOI_BSRR EQU (GPIOI_BASE + 0x0018)
11
12 AREA |.text|, CODE, READONLY, ALIGN=2
13 THUMB
14 EXPORT Start
15
16 Start
17 ;1、设置RCC_MC_AHB4ENSETR寄存器,使能GPIOI时钟
18 LDR R0, =RCC_MC_AHB4ENSETR
19 LDR R1, [R0] ;读取RCC_MC_AHB4ENSETR寄存器的值到R1
20 ORR R1, #(1 << 8) ;bit4置1,使能GPIOI时钟
21 STR R1, [R0] ;写入到RCC_MC_AHB4ENSETR寄存器
22
23 ;2、GPIOI_MODER寄存器,设置GPIOI_0输出模式
24 LDR R0, =GPIOI_MODER
25 LDR R1, [R0] ;读取GPIOI_MODER寄存器的值到R1
26 BIC R1, #(3 << (2 * 0)) ;bit1:0清零
27 ORR R1, #(1 << (2 * 0)) ;bit1:0设置为01
28 STR R1, [R0] ;写入到GPIOI_MODER寄存器
29
30 ;3、GPIOI_OTYPER寄存器,设置GPIOI_0为推挽模式
31 LDR R0, =GPIOI_OTYPER
32 LDR R1, [R0] ;读取GPIOI_OTYPER寄存器值到R1
33 BIC R1, #(1 << 0) ;bit0清零,设置为上拉
34 STR R1, [R0] ;写入到GPIOI_OTYPER寄存器
35
36 ;4、GPIOI_OSPEEDR寄存器,设置GPIOI_0为高速
37 LDR R0, =GPIOI_OSPEEDR
38 LDR R1, [R0] ;读取GPIOI_OSPEEDR寄存器的值到R1
39 BIC R1, #(3 << (2 * 0)) ;bit1:0清零
40 ORR R1, #(2 << (2 * 0)) ;bit1:0设置为10
41 STR R1, [R0] ;写入到GPIOI_OSPEEDR寄存器
42
43 ;5、GPIOI_PUPDR寄存器,设置GPIOI_0上拉
44 LDR R0, =GPIOI_PUPDR
45 LDR R1, [R0] ;读取GPIOI_PUPDR寄存器的值到R1
46 BIC R1, #(3 << (2 * 0)) ;bit1:0清零
47 ORR R1, #(1 << (2 * 0)) ;bit1:0设置为01
48 STR R1, [R0] ;写入到GPIOI_PUPDR寄存器
49
50 ;6、GPIOI_BSRR寄存器,设置GPIOI_0为低,点亮LED0
51 LDR R0, =GPIOI_BSRR
52 LDR R1, [R0] ;读取GPIOI_BSRR寄存器的值到R1
53 ORR R1, #(1 << 16) ;bit16设置为0
54 STR R1, [R0] ;写入到GPIOI_BSRR寄存器
55
56循环
57 Loop
58 BL Led0_on ;开灯
59 BL Delay ;延时
60 BL Led0_off ;关灯
61 BL Delay ;延时
62 B Loop
63
64
65 ;打开LED0
66 Led0_on
67 LDR R0, =GPIOI_BSRR
68 LDR R1, [R0] ;读取GPIOI_BSRR寄存器的值到R1
69 ORR R1, #(1 << 16) ;bit16置1,输出低电平
70 STR R1, [R0] ;写入到GPIOI_BSRR寄存器
71 BX LR
72
73 ;关闭LED0
74 Led0_off
75 LDR R0, =GPIOI_BSRR
76 LDR R1, [R0] ;读取GPIOI_BSRR寄存器的值到R1
77 ORR R1, #(1 << 0) ;bit15置1,输出高电平
78 STR R1, [R0] ;写入到GPIOI_BSRR寄存器
79 BX LR
80
81延时函数
82 Delay
83 LDR R2, =0X4FFFFF
84 LDR R3, =0X0
85 Delay_loop
86 SUB R2, R2, #1 ;R2寄存器减1
87 CMP R2, R3 ;R2和R3寄存器的值进行比较
88 BNE Delay_loop ;R2与R3的值不相等,说明没有R2还没有减完,继续
89 BX LR ;返回LR
90 END
6.3.2 设置分散加载文件
对于以前学习过STM32单片机的朋友来说这里可能会有疑问,程序编写好就可以编译了呀,为什么这里还要创建分散加载文件。分散加载文件也叫做链接脚本,用来描述程序在编译的时候应该怎么链接,比如代码部分应该链接到哪个地址范围?数据部分应该链接到哪个地址范围,甚至可以单独指定某一个.c或.s文件连接到哪个地址范围。以前STM32单片机不需要链接脚本,只需要直接在MDK上设置好ROM和RAM地址范围即可,以STM32F103为例,设置如图6.3.2.1所示:
图6.3.2.1 STM32F103 ROM和RAM设置
图6.3.2.1中设置了STM32F103的ROM起始地址为0X8000000,大小为0X80000,这就是STM32F103内部Flash地址范围;RAM起始地址为0X20000000,大小为0X1000,这就是STM32F103的内存RAM范围。此时MDK在编译的时候就会将代码从0X8000000这个地址空间开始存储,内存的话就会从0X20000000这个地方开始。
前面在5.3.1小节讲解STM32MP157的内存映射的时候说过,STM32MP157内部SRAM1~4是供M4内核使用的,可以用来作为ROM和RAM,这段内存起始地址为0X10000000,大小为384KB。那么我们可不可以按照6.3.2.1中的STM23F103那样直接配置ROM和RAM地址空间呢?事实上我们新建MDK工程的时候MDK默认已经这么分配了,如图6.3.2.2所示:
图6.3.2.2 STM32MP157 ROM和RAM默认分配
从图6.3.2.2可以看出,默认情况下MDK将0X10000000开始的128KB(0X20000)内存空间作为ROM使用,用来存放代码。将0X10020000开始的128KB(0X20000)作为RAM。
既然MDK默认已经设置好了ROM和RAM范围,为什么不能直接编译,需要另外创建分散加载文件,这不是多此一举吗?原因在于STM32MP157中M4内核的中断向量表要放到RETRAM区域,也就是0X00000000地址处,相当于第一行代码要放到0X00000000处,这个时候就涉及到将某个文件链接到指定地址,这个功能就需要借助分散加载文件,关于分散加载文件后面会详细讲解,这里大家知道有这个东西即可。
这里我们直接使用开发板例程里面的分散加载文件,找到“实验0 汇编LED灯实验”这个例程,将此例程中的stm32mp15xx_m4.sct这个文件复制到本例程中。设置MDK工程,使用这个分散加载文件,点击
,打开相应的配置界面,点击Linker标签,按照图6.3.2.3所示设置:
图6.3.2.3 链接脚本设置
图6.3.2.3中:
①、点击Linker标签,进行链接相关配置。
②、取消勾选“Use Memory Layout from Target Dialog”,不使用MDK默认的配置,也就是不使用图6.3.2.2中的链接配置,因为要使用我们自己创建的分散加载文件。
③、点击此按钮找到要使用的分散加载文件。
④、选择好的分散加载文件。
⑤、如果要编辑分散加载文件的话就直接点击“Edit”即可在MDK下编辑。
6.3.3 编译程序
程序和分散加载文件准备好以后就可以编译了,按照4.1小节讲解的方法编译工程,由于本章程序都是直接复制例程的,因此编译不会出错,结果如图6.3.3.1所示:
图6.3.3.1 编译结果
从图6.3.3.1可以看出,此时有0个错误(Error),3个警告(Warning),这三个警告可以忽略不计,如果编译有错误(Error)的话就自行检查错误。
6.4 代码调试验证
代码编译完成以后就可以进行调试验证了,因为是全新创建的工程,因此在调试验证之前需要对工程进行一些设置。点击
,打开配置项,点击Output选项,选择创建HEX文件文件,如图6.4.1所示:
图6.4.1 Output选项卡
虽然STM32MP157 M4内核用不到hex文件,但是这里还是选择上。继续点击“Debug”选项卡,根据自己的实际使用,选择调试器,比如我使用ST LINk,因此就选择ST-Link Debuger,如图6.4.2所示:
图6.4.2 Debug选项卡
图6.4.2中选择仿真器为“ST-Link Debugger”,然后勾选“Run to main()”,虽然本章节是汇编语言编写的,没有main函数,但是为了例程一致性,还是勾选上此选项。
点击图6.4.2中的“Settings”按钮,打开调试设置界面,设置ST LINK的一些参数,如图6.4.3所示:
图6.4.3 ST LINK模式设置
①、表示MDK找到了ST-LINK/V2仿真器。
②、设置接口方式,这里选择SW(比JTAG省IO)。
③、表示MDK通过仿真器的SW接口找到了目标芯片,ID为:0x6BA02477。如果这里显示:No target connected,则表示没找到任何器件,请检查仿真器和开发板连接是否正常?开发板是否供电了?如果一切都正常,但还是提示No target connected,点击图6.4.3中的“Pack”标签,如图6.4.4所示:
图6.4.4 Pack设置
勾选图6.4.4中的“Enable”,点击“确认”退出。然后重新点击“Debug”标签,查看图6.4.3中③的芯片ID有没有识别出来。识别出来以后就再重新点击“Pack”,然后取消图6.4.4中刚刚勾选的 “Enable”,否则无法仿真。
最后设置Utilities选项卡里面设置下载时的目标编程器,如图6.4.5所示:
图6.4.5 FLASH编程器选择
①、勾选“Use Debug Driver”,选择ST LINK来调试、下载。
②、“Update Target before Debuggin”顾名思义,就是在调试之前先将代码下载到目标器件中,由于STM32MP157 M4没有内部Flash,因此绝对不能勾选此选项!否则仿真就会失败,对于那些内置Flash的STM32单片机需要勾选此选项。
③、对于内置Flash的STM32单片机,还需要设置烧写算法,STM32MP157 M4内核不需要设置这一步。
到这里MDK就配置好了,接下来就可以仿真运行了。按照4.2.2小节讲的方法进行调试验证,选择全速运行,如果开发板上的红色LED灯闪烁的话就说明代码运行正常。一个完整的、简单的MDK工程创建就到此结束了,本章讲解的是最基本的MDK工程创建,后面学习到HAL库的时候就会接触到更加复杂的操作。
6.5 清理工程
编译的过程中会产生很多中间文件,如果我们要清除中间文件以及编译后生成的最终文件,可以选择MDK自带的清理工程选项,不过此选项清理的工程并不完全:
图6.5.1清理工程
或者可以使用通过批处理来实现,正点原子的源代码工程就是用批处理来清除的,该批处理文件keilkill.bat在我们实验工程的每个代码中:
图6.5.2正点原子提供的批处理文件
只要双击该文件,就可以把我们编译生成的所有文件删除。自己也可以修改批处理脚本keilkill.bat来选择删除哪些文件,如下:
1 del *.bak /s
2 del *.ddk /s
3 del *.edk /s
4 del *.lst /s
5 del *.lnp /s
6 del *.mpf /s
7 del *.mpj /s
8 del *.obj /s
9 del *.omf /s
10 ::del *.opt /s ::两个冒号表示该语句被注释了,不会执行
11 del *.plg /s
12 del *.rpt /s
13 del *.tmp /s
14 del *.__i /s
15 del *.crf /s
16 del *.o /s
17 del *.d /s
18 del *.axf /s
19 del *.tra /s
20 del *.dep /s
21 del JLinkLog.txt /s
22
23 del *.iex /s
24 del *.htm /s
25 del *.map /s
26
27 del *.dbgconf /s
28 del *.LINGZHUNING /s
29 del *.Administrator /s
30 exit
以上脚本中是删掉哪些文件,*号是通配符,例如第1行,删掉所有.bak后缀的文件。两个冒号“::”表示注释该行,相当于C语言的“//”,被注释的行不会被执行,如果我们不想删除.axf文件,则把第18行用两个冒号注释掉即可。
::del *.axf /s