一、 HAL库
说到STM32的HAL库,就不得不提STM32CubeMX,其作为一个可视化的配置工具,对于开发者来说,确实大大节省了开发时间。STM32CubeMX就是以HAL库为基础的,且目前仅支持HAL库及LL库!
STM32CubeMX是ST意法半导体近几年主推的STM32芯片图形化配置工具,允许用户使用图形化向导生成C初始化代码,也大大的减轻开发工作和时间,且STM32CubeMX几乎覆盖了STM32全系列芯片。
STM32CubeL4固件包的文件结构如下图所示:
根据HAL库的命名规则,其API可以分为以下三大类:
- 初始化/反初始化函数: HAL_PPP_Init(), HAL_PPP_DeInit()
- IO 操作函数: HAL_PPP_Read(), HAL_PPP_Write(),HAL_PPP_Transmit(),
HAL_PPP_Receive() - 控制函数: HAL_PPP_Set (), HAL_PPP_Get ().
- 状态和错误: HAL_PPP_GetState (), HAL_PPP_GetError ().
- 回调函数: HAL_PPP_ProcessCpltCallback(), HAL_PPP_ErrorCallback;
HAL库最大的特点就是对底层进行了抽象。在此结构下,用户代码的处理主要分为三部分:
- 处理外设句柄: HAL库对每个外设抽象成了一个ppp_HandleTypeDef的结构体,ppp外设的所有API函数都是对ppp_HandleTypeDef结构体实例化变量的操作,每个外设/模块实例资源相互独立且都有自己的句柄,用户根据自己需求对相关外设句柄进行处理;
- 处理MSP: 用来初始化底层MCU级相关的设备,如GPIOx, Clock, DMA,
Interrupt等,该部分也是在不同MCU间移植时需要重新实现的部分; - 处理各种回调函数: 当外设或者DMA工作完成后触发中断,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用,可以在相应的回调函数中实现用户的控制逻辑。
二、CubeMX使用
1. GPIO / AFIO配置
下面先看看GPIO / AFIO端口位的基本结构(参考自《SMT32L475VE Reference manual.pdf》):
从上图来看,每个I / O端口可以自由编程,通过寄存器配置可实现多种输入、输出、复用模式,比如浮空输入、上拉输入、下拉输入、模拟输入、复用功能输入、开漏输出、推挽输出、复用功能输出等。
简单介绍下各种模式的特点:
- GP(General Purpose): 作为普通的输入 / 输出引脚使用,比如可以驱动LED、产生PWM、驱动蜂鸣器等,也可以接收来自外部的中断信号或事件;
- PP(Push Pull)Output: 推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通的时候另一个截止,可以由IC控制输出高、低电平;
- OD(Open Drain)Output: 输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行,适合于做电流型的驱动,其吸收电流的能力相对强(一般 20ma以内);
- AF(Alternate Function): 作为内置外设的输入 / 输出引脚使用,比如AF_PP复用推挽输出常用于USART /SPI外设,AF_OU复用开漏输出常用于IIC外设;
- PU(Pull Up): 内部接通上拉电阻,将不确定的信号通过一个电阻嵌位在高电平VDD;
- PD(Pull Down): 内部接通下拉电阻,将不确定的信号通过一个电阻嵌位在低电平VSS;
- Floating Input: 浮空输入(内部上拉、下拉电阻均不接通),比如用作外部按键输入;
- Analog Input: 应用ADC模拟输入,或者低功耗下省电;
下面介绍关于GPIO函数
- void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
//在stm32f4xx_hal_gpio.c文件中,用来初始化引脚的工作模式,包括具体引脚的工作速度、是否复用模式、上下拉等参数。 - void HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin); //将函数初始化之后的引脚恢复成默认的状态。
例:HAL_GPIO_DeInit(GPIOA, GPIO_PIN_1);
- GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); //读取引脚的电平状态,函数返回值为0或1。
例:State = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1);
- void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState); //写某个引脚写0或1。
例:HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1,GPIO_PIN_RESET);
- void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); //翻转某个引脚的电平状态
例:HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1);
- HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); //读管脚值并锁定,重置才能改变。
HAL_StatusTypeDef State; State = HAL_GPIO_LockPin(GPIOF, GPIO_PIN_9);
- void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin); //外部中断服务函数
例:HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);
- void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin); //中断回调函数
例:HAL_GPIO_EXTI_Callback(GPIO_Pin);
2. CubeMX配置
这里介绍使用STM32CubeMX配置工程的一般步骤:
- 工程初步建立和保存,选择MCU型号,可指定系列、封装、外设数量等条件;
- 配置(时钟树)RCC时钟源选择和时钟系统;
- 配置GPIO引脚外设功能;
- 配置NVIC系统中断;
- 生成工程代码;
- 编写用户逻辑控制代码;
下面以控制KEY_LED的工程为例,先打开CubeMX–>File–>New Project 弹出如下的界面,搜索并选择目标芯片STM32L431RC:
![](https://img-blog.csdnimg.cn/20210422111816719.png?watmark/2/text/aHR0cDovL2cuY3Nkbi5uZXQvV5bm1hbjIzMw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
下面配置RCC时钟,我的STM32L4板子使用外部高速时钟源HSE的晶振频率为8MHZ,外部高速时钟源HSE选择陶瓷晶振:
配置好了RCC时钟源及时钟引脚,接下来配置时钟树,首先是时钟源HSE的时钟频率配置为8MHZ,接下来就是时钟信号选择和时钟的分频、倍频系数配置了,STM32L431支持的最高频率80MHZ,具体的参数配置如下图所示:
下面开始配置GPIO外设的图形化配置过程,先搜索并配置目标引脚PA5 / PA6 / PA7为输出模式GPIO_Output:
- GPIO output level: High 默认高电平,low 默认低电平。
- GPIO mode: Out Push Pull 推挽输出模式,Open Drain 开漏输出。
- GPIO Pull-up/Pull-down:
如果硬件上已外部上拉或下拉中选择 No pull-up and nopull-down 既不上拉也不下拉。
如果硬件外部没有上拉,则中选择 Pull-up 内部上拉或 Pull-down 内部下拉。
下面配置中断线PC1的配置如下图所示:
- GPIO output level:
External Interrupt Mode with Falling edge trigger detection 下降沿外部中断模式
External Interrupt Mode with Rising edge trigger detection 上降沿外部中断模式
External Interrupt Mode with Rising/Falling edge trigger detection 双边沿外部中断模式 - GPIO mode: Out Push Pull 推挽输出模式,Open Drain 开漏输出。
- GPIO Pull-up/Pull-down:
如果硬件上已外部上拉或下拉中选择 No pull-up and nopull-down 既不上拉也不下拉。
如果硬件外部没有上拉,则中选择 Pull-up 内部上拉或 Pull-down 内部下拉。
按键均配置为下降沿触发中断模式,接上拉电阻,依旧使用了宏定义别名。
下面配置NVIC中断控制器 外部中断线的使能、优先级分组、抢占优先级、子优先级
GPIO、EXTI、NVIC配置完了,接下来配置工程管理器后就可以生成工程代码了。
STM32CubeMX配置完毕,直接点击右上角的GENERATE CODE即可生成工程代码,生成完成后打开项目,即可直接调用配置的KEIL V5 IDE打开生成的工程,接下来在KEIL MDK中完成用户逻辑代码和回调。
*三、完成回调函数编写
// Core\Src\stm32l4xx_it.c
/**
* @brief This function handles EXTI line1 interrupt.
*/
void EXTI1_IRQHandler(void)
{
/* USER CODE BEGIN EXTI1_IRQn 0 */
/* USER CODE END EXTI1_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);
/* USER CODE BEGIN EXTI1_IRQn 1 */
/* USER CODE END EXTI1_IRQn 1 */
}
/**
* @brief Handle EXTI interrupt request.
* @param GPIO_Pin Specifies the port pin connected to corresponding EXTI line.
* @retval None
*/
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
// stm32l4xx_hal_gpio.c
/**
* @brief EXTI line detection callback.
* @param GPIO_Pin Specifies the port pin connected to corresponding EXTI line.
* @retval None
*/
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(GPIO_Pin);
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
}
可以从上面代码可知,中断触发后执行 HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin) 中断服务函数后,执行 HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) 中断回调函数,我们只需要重新定义回调函数就可以(函数HAL库中声明过,不需要在次声明,__weak 是一个弱化标识,意思就是你可以在其他地方写一个名称和参数都一模一样的函数,编译器就会忽略这一个函数,而去执行你写的那个函数),使用时只需要需要调用即可,不用关心中断标志位的读取和清除。
下面编写中断回调函数代码:
// Core\Src\gpio.c
/* USER CODE BEGIN 2 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
HAL_Delay(10); //消除抖动
switch(GPIO_Pin)
{
case KEY1_Pin: //控制LED灯状态翻转
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == RESET)
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
break;
case KEY1_Pin: //控制LED灯状态翻转
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == RESET)
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
break;
default:
break;
}
}
/* USER CODE END 2 */
主程序main函数保持默认即可,通过按键KEY1控制LED就完成了。
如果想实现下载后立即执行,需要在下面的对话框中勾选Reset and Run选项:
我们使用KEIL 5编译HAL库的程序编译速度巨慢,因为生成的工程默认是使用ARM Compiler V5(也即ARM GCC)编译的,如果取消Browse Infomation选项可以明显加快编译速度,但取消该选项后就无法实现跳转到某函数或变量的定义与声明了,不便于调试。新版的KEIL MDK V5(可能是V5.21之后的版本吧)支持ARM Compiler V6(也即ARM CLANG),该编译器得益于LLVM和Clang技术可以大大提升编译速度,下面我们选择ARM Compiler V6重新编译,配置界面如下(可参考:Migrate ARM Compiler 5 to ARM Compiler 6):
如果使用ARM Compiler V6编译器,C语言需要选择gnu99(或gnu11),否则会出现很多编译错误(可能HAL库中使用了一些GNU C语法)