4. GPIO 基础知识:推挽输出、开漏输出、复用功能,以及如何将 GPIO 配置为 PWM 输出模式
各位同学,欢迎来到第四章。前面我们聊了 PWM 的基本概念,也看了定时器怎么产生那个方波。但有个关键问题我一直憋着没说——PWM 信号最终是从哪个引脚出去的?
答案当然是 GPIO。但 GPIO 不是只有「输出高」和「输出低」这么简单。你想想看,如果直接把定时器的输出接到引脚上,引脚内部的结构会不会影响波形?会不会把信号搞变形?
嗯,这就要从 GPIO 的几种工作模式讲起了。我个人觉得,搞懂这部分,才算真正入了嵌入式控制的门。
4.1 GPIO 的三种核心输出模式
先看一张总览图,把今天要讲的知识体系串起来。
4.2 推挽输出(Push-Pull Output)
推挽输出,说白了就是引脚内部有两个「开关管」:一个 PMOS 管负责拉高,一个 NMOS 管负责拉低。输出 1 时 PMOS 导通,引脚直接连 VDD;输出 0 时 NMOS 导通,引脚直接连 GND。
这种结构的好处是什么?驱动能力强。我在项目中用推挽输出直接驱动一个 5V 的继电器线圈(当然串了限流电阻),一点问题没有。你想想看,两个管子轮流干活,谁也不闲着,效率自然高。
推挽输出的典型应用场景:
- 驱动 LED(串个 330Ω 电阻)
- 驱动蜂鸣器(有源/无源都行)
- 驱动小功率直流电机(加三极管或 MOS 管驱动)
- 普通 GPIO 高低电平输出
但推挽也有个限制——不能直接「线与」。如果两个推挽输出的引脚直接连在一起,一个输出高一个输出低,那就是短路。我见过有新手把两个推挽引脚并联驱动同一个负载,结果芯片发热严重,差点烧掉。
4.3 开漏输出(Open-Drain Output)
开漏输出就很有意思了。它内部只有 NMOS 管负责拉低,没有 PMOS 管。输出 0 时 NMOS 导通,引脚拉低;输出 1 时 NMOS 关断,引脚处于高阻态。
那高电平谁来提供?外部上拉电阻。你需要在引脚和 VDD 之间接一个电阻,阻值通常选 4.7kΩ 或 10kΩ。
开漏输出最大的优势是支持「线与」——多个开漏引脚可以直接连在一根线上,任何一个输出低,整条线就是低。I²C 总线就是典型的开漏应用。
我的经验:开漏输出还常用于电平转换。比如你的 MCU 是 3.3V,但外设需要 5V 电平。用开漏输出,上拉电阻接到 5V,引脚输出高时就是 5V,完美解决电平不匹配的问题。
不过开漏输出的驱动能力有限,因为高电平全靠上拉电阻「慢慢拉」。频率高了波形会变圆,不适合高速信号。PWM 输出一般不用开漏,除非你确实需要电平转换。
4.4 复用功能(Alternate Function)
好,重点来了。前面讲的推挽和开漏,都是 GPIO 作为普通 I/O 时的模式。但 PWM 信号是从定时器产生的,不是 CPU 直接写寄存器控制的。那怎么让定时器的输出连到引脚上?
答案就是复用功能。
每个 MCU 引脚都可能绑定多个片上外设——定时器、USART、SPI、I²C 等等。你需要告诉芯片:「这个引脚现在归定时器管,CPU 别插手。」这就是复用功能做的事。
注意:配置复用功能时,必须查数据手册或参考手册,确认你用的定时器通道对应哪个引脚。比如 STM32F103 的 TIM2_CH1 默认在 PA0,但重映射后可以到 PA15。配错了引脚,程序跑得再欢也没波形输出。
我曾经在一个项目里,把 TIM3_CH1 配到了 PB4,结果怎么调都没波形。查了半天手册才发现,TIM3_CH1 的默认引脚是 PA6,PB4 需要配置重映射。嗯,从那以后我养成了先看引脚复用表再写代码的习惯。
4.5 如何将 GPIO 配置为 PWM 输出模式
现在我们把前面三个概念串起来。PWM 输出的 GPIO 配置,标准套路是:
- 开启 GPIO 时钟(别忘!)
- 开启定时器时钟
- 配置 GPIO 为复用功能
- 选择复用功能编号(AF1、AF2 等,看芯片)
- 配置输出类型为推挽
- 配置输出速度(一般选 50MHz 或最高档)
- 配置无上拉/无下拉
下面给一个 STM32F4 系列的代码示例,配置 PA0 为 TIM2_CH1 的 PWM 输出:
// 开启 GPIOA 和 TIM2 时钟
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
// 配置 PA0 为复用功能
GPIOA->MODER &= ~GPIO_MODER_MODER0_Msk; // 清除原模式
GPIOA->MODER |= GPIO_MODER_MODER0_1; // 设置为复用功能 (10)
// 选择复用功能 AF1 (TIM2 的复用编号)
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL0_Msk; // 清除原 AF
GPIOA->AFR[0] |= (1 << GPIO_AFRL_AFRL0_Pos); // 设置为 AF1
// 配置输出类型为推挽
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_0; // 0 = 推挽输出
// 配置输出速度 (50MHz)
GPIOA->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR0_Msk;
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR0_1; // 50MHz
// 配置无上拉/无下拉
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR0_Msk; // 00 = 无上拉/下拉
关键点总结:
- PWM 输出必须用复用功能模式,不能用普通推挽输出
- 输出类型选推挽,因为 PWM 需要快速切换高低电平
- 输出速度选高一些(50MHz 或以上),避免波形边沿变缓
- 上拉/下拉电阻不要开,否则会影响 PWM 波形质量
4.6 常见配置错误与避坑
我整理了几个新手最容易踩的坑,你看看自己中过几个:
| 错误现象 | 可能原因 | 解决方法 |
|---|---|---|
| 引脚无波形,始终低电平 | GPIO 模式未配为复用功能 | 检查 MODER 寄存器,确保为 10 |
| 引脚无波形,始终高电平 | 复用功能编号配错 | 查手册确认 AF 编号 |
| 波形有毛刺或边沿不陡 | 输出速度配得太低 | 将 OSPEEDR 设为 50MHz 或最高档 |
| 波形幅值偏低 | 误开了内部下拉电阻 | PUPDR 设为 00,关闭上拉/下拉 |
| 多个 PWM 引脚互相干扰 | 推挽输出直接并联 | 每个 PWM 用独立引脚,不要并联 |
我的习惯:每次配置完 GPIO,先用逻辑分析仪或示波器看引脚状态。如果没波形,先查时钟有没有开,再查复用功能编号,最后查输出速度。按这个顺序排查,90% 的问题都能解决。
4.7 小结
这一章我们聊了 GPIO 的三种输出模式。推挽输出驱动能力强,适合普通高低电平输出;开漏输出支持线与和电平转换,适合总线通信;复用功能则是 PWM 输出的必经之路。
配置 PWM 输出时,记住一个口诀:复用推挽无上下拉。把这七个字记牢,配 PWM 引脚基本不会出错。