4. GPIO 基础知识:推挽输出、开漏输出、复用功能,以及如何将 GPIO 配置为 PWM 输出模式

各位同学,欢迎来到第四章。前面我们聊了 PWM 的基本概念,也看了定时器怎么产生那个方波。但有个关键问题我一直憋着没说——PWM 信号最终是从哪个引脚出去的?

答案当然是 GPIO。但 GPIO 不是只有「输出高」和「输出低」这么简单。你想想看,如果直接把定时器的输出接到引脚上,引脚内部的结构会不会影响波形?会不会把信号搞变形?

嗯,这就要从 GPIO 的几种工作模式讲起了。我个人觉得,搞懂这部分,才算真正入了嵌入式控制的门。

4.1 GPIO 的三种核心输出模式

先看一张总览图,把今天要讲的知识体系串起来。

GPIO 输出模式与 PWM 配置知识体系 GPIO 输出模式 推挽输出 开漏输出 复用功能 推挽特点 • 强驱动:PMOS+NMOS 交替导通 • 无需上拉电阻 • 适合驱动 LED、蜂鸣器 开漏特点 • 只能输出低电平 • 高电平需外部上拉 • 支持线与、电平转换 复用特点 • 引脚归片上外设控制 • 输出源由定时器决定 • 配置错误则无波形 PWM 输出配置:复用推挽输出 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 配置,标准套路是:

  1. 开启 GPIO 时钟(别忘!)
  2. 开启定时器时钟
  3. 配置 GPIO 为复用功能
  4. 选择复用功能编号(AF1、AF2 等,看芯片)
  5. 配置输出类型为推挽
  6. 配置输出速度(一般选 50MHz 或最高档)
  7. 配置无上拉/无下拉

下面给一个 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 引脚基本不会出错。