
04_STM32_接口篇
第一章 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。这些电阻可编程启用或禁用,用于保持引脚在未连接时的稳定电平,防止悬空。
对于上下拉电阻设置一般有三种状态:
-
上拉: 通过配置引脚为上拉,可以让引脚的默认电平为 高电平,此外,配置引脚为上拉还可以 提高端口驱动能力,只不过STM32的内部上拉其实是一种 弱上拉 ,通过上拉输出的驱动能力也是比较弱的,但是比下拉或者悬空状态要好的多。
-
下拉: 通过配置引脚为下拉,可以让引脚的默认电平为 低电平 。
-
悬空: 当在软件中配置上下拉模式为即不上拉,也不下拉的时候,这种状态就是悬空状态,使用仪器测量,一般为 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) // 输入
- 感谢你赐予我前进的力量