STM32从固件库到HAL库_固件库与hal库

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

让坚持成为一种热爱极致成为一种精神。历时10个月目前我又重新回到了程序员的身份2023想玩不一样的嵌入式。🚀

目录


前言

我学STM32是基于固件库的之后的一年都是用固件库开发STM32。其实固件库还是挺好用的很稳定即使ST已经放弃很多年了。
为什么改用HAL库
1CubeMX+HAL库是大势所趋。
2CubeMX可以直接生成驱动代码而且CubeMX是很好的工具即使不用HAL库里面的一些工具对开发也很有帮助。
3HAL库效率不如固件库一些关键代码可以改为寄存器操作比如串口、DMA、ADC。
4现在很多优秀的开源项目都是基于HAL库开发看不懂代码怎么CV[doge]
一些个人做法
直接用CubeMX生成的MDK工程开发我认为是不可取的参考文末提出的做法 。开发过程中随时会修改驱动参数而CubeMX每次修改都会覆盖原工程只能在固定区域编程极其不优雅。因此我的做法是用自己配置的工程开发然后把CubeMX生成的驱动代码复制到自己工程里。本文基于这种做法展开。

开发环境
操作系统Windows 11
JAVA 1.8.0_351
STM32CubeMX 6.5
STM32Cube_FW_F1_V 1.8.4
STM32Cube_FW_F4_V 1.26.1
keil MDK 5.36

一、安装STM32CubeMX

CubeMX需要JAVA环境首先需要安装JAVA。适用于 Windows 的 64位 Java
然后安装CubeMX直接在ST官网搜即可。ST官网
安装完配置仓库地址然后下载对应的芯片包即可。
第一次打开设置时会报错等几分钟再打开
在这里插入图片描述
在这里插入图片描述
下载芯片包
在这里插入图片描述
在这里插入图片描述
开启工程后第一步应该先设置RCC时钟源和SYS调试接口根据我的做法这部分可以直接跳过因为只需要驱动代码所以直接配置IO口生成即可。

二、配置自己的HAL库MDK工程

这部分跟配置固件库工程步骤相同只是把对应文件改为HAL库这里我直接在正点原子的例程代码基础上进行修改HAL库版本的原子例程依旧带有system文件夹。然后建立BSP文件把CubeMX生成的驱动代码复制过来即可。下面讲下HAL库跟固件库的一些区别。

三、HAL库与固件库的区别

这里简单说下除命名外的一些区别。

1.句柄

所谓句柄在HAL库里就是一个外设的结构体。
在固件库中我们是这样初始化外设的先定义一个函数在函数里定义一个结构体开启时钟然后操作这个结构体成员最后利用这个结构体初始化外设。
在HAL库中需要定义一个全局变量结构体也就是句柄为什么是全局变量因为不只是初始化外设要用到后面对外设的一系列操作都需要用到比如说ADC的校正、读取串口的接发。

2.回调函数

在固件库初始化外设中一般都是先打开时钟配置GPIO配置复用功能。而在HAL库中时钟、GPIO和中断的配置都放到了回调函数中带有MspInit结尾因为HAL库在Init外设后会自动调用回调函数回调函数是weak弱定义函数默认为空也就是ST让用户重新定义的函数。既然回调函数中放入了时钟和GPIO配置那么初始化函数中就只剩下外设的复用配置。
还有一个问题例如串口只有一个回调函数HAL_UART_MspInit在配置USART1时会调用函数HAL_UART_MspInit配置USART2时也会调用函数HAL_UART_MspInit所以HAL_UART_MspInit中必须判断目前在配置哪个串口这样才不会出错。
另外回调函数默认是一个空函数不一定要按照ST的思路来编程我们也可以把回调函数中的代码放回到初始化函数中也就是像用固件库编程那样。下面是串口的回调函数代码。

void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    GPIO_InitTypeDef gpio_init_struct;
    if(huart->Instance == USART_UX)                             /* 如果是串口1进行串口1 MSP初始化 */
    {
        USART_UX_CLK_ENABLE();                                  /* USART1 时钟使能 */
        USART_TX_GPIO_CLK_ENABLE();                             /* 发送引脚时钟使能 */
        USART_RX_GPIO_CLK_ENABLE();                             /* 接收引脚时钟使能 */

        gpio_init_struct.Pin = USART_TX_GPIO_PIN;               /* TX引脚 */
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;                /* 复用推挽输出 */
        gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* 高速 */
        gpio_init_struct.Alternate = USART_TX_GPIO_AF;          /* 复用为USART1 */
        HAL_GPIO_Init(USART_TX_GPIO_PORT, &gpio_init_struct);   /* 初始化发送引脚 */

        gpio_init_struct.Pin = USART_RX_GPIO_PIN;               /* RX引脚 */
        gpio_init_struct.Alternate = USART_RX_GPIO_AF;          /* 复用为USART1 */
        HAL_GPIO_Init(USART_RX_GPIO_PORT, &gpio_init_struct);   /* 初始化接收引脚 */

#if USART_EN_RX
        HAL_NVIC_EnableIRQ(USART_UX_IRQn);                      /* 使能USART1中断通道 */
        HAL_NVIC_SetPriority(USART_UX_IRQn, 3, 3);              /* 抢占优先级3子优先级3 */
#endif
    }

3.时钟配置

在固件库中根据外设对应的AHB、APB1、APB2时钟用对应的RCC函数打开时钟。如

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1, ENABLE );

在HAL库中把每个外设和GPIO的时钟都单独分开为一个宏。如

ADC_ADCX_CHY_CLK_ENABLE();      /* 使能ADCx时钟 */
ADC_ADCX_CHY_GPIO_CLK_ENABLE(); /* 开启GPIO时钟 */

4.HAL_Init()

main函数中需要加入HAL_Init()函数进行系统初始化。
HAL_Init()的功能是配置Flash设置中断分组、系统时钟。默认的中断分组是4如果后面不进行修改

HAL_StatusTypeDef HAL_Init(void)
{
  /* Configure Flash prefetch, Instruction cache, Data cache */ 
#if (INSTRUCTION_CACHE_ENABLE != 0U)
  __HAL_FLASH_INSTRUCTION_CACHE_ENABLE();
#endif /* INSTRUCTION_CACHE_ENABLE */

#if (DATA_CACHE_ENABLE != 0U)
  __HAL_FLASH_DATA_CACHE_ENABLE();
#endif /* DATA_CACHE_ENABLE */

#if (PREFETCH_ENABLE != 0U)
  __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif /* PREFETCH_ENABLE */

  /* Set Interrupt Group Priority */
  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

  /* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */
  HAL_InitTick(TICK_INT_PRIORITY);

  /* Init the low level hardware */
  HAL_MspInit();

  /* Return function status */
  return HAL_OK;
}

不难发现里面也有一个回调函数这里也是一个空函数。

5.其他

以上四个是重要区别其他基本都是命名区别可以结合例程代码进行配置在熟悉固件库的基础上花几个小时就可以快速入门HAL库。

另外我在配置时钟发现我之前一直忽视了32的ADC时钟以F1为例官方给出的ADC最高采样率是1M时钟频率最高为14MHz。
采样率 = 1 / 用户配置时钟周期 + 12.5固定转换时钟周期×1/14MHz
用户配置周期最小1.5在这种情况下可以达到1M采样率。
但是当系统为72M时ADC并不能达到14MHz如下图所示ADC时钟最高只能达到12MHz
此时最高率为1 / 1.5 + 12.5固定转换时钟周期×1/12MHz≈ 857KHz
也就是说如果要ADC跑到1MF1芯片只能更换系统时钟频率降频。
在这里插入图片描述

结语

以上是本篇文章的所有内容。
更改日志
2023.1.13上文说到“用自己配置的工程开发然后把CubeMX生成的驱动代码复制到自己工程里。”其实还有一种更巧妙的方法不需要再新建一个工程直接用CubeMX生成的工程然后新键文件编写一个自己的主函数在CubeMX工程的main函数初始完外设后直接调用自己的主函数while(1)之前且所有的APP都新建文件开发这样就不怕被覆盖了。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6