STM32F1 LL 库和HAL 库在GPIO 上的区别
区别
STM32F1 的LL 库在GPIO_PIN 定义上用了特别的处理,和HAL 库不一样,不能按HAL 库或标准库的惯例处理。LL 的GPIO 头文件中Pin 的定义如下:
/** @defgroup GPIO_LL_EC_PIN PIN
* @{
*/
#define LL_GPIO_PIN_0 ((GPIO_BSRR_BS0 << GPIO_PIN_MASK_POS) | 0x00000001U) /*!< Select pin 0 */
#define LL_GPIO_PIN_1 ((GPIO_BSRR_BS1 << GPIO_PIN_MASK_POS) | 0x00000002U) /*!< Select pin 1 */
#define LL_GPIO_PIN_2 ((GPIO_BSRR_BS2 << GPIO_PIN_MASK_POS) | 0x00000004U) /*!< Select pin 2 */
#define LL_GPIO_PIN_3 ((GPIO_BSRR_BS3 << GPIO_PIN_MASK_POS) | 0x00000008U) /*!< Select pin 3 */
#define LL_GPIO_PIN_4 ((GPIO_BSRR_BS4 << GPIO_PIN_MASK_POS) | 0x00000010U) /*!< Select pin 4 */
#define LL_GPIO_PIN_5 ((GPIO_BSRR_BS5 << GPIO_PIN_MASK_POS) | 0x00000020U) /*!< Select pin 5 */
#define LL_GPIO_PIN_6 ((GPIO_BSRR_BS6 << GPIO_PIN_MASK_POS) | 0x00000040U) /*!< Select pin 6 */
#define LL_GPIO_PIN_7 ((GPIO_BSRR_BS7 << GPIO_PIN_MASK_POS) | 0x00000080U) /*!< Select pin 7 */
#define LL_GPIO_PIN_8 ((GPIO_BSRR_BS8 << GPIO_PIN_MASK_POS) | 0x04000001U) /*!< Select pin 8 */
// ...
而HAL 库的定义是:
/** @defgroup GPIO_pins_define GPIO pins define
* @{
*/
#define GPIO_PIN_0 ((uint16_t)0x0001) /* Pin 0 selected */
#define GPIO_PIN_1 ((uint16_t)0x0002) /* Pin 1 selected */
#define GPIO_PIN_2 ((uint16_t)0x0004) /* Pin 2 selected */
#define GPIO_PIN_3 ((uint16_t)0x0008) /* Pin 3 selected */
#define GPIO_PIN_4 ((uint16_t)0x0010) /* Pin 4 selected */
#define GPIO_PIN_5 ((uint16_t)0x0020) /* Pin 5 selected */
#define GPIO_PIN_6 ((uint16_t)0x0040) /* Pin 6 selected */
// ...
HAL 库的Pin 定义就是简单的pin mask
,GPIO_PIN_N = 0x1 << N
,和GPIO 里ODR,IDR 等寄存器的操作对应,而LL 库把pin mask 左移了八位,然后在低8 位和高8 位附加了其他信息。
所以,首先,HAL 的pin_mask
是uint16_t
,而LL 要用uint32_t
。如果自己写了函数接收Pin 作为参数,LL 库的类型可能会不匹配。其次,使用LL 库的Pin 定义时,需要一些额外处理,比如LL 里面给引脚设置高电平的函数:
__STATIC_INLINE void LL_GPIO_SetOutputPin(GPIO_TypeDef *GPIOx, uint32_t PinMask)
{
WRITE_REG(GPIOx->BSRR, (PinMask >> GPIO_PIN_MASK_POS) & 0x0000FFFFU);
}
给BSRR 赋值之前,先要把Pin 右移8 位,也就是把pin mask 挪回去,还要把高8 位的东西去掉。对比HAL 库的写引脚电平函数:
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
assert_param(IS_GPIO_PIN_ACTION(PinState));
if (PinState != GPIO_PIN_RESET)
{
GPIOx->BSRR = (uint32_t)GPIO_Pin;
}
else
{
GPIOx->BRR = (uint32_t)GPIO_Pin;
}
}
直接用Pin 给BSRR 赋值,不需要额外处理。另外,STM32F0 的LL 库也是没有额外处理的:
__STATIC_INLINE void LL_GPIO_SetOutputPin(GPIO_TypeDef *GPIOx, uint32_t PinMask)
{
WRITE_REG(GPIOx->BSRR, PinMask);
}
没去细究pin_mask 里附加的那些东西有什么用,估计是和F1 系列特殊的GPIO 模式寄存器有关。F0 和F4 系列GPIO 寄存器把输出模式、速度、上下拉之类的配置都用单独的寄存器实现,而F1 系列是全部塞进CRH 和CRL 寄存器里,各个模式之间相互耦合,某一个寄存器位的含义取决于其他寄存器位的值;这就给驱动代码编写造成了麻烦,比如,没办法临时将输出切换成输入,而不修改原来配置的输出速度。
影响
如果实际代码中,调用写GPIO 引脚函数时用的参数都是常量,比如:
#define LED_PIN LL_GPIO_PIN_0
#define LED_PORT GPIOB
// ...
// 拉高LED 引脚
LL_GPIO_SetOutputPin(LED_PORT, LED_PIN);
这种情况编译器有可能把右移和位运算都优化省略掉,变成常量操作。但是如果引脚参数不是常量,而是用变量传入的,比如结构体:
typedef struct {
GPIO_TypeDef *port;
uint32_t pin_mask;
} PinType;
// 多个引脚放进一个数组
const PinType pins[] = {{GPIOB, LL_GPIO_PIN_0}, {GPIOB, LL_GPIO_PIN_1}, {GPIOB, LL_GPIO_PIN_2}};
void 拉高所有引脚() {
for(int i = 0; i< 3; ++i) {
LL_GPIO_SetOutputPin(pins[i].port, pins[i].pin_mask);
}
}
此时,对LL 函数的调用难以被优化,引脚操作的效率有所降低。当然效率低一点还是其次,重点是我的代码不兼容STM32F1 的LL 库这种引脚定义ヽ(≧□≦)ノ。除了STM32F1,其他单片机的库代码基本都是HAL 库那样定义的,所以我就默认pin mask
可以用(0x1 << N)
这种方式生成,然而拿这种pin mask
去调用LL 库的函数是没效果的。总之,建议别用STM32F1 的LL 库,实在用不了HAL,GPIO 操作很简单,就自己写点寄存器代码喽。