库开发与寄存器开发的关系

在 51单片机的开发中我们常常的作法是直接操作寄存器,比如要控制某些 IO 口的状态,我们直接操作寄存器:

1
P0=0x11;

而在 STM32 的开发中,我们同样可以操作寄存器:

1
GPIOF->BSRR=0x00000001; //这里是针对 STM32F4 系列

这种方法当然可以,但是这种方法的劣势是你需要去掌握每个寄存器的用法,你才能正确使用STM32。

于是 ST推出了官方固件库,固件库将这些寄存器底层操作都封装起来,提供一整套接口( API)供开发者调用,大多数场合下,你不需要去知道操作的是哪个寄存器,你只需要知道调用哪些函数即可。比如上面的控制 BSRRL 寄存器实现电平控制,官方 HAL 库封装了一个函数:

1
2
3
4
5
6
7
8
9
10
11
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
if(PinState != GPIO_PIN_RESET)
{
GPIOx->BSRR = GPIO_Pin;
}
else
{
GPIOx->BSRR = (uint32_t)GPIO_Pin << 16;
}
}

这个时候你不需要再直接去操作BSRRL寄存器了,你只需要知道怎么使用HAL_GPIO_WritePin这个函数就可以了。

HAL 库和标准库选择

ST 先后提供了两套固件库:标准库和HAL库。STM32芯片面市之初只提供了丰富全面的标准库,大大便利了用户程序开发。

2014 年左右, ST 在标准库的基础上又推出了 HAL 库。 实际上, HAL库和标准库本质上是一样的,都是提供底层硬件操作 API,而且在使用上也是大同小异。ST 官方之所以这几年大力推广 HAL 库,是因为 HAL 的结构更加容易整合 STM32Cube,而 STM32CubeMX 是 ST 这几年极力推荐的程序生成开发工具。所以这两年新出的 STM32 芯片, ST 直接只提供 HAL 库。在新型的 STM32 芯片中,用 HAL 库逐步淘汰标准库。

HAL 库中__weak 修饰符讲解

weak 顾名思义是“弱”的意思,所以如果函数名称前面加上weak 修饰符,我们一般称这个函数为“弱函数”。加上了weak 修饰符的函数, 用户可以在用户文件中重新定义一个同名函数,最终编译器编译的时候,会选择用户定义的函数,如果用户没有重新定义这个函数,那么编译器就会执行__weak 声明的函数,并且编译器不会报错。

stm32f4xx_hal.c 文件,里面定义了一个函数 HAL_MspInit,定义如下:

1
2
3
4
5
6
__weak void HAL_MspInit(void)
{
__IO uint32_t tmpreg = 0x00;

UNUSED(tmpreg);
}

大家可以看出, HAL_MspInit 函数前面有加修饰符__weak。同时,在该文件的前面有定义函数
HAL_Init,并且 HAL_Init 函数中调用了函数 HAL_MspInit。

1
2
3
4
5
6
	HAL_StatusTypeDef HAL_Init(void)
{
//此处省略部分代码
HAL_MspInit();
return HAL_OK;
}

如果我们没有在工程中其他地方重新定义 HAL_MspInit()函数,那么 HAL_Init 初始化函数执行的时候,会默认执行 stm32f4xx_hal.c 文件中定义的 HAL_MspInit 函数,而这个函数没有任何控制逻辑。如果用户在工程中重新定义函数 HAL_MspInit,那么调用 HAL_Init 之后,会执行用户自己定义的 HAL_MspInit 函数而不会执行 stm32f4xx_hal.c 默认定义的函数。也就是说,表面上我们看到函数 HAL_MspInit 被定义了两次,但是因为有一次定义是弱函数,使用了__weak修饰符,所以编译器不会报错。

在回调函数的时候经常用到weak ,这样的好处是,系统默认定义了一个空的回调函数,保证编译器不会报错。同时,如果用户自己要定义用户回调函数,那么只需要重新定义即可,不需要考虑函数重复定义的问题,使用非常方便,在 HAL库中weak 关键字被广泛使用。