一、基本介绍GPIO是通用输入输出端口。特点①不同型号IO口数量可能不同。②快速翻转每次翻转最快只需要两个时钟周期。③每个IO口均可作中断。④支持8种工作模式。电气特性①STM32工作电压范围2V≤VDD≤3.6V②GPIO识别电压范围COMS端口-0.3V≤Vil≤1.164V逻辑01.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. 开漏输出特点只能输出低电平不能主动输出高电平。要获得高电平必须依靠外部上拉电阻。内部状态写 1N-MOS 管截止引脚状态由外部上拉电阻决定通常为高电平。写 0N-MOS 管导通引脚输出低电平。6. 推挽输出特点可以输出高电平VDD和低电平VSS驱动能力强灌电流和拉电流能力较好。7. 开漏式复用功能特点与开漏输出类似不能主动输出高电平需要外接上拉电阻。但区别在于输出数据的来源不是 CPU 写入 ODR 寄存器而是片上外设如 USART 的 TX 引脚、I2C 的 SDA/SCL 引脚等。8. 推挽式复用功能特点与推挽输出类似可输出高低电平驱动能力强。但输出数据由片上外设控制四、GPIO寄存器介绍1. 基本介绍F4/F7/H7系列GPIO通用寄存器GPIOX_yyyMODEROTYPEROSPEEDRPUPDRIDRODRBSRRLCKR设置模式设置输出类型设置输出速度设置上下拉电阻输入数据输出数据设置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.端口输入数据寄存器IDR4.端口输出数据寄存器ODR5.端口置位/复位寄存器BSRRODR和BSRR寄存器控制输出有什么区别?ST官方给的答案:使用ODR在读和修改访问之间产生中断时可能会发生风险;BSRR则无风险。BSRR VS ODRGPIOB-ODR | 13; /*PB31*/ ODR | 13 等同于 ODR ODR | (13GPIOB-BSRR 0x00000008;/*PB31*/ODR修改:读改写BSRR修改: 写综上建议使用BSRR寄存器控制输出五、通用外设驱动模型四步法六、GPIO配置步骤1. HAL_GPIO_Init 函数函数原型voidHAL_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 函数函数原型voidHAL_GPIO_WritePin(GPIO_TypeDef *GPIOx,uint16_tGPIO_Pin, GPIO_PinState PinState);功能用于设置引脚输出高电平或者低电平通过BSRR寄存器复位或者置位操作。关键参数GPIOx端口号。GPIO_Pin引脚号。PinState输出状态可选GPIO_PIN_SET高电平或GPIO_PIN_RESET低电平。返回值无3. HAL_GPIO_TogglePin 函数函数原型voidHAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx,uint16_tGPIO_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.下载验证