研二小白硬件学习Day1——GPIO(资源:正点原子STM32F407探索者开发板)
(F4/F7/H7系列)GPIO通用寄存器GPIOX_yyyMODEROTYPEROSPEEDRPUPDRIDRODRBSRRLCKR设置模式设置输出类型设置输出速度设置上下拉电阻输入数据输出数据设置ODR寄存器值配置锁定,用的不多。
一、基本介绍
GPIO是通用输入输出端口。
特点:①不同型号,IO口数量可能不同。
②快速翻转,每次翻转最快只需要两个时钟周期。
③每个IO口均可作中断。
④支持8种工作模式。
电气特性:①STM32工作电压范围:2V≤VDD≤3.6V
②GPIO识别电压范围:(COMS端口)-0.3V≤Vil≤1.164V(逻辑0)1.833V≤Vih≤3.6V(逻辑1)
注:查看数据手册,标FT为TTL端口,没标为COMS,电压范围不能处于(1.164V,1.833V)之间,这样状态不确定。
③GPIO输出电流:单个IO,最大25mA
二、基本结构

① 保护二极管
保护二极管共有两个,用于保护引脚外部过高或过低的电压输入。当引脚输入电压高于 VDD 时,上面的二极管导通,当引脚输入电压低于VSS时,下面的二极管导通,从而使输入芯片内部的电压处于比较稳定的值。虽然有二极管的保护,但这样的保护却很有限,大电压大电流的接入很容易烧坏芯片。所以在实际的设计中要考虑设计引脚的保护电路。
② 上拉、下拉电阻
它们阻值大概在30~50K 欧之间,可以通过上、下两个对应的开关控制,这两个开关由寄存器控制。当引脚外部的器件没有干扰引脚的电压时,即没有外部的上、下拉电压,引脚的电平由引脚内部上、下拉决定,开启内部上拉电阻工作,引脚电平为高,开启内部下拉电阻工作,则引脚电平为低。同样,如果内部上、下拉电阻都不开启,这种情况就是我们所说的浮空模式。 浮空模式下,引脚的电平是不可确定的。引脚的电平可以由外部的上、下拉电平决定。需要注 意的是,STM32的内部上拉是一种“弱上拉”,这样的上拉电流很弱,如果有要求大电流还是 得外部上拉。
③ 施密特触发器
对于标准施密特触发器,具有双阈值(正向阈值电压和负向阈值电压),当输入电压高于正向阈值电压,输出为高;当输入电压低于负向阈值电压,输出为低;输入电压在两者之间时输出保持不变,这种特性称为滞回现象,因此施密特触发器有记忆性。从本质上来说,施密特触发器是一种双稳态多谐振荡器。它可作为波形整形电路,将模拟信号整形为方波,并且由于滞回特性,可用于抗干扰。
④ P-MOS管和N-MOS管
这个结构控制GPIO的开漏输出和推挽输出两种模式。开漏输出:输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行。推挽输出:这两只对称的MOS管每次只有一只导通,所以导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载拉电流。推挽输出既能提高电路的负载能力,又能提高开关速度。
注:关于开漏输出和推挽输出可看这个链接https://zhuanlan.zhihu.com/p/1913227702481158423
三、八种工作模式
1. 输入浮空
-
特点:输入用,电平状态完全由外部决定。由于内部既不上拉也不下拉,引脚悬空时电平不确定(容易受到干扰)。

2. 输入上拉
-
特点:输入用,内部上拉电阻接通。默认状态下(外部无驱动时),读取为高电平。

3. 输入下拉
-
特点:输入用,内部下拉电阻接通。默认状态下(外部无驱动时),读取为低电平。

4. 模拟功能
-
特点:用于 ADC 采集电压或 DAC 输出电压。在此模式下,GPIO 的施密特触发器被关闭(禁止输入),上下拉电阻也被断开,引脚模拟信号直接进入片上外设(如 ADC)。

5. 开漏输出
-
特点:只能输出低电平,不能主动输出高电平。要获得高电平,必须依靠外部上拉电阻。
-
内部状态:
-
写 1:N-MOS 管截止,引脚状态由外部上拉电阻决定(通常为高电平)。
-
写 0:N-MOS 管导通,引脚输出低电平。

-
6. 推挽输出
-
特点:可以输出高电平(VDD)和低电平(VSS),驱动能力强(灌电流和拉电流能力较好)。

7. 开漏式复用功能
-
特点:与开漏输出类似,不能主动输出高电平,需要外接上拉电阻。但区别在于,输出数据的来源不是 CPU 写入 ODR 寄存器,而是片上外设(如 USART 的 TX 引脚、I2C 的 SDA/SCL 引脚等)。

8. 推挽式复用功能
-
特点:与推挽输出类似,可输出高低电平,驱动能力强。但输出数据由片上外设控制

四、GPIO寄存器介绍
1. 基本介绍
|
(F4/F7/H7系列)GPIO通用寄存器GPIOX_yyy |
|||||||
|
MODER |
OTYPER |
OSPEEDR |
PUPDR |
IDR |
ODR |
BSRR |
LCKR |
|
设置模式 |
设置输出类型 |
设置输出速度 |
设置上下拉电阻 |
输入数据 |
输出数据 |
设置ODR寄存器值 |
配置锁定,用的不多 |
2.8种工作模式对应的配置
|
GPIO工作模式 |
模式寄存器 MODER[0:1] |
输出类型寄存器 OTYPER |
输出速度寄存器 OSPEEDR[0:1] |
上拉/下拉寄存器 PUPDR[0:1] |
|
输入浮空 |
00-输入模式 |
无效 |
无效 |
00-无上拉或下拉 |
|
输入上拉 |
01-上拉 |
|||
|
输入下拉 |
10-下拉 |
|||
|
模拟功能 |
11-模拟模式 |
00-无上拉或下拉 |
||
|
开漏输出 |
01-通用输出 |
1-开漏输出 |
00-低速 01-中速 10-高速 11-超高速 |
00-无上拉或下拉 01-上拉 10-下拉 11-保留 |
|
推挽输出 |
0-推挽输出 |
|||
|
开漏式复用功能 |
10-复用功能 |
1-开漏输出 |
||
|
推挽式复用功能 |
0-推挽输出 |
3.端口输入数据寄存器(IDR)

4.端口输出数据寄存器(ODR)

5.端口置位/复位寄存器(BSRR)

ODR和BSRR寄存器控制输出有什么区别?
ST官方给的答案:使用ODR,在读和修改访问之间产生中断时,可能会发生风险;BSRR则无风险。
BSRR VS ODR
GPIOB->ODR |= 1<<3; /*PB3=1*/ ODR |= 1<<3 等同于 ODR = ODR | (1<<3)
GPIOB->BSRR = 0x00000008;/*PB3=1*/
ODR修改:读>改>写
BSRR修改: 写
综上,建议使用BSRR寄存器控制输出!
五、通用外设驱动模型(四步法)

六、GPIO配置步骤

1. HAL_GPIO_Init 函数
函数原型void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
功能
初始化 GPIO 引脚,配置工作模式、上下拉、输出速度、复用功能,并可设置 EXTI 中断。
关键参数
-
GPIOx:端口基址,如 GPIOA、GPIOB 等(STM32F407支持 GPIOA~GPIOG)。
-
GPIO_Init:指向初始化结构体的指针,包含以下成员:
-
Pin:引脚号(0~15),可单引脚或组合(如 GPIO_PIN_All)。
-
Mode:模式选择,包括输入、输出(推挽/开漏)、复用(推挽/开漏)、模拟、外部中断/事件(上升沿/下降沿/双边沿)。
-
Pull:上下拉设置(无、上拉、下拉)。
-
Speed:输出速度(低、中、高)。
-
Alternate:复用功能编号,需查阅数据手册确定。
-
返回值
无
注意事项
EXTI 外部中断的配置也在此函数中完成,不再单独设置。
2. HAL_GPIO_WritePin 函数
函数原型void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);
功能
用于设置引脚输出高电平或者低电平,通过BSRR寄存器复位或者置位操作。
关键参数
-
GPIOx:端口号。
-
GPIO_Pin:引脚号。
-
PinState:输出状态,可选
GPIO_PIN_SET(高电平)或GPIO_PIN_RESET(低电平)。
返回值
无
3. HAL_GPIO_TogglePin 函数
函数原型void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
功能
用于设置引脚的电平翻转,也是通过BSRR寄存器复位或者置位操作。
关键参数
-
GPIOx:端口号。
-
GPIO_Pin:引脚号。
返回值
无
七、编程实战:跑马灯
1.0805贴片发光二极管

2.LED原理图
3.GPIO输出配置步骤
1)使能对应GPIO时钟
STM32 在使用任何外设之前,都要先使能其时钟。本实验用到PF9和PF10两个IO口,因此需要先使能GPIOF的时钟,代码如下: __HAL_RCC_GPIOF_CLK_ENABLE();
2)设置对应GPIO工作模式
本实验GPIO使用推挽输出模式,控制LED亮灭,通过函数HAL_GPIO_Init设置实现。
3)控制GPIO引脚输出高低电平
在配置好GPIO工作模式后,可以通过HAL_GPIO_WritePin函数控制GPIO引脚输出高低电平,从而控制LED的亮灭了。
4.代码详解(led.h)
#ifndef __LED_H // 防止头文件被重复包含:如果未定义 __LED_H 宏,则编译下面的代码
#define __LED_H // 定义 __LED_H 宏,标记本文件已被包含
#include "./SYSTEM/sys/sys.h" // 包含系统级头文件,提供 HAL 库依赖、数据类型等基础定义
/******************************************************************************************/
/* 引脚 定义 */
// LED0 连接在 GPIOF 端口的第 9 号引脚上
#define LED0_GPIO_PORT GPIOF // LED0 使用的端口
#define LED0_GPIO_PIN GPIO_PIN_9 // LED0 使用的引脚编号
// LED1 连接在 GPIOF 端口的第 10 号引脚上
#define LED1_GPIO_PORT GPIOF // LED1 使用的端口
#define LED1_GPIO_PIN GPIO_PIN_10 // LED1 使用的引脚编号
// 使能 GPIOF 端口时钟的宏(必须调用后才能操作该端口的寄存器)
// do{ ... }while(0) 是一种安全封装,确保宏在使用时行为正确(加分号也不会出错)
#define LED0_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0)
#define LED1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0)
/******************************************************************************************/
/* LED 控制宏(设置高低电平) */
// 如果参数 x 为真(非零),则调用 HAL_GPIO_WritePin 设置引脚为高电平(LED灭)
// 如果 x 为假(0),则设置引脚为低电平(LED亮)
// 注意:具体亮灭取决于硬件连接方式,此处低电平点亮
#define LED0(x) do{ x ? \
HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* LED0 控制,RED */
#define LED1(x) do{ x ? \
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* LED1 控制,GREEN */
/* LED 电平翻转宏(每次调用改变一次状态) */
#define LED0_TOGGLE() do{ HAL_GPIO_TogglePin(LED0_GPIO_PORT, LED0_GPIO_PIN); }while(0) // 翻转 LED0
#define LED1_TOGGLE() do{ HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_GPIO_PIN); }while(0) // 翻转 LED1
/******************************************************************************************/
/* 外部接口函数声明 */
void led_init(void); // LED 初始化函数,需在源文件(led.c)中实现
#endif // 结束头文件保护
补充说明:
-
头文件保护:
#ifndef __LED_H ... #endif确保编译器只处理一次本文件,避免重复定义错误。 -
宏定义:使用大写命名,增加可读性。宏在预处理阶段直接替换文本,无函数调用开销。
-
do{ ... }while(0)的作用:让宏在使用时可以像普通函数一样加上分号,且不会引发语法错误(例如在if语句中不加花括号时也能正确执行)。 -
时钟使能:STM32 外设使用前必须开启对应时钟,否则寄存器操作无效。
-
模块化设计:将硬件相关的引脚定义和操作封装在头文件中,上层代码(如
main.c)只需调用led_init()和宏即可控制 LED,提高了代码的可移植性。
5.代码详解(led.c)
#include "./BSP/LED/led.h" // 包含 LED 相关的头文件,里面定义了引脚、宏等
/**
* @brief 初始化LED相关IO口, 并使能时钟
* @param 无
* @retval 无
*/
void led_init(void)
{
GPIO_InitTypeDef gpio_init_struct; // 定义一个 GPIO 初始化结构体变量,用于配置引脚参数
// 使能 LED0 和 LED1 所在 GPIO 端口的时钟
// 在 STM32 中,任何外设(包括 GPIO)在使用前都必须先开启其时钟,否则无法工作
LED0_GPIO_CLK_ENABLE(); // 使能 GPIOF 时钟(根据宏定义,LED0 在 GPIOF)
LED1_GPIO_CLK_ENABLE(); // 同样使能 GPIOF 时钟(两个 LED 共用同一端口,只使能一次也可以)
// 配置 LED0 引脚
gpio_init_struct.Pin = LED0_GPIO_PIN; // 指定要配置的引脚:LED0 的引脚号(GPIO_PIN_9)
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; // 设置为推挽输出模式:可以输出高/低电平,驱动能力强
gpio_init_struct.Pull = GPIO_PULLUP; // 设置内部上拉电阻:引脚默认被拉高(配合低电平点亮 LED 的设计,关灯时输出高)
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; // 设置输出速度为高速:对于 LED 这样简单的负载,低速也可以,但这里设为高速更通用
HAL_GPIO_Init(LED0_GPIO_PORT, &gpio_init_struct); // 调用 HAL 库函数,将配置写入寄存器,完成 LED0 引脚的初始化
// 配置 LED1 引脚(与 LED0 使用相同的参数,只是引脚号不同)
gpio_init_struct.Pin = LED1_GPIO_PIN; // 修改引脚号为 LED1 的引脚(GPIO_PIN_10)
HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct); // 再次调用初始化函数,配置 LED1 引脚
// 初始化完成后,默认关闭两个 LED
// 根据硬件设计,LED 低电平点亮,高电平熄灭
// LED0(1) 宏定义为:参数为 1 时输出高电平,即关闭 LED
LED0(1); // 关闭 LED0
LED1(1); // 关闭 LED1
}
补充说明:
-
为什么要使能时钟?
STM32 的每个外设都有一个时钟开关,关闭时钟时外设完全不工作,这样可以省电。使用前必须通过__HAL_RCC_GPIOx_CLK_ENABLE()开启时钟。 -
什么是推挽输出?
推挽输出模式下,引脚可以主动输出高电平(连接 VDD)或低电平(连接 VSS),驱动能力较强,适合驱动 LED。 -
为什么设置上拉?
设置上拉电阻可以使引脚在无外部信号时保持高电平。这里配合 LED 低电平点亮的设计,上拉确保初始状态为高电平,LED 熄灭。 -
速度设置有什么影响?
速度指的是 I/O 口的响应速度,对于 LED 这种低速设备,低速即可,但高速设置也不影响功能,所以这里用高速保证兼容性。 -
为什么初始化后要关灯?
防止上电瞬间引脚电平不确定导致 LED 误亮,初始化后明确设置输出状态,保证系统启动时 LED 是熄灭的。
6.代码解析(main.c)
// 包含必要的头文件
#include "./SYSTEM/sys/sys.h" // 系统核心文件,包含时钟配置、中断分组等基础函数
#include "./SYSTEM/usart/usart.h" // 串口通信相关函数(本示例未使用,但保留以便后续扩展)
#include "./SYSTEM/delay/delay.h" // 延时函数头文件,提供毫秒级和微秒级延时
#include "./BSP/LED/led.h" // LED 控制头文件,包含 LED 初始化及控制宏
/**
* @brief 主函数:程序入口
* @param 无
* @retval 无
*/
int main(void)
{
// 1. 初始化 HAL 库
// HAL_Init() 是 STM32 HAL 库的初始化函数,它负责:
// - 设置 SysTick 定时器(用于提供 HAL 库内部的时基,如 HAL_Delay)
// - 初始化全局中断优先级分组(默认设置为 NVIC_PRIORITYGROUP_4,即 4 位抢占优先级)
// - 初始化底层硬件(如 Flash 接口)
HAL_Init();
// 2. 配置系统时钟
// sys_stm32_clock_init 是自定义的时钟初始化函数(来自 sys.c)
// 参数说明:336, 8, 2, 7 分别对应 PLL 相关分频系数,最终得到系统时钟 168MHz
// 具体计算方式:外部晶振一般为 8MHz,经过一系列倍频/分频后得到 168MHz
// 这里不深入计算细节,只需知道系统时钟被设置为 168MHz 即可
sys_stm32_clock_init(336, 8, 2, 7); // 设置系统时钟为 168MHz
// 3. 初始化延时函数
// delay_init() 需要传入系统时钟频率(单位 MHz),用于精确计算延时
// 参数 168 表示系统时钟为 168MHz,这样后续 delay_ms() 和 delay_us() 才能正常工作
delay_init(168);
// 4. 初始化 LED 相关的 GPIO 引脚
// led_init() 在 led.c 中实现,会配置 LED0 和 LED1 的引脚为推挽输出模式
// 并使能对应 GPIO 端口的时钟,最后默认关闭两个 LED(输出高电平)
led_init();
// 5. 主循环:程序会一直在这里循环执行
while(1)
{
// LED0(0) 是一个宏定义,参数为 0 表示输出低电平(点亮 LED0)
// 根据硬件设计,LED 低电平点亮,高电平熄灭
LED0(0); // 点亮 LED0(红色)
LED1(1); // 熄灭 LED1(绿色),参数 1 表示输出高电平
delay_ms(500); // 延时 500 毫秒,保持当前状态
LED0(1); // 熄灭 LED0
LED1(0); // 点亮 LED1
delay_ms(500); // 延时 500 毫秒,切换状态
}
}
补充说明:
-
头文件包含顺序:通常先包含系统文件(如
sys.h),再包含外设驱动文件,最后包含板级支持包(led.h)。这样能保证依赖关系正确。 -
时钟配置:STM32 的时钟系统比较复杂,新手可以先使用现成的配置函数(如本例中的
sys_stm32_clock_init),后续再深入学习时钟树。 -
延时函数:
delay_ms是基于 SysTick 定时器实现的,必须在使用前调用delay_init进行初始化,否则延时不准。 -
主循环:
while(1)是嵌入式程序的标准写法,保证程序持续运行,不会退出。 -
LED 亮灭逻辑:代码中 LED 交替亮灭,间隔 0.5 秒,形成一个简单的流水灯效果。可以通过修改延时时间改变闪烁频率。
7.下载验证



更多推荐



所有评论(0)