【STM32】HAL库的本质 及 芯片内部GPIO模块细节
文章目录
- [【STM32】HAL库的本质 及 芯片内部GPIO模块细节](#【STM32】HAL库的本质 及 芯片内部GPIO模块细节)
-
- 一、芯片摆放与引脚编号识别
- [二、片上系统-SOC_(System On Chip)](#二、片上系统-SOC_(System On Chip))
-
- [2.1 HAL库的本质](#2.1 HAL库的本质)
-
- [2.1.1 HAL库实现点灯](#2.1.1 HAL库实现点灯)
- [2.1.2 操作寄存器](#2.1.2 操作寄存器)
- [2.2 `GPIO`模块](#2.2
GPIO模块) -
- [2.2.1 `GPIO`的常见用法](#2.2.1
GPIO的常见用法) - 2.2.2`GPIO`的工作模式
-
- [2.2.2.1 输入模式(上半张图)](#2.2.2.1 输入模式(上半张图))
- [2.2.2.2 施密特触发器_0和1的问题](#2.2.2.2 施密特触发器_0和1的问题)
- [2.2.2.3 输出模式(下半张图)](#2.2.2.3 输出模式(下半张图))
- [2.2.2.4 开漏输出是双向通信的示例(类`I2C`硬件原理)](#2.2.2.4 开漏输出是双向通信的示例(类
I2C硬件原理)) - [2.2.2.3 输出模式(下半张图 复用)](#2.2.2.3 输出模式(下半张图 复用))
- [2.2.2.5 操作GPIO](#2.2.2.5 操作GPIO)
- [2.2.3 `GPIO_HAL`库源码解析](#2.2.3
GPIO_HAL库源码解析)
- [2.2.1 `GPIO`的常见用法](#2.2.1
- **从底层本质来看,HAL库归根结底就是在操作寄存器**。**它是一个硬件抽象层,让你不用直接面对寄存器。**
一、芯片摆放与引脚编号识别
- 圆圈放在左上角
- 逆时针数引脚编号

二、片上系统-SOC_(System On Chip)
HAL库的本质就是读写寄存器;
HAL库的本质,就是帮我们去操作这些寄存器,我们就不用那么幸苦的去查看芯片手册,确认一下它的基地址,确认一下它的偏移地址,还得去确认一下里面每一位的含义,方便了很多。
工作中,建议大家使用厂家提供的库函数,不要自己去写基于寄存器的那些函数,基于寄存器的那些操作。
2.1 HAL库的本质
- 组装PC机需要
CPU、内存条、硬盘==> 放在主板上 - 对于单片机的芯片内部呢?
- 集成
CPU - 集成
RAM(内存条) - 集成
Flash(硬盘) - 集成
USB控制器 GPIO:General-purpose I/O(通用目的的输入输出引脚)
- 集成
- SOC简易图
| 简易SOC示意图_GPIO配置 | 简易SOC示意图_地址访问GPIO寄存器 |
|---|---|
![]() |
![]() |
在SOC中,
-
在CPU眼里,我可以直接访问到内存、GPIO、Flash;
-
CPU使用某些地址的时候,根据不同的地址来访问不同的设备。ABCDEF均是地址,在芯片手册中的内存映射(Memory map)中可以找到。
-
CPU可以去读写内存,或者GPIOC,读写GPIOC里面的寄存器
- 我们为什么称为寄存器?
- RAM(内存条):写入
val,读出仍是val;(暂存) - Flash(硬盘):读出指令,不可以直接写,写之前需要擦除;(长期保存)
- RAM(内存条):写入
- 我们为什么称为寄存器?
2.1.1 HAL库实现点灯
- 我们从最简单的操作说起,看看用HAL库点灯和操作寄存器电灯,有什么区别,看看HAL库到底在干什么。
| 操作LED灯的步骤 |
|---|
1、使能GPIO模块 |
2、选择Pin2的功能,连接到GPIO模块 |
3、配置GPIO模块,让引脚作为输出引脚 |
4、配置GPIO模块,让引脚输出 高/低 电平 |
前三个步骤可以用
cubeMX生成,第四步一般看HAL库手册,找到对应函数,进行调用就行,或者直接在程序中找到对应函数;对于
CubeMX,它省去了我们繁琐的硬件初始化的代码,我们只要专注于应用程序的开发就可以了。
c
//HAL库实现电灯程序
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
while(1)
{
/*set PC13 output high*/
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
HAL_Delay(500);
/*set PC13 output low*/
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_Delay(500);
}
/* USER CODE END 2 */
}
我们只要专注于应用程序的开发就可以了,正在要我们写的只有几个HAL库 函数调用而已,这些函数在芯片手册上面都有详细的解释。
c
/*set PC13 output high*/
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
HAL_Delay(500);
/*set PC13 output low*/
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_Delay(500);
2.1.2 操作寄存器
| GPIOC寄存器类型 | GPIOC寄存器 |
|---|---|
![]() |
![]() |
在GPIOC中
-
里面有很多寄存器,功能各不相同
-
根据
memory map可知,基地址为0x4001 1000,要读写第13位寄存器,就要对地址 0x4001 100C 进行操作。
C
//操作寄存器实现点灯程序
int main(void)
{
/* USER CODE BEGIN 1 */
//unsigned int a;
unsigned int *p;
//p = &a;
p = (unsigned int *)0x4001100c;
//*p = val; // a = val
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
while(1)
{
/*set PC13 output high*/
//HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
unsigned val = *p;
val = val | (1<<13);
*p = val;
HAL_Delay(500);
/*set PC13 output low*/
//HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
val = *p;
val = val & ~(1<<13);
*p = val;
HAL_Delay(500);
}
/* USER CODE END 2 */
}
我们真正要做的
c
unsigned int *p;
//p = &a;
p = (unsigned int *)0x4001100c;
while(1)
{
/*set PC13 output high*/
//HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
unsigned val = *p; // read
val = val | (1<<13);
*p = val; // 写回去
HAL_Delay(500);
/*set PC13 output low*/
//HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
val = *p; // read
val = val & ~(1<<13); // 修改
*p = val; // 写回去
HAL_Delay(500);
}
2.2 GPIO模块
General-purpose( 常规用途)和alternate-function(多用途)I/Os ==> (GPIO and AFIOS)
2.2.1 GPIO的常见用法
- 使能
GPIO - 选择PIN功能
- 选择方向
- 设置输出值 / 读取引脚值

2.2.2GPIO的工作模式
对于某个GPIO引脚 |
|---|
![]() |
2.2.2.1 输入模式(上半张图)
- 输入模式有四种:上拉输入、下拉输入、浮空输入、模拟输入(走模拟信号的分支);
| 使能 上拉 电阻 | 使能 下拉 电阻 |
|---|---|
![]() |
![]() |
| 浮空输入 | 模拟输入 |
|---|---|
![]() |
![]() |
-
TTL Schmitt trigger:施密特触发器(输入信号抗干扰神器,起到稳定输入信号的作用) ==> 输出 0/1;如何判断是 1 还是 0 呢?
2.2.2.2 施密特触发器_0和1的问题
| 施密特触发器(TTL Schmitt trigger)的用途 |
|---|
![]() |
2.2.2.3 输出模式(下半张图)
- 输出模式也有四种:推挽输出、开漏输出、复用推挽输出、复用开漏输出;
| 推挽输出(push pull) | |
|---|---|
![]() |
|
- 引入开漏输出 (两个
IC通信)
| 存在的问题:两个IC通信的情况 |
|---|
IC1输出1,IC2输出0 ![]() |
| 开漏输出(open drain)(复杂一点) |
![]() |
- 开漏输出,就是开漏连接,高电平为高阻抗,低电平就是拉低电平
VDD:供电正电压;VSS:公共地GND场效应管 P-MOS & N-MOS
2.2.2.4 开漏输出是双向通信的示例(类I2C硬件原理)
-
双向通信
-
IC1想发 Data:-
输出1:意思是,我不驱动P-MOS,这引脚
I/O pin我不管了,电平由外部电路决定 (由外部的上拉电阻决定,由外部的芯片决定) -
IC1读引脚:V==0 = =>IC2驱动pin,占用 V==1 = = >
IC2未驱动,可用 ==> 下一步 -
IC1驱动 Data 为 0 ==> 目的是起通知作用:通知对方说,我占用了这个引脚,我要给你发数据了
-
-
2.2.2.3 输出模式(下半张图 复用)
| 复用推挽&开漏输出 |
|---|
![]() |
2.2.2.5 操作GPIO
操作GPIO |
寄存器 |
|---|---|
![]() |
![]() |
-
设置方向:输入、输出
-
设置模式
- 输入:上拉、下拉
- 输出:推挽、开漏
设置速率:跳变速度,若快,对外部电磁干扰比较大;(小毛刺幅度更大)
-
读写
-
输入:Read val;
-
输出:数据寄存器写值:(先读,再改,再写)
bit set/reset 寄存器,操作寄存器一次就行,直接写
-
2.2.3 GPIO_HAL库源码解析
GPIO_HAL 库 GPIO 初始化 |
|---|
![]() |
从底层本质来看,HAL库归根结底就是在操作寄存器 。它是一个硬件抽象层,让你不用直接面对寄存器。
本文仅为个人学习总结,若有不对之处,敬请批评指正。期待与大家一起讨论、共同进步。
















