1)实验平台:ALIENTEK NANO STM32F411 V1开发板
2)摘自《正点原子STM32F4 开发指南(HAL 库版》关注官方微信号公众号,获取更多资料:正点原子
![3bbe93e798b3956493ed43e0b3bb8a2a.png](https://i-blog.csdnimg.cn/blog_migrate/8760cf85ec5f714fb6db3381ef6600b5.jpeg)
3.3.2.3 Msp 回调函数执行过程解读
大家先打开我们前面新建的工程模板,搜索 MspInit 字符串可以发现,在我们的工程模板文件中,有 70 多个文件定义或者调用了函数名字中包含 MspInit 字符串的函数,而且函数名字
基本基本遵循 HAL_PPP_MspInit 格式(PPP 代表任意外设)。那么这些函数是怎么被程序调用,
又是什么作用呢?下面我们以串口为例进行讲解。
大家打开我们的工程模板 SYSTEM 分组下面的 usart.c 文件可以看到,内部我们定义了两
个函数 uart_init 和 HAL_UART_MspInit。我们先来大致看看这两个函数的定义(基于篇幅考虑
我们省略部分非关键代码行):
void uart_init(u32 bound)
{
//UART 初始化设置
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
...//此处省略部分串口 1 参数设置代码
}
//UART 底层初始化,时钟使能,引脚配置,中断配置
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
...//此处省略部分代码
GPIO_Initure.Pin=GPIO_PIN_9;
//PA9
GPIO_Initure.Mode=GPIO_MODE_AF_PP;
//复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP;
//上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
//初始化 PA9
GPIO_Initure.Pin=GPIO_PIN_10;
//PA10
GPIO_Initure.Mode=GPIO_MODE_AF_INPUT;//模式要设置为复用输入模式!
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
//初始化 PA10
...//此处省略部分代码
}
用户函数 uart_init 主要作用是设置串口 1 相关参数,包括波特率,停止位,奇偶校验位等,
并且最终是通过调用 HAL_UART_Init 函数进行参数设置。而函数 HAL_UART_MspInit 则主要
进行串口 GPIO 引脚初始化设置。接下来我们打开 usart_init 函数内部调用的 UART 初始化函数
HAL_UART_Init 可以看到代码如下:
HAL_StatusTypeDefHAL_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 进 行 相 关 初 始 化 设 置 。 同 时 , 我 们 可 以 看 到 , 在 文 件
stm32f4xx_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 的承载,例如用 STM32F4 来做承载,PA9 做为发
送,PA10 做为接收,MSP 就是要初始化 STM32F4 的 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,通过对本小节学习,大
家对这些回调函数的作用和调用过程会非常熟悉,这里我们就不做一一列举。
3.3.2.4 程序执行流程图
经过前面的讲解,大家对工程模板以及关键文件有了比较详细的了解。接下来我们看看程
序执行流程如下图 3.3.2.4.1 所示:
![f5bc9f322232b6c72a631ace09984754.png](https://i-blog.csdnimg.cn/blog_migrate/2d0d5a7df8ec8cfaff38ccdefcf0577e.jpeg)
图 3.3.2.4.1 程序执行流程
从该流程图可以非常清晰的理解整个程序执行流程,这里我们略微讲解一下。启动文件
startup_stm32f411xe.s 中 Rest_Handler 部分会引导先执行 SystemInit 函数,然后再进入 main 函
数。在 main 函数内部,一般情况下,我们会把 HAL 初始化函数 HAL_Init 放在最开头部分,然
后再进行时钟初始化设置。这些设置完成之后,接下来便是调用外设初始化函数 HAL_PPP_Init
进行外设参数初始化设置,同时重写回调函数 HAL_PPP_MspInit 进行外设 MCU 相关的参数设
置。最后编写我们的控制逻辑。关于程序执行流程我们就给大家介绍到这里。
3.4 程序下载与调试
上一节,我们学会了如何在 MDK 下创建 STM32F4 工程。本节,我们将向读者介绍 STM32F4
的代码下载以及调试。包括了程序下载、软件仿真和硬件调试(在线调试)。通过本章的学习,
你将了解到:1、STM32F4 程序下载;2、STM32F4 硬件调试;
3.4.1 ST_LINK 仿真器驱动安装
STM32F411 的程序下载有多种方法:USB、串口、JTAG、SWD 等,这几种方式,都可以
用来给 STM32F4 下载代码。NANO 板板载了 ST_LINK V2.1 版的仿真器,不用外接仿真器即
可下载仿真使用,十分的方便。
本节,我们将向大家介绍,板载的 ST_LINK 仿真器驱动的安装。
仿真器的 USB 口在 USB_JTAG 口,连接好 USB 口,首次上电,会提示安装 ST_LINK 驱
动,如图 3.4.2.1 所示:
![721c64c2f22a3167dbc704f01f62bb87.png](https://i-blog.csdnimg.cn/blog_migrate/43b905a399c8ed647f931dda56e61ae6.jpeg)
图 3.4.1.1 安装驱动
若之前电脑是没有安装过 ST_LINK 驱动,这时,会提示安装失败,如图 3.4.2.2 所示: