第一章 GPIO 基础

1.1 GPIO介绍

GPIO(General Purpose Input/Output,通用输入输出)是STM32微控制器中最基础且核心的外设之一,其功能是通过软件配置实现引脚的电平控制或状态检测。以STM32F407IGH6芯片为例,其GPIO模块具备高度灵活性和丰富的配置选项,支持多种工作模式,适用于数字信号输入输出、外设功能复用等场景。

STM32F407IGH6 的 GPIO 外设具有以下关键特性:

多端口支持

包含GPIOA至GPIOI共9组独立端口,每组最多 16 个引脚,总计最多 140 个 GPIO 引脚(具体数量由封装决定)。

多种工作模式

  • 输入模式:浮空输入(检测外部信号)、上拉/下拉输入(避免悬空干扰)、模拟输入(连接ADC/DAC)。

  • 输出模式:推挽输出(驱动能力强)、开漏输出(支持电平转换或总线通信)。

  • 复用功能模式:引脚可配置为 SPI、I2C、USART 等外设的专用功能引脚。

  • 中断功能:支持引脚电平变化触发中断,用于事件快速响应。

高速驱动能力

引脚最大翻转速度可配置为2MHz、25MHz、50MHz或100MHz(通过GPIOx_OSPEEDR寄存器设置),适应不同场景的时序需求。

灵活配置

每个引脚可独立配置模式、速度、上下拉电阻及复用功能,通过 GPIOx_MODER 、GPIOx_PUPDR等寄存器实现。

例如,当配置PA5引脚为推挽输出模式时,可通过置位/清零GPIOA_ODR寄存器的对应位控制输出高/低电平,驱动LED或控制外部电路;若配置为浮空输入模式,则可读取GPIOA_IDR寄存器状态,检测按键或传感器信号。此外,通过AFRL/AFRH寄存器可将引脚映射为特定外设功能(如PA9/USART1_TX),实现串口通信等复杂功能。

1.2 电路基础

1.2.1 二极管

1.2.2 三极管

1.2.3 MOS管

1.2.4 比较器

1.2.5 施密特触发器

1.3 功能框图

1.3.1 端口部分

1、保护二极管

I/O 引脚两端各接有两个保护二极管,一个接至 VDD,一个接至 VSS,用于防止输入电压超出供电电压范围(即高于 VDD 或低于 VSS)时损坏芯片,同时具有静电防护功能。

当然,尽管在STM32中有这样的保护设计,但是并不代表可以直接使用芯片去连接大功率器件,例如 直接 驱动电机 启动加热设计 等,这种操作导致的结果,要么是设备无反应,要么直接 点亮一颗MCU

2、上下拉电阻

I/O 引脚连接有内部上拉和下拉电阻,分别连接至 VDD 和 VSS。这些电阻可编程启用或禁用,用于保持引脚在未连接时的稳定电平,防止悬空。

对于上下拉电阻设置一般有三种状态:

  1. 上拉: 通过配置引脚为上拉,可以让引脚的默认电平为 高电平,此外,配置引脚为上拉还可以 提高端口驱动能力,只不过STM32的内部上拉其实是一种 弱上拉 ,通过上拉输出的驱动能力也是比较弱的,但是比下拉或者悬空状态要好的多。

  2. 下拉: 通过配置引脚为下拉,可以让引脚的默认电平为 低电平

  3. 悬空: 当在软件中配置上下拉模式为即不上拉,也不下拉的时候,这种状态就是悬空状态,使用仪器测量,一般为 1v多一点的状态,这种状态的电平输出是不确定值。

1.3.2 输入部分

1、施密特触发器

输入路径中包含施密特触发器,具备一定的电平迟滞功能,可以提高抗干扰能力,适用于噪声较大的输入信号。

2、输入数据寄存器

输入信号经过施密特触发器后,送入输入数据寄存器中,CPU 可通过读取该寄存器获得当前引脚电平状态。

3、复用功能输入

引脚支持复用功能,通过内部逻辑控制可以将引脚用作通用输入、外设信号输入(如串口、定时器等),实现多功能配置。

4、模拟输入

部分引脚还可配置为模拟输入端口,用于连接 ADC 模块,输入模拟电压信号进行转换。

1.3.3 输出部分

1、推挽和开漏

输出驱动部分由 P-MOS 和 N-MOS 管组成,可配置为推挽输出或开漏输出。

  • 推挽输出:P-MOS 和 N-MOS 轮流导通,能提供较强的高、低电平驱动能力。

  • 开漏输出:仅 N-MOS 导通,需要外部上拉电阻,适合用于总线或中断信号。

2、读出数据寄存器

输出的数据由输出数据寄存器控制,同时支持读取输出状态,用于验证输出是否成功设置。

3、置位\复位寄存器

设置或清除输出电平时,可通过写入置位寄存器(置 1)或复位寄存器(清 0)来控制对应 I/O 引脚的电平,便于实现单个引脚操作而不影响其他引脚。

1.4 工作模式

STM32 的每个 I/O 引脚都可配置为多种工作模式,适用于不同的应用场景。主要包括输入模式、输出模式和复用模式。

1.4.1 输入模式

输入模式主要用于采集外部电平信号,结合上拉、下拉和模拟功能可分为以下几种:

1、浮空输入

  • 引脚不连接任何内部上下拉电阻。

  • 适合外部已确定电平的电路,如常开按键、信号检测等。

  • 电平不稳定时可能引起悬空,易受干扰。

2、上拉输入

  • 内部连接一个上拉电阻到 VDD。

  • 默认引脚电平为高,适合接地有效的输入信号(如按键下拉触发)。

3、下拉输入

  • 内部连接一个下拉电阻到 VSS。

  • 默认引脚电平为低,适合接高电平有效的输入信号。

4、模拟输入

  • 关闭输入/输出电路,避免干扰信号产生。

  • 仅保留与 ADC 模块相关的模拟路径。

  • 适合连接电压、电流、传感器等模拟量信号。

1.4.2 输出模式

输出模式用于驱动外部电路,通过不同驱动方式控制引脚的电平状态:

1、开漏输出

  • 仅 N-MOS 导通,低电平由芯片输出,高电平需外部上拉电阻。

  • 可用于多设备共享通信线路(如 I²C 总线)。

  • 输出结构灵活,但不能主动输出高电平。

2、推挽输出

  • P-MOS 与 N-MOS 交替导通,能主动输出高电平或低电平。

  • 驱动能力强,适合驱动 LED、继电器等负载。

  • 常用于通用 GPIO 输出。

1.4.3 复用模式

复用模式用于将引脚与片内外设(如 USART、SPI、TIM)连接,实现数据通信、控制等功能。

1、推挽式复用功能

  • 外设控制引脚输出,采用推挽结构,驱动能力强。

  • 常用于串口发送、PWM 输出、SPI 等高速通信接口。

2、开漏式复用功能

  • 外设控制引脚输出,采用开漏结构,需外接上拉电阻。

  • 适合用于 I²C 总线、开漏信号通信等场景。

1.5 寄存器介绍

1.5.1 端口配置寄存器

1、GPIO 端口模式寄存器 - MODER

2、 GPIO 端口输出类型寄存器 - OTYPER

3、GPIO 端口输出速度寄存器 - OSPEEDR

4、GPIO 端口上拉/下拉寄存器 - PUPDR

1.5.2 输入/输出 寄存器

1、GPIO 端口输入数据寄存器 - IDR

2、GPIO 端口输出数据寄存器 - ODR

3、GPIO 端口置位/复位寄存器 - BSRR

1.5.3 复用功能寄存器

1、GPIO 复用功能低位寄存器 - AFRL

2、GPIO 复用功能高位寄存器 - AFRH

1.6 固件库介绍

1.6.1 端口配置

STM32 中配置 GPIO 端口,主要通过配置结构体 GPIO_InitTypeDef,并调用配置函数 GPIO_Init() 完成。以下是相关结构体和枚举的整理说明:

1、配置结构体

用于设置 GPIO 的各项属性:

typedef struct
{
  uint32_t GPIO_Pin;              // 要配置的 GPIO 引脚,可选 GPIO_Pin_0 ~ GPIO_Pin_15

  GPIOMode_TypeDef GPIO_Mode;     // 引脚模式:输入/输出/复用/模拟

  GPIOSpeed_TypeDef GPIO_Speed;   // 引脚输出速度

  GPIOOType_TypeDef GPIO_OType;   // 输出类型:推挽/开漏

  GPIOPuPd_TypeDef GPIO_PuPd;     // 上拉/下拉配置
} GPIO_InitTypeDef;

2、模式枚举

设置 GPIO 的工作模式:

typedef enum
{ 
  GPIO_Mode_IN   = 0x00, // 输入模式
  GPIO_Mode_OUT  = 0x01, // 输出模式
  GPIO_Mode_AF   = 0x02, // 复用功能模式
  GPIO_Mode_AN   = 0x03  // 模拟模式
} GPIOMode_TypeDef;

3、输出模式枚举

配置输出引脚是推挽还是开漏:

typedef enum
{ 
  GPIO_OType_PP = 0x00, // 推挽输出
  GPIO_OType_OD = 0x01  // 开漏输出
} GPIOOType_TypeDef;

4、速度枚举

配置输出速度,影响信号上升下降时间:

typedef enum
{ 
  GPIO_Low_Speed     = 0x00, // 低速
  GPIO_Medium_Speed  = 0x01, // 中速
  GPIO_Fast_Speed    = 0x02, // 快速
  GPIO_High_Speed    = 0x03  // 高速
} GPIOSpeed_TypeDef;

5、上/下拉枚举

设置是否使用内部上拉或下拉电阻:

typedef enum
{ 
  GPIO_PuPd_NOPULL = 0x00, // 无上拉下拉
  GPIO_PuPd_UP     = 0x01, // 上拉
  GPIO_PuPd_DOWN   = 0x02  // 下拉
} GPIOPuPd_TypeDef;

6、配置函数

// 1. 复位指定端口的 GPIO 配置
void GPIO_DeInit(GPIO_TypeDef* GPIOx);

// 2. 初始化指定 GPIO 引脚
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

// 3. 初始化结构体为默认值
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);

// 4. 锁定指定引脚配置,防止被误修改
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

1.6.2 输入/输出函数

1、输入函数

// 读取指定引脚输入电平(0或1)
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); 

// 读取整个端口输入值(16位)
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx); 

// 读取指定引脚当前输出电平
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); 

// 读取整个端口输出值
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx); 

2、输出函数

// 将指定引脚输出高电平
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);   

// 将指定引脚输出低电平
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); 

// 写指定引脚为高或低电平(Bit_SET / Bit_RESET)
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal); 

// 写整个端口输出值(16位)
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal); 

// 翻转引脚当前电平(高->低,低->高)
void GPIO_ToggleBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); 

1.6.3 端口复用函数

// 将指定引脚设置为某种复用功能(如 USART、TIM、SPI 等)
void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF); 

第二章 GPIO 输出示例

2.1 示例程序:点亮LED

2.1.1 硬件分析

1、核心板

由图纸可知,LED 为低电平驱动

2、扩展板

由图纸可知,LED 为高电平驱动

2.1.3 程序编写

1、初始化

void LEDx_LED_Init(void)
{
	/* 1、启动时钟 */
	RCC_AHB1PeriphClockCmd(	RCC_AHB1Periph_GPIOC , ENABLE );
	

	/* 2、配置GPIO */
	
	// 2.1 定义一个 GPIO_InitTypeDef 类型的结构体
	GPIO_InitTypeDef GPIO_InitStruct;
	
	// 2.2 配置	GPIO 模式 为 输出模式
	GPIO_InitStruct.GPIO_Mode 	= GPIO_Mode_OUT;
	
	// 2.3 配置	GPIO 输出模式 为 推挽输出
	GPIO_InitStruct.GPIO_OType 	= GPIO_OType_PP;
	
	// 2.4 配置	GPIO 引脚
	GPIO_InitStruct.GPIO_Pin 	= GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_13;
	
	// 2.5 配置	GPIO 模式 为 输出模式
	GPIO_InitStruct.GPIO_PuPd 	= GPIO_PuPd_UP;
	
	// 2.6 配置	GPIO 模式 为 输出模式
	GPIO_InitStruct.GPIO_Speed 	= GPIO_Speed_2MHz;
	
	// 2.7 初始化 GPIO
	GPIO_Init(LEDx_GPIO_PORT, &GPIO_InitStruct);
	
	/* 3、设置初始电平 */
	
	GPIO_ResetBits(LEDx_GPIO_PORT, GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_13);

}
void D2_LED_Init(void)
{
	/* 1、启动时钟 */
	RCC_AHB1PeriphClockCmd(	RCC_AHB1Periph_GPIOF , ENABLE );
	

	/* 2、配置GPIO */
	
	// 2.1 定义一个 GPIO_InitTypeDef 类型的结构体
	GPIO_InitTypeDef GPIO_InitStruct;
	
	// 2.2 配置	GPIO 模式 为 输出模式
	GPIO_InitStruct.GPIO_Mode 	= GPIO_Mode_OUT;
	
	// 2.3 配置	GPIO 输出模式 为 推挽输出
	GPIO_InitStruct.GPIO_OType 	= GPIO_OType_PP;
	
	// 2.4 配置	GPIO 引脚
	GPIO_InitStruct.GPIO_Pin 	= D1_GPIO_Pin;
	
	// 2.5 配置	GPIO 模式 为 输出模式
	GPIO_InitStruct.GPIO_PuPd 	= GPIO_PuPd_UP;
	
	// 2.6 配置	GPIO 模式 为 输出模式
	GPIO_InitStruct.GPIO_Speed 	= GPIO_Speed_2MHz;
	
	// 2.7 初始化 GPIO
	GPIO_Init(D1_GPIO_PORT, &GPIO_InitStruct);
	
	/* 3、设置初始电平 */
	GPIO_SetBits(D1_GPIO_PORT, D1_GPIO_Pin);

}

2、操作GPIO

// 点亮LED 

// 熄灭LED

// 反转LED



2.2 示例程序:启动蜂鸣器

2.2.1 硬件分析

2.2.3 程序编写

2.3 示例程序:震动马达

2.3.1 硬件分析

2.3.2 程序编写

2.4 示例程序:散热风扇

2.4.1 硬件分析

2.4.2 程序编写

第三章 GPIO 输入模式

3.1 示例程序:按钮

1、硬件分析

3.2 示例程序:光电开关

3.3 扩展板:火焰传感器

第三章 位段操作

3.1 位段介绍

在嵌入式开发中,位操作是一种非常常见的方式,尤其是在控制寄存器、IO口时。但如果我们能像访问变量那样,直接访问某一位,是不是就更方便了?——这就是“位带操作”的由来。

3.1.1 位段介绍

位段(Bit-Band)是 ARM Cortex-M3/M4/M7 内核提供的一种特殊机制。它把一块内存区域(比如 SRAM 或 外设寄存器)中的每一位映射到另一个区域的一个字地址上。通过这个地址,我们可以像访问变量一样,读写这一位的值

换句话说:

一位 → 映射成一个完整的32位地址

这就相当于 ARM 偷偷帮我们做了件好事:

  • 不用再使用掩码操作(什么 |=, &=, ~ 等)

  • 可以像操作普通变量一样操作某一位

1、位段区域

在进行位段操作之前,需要对位段的区域进行了解:

  • 位段区域:支持位段操作的存储器地址区域,以GPIO 为例子,也就是我们要操作的区域

  • 位段别名:访问位段别名(位段操作)会引起位段区域的访问。

在 ARM Cortex-M3 和 Cortex-M4 的设备中,对可以位段操作的存储器区域有两个存储空间,现在我们一一分析:

SRAM

  • 位段区域 : 0x20000000 ~ 0x200FFFFF

  • 位段别名 : 0x22000000

外设

  • 位段区域 : 0x40000000 ~ 0x200FFFFF

  • 位段别名 : 0x42000000

2、映射示例

SRAM 映射示例

位段区域地址(Bit-Band Region)对应别名地址(Alias)
0x20000000 bit[0]0x22000000 bit[0]
0x20000000 bit[1]0x22000004 bit[0]
0x20000000 bit[2]0x22000008 bit[0]
......
0x20000000 bit[31]0x2200007C bit[0]
0x20000004 bit[0]0x22000080 bit[0]
......
0x20000004 bit[31]0x220000FC bit[0]
......
0x200FFFFC bit[31]0x23FFFFFC bit[0]

外设 映射示例

位段区域地址(Bit-Band Region)对应别名地址(Alias)
0x40000000 bit[0]0x42000000 bit[0]
0x40000000 bit[0]0x42000004 bit[0]
0x40000000 bit[0]0x42000008 bit[0]
......
0x40000000 bit[31]0x4200007C bit[0]
0x40000004 bit[1]0x42000080 bit[0]
......
0x40000004 bit[31]0x420000FC bit[0]
......
0x400FFFFC bit[31]0x43FFFFFC bit[0]

3.1.2 位带操作公式

对于某个位的访问地址,我们可以用下面这个公式来计算:

SRAM 别名地址 = 0x22000000 + ( 寄存器地址 - 0x20000000 ) * 32 + ( 位编号 * 4 )
外设    别名地址 =  0x42000000 + ( 寄存器地址 - 0x40000000 ) * 32 + ( 位编号 * 4 )

3.2 位段操作

新建一个 Bit_Band.h

// IO口操作的宏定义

// BITBAND宏用于访问指定地址和位号的内存映射

#define BITBAND(addr, bitnum) ((addr & 0xF0000000) + 0x2000000 + ((addr & 0xFFFFF) << 5) + (bitnum << 2))


// MEM_ADDR宏用于获取某个地址的值(作为指针)

#define MEM_ADDR(addr)  *((volatile unsigned long *)(addr))


// BIT_ADDR宏用于通过BITBAND映射访问具体位

#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))

  

// 以下是各个GPIO口的输出数据寄存器(ODR)和输入数据寄存器(IDR)地址映射

  

// GPIOA~GPIOI的输出数据寄存器地址(ODR)

#define GPIOA_ODR_Addr    (GPIOA_BASE + 20) // 0x40020014

#define GPIOB_ODR_Addr    (GPIOB_BASE + 20) // 0x40020414

#define GPIOC_ODR_Addr    (GPIOC_BASE + 20) // 0x40020814

#define GPIOD_ODR_Addr    (GPIOD_BASE + 20) // 0x40020C14

#define GPIOE_ODR_Addr    (GPIOE_BASE + 20) // 0x40021014

#define GPIOF_ODR_Addr    (GPIOF_BASE + 20) // 0x40021414

#define GPIOG_ODR_Addr    (GPIOG_BASE + 20) // 0x40021814

#define GPIOH_ODR_Addr    (GPIOH_BASE + 20) // 0x40021C14

#define GPIOI_ODR_Addr    (GPIOI_BASE + 20) // 0x40022014

  

// GPIOA~GPIOI的输入数据寄存器地址(IDR)

#define GPIOA_IDR_Addr    (GPIOA_BASE + 16) // 0x40020010

#define GPIOB_IDR_Addr    (GPIOB_BASE + 16) // 0x40020410

#define GPIOC_IDR_Addr    (GPIOC_BASE + 16) // 0x40020810

#define GPIOD_IDR_Addr    (GPIOD_BASE + 16) // 0x40020C10

#define GPIOE_IDR_Addr    (GPIOE_BASE + 16) // 0x40021010

#define GPIOF_IDR_Addr    (GPIOF_BASE + 16) // 0x40021410

#define GPIOG_IDR_Addr    (GPIOG_BASE + 16) // 0x40021810

#define GPIOH_IDR_Addr    (GPIOH_BASE + 16) // 0x40021C10

#define GPIOI_IDR_Addr    (GPIOI_BASE + 16) // 0x40022010

  

// IO口操作宏定义, 用于单一IO口的输入输出操作

// 注意:确保n的值小于16,表示具体的GPIO管脚编号(0~15)

  

// GPIOA的输出和输入操作

#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr, n)  // 输出

#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr, n)  // 输入

  

// GPIOB的输出和输入操作

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr, n)  // 输出

#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr, n)  // 输入

  

// GPIOC的输出和输入操作

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr, n)  // 输出

#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr, n)  // 输入

  

// GPIOD的输出和输入操作

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr, n)  // 输出

#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr, n)  // 输入

  

// GPIOE的输出和输入操作

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr, n)  // 输出

#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr, n)  // 输入

  

// GPIOF的输出和输入操作

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr, n)  // 输出

#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr, n)  // 输入

  

// GPIOG的输出和输入操作

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr, n)  // 输出

#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr, n)  // 输入

  

// GPIOH的输出和输入操作

#define PHout(n)   BIT_ADDR(GPIOH_ODR_Addr, n)  // 输出

#define PHin(n)    BIT_ADDR(GPIOH_IDR_Addr, n)  // 输入

  

// GPIOI的输出和输入操作

#define PIout(n)   BIT_ADDR(GPIOI_ODR_Addr, n)  // 输出

#define PIin(n)    BIT_ADDR(GPIOI_IDR_Addr, n)  // 输入