前言
大家好,这里是 Hello_Embed。上一篇我们用 HAL 库函数实现了 LED 闪烁,而函数的底层其实是对寄存器的操作。本篇将带你跳出 HAL 库的 “封装”,亲手通过寄存器控制 LED,理解 HAL 库的本质 —— 它其实是寄存器操作的 “简化版”。同时,我们会对比不同寄存器的操作方式,体会 HAL 库带来的便利。
一、HAL 函数的底层:本质是操作寄存器
HAL 库的函数(如HAL_GPIO_WritePin
)看似复杂,但其核心功能是通过代码操作单片机的寄存器。我们可以通过 “跳转定义”(选中函数按 F12)查看源码,但不必深究细节 —— 现在,我们试着自己实现类似功能,直接操作寄存器控制 LED。
二、直接操作 ODR 寄存器:控制引脚电平
要通过寄存器控制 GPIO,需先明确目标寄存器的地址,再通过指针操作该地址的内存(即寄存器)。
步骤 1:确定 GPIOC 的 ODR 寄存器地址
寄存器的地址由 “基地址” 和 “偏移地址” 组成:
- 基地址:GPIOC 模块的起始地址,查手册可知为
0x40011000
;
- 偏移地址:ODR 寄存器(输出数据寄存器)相对于基地址的偏移量,查手册得
0x0C
- 完整地址:基地址 + 偏移地址 =
0x40011000 + 0x0C = 0x4001100C
。
步骤 2:用指针操作 ODR 寄存器实现 LED 闪烁
ODR 寄存器的第 13 位对应 PC13 引脚(1 为高电平,0 为低电平)。通过逻辑运算修改该位(不影响其他位),代码如下:
{
/* USER CODE BEGIN 1 */
unsigned int *p; // 定义指针,指向ODR寄存器
p = (unsigned int *)0x4001100C; // 赋值ODR地址(强制类型转换为指针)
/* USER CODE END 1 */
while (1)
{
/* 熄灭LED:将ODR第13位置1(高电平) */
unsigned val = *p; // 读取当前ODR值
val = val | (1 << 13); // 第13位置1(其他位不变)
*p = val; // 写回ODR寄存器
HAL_Delay(500); // 延时500ms
/* 点亮LED:将ODR第13位清0(低电平) */
val = *p; // 读取当前ODR值
val = val & ~(1 << 13); // 第13位清0(其他位不变)
*p = val; // 写回ODR寄存器
HAL_Delay(500);
/* USER CODE BEGIN 3 */
}
}
代码解析:
- 用
unsigned int *p
定义指针,指向0x4001100C
(ODR 寄存器); - 通过
| (1 << 13)
和& ~(1 << 13)
操作,只修改第 13 位,保护其他引脚的电平状态(这是操作 ODR 的关键,需要用到前面笔记中讲的逻辑运算知识); - 最终实现的效果与
HAL_GPIO_WritePin
完全一致,LED 间隔 500ms 闪烁。
三、更简洁的方式:操作 BSRR 寄存器
ODR 寄存器需要通过逻辑运算保护其他位,操作较繁琐。而 BSRR 寄存器(位设置 / 清除寄存器)可以直接操作目标位,无需考虑其他位,更方便。
BSRR 寄存器的特性
查手册可知:
- BSRR 寄存器共 32 位,低 16 位(0~15)用于 “置位”(写 1 时对应引脚输出高电平,写 0 无影响);
- 高 16 位(16~31)用于 “清零”(写 1 时对应引脚输出低电平,写 0 无影响)。
步骤 1:确定 GPIOC 的 BSRR 寄存器地址
- 基地址仍为
0x40011000
; - BSRR 的偏移地址为
0x10
; - 完整地址:
0x40011000 + 0x10 = 0x40011010
。
步骤 2:用 BSRR 寄存器实现 LED 闪烁
直接向 BSRR 的对应位写 1 即可,无需逻辑运算,代码更简洁:
int main(void)
{
/* USER CODE BEGIN 1 */
unsigned int *p; // 定义指针,指向BSRR寄存器
p = (unsigned int *)0x40011010; // 赋值BSRR地址
/* USER CODE END 1 */
while (1)
{
/* 熄灭LED:低13位写1(置位PC13) */
*p = (1 << 13); // 等价于设置PC13为高电平
HAL_Delay(500);
/* 点亮LED:高13位(即第29位)写1(清零PC13) */
*p = (1 << 29); // 等价于设置PC13为低电平(13 + 16 = 29)
HAL_Delay(500);
/* USER CODE BEGIN 3 */
}
}
代码解析:
- 无需读取寄存器当前值,直接写
(1 << 13)
即可置位 PC13(高电平),写(1 << 29)
即可清零 PC13(低电平); - 其他位写 0 无影响,省去了 ODR 操作中的逻辑运算,更高效。
四、HAL 库的本质:封装寄存器操作
亲手操作寄存器后会发现:直接编写底层代码需要记忆地址、计算位运算,非常繁琐。而 HAL 库的核心作用,就是将这些复杂的寄存器操作封装成简单的函数(如HAL_GPIO_WritePin
),通过填写参数(端口、引脚、电平)即可完成操作,大大降低了开发难度。
补充说明
韦东山老师视频合集的 5-5~5-8 节补充了相关 C 语言基础知识,这些内容在我前面的笔记中都有记录,大家可以结合笔记辅助学习,加深对指针、位运算等概念的理解。
结尾
本文通过直接操作 ODR 和 BSRR 寄存器,实现了与 HAL 库函数相同的功能,让我们看清了 HAL 库的本质 —— 它是寄存器操作的 “封装层”。理解这一点后,使用 HAL 库时会更清楚其底层逻辑。
下一篇笔记,我们将彻底分析 GPIO 的工作模式(如推挽输出、开漏输出等),看看不同模式下寄存器的配置有何差异。Hello_Embed 继续带你深入 STM32 的硬件细节,敬请期待~