1)实验平台:alientek 阿波罗 STM32F767 开发板
2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子
)
最后还一个地方需要大家修改一下,那就是关于系统初始化之后的中断优先级分组组号的设置。默认情况下调用 HAL 初始化函数 HAL_Init 之后,会设置分组为组 4,这里我们正
点原子所有实验使用的是分组 2,所以我们修改 HAL_Init 函数内部,重新设置分组为组 2 即可。
具体方法是:打开 HALLIB 分组之下的 stm32f7xx_hal.c 文件,搜索函数 HAL_Init,找到函数体,
里面默认有这样一行代码:
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
我们将入口参数 NVIC_PRIORITYGROUP_4 修改为 NVIC_PRIORITYGROUP_2 即可。关于中
断优先级分组相关知识请参考本手册 小节即可。
工程模板解读
上一节,我们新建了一个基于 HAL 库的 STM32F7 工程模板,本节,我们将给大家讲解工
程模板中的一些关键文件的作用以及整个工程模板程序运行流程。通过对本节内容的学习,大
家将对 STM32F7 工程有一个比较全面的了解,为后面实验学习打下良好的基础。
关键文件介绍
在讲解之前我们需要说明一点,任何一个 MDK 工程,不管它有多复杂,无非就是一些.c
源文件和.h 头文件,还有一些类似.s 的启动文件或者 lib 文件等。在工程中,他们通过各种包含
关系组织在一起,被我们用户代码最终调用或者引用。所以我们必须了解这些文件的作用以及
他们之间的包含关系,从而理解这个工程的运行流程,这样我们才能在项目开发中得心应手。
1) HAL 库关键文件介绍如下表 所示:
2)
stm32f7xx_it.c/stm32f7xx_it.h 文件
这两个文件非常简单,也非常好理解。stm32f7xx_it.h 中主要是一些中断服务函数的申明。
stm32f7xx_it.h 中是这些中断服务函数定义,而这些函数定义除了 Systick 中断服务函数
SysTick_Handler 外基本都是空函数,没有任何控制逻辑。一般情况下,我们可以去掉这两
个文件,然后把中断服务函数写在工程中的任何一个可见文件中。
3) stm32f7xx.h 头文件
头文件 stm32f7xx.h 内容看似非常少,却非常重要,它是所有 stm32f7 系列的顶层头文件。
使用 STM32F7 任何型号的芯片,都需要包含这个头文件。同时,因为 stm32f7 系列芯片型
号非常多,ST 为每种芯片型号定义了一个特有的片上外设访问层头文件,比如 STM32F767
系列,ST 定义了一个头文件 stm32f767xx.h,然后 stm32f7xx.h 顶层头文件会根据工程芯片
型号,来选择包含对应芯片的片上外设访问层头文件。我们可以打开 stm32f7xx.h 头文件可
以看到,里面有如下几行代码:
#if defined(STM32F756xx)
#include "STM32F756xx.h"
…
#elif defined(stm32f767xx)
#include "stm32f767xx.h"
…
#else
#error "Please select first the target STM32F7xx device used in your application"
#endif
这几行代码非常好理解,我们以 stm3f767 为例,如果定义了宏定义标识符 STM32F767xx,
那么头文件 stm32f7xx.h 将会包含头文件 stm32f767xx.h。实际上,在我们上一节新建工程
的时候,我们在 C/C++选项卡里面输入的全局宏定义标识符中就包含了标识符 STM32F767xx
(请参考图 )。所以头文件 stm32f767xx.h 一定会被整个工程所引用。
4) stm32f767xx.h 头文件
根据前面的讲解,stm32f767xx.h 是 stm32f767 系列芯片通用的片上外设访问层头文件,只
要我们进行 STM32F767 开发,就必然要使用到该文件。打开该文件我们可以看到里面主要
是一些结构体和宏定义标识符。这个文件的主要作用是寄存器定义声明以及封装内存操作。
在后面寄存器地址名称映射分析小节我们会给大家详细讲解。
5)
system_stm32f7xx.c/system_stm32f7xx.h 文件
头文件system_stm32f7xx.h和源文件system_stm32f7xx.c主要是声明和定义了系统初始化函
数 SystemInit 以及系统时钟更新函数 SystemCoreClockUpdate。SystemInit 函数的作用是进行
时钟系统的一些初始化操作以及中断向量表偏移地址设置,但它并没有设置具体的时钟值,
这是与标准库的最大区别,在使用标准库的时候,SystemInit 函数会帮我们配置好系统时钟
配置相关的各个寄存器。在启动文件 startup_stm32f767xx.s 中会设置系统复位后,直接调
用 SystemInit 函数进行系统初始化。SystemCoreClockUpdate 函数是在系统时钟配置进行修
改后,调用这个函数来更新全局变量 SystemCoreClock 的值,变量 SystemCoreClock 是一个
全局变量,开放这个变量可以方便我们在用户代码中直接使用这个变量来进行一些时钟运
算。
6) stm32f7xx_hal_msp.c 文件
MSP,全称为 MCU support package,关于怎么理解 MSP,我们后面在讲解程序运行流程的时
候会给大家举例详细讲解,这里大家只需要知道,函数名字中带有 MspInit 的函数,它们
的作用是进行 MCU 级别硬件初始化设置,并且它们通常会被上一层的初始化函数所调用,
这样做的目的是为了把MCU相关的硬件初始化剥夺出来,方便用户代码在不同型号的MCU
上移植。stm32f7xx_hal_msp.c 文件定义了两个函数 HAL_MspInit 和 HAL_MspDeInit。这两个
函数分别被文件 stm32f7xx_hal.c 中的 HAL_Init 和 HAL_DeInit 所调用。HAL_MspInit 函数的
主要作用是进行 MCU 相关的硬件初始化操作。例如我们要初始化某些硬件,我们可以硬
件相关的初始化配置写在 HAL_MspDeinit 函数中。这样的话,在系统启动后调用了 HAL_Init
之 后 , 会 自 动 调 用 硬 件 初 始 化 函 数 。 实 际 上 , 我 们 在 工 程 模 板 中 直 接 删 掉
stm32f7xx_hal_msp.c 文件也不会对程序运行产生任何影响。对于这个文件存在的意义,我
们在后面讲解完程序运行流程之后,大家会有更加清晰的理解。
7) startup_stm32f767xx.s 启动文件
STM32 系列所有芯片工程都会有一个.s 启动文件。对于不同型号的 stm32 芯片启动文件也
是不一样的。我们的开发板是 STM32F767 系列,所以我们需要使用与之对应的启动文件
startup_stm32f767xx.s。启动文件的作用主要是进行堆栈的初始化,中断向量表以及中断函
数定义等。启动文件有一个很重要的作用就是系统复位后引导进入 main 函数。打开启动文
件 startup_stm32f767xx.s,可以看到下面几行代码:
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
Reset_Handler 在我们系统启动的时候会执行,这几行代码的作用是在系统启动之后,首先
调用 SystemInit 函数进行系统初始化,然后引导进入 main 函数执行用户代码。
接下来我们看看 HAL 库工程模板中各个文件之间的包含关系,如下图 所示:
从上面工程文件包含关系图可以看出,顶层头文件 stm32f7xx.h 直接或间接包含了其他所
有工程必要头文件,所以在我们的用户代码中,我们只需要包含顶层头文件 stm32f7xx.h 即可。
这里我们还需要说明一下,在我们 ALIENTEK 提供的 SYSTEM 文件夹内部的 sys.h 头文件中,
我们默认包含了 stm32f7xx.h 头文件,所以在我们用户代码中,只需要包含 sys.h 头文件即可,
当然也可以直接包含顶层头文件 stm32f7xx.h。关于工程模板中关键文件内容我们就给大家介绍
到这里。
HAL 库中__weak 修饰符讲解
在 HAL 库中,很多回调函数前面使用__weak 修饰符,这里我们有必要给大家讲解__weak
修饰符的作用。
weak 顾名思义是“弱”的意思,所以如果函数名称前面加上__weak 修饰符,我们一般称
这个函数为“弱函数”。加上了__weak 修饰符的函数,用户可以在用户文件中重新定义一个同
名函数,最终编译器编译的时候,会选择用户定义的函数,如果用户没有重新定义这个函数,
那么编译器就会执行__weak 声明的函数,并且编译器不会报错。
这里我给大家举个例子来加深大家的理解。比如我们打开工程模板,找到并打开文件
stm32f7xx_hal.c 文件,里面定义了一个函数 HAL_MspInit,定义如下:
__weak void HAL_MspInit(void)
{
}
大家可以看出,HAL_MspInit 函数前面有加修饰符__weak,是一个空函数,没有任何控制逻辑。
同时,在 stm32f7xx_hal.c 文件的前面有定义函数 HAL_Init,并且 HAL_Init 函数中调用了函数
HAL_MspInit。
HAL_StatusTypeDef HAL_Init(void)
{
…//此处省略部分代码
HAL_MspInit();
return HAL_OK;
}
如果我们没有在工程中其他地方重新定义 HAL_MspInit()函数,那么 HAL_Init 初始化函数执行
的时候,会默认执行 stm32f7xx_hal.c 文件中定义的 HAL_MspInit 函数,而这个函数没有任何控
制逻辑。如果用户在工程中重新定义函数 HAL_MspInit,那么调用 HAL_Init 之后,会执行用
户自己定义的 HAL_MspInit 函数而不会执行 stm32f7xx_hal.c 默认定义的函数。也就是说,表面
上我们看到函数 HAL_MspInit 被定义了两次,但是因为有一次定义是弱函数,使用了__weak
修饰符,所以编译器不会报错。
__weak 在回调函数的时候经常用到。这样的好处是,系统默认定义了一个空的回调函数,
保证编译器不会报错。同时,如果用户自己要定义用户回调函数,那么只需要重新定义即可,
不需要考虑函数重复定义的问题,使用非常方便,在 HAL 库中__weak 关键字被广泛使用。
Msp 回调函数执行过程解读
大家先打开我们前面新建的工程模板,搜索 MspInit 字符串可以发现,在我们的工程模板
文件中,有 多个文件定义或者调用了函数名字中包含 MspInit 字符串的函数,而且函数名字
基本遵循 HAL_PPP_MspInit 格式(PPP 代表任意外设)。那么这些函数是怎么被程序调用,又
是什么作用呢?下面我们以串口为例进行讲解。
大家打开我们的工程模板 SYSTEM 分组下面的 usart.c 文件可以看到,内部我们定义了两
个函数 uart_init 和 HAL_UART_MspInit。我们先来大致看看这两个函数的定义(基于篇幅考虑
我们省略部分非关键代码行):
void uart_init(u32 bound)
{
UART1_Handler.Instance=USART1;
//USART1
UART1_Handler.Init.BaudRate=bound;
//波特率
…//此处省略部分串口 1 参数设置代码
UART1_Handler.Init.Mode=UART_MODE_TX_RX;
//收发模式
HAL_UART_Init(&UART1_Handler); //HAL_UART_Init()会使能 UART1
}
//UART 底层初始化,时钟使能,引脚配置,中断配置
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
…//此处省略部分代码
GPIO_Initure.Pin=GPIO_PIN_9;
//PA9
GPIO_Initure.Mode=GPIO_MODE_AF_PP;
//复用推挽输出
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PA9
GPIO_Initure.Pin=GPIO_PIN_10;
//PA10
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PA10
…//此处省略部分代码
}
用户函数 uart_init 主要作用是设置串口 1 相关参数,包括波特率,停止位,奇偶校验位等,
并且最终是通过调用 HAL_UART_Init 函数进行参数设置。而函数 HAL_UART_MspInit 则主要
进行串口 GPIO 引脚初始化设置。接下来我们打开 usart_init 函数内部调用的 UART 初始化函数
HAL_UART_Init 可以看到代码如下:
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
{
…//此处省略部分代码
if(huart->State == HAL_UART_STATE_RESET)//如果串口没有进行过初始化
{
huart->Lock = HAL_UNLOCKED;
HAL_UART_MspInit(huart);
}
…//此处省略部分代码
return HAL_OK;
}
在函数 HAL_UART_Init 内部,通过判断逻辑判断如果串口还没有进行初始话,那么会调
用函数 HAL_UART_MspInit 进 行 相 关 初 始 化 设 置 。 同 时 , 我 们 可 以 看 到 , 在 文 件
stm32f7xx_hal_uart.c 内部,有定义一个弱函数 HAL_UART_MspInit,内容如下:
__weak void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
UNUSED(huart);
}
这里定义的弱函数 HAL_UART_MspInit 是一个空函数,没有任何实际的控制逻辑。根据前面的
讲解可知,__weak 修饰符定义的弱函数如果用户自己重新定义了这个函数,那么会优先执行用
户定义函数。所以,实际上在函数 HAL_UART_Init 内部调用的 HAL_UART_MspInit()函数,最
终执行的是用户在 usart.c 中自定义的 HAL_UART_MspInit()函数。
那 么 整 个 串 口 初 始 化 的 过 程 为 : 用 户 函 数 usart_init? HAL_UART_Init?
HAL_UART_MspInit。学到这里有同学会问,为什么串口相关初始化不在 HAL_UART_Init 函数
内部一次初始化而还要调用函数 HAL_UART_MspInit()呢?这实际就是 HAL 库的一个优点,它
通过开放一个回调函数 HAL_UART_MspInit(),让用户自己去编写与串口相关的 MCU 级别的
硬件初始化,而与 MCU 无关的串口参数相关的通用配置则放在 HAL_UART_Init。
我们要初始化一个串口,首先要设置和 MCU 无关的东西,例如波特率,奇偶校验,停止
位等,这些参数设置和 MCU 没有任何关系,可以使用 STM32F1,也可以是 STM32F2/F3/F4/F7
上的串口。而一个串口设备它需要一个 MCU 来承载,例如用 STM32F7 来做承载,PA9 做为发
送,PA10 做为接收,MSP 就是要初始化 STM32F7 的 PA9,PA10,配置这两个引脚。所以 HAL
驱动方式的初始化流程就是:HAL_USART_Init()—>HAL_USART_MspInit() ,先初始化与 MCU
无关的串口协议,再初始化与 MCU 相关的串口引脚。在 STM32 的 HAL 驱动中
HAL_PPP_MspInit()作为回调,被 HAL_PPP_Init()函数所调用。当我们需要移植程序到 STM32F1
平台的时候,我们只需要修改 HAL_PPP_MspInit 函数内容而不需要修改 HAL_PPP_Init 入口参
数内容。
在 STM32 的 HAL 库中,大部分外设都有回调函数 HAL_MspInit,通过对本小节学习,大
家对这些回调函数的作用和调用过程会非常熟悉,这里我们就不做一一列举。
程序执行流程图
经过前面的讲解,大家对工程模板以及关键文件有了比较详细的了解。接下来我们看看程
序执行流程如下图 .1 所示:
从该流程图可以非常清晰的理解整个程序执行流程,这里我们略微讲解一下。启动文件
startup_stm32f767xx.s 中 Reset_Handler 部分会引导先执行 SystemInit 函数,然后再进入 main 函
数。在 main 函数内部,一般情况下,我们会把 HAL 初始化函数 HAL_Init 放在最开头部分,然
后再进行时钟初始化设置。这些设置完成之后,接下来便是调用外设初始化函数 HAL_PPP_Init
进行外设参数初始化设置,同时重写回调函数 HAL_PPP_MspInit 进行外设 MCU 相关的参数设
置。最后编写我们的控制逻辑。关于程序执行流程我们就给大家介绍到这里。
程序下载与调试
上一节,我们学会了如何在 MDK 下创建 STM32F7 工程。本节,我们将向读者介绍 STM32F7
的代码下载以及调试。这里的调试包括了软件仿真和硬件调试(在线调试)。通过本章的学习,
你将了解到:1、STM32F7 程序下载;2、利用 ST-LINK 对 STM32F7 进行下载与在线调试。
这里大家要注意,为了让大家能够更好的学习调试,我们将 小节新建的工程模板中的
main.c 文件内容进行了简单的修改。修改后的工程模板在光盘目录:\4,程序源码\2,标准例
程-库函数版本\实验 Template 工程模板-调试章节使用。本小节下载和调试的工程请参考该
工程模板。
STM32F7 程序下载
给 STM32F767 下载代码。由于 STM32F7 暂时没有比较好的串口下载软件,所以,我们一
般通过仿真器下载,接下来,我们将介绍如何使用 ST LINK V2,结合 MDK,来给 STM32F7
下载代码。
ST LINK 支持 JTAG 和 SWD 两种通信接口,同时 STM32F767 也支持 JTAG 和 SWD。所
以,我们有 2 种方式可以用来下载代码,JTAG 模式,占用的 IO 线比较多,而 SWD 模式则占
用的 IO 线很少,只需要两根即可。所以,我们一般选择 SW 模式来给 STM32F767 下载代码。
首先,我们需要安装 ST LINK 的驱动。驱动安装,请大家参考:光盘?6,软件资料?1,
软件?ST LINK 驱动及教程 文件夹里面的《STLINK 调试补充教程.pdf》自行安装。
在安装了 ST LINK 的驱动之后,我们接上 ST LINK,并用灰排线连接 ST LINK 和开发板
JTAG 接口,打开之前 节新建的工程,点击 ,打开 Options for Target 选项卡,在 Debug 栏
选择仿真工具为 ST-Link Debugger,如图 所示:
上图中我们还勾选了 Run to main(),该选项选中后,只要点击仿真就会直接运行到 main 函
数,如果没选择这个选项,则会先执行 startup_stm32f767xx.s 文件的 Reset_Handler,再跳到 main
函数。
然后我们点击 Settings,设置 ST LINK 的一些参数,如图 所示:
图 中,我们使用 STLINK 的 SW 模式调试,因为我们 JTAG 需要占用比 SW 模式多
很多的 IO 口,而在开发板上这些 IO 口可能被其他外设用到,可能造成部分外设无法使用。所
以,我们建议大家在下载/调试代码的时候,一定要选择 SW 模式。Max Clock 我们设置为最大:
4Mhz(需要更新固件,否则最大只能到 1.8Mhz),这里,如果你的 USB 数据线比较差,那么
可能会出问题,此时,你可以通过降低这里的速率来试试。
单击 OK,完成此部分设置,接下来我们还需要在 Utilities 选项卡里面设置下载时的目标编
程器,如图 所示:
图 中,我们直接勾选 Use Debug Driver,即和调试一样,选择 ST LINK 来给目标器
件的 FLASH 编程,然后点击 Settings,设置如图 所示:
这里 MDK5 会根据我们新建工程时选择的目标器件,自动设置 flash 算法。我们使用的是
STM32F767IGT6,FLASH 容量为 1M 字节,所以 Programming Algorithm 里面默认会有 1M 型
号的 STM32F7xx FLASH 算法。MDK 默认选择的是 AXIM 总线访问的 FLASH 算法(起始地址
为:0X0800 ),为了方便大家使用,我们将 ITCM 总线访问的 FLASH 算法(起始地址为:
0X0020 )也添加进来,由 MDK 自动选择下载算法(实际上是根据 Target 选项卡的 on-chip
IROM 地址范围设置来选择的,默认为 0X0800 )。
特别提醒:这里的 1M flash 算法,不仅仅针对 1M 容量的 STM32F767,对于小于 1M FLASH
的型号,也是采用这个 flash 算法的。最后,选中 Reset and Run 选项,以实现在编程后自动运
行,其他默认设置即可。设置完成之后,如图 所示。
在设置完之后,点击 OK,然后再点击 OK,回到 IDE 界面,编译一下工程。然后点击:
(下载按钮),就可以下载代码到 STM32F767 上面了,如图 所示:
下载完成后,在 Build Output 窗口,会提示 Programming Down,Application running…,如
图 所示:
下载完后,会自动运行我们刚刚下载的代码(因为我们勾选了 Reset and run,见图 ),
接下来我们就可以打开串口调试助手,来验证是否收到了 STM32F767 串口发送出来的数据。
我们在开发板的 USB_232 处插入 USB 线,并接上电脑,如果之前没有安装 CH340G 的驱
动(如果已经安装过了驱动,则应该能在设备管理器里面看到 USB 串口,如果不能则要先卸载
之前的驱动,卸载完后重启电脑,再重新安装我们提供的驱动),则需要先安装 CH340G 的驱
动,找到光盘?软件资料?软件 文件夹下的 CH340 驱动,安装该驱动,如图 所示:
在驱动安装成功之后,拔掉 USB 线,然后重新插入电脑,此时电脑就会自动给其安装驱动
了。在安装完成之后,可以在电脑的设备管理器里面找到 USB 串口(如果找不到,则重启下电
脑),如图 所示:
在图 中可以看到,我们的 USB 串口被识别为 COM3,这里需要注意的是:不同电
脑可能不一样,你的可能是 COM4、COM5 等,但是 USB-SERIAL CH340,这个一定是一样的。
如果没找到 USB 串口,则有可能是你安装有误,或者系统不兼容。
在安装完 USB 串口驱动之后,我们就可以开始验证了(注意,开发板的 B0 必须接 GND,
否则将不会运行用户下载的代码!!),打开串口调试助手(XCOM V2.0,在光盘?6,软件
资料?软件?串口调试助手里面)选择COM3(得根据你的实际情况选择),设置波特率为,
会发现从 ALIENTEK 阿波罗 STM32F767 开发板发回来的信息,如图 所示:
接收到的数据和我们期望的是一样的,证明程序没有问题。至此,说明我们下载代码成功
了,并且从硬件上验证了我们代码的正确性。
STM32F7 在线调试
上一节,我们介绍了如何利用 ST LINK给 STM32下载代码,并在 ALIENTEK阿波罗STM32
开发板上验证了我们程序的正确性。这个代码比较简单,所以不需要硬件调试,我们直接就一
次成功了。可是,如果你的代码工程比较大,难免存在一些 bug,这时,就有必要通过在线调
试来解决问题了。
利用调试工具,比如 JLINK(必须是 JLINK V9 或者以上版本!!)、ULINK、STLINK 等,
可以实时跟踪程序,从而找到你程序中的 bug,使你的开发事半功倍。这里我们以 ST 公司自
家的仿真器:ST LINK V2 为例,说说如何在线调试 STM32F767。
通过上一节的学习,我们知道 ST LINK 支持 JTAG 和 SWD 两种通信方式,而且 SWD 方
式具有占用 IO 少的优点(2 个 IO 口),所以,我们一般选择 SWD 方式进行调试。MDK 里面,
对 ST LINK 的相关设置,同上一节完全一样,请参考上一节的相关介绍。
在 MDK 的 IDE 界面,编译一下工程。然后点击:
(开始/停止仿真按钮),开始仿真
(如果开发板的代码没被更新过,则会先更新代码(即下载代码),再仿真。特别注意:开发板
上的 B0 脚要接 GND,否则不会运行我们下载的代码!!),如图 所示:
因为我们之前勾选了 Run to main()选项,所以,程序直接就运行到了 main 函数的入口处。
另外,此时 MDK 多出了一个工具条,这就是 Debug 工具条,这个工具条在我们仿真的时候是
非常有用的,下面简单介绍一下 Debug 工具条相关按钮的功能。Debug 工具条部分按钮的功能
如图 所示:
复位:其功能等同于硬件上按复位按钮。相当于实现了一次硬复位。按下该按钮之后,代
码会重新从头开始执行。
执行到断点处:该按钮用来快速执行到断点处,有时候你并不需要观看每步是怎么执行的,
而是想快速的执行到程序的某个地方看结果,这个按钮就可以实现这样的功能,前提是你在查
看的地方设置了断点停止运行:此按钮在程序一直执行的时候会变为有效,通过按该按钮,就可以使程序停止
下来,进入到单步调试状态。
执行进去:该按钮用来实现执行到某个函数里面去的功能,在没有函数的情况下,是等同
于执行过去按钮的。
执行过去:在碰到有函数的地方,通过该按钮就可以单步执行过这个函数,而不进入这个
函数单步执行。
执行出去:该按钮是在进入了函数单步调试的时候,有时候你可能不必再执行该函数的剩
余部分了,通过该按钮就直接一步执行完函数余下的部分,并跳出函数,回到函数被调用的位
置。
执行到光标处:该按钮可以迅速的使程序运行到光标处,其实是挺像执行到断点处按钮功
能,但是两者是有区别的,断点可以有多个,但是光标所在处只有一个。
汇编窗口:通过该按钮,就可以查看汇编代码,这对分析程序很有用。
堆栈局部变量窗口:通过该按钮,显示 Call Stack+Locals 窗口,显示当前函数的局部变量
及其值,方便查看。
观察窗口:MDK5 提供 2 个观察窗口(下拉选择),该按钮按下,会弹出一个显示变量的
窗口,输入你所想要观察的变量/表达式,即可查看其值,是很常用的一个调试窗口。
内存查看窗口:MDK5 提供 4 个内存查看窗口(下拉选择),该按钮按下,会弹出一个内
存查看窗口,可以在里面输入你要查看的内存地址,然后观察这一片内存的变化情况。是很常
用的一个调试窗口
串口打印窗口:MDK5 提供 4 个串口打印窗口(下拉选择),该按钮按下,会弹出一个类
似串口调试助手界面的窗口,用来显示从串口打印出来的内容。
逻辑分析窗口:该图标下面有 3 个选项(下拉选择),我们一般用第一个,也就是逻辑分析
窗口(Logic Analyzer),点击即可调出该窗口,通过 SETUP 按钮新建一些 IO 口,就可以观察这
些 IO 口的电平变化情况,以多种形式显示出来,比较直观。
系统查看窗口:该按钮可以提供各种外设寄存器的查看窗口(通过下拉选择),选择对应外
设,即可调出该外设的相关寄存器表,并显示这些寄存器的值,方便查看设置的是否正确。
Debug 工具条上的其他几个按钮用的比较少,我们这里就不介绍了。以上介绍的是比较常
用的,当然也不是每次都用得着这么多,具体看你程序调试的时候有没有必要观看这些东西,
来决定要不要看。
特别注意:串口打印窗口和逻辑分析窗口仅在软件仿真的时候可用,而 MDK5 对
STM32F767 的软件仿真,基本上不支持(故本教程直接没有对软件仿真进行介绍了),所以,
基本上这两个窗口用不着。但是对 STM32F1 的软件仿真,MDK5 是支持的,在 F1 开发的时候,
可以用到。
这样,我们在上面的仿真界面里面调出:堆栈局部变量窗口。如图 所示:
我们把光标放到 main.c 的第 行左侧的灰色区域,然后按下鼠标左键,即可放置一个断
点(红色的实心点,也可以通过鼠标右键弹出菜单来加入),再次单击则取消。
然后点击
,执行到该断点处,如图 所示
现在先不忙着往下执行,点击菜单栏的 Peripherals?System Viewer?USART?USART1。
可以看到,有很多外设可以查看,这里我们查看的是串口 1 的情况。如图 所示:
单击 USART1 后会在 IDE 右侧出现一个如图 (a)所示的界面:
图 (a)是 STM32 的串口 1 的默认设置状态,从中可以看到所有与串口相关的寄存
器全部在这上面表示出来了。我们接着单击一下
,执行完串口初始化函数,得到了如图
(b)所示的串口信息。大家可以对比一下这两个图的区别,就知道在 uart_init);这个函
数里面大概执行了哪些操作。
通过图 (b),我们可以查看串口 1 的各个寄存器设置状态,从而判断我们写的代码
是否有问题,只有这里的设置正确了之后,才有可能在硬件上正确的执行。同样这样的方法也
可以适用于很多其他外设,这个读者慢慢体会吧!这一方法不论是在排错还是在编写代码的时
候,都是非常有用的。
此时,我们先打开串口调试助手(XCOM V2.0,在光盘?6,软件资料?软件?串口调试
助手里面)设置好串口号和波特率,然后我们继续单击
按钮,一步步执行,此时在堆栈局部
变量窗口可以看到 t 的值变化,同时在串口调试助手中,也可看到打印出 t 的值,如图
和 所示:
关于 STM32F767 的硬件调试,我们就介绍到这里,这仅仅是一个简单的 demo 演示,在实际使
用中,硬件调试更是大有用处,所以大家一定要好好掌握。
MDK5 使用技巧
通过前面的学习,我们已经了解了如何在 MDK5 里面建立属于自己的工程。下面,我们将
向大家介绍 MDK5 软件的一些使用技巧,这些技巧在代码编辑和编写方面会非常有用,希望大
家好好掌握,最好实际操作一下,加深印象。
文本美化
文本美化,主要是设置一些关键字、注释、数字等的颜色和字体。前面我们在介绍 MDK5
新建工程的时候看到界面如图 所示,这是 MDK 默认的设置,可以看到其中的关键字和
注释等字体的颜色不是很漂亮,而 MDK 提供了我们自定义字体颜色的功能。我们可以在工具
条上点击
(配置对话框)弹出如图 所示界面:
在该对话框中,先设置 Encoding 为:Chinese GB2312(Simplified),然后设置 Tab size 为:4。
以更好的支持简体中文(否则,拷贝到其他地方的时候,中文可能是一堆的问号),同时 TAB
间隔设置为 4 个单位。然后,选择:Colors&Fonts 选项卡,在该选项卡内,我们就可以设置自
己的代码的子体和颜色了。由于我们使用的是 C 语言,故在 Window 下面选择:C/C++ Editor Files
在右边就可以看到相应的元素了。如图 示:
然后点击各个元素修改为你喜欢的颜色(注意双击,且有时候可能需要设置多次才生效,
MDK 的 bug),当然也可以在 Font 栏设置你字体的类型,以及字体的大小等。设置成之后,点
击 OK,就可以在主界面看到你所修改后的结果,例如我修改后的代码显示效果如图 示:
这就比开始的效果好看一些了。字体大小,则可以直接按住:ctrl+鼠标滚轮,进行放大或
者缩小,或者也可以在刚刚的配置界面设置字体大小。
细心的读者可能会发现,上面的代码里面有一个 u8,还是黑色的,这是一个用户自定义的
关键字,为什么不显示蓝色(假定刚刚已经设置了用户自定义关键字颜色为蓝色)呢?这就又
要回到我们刚刚的配置对话框了,单这次我们要选择 User Keywords 选项卡,同样选择:C/C++
Editor Files,在右边的 User Keywords 对话框下面输入你自己定义的关键字,如图 示:
图 中我定义了 u8、u16、u32 等 3 个关键字,这样在以后的代码编辑里面只要出现
这三个关键字,肯定就会变成蓝色。点击 OK,再回到主界面,可以看到 u8 变成了蓝色了,如
图 示:
其实这个编辑配置对话框里面,还可以对其他很多功能进行设置,比如动态语法检测等,
我们将 节介绍。
语法检测&代码提示
MDK5 支持代码提示与动态语法检测功能,使得 MDK 的编辑器越来越好用了,这里我们
简单说一下如何设置,同样,点击
,打开配置对话框,选择 Text Completion 选项卡,如图
所示:
Strut/Class Members,用于开启结构体/类成员提示功能。
Function Parameters,用于开启函数参数提示功能。
Symbols after xx characters,用于开启代码提示功能,即在输入多少个字符以后,提示匹配
的内容(比如函数名字、结构体名字、变量名字等),这里默认设置 3 个字符以后,就开始提示。
如图 所示:
Dynamic Syntax Checking,则用于开启动态语法检测,比如编写的代码存在语法错误的时
候,会在对应行前面出现 图标,如出现警告,则会出现
图标,将鼠标光标放图标上面,则
会提示产生的错误/警告的原因,如图 所示
这几个功能,对我们编写代码很有帮助,可以加快代码编写速度,并且及时发现各种问题。
不过这里要提醒大家,语法动态检测这个功能,有的时候会误报(比如 sys.c 里面,就有误报),
大家可以不用理会,只要能编译通过(0 错误,0 警告),这样的语法误报,一般直接忽略即可。
代码编辑技巧
这里给大家介绍几个我常用的技巧,这些小技巧能给我们的代码编辑带来很大的方便,相
信对你的代码编写一定会有所帮助。
1)TAB 键的妙用
首先要介绍的就是 TAB 键的使用,这个键在很多编译器里面都是用来空位的,每按一下移
空几个位,如果你经常编写程序,对这个键一定再熟悉不过了。MDK 的 TAB 键还可以支持块
操作:也就是可以让一片代码整体右移固定的几个位,也可以通过 SHIFT+TAB 键整体左移固
定的几个位。
假设我们前面的串口 1 中断响应函数如图 所示:
图 中这样的代码大家肯定不会喜欢,这还只是短短的 来行代码,如果你的代码
有几千行,全部是这个样子,不头大才怪。看到这样的代码我们就可以通过 TAB 键的妙用来快
速修改为比较规范的代码格式。
选中一块然后按 TAB 键,你可以看到整块代码都跟着右移了一定距离,如图 所示:
接下来我们就是要多选几次,然后多按几次 TAB 键就可以达到迅速使代码规范化的目的,
最终效果如图 所示
图 中的代码相对于图 中的要好看多了,经过这样的整理之后,整个代码一下就变得有条理多了,看起来很舒服。
2) 快速定位函数/变量被定义的地方
上一节,我们介绍了 TAB 键的功能,接下来我们介绍一下如何快速查看一个函数或者变量
所定义的地方。
大家在调试代码或编写代码的时候,一定有想看看某个函数是在那个地方定义的,具体里
面的内容是怎么样的,也可能想看看某个变量或数组是在哪个地方定义的等。尤其在调试代码
或者看别人代码的时候,如果编译器没有快速定位的功能的时候,你只能慢慢的自己找,代码
量比较少还好,如果代码量一大,那就郁闷了,有时候要花很久的时间来找这个函数到底在哪
里。型号 MDK 提供了这样的快速定位的功能。只要你把光标放到这个函数/变量(xxx)的上
面(xxx 为你想要查看的函数或变量的名字),然后右键,弹出如图 所示的菜单栏 :
在图 中,我们找到 Go to Definition Of‘STM32_Clock_Init’ 这个地方,然后单击
左键就可以快速跳到 STM32_Clock_Init 函数的定义处(注意要先在 Options for Target 的 Output
选项卡里面勾选 Browse Information 选项,再编译,再定位,否则无法定位!)。如图 所
示:
对于变量,我们也可以按这样的操作快速来定位这个变量被定义的地方,大大缩短了你查
找代码的时间很多时候,我们利用 Go to Definition 看完函数/变量的定义后,又想返回之前的代码继续看,
此时我们可以通过 IDE 上的
按钮(Back to previous position)快速的返回之前的位置,这个
按钮非常好用!
3) 快速注释与快速消注释
接下来,我们介绍一下快速注释与快速消注释的方法。在调试代码的时候,你可能会想注
释某一片的代码,来看看执行的情况,MDK 提供了这样的快速注释/消注释块代码的功能。也
是通过右键实现的。这个操作比较简单,就是先选中你要注释的代码区,然后右键,选择
Advanced?Comment Selection 就可以了。
以 Stm32_Clock_Init 函数为例,比如我要注释掉下图中所选中区域的代码,如图 所
示:
图 选中要注释的区域
我们只要在选中了之后,选择右键,再选择 Advanced?Comment Selection 就可以把这段代
码注释掉了。执行这个操作以后的结果如图 所示:
这样就快速的注释掉了一片代码,而在某些时候,我们又希望这段注释的代码能快速的取消注释,MDK 也提供了这个功能。与注释类似,先选中被注释掉的地方,然后通过右键
?Advanced,不过这里选择的是 Uncomment Selection。
其他小技巧
除了前面介绍的几个比较常用的技巧,这里还介绍几个其他的小技巧,希望能让你的代码
编写如虎添翼。
第一个是快速打开头文件。在将光标放到要打开的引用头文件上,然后右键选择 Open
Document“XXX”,就可以快速打开这个文件了(XXX 是你要打开的头文件名字)。如图
所示:
第二个小技巧是查找替换功能。这个和 WORD 等很多文档操作的替换功能是差不多的,
在 MDK 里面查找替换的快捷键是“CTRL+H”,只要你按下该按钮就会调出如图 所示界
面:
这个替换的功能在有的时候是很有用的,它的用法与其他编辑工具或编译器的差不多,相
信各位都不陌生了,这里就不啰嗦了。
第三个小技巧是跨文件查找功能,先双击你要找的函数/变量名(这里我们还是以系统时钟初始化函数:Stm32_Clock_Init 为例),然后再点击 IDE 上面的,弹出如图 所示对话框:
点击 Find All,MDK 就会帮你找出所有含有 Stm32_Clock_Init 字段的文件并列出其所在位
置,如图 所示:
该方法可以很方便的查找各种函数/变量,而且可以限定搜索范围(比如只查找.c 文件和.h
文件等),是非常实用的一个技巧。