文章目录
开始GPIO进阶学习吧
📖理论
对于输出电压,GPIO最大只能输出3.3V,因为系统供电是3.3V。
对于输入电压,STM32中部分引脚能够持续承受5V电压。数据手册中, 标注FT的引脚就能够承受5V电压。

图 1:STM32引脚定义

图 2:GPIO结构图
保护二极管:当瞬间电压进入I/O引脚时,若此电压大于VDD(3.3V),则上方的保护二极管导通。
当瞬间电压小于VSS(0V),下方保护二极管导通。
若输入电压在0~3.3V之间,那么2个二极管均不会导通。
此结构只能抵御一瞬间的电压波动,长时间高电压接入I/O口依旧会损坏芯片。
上拉/下拉电阻输入 :为悬空的输入引脚提供一个默认的稳定电平。
上拉 (P-UP) : 内部电阻将其连接到 VDD,使默认电平为高电平。
下拉 (P-DOWN) : 内部电阻将其连接到 VSS,使默认电平为低电平。
施密特触发器/TTL肖特基触发器:电压比较器,有效消除输入信号中的噪声和抖动。高于上限输出高,低于上限输出低。
输入数据寄存器-IDR Input Data Register:存储施密特触发器输出的电平值(0或1),CPU 通过读取IDR得知引脚电平。
输出数据寄存器-ODR Output Data Register):存储引脚期望的输出电平,ODR 的值会直接控制P-MOS 和 N-MOS,CPU 读取 ODR👉 读取"期望的输出值",而非"引脚的实际电平"。
位设置/清除寄存器-BSRR Bit Set/Reset Register:快速地设置或清除 ODR 中的某一位。
N-MOS和P-MOS:
在推挽模式下,STM32对IO口输出有绝对的控制权
输出数据寄存器为1,P-MOS激活,N-MOS断开,输出接入VDD,输出高电平;
输出数据寄存器为0,P-MOS断开,N-MOS激活,输出接入VSS,输出低电平;
在开漏模式下,P-MOS管失效,只有N-MOS管工作
输出数据寄存器为1,P-MOS无效,N-MOS关闭,GPIO引脚处于"高阻态",输出电压由外部电路决定。
输出数据寄存器为0,P-MOS无效,N-MOS导通,输出接入VSS,输出低电平;
🔍"高阻态":就是悬空,既不是高电平,也不是低电平,引脚上的电压未定义,易收到电磁信号的干扰。

图3: GPIO模式 (具体看STM32参考手册)
🚀实战

图 4:RCC 外设时钟使能函数
在STM32内部为了省电,所有外设(GPIO、USART、TIM...)默认是"关机"状态,即时钟被关闭。第一步永远是打开这个部门的电源(时钟)。

图 5:GPIO 配置与操作函数
💡补充
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
GPIO_Init(GPIOA , &GPIO_InitStruct) 为什么第一个参数实际填写GPIOA而不是&GPIOA
在
stm32f10x.h头文件中,有这样一行定义:#define GPIOA ((GPIO_TypeDef *) 0x40010800)所以
GPIOA本身已经是一个指针,它不是一个变量,而是一个宏定义 ,它代表的就是一个地址。
- 这些参数在
stm32f10x_gpio.h头文件搜索

图 6:GPIO初始化结构体

图 7:GPIO模式

图 8:GPIO引脚宏定义
GPIO_InitStruct.GPIO_Pin
GPIO_Pin是``GPIO_InitTypeDef结构体中被定义为uint16_t` (无符号16位整数),是一个 16位 的"坑"。C语言编译器默认 把
0x0001这样的数字当作int(标准整数)。在STM32中int是 32位 的。问题 (类型不匹配):
16位 ≠ 32位编译器会发出警告:"你正把一个 32位 的值塞进一个 16位 的坑里,这不安全!"
解决方法 (强制类型转换):
(uint16_t)
(uint16_t)是一个C语言语法,意思是"请把后面的数字强制当作16位处理"。最终结果:
16位 = 16位赋值变成了
uint16_t = uint16_t,类型完美匹配,编译器不再警告。

图9:GPIO 寄存器地图 (GPIO_TypeDef)
✨STM32 寄存器地址与C语言
核心原理: STM32中每一个硬件寄存器 (ODR, IDR...)都对应一个固定的物理内存地址 。CPU 通过读写固定的物理内存地址来控制硬件(寄存器)。
基地址 (Base Address):
- 每个外设 (如
GPIOA)都有一个起始地址。GPIOA基地址 =0x40010800GPIOB基地址 =0x40010C00
GPIO_TypeDef结构体:
- 它是一个C语言的 "地图" ,定义了
GPIOA内部所有寄存器(CRL,CRH,IDR,ODR...)的排列顺序和偏移量。
- 偏移地址 (Offset):
- 寄存器在"地图"上相对于基地址的位置。
- 因为寄存器大多是
uint32_t(4字节),所以偏移量是0x00,0x04,0x08...ODR寄存器的偏移量 =0x0C
- 绝对地址 (Absolute Address):
- 绝对地址 = 基地址 + 偏移地址
- 举例 (GPIOA 的 ODR):
0x40010800(GPIOA 基地址) +0x0C(ODR 偏移量) =0x4001080C
- C 代码翻译:
- C 代码:
GPIOA->ODR = 0x0001;GPIOA是一个指向基地址0x40010800的指针 👉 #define GPIOA ((GPIO_TypeDef *)GPIOA_BASE)- ->ODR是C语言语法,告诉编译器去
GPIO_TypeDef结构体"地图"上查找ODR的偏移量(0x0C)。- 编译器操作: 生成机器码,将
0x0001这个值,写入到最终的绝对地址0x4001080C。
✅个人理解:STM32 正是利用了 struct 结构体能自动处理偏移量的特性,来满足了访问硬件寄存器时"基地址 + 偏移地址"的需求。
c
GPIOA->ODR = 0x0001;
GPIOA:获取基地址 (0x40010800)。
>ODR:查找ODR成员对应的偏移地址 (0x0C)。= 0x0001:将值
0x0001写入到算出来的绝对地址 (0x40010800 + 0x0C = 0x4001080C) 中。
🎉代码
c
int main(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_4;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //等价于GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);
}
⚠️ 注意:如果把GPIO_Mode改成GPIO_Mode_Out_OD开漏模式:
GPIO_Mode_Out_OD(开漏输出) 只能拉低,不能拉高。所以只能用于低电平驱动的LED(LED接VCC)。