4. GPIO与PWM输出:配置MCU的GPIO复用功能,将定时器通道映射到特定引脚,输出PWM信号。

好,咱们进入第四讲。这一讲要解决一个很实际的问题:怎么让MCU的引脚输出PWM信号。

你可能会想,不就是输出个方波吗?其实没那么简单。MCU的引脚默认是普通GPIO,你要让它输出PWM,得先告诉它:「嘿,别当普通IO了,去连定时器通道吧。」这个过程,就是GPIO的复用功能配置。

我个人习惯把这一步叫做「引脚认亲」——你得把定时器的通道和具体的物理引脚绑定起来。绑不对,PWM就出不来。

4.1 为什么需要GPIO复用?

MCU的引脚资源是有限的。一个引脚可能同时连接着USART、SPI、定时器等多个外设。你不能让它们同时用,得选一个。

GPIO复用,说白了就是让引脚放弃默认的通用输入输出功能,转而连接到某个特定外设的接口上。对于PWM输出,我们就是把定时器的某个通道(比如TIM1_CH1)映射到对应的引脚(比如PA8)。

核心要点: 不配置复用功能,定时器就算跑得再欢,PWM信号也出不了芯片。引脚只会输出高电平或低电平,而不是你想要的PWM波形。

我在项目中遇到过一位同事,调了半天PWM没输出,最后发现是GPIO模式配成了推挽输出,而不是复用推挽输出。嗯,这种坑踩一次就记住了。

4.2 配置步骤:三步走

配置GPIO复用功能输出PWM,其实就三步。我习惯这么记:

  1. 使能时钟 —— GPIO时钟和定时器时钟都得打开
  2. 配置GPIO模式 —— 设为复用功能,并指定复用到的外设
  3. 配置定时器 —— 设置通道为PWM模式,输出极性等

你想想看,这三步缺一不可。时钟没开,寄存器写不进去;模式没配对,信号出不来;定时器没配好,波形不对。

4.3 代码示例:以STM32F4为例

咱们直接上代码。以STM32F407为例,用TIM1的通道1输出PWM,引脚是PA8。

// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

// 2. 配置GPIO复用功能
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;       // 复用功能模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;     // 推挽输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;       // 上拉
GPIO_Init(GPIOA, &GPIO_InitStructure);

// 3. 将PA8连接到TIM1_CH1
GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_TIM1);

// 4. 配置定时器PWM
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;

TIM_TimeBaseStructure.TIM_Prescaler = 83;          // 84MHz / (83+1) = 1MHz
TIM_TimeBaseStructure.TIM_Period = 999;            // 1MHz / (999+1) = 1kHz
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;  // PWM模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 500;               // 占空比50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);

// 5. 使能定时器
TIM_Cmd(TIM1, ENABLE);

// 注意:高级定时器TIM1还需要使能主输出
TIM_CtrlPWMOutputs(TIM1, ENABLE);

个人经验: 很多新手会漏掉最后一行 TIM_CtrlPWMOutputs。高级定时器(TIM1、TIM8)有个主输出使能位,不打开它,PWM就是出不来。我当年调试TIM1时,卡了整整一个下午才发现这个问题。

4.4 引脚映射表:你得知道哪个引脚能用

不同MCU的引脚映射不一样。同一个定时器通道,在不同型号上可能对应不同引脚。你得查数据手册或参考手册。

我整理了一个常见映射表,以STM32F4系列为例:

定时器 通道 可用引脚 复用功能号
TIM1 CH1 PA8, PE9 AF1
TIM1 CH2 PA9, PE11 AF1
TIM2 CH1 PA0, PA5, PA15 AF1
TIM3 CH1 PA6, PB4, PC6 AF2
TIM4 CH1 PB6, PD12 AF2

为什么要记这个?因为你在画PCB时就得确定引脚分配。等板子打回来再改,那就得飞线了。我吃过这个亏,所以现在做项目前一定会先确认引脚复用关系。

4.5 避坑指南

我曾经犯过的几个错误:

  • 复用功能号配错 —— 同一个引脚可能支持多个外设的复用,你得选对AF号。比如PA8可以接TIM1_CH1(AF1),也可以接USART1_CK(AF7)。配错了,PWM出不来,串口倒是能用了。
  • 忘记配置GPIO速度 —— PWM频率较高时,GPIO速度要配够。我见过有人配了2MHz,结果输出50kHz的PWM波形都变形了。
  • 上下拉电阻冲突 —— 有些外设对引脚电平有要求。PWM输出一般用浮空或上拉,别配成下拉,否则波形底部会被拉低。

4.6 调试技巧:怎么确认PWM输出正常?

代码写完了,怎么验证?我一般这么做:

  1. 看寄存器 —— 读定时器的CCR1寄存器,确认写入的占空比值正确。
  2. 看引脚电平 —— 用万用表测引脚电压。50%占空比时,如果PWM频率够高,万用表会显示约VCC/2的电压。
  3. 看波形 —— 最靠谱的方法是用示波器。看频率对不对,占空比对不对,波形有没有毛刺。

我记得有一次,示波器显示频率是2kHz,但我配的是1kHz。查了半天,发现是定时器时钟源选错了——用了内部低速时钟。这种问题,光看代码很难发现,必须上示波器。

4.7 小结

GPIO复用配置PWM输出,是嵌入式驱动开发的基础操作。说白了就是三步:开时钟、配引脚、配定时器。但每一步都有细节,漏一个都不行。

我个人建议,刚开始学的时候,先拿一个定时器、一个通道、一个引脚,把流程跑通。别一上来就搞多通道、多定时器。基础打牢了,后面自然就快了。

下一讲,咱们会深入PWM的占空比和频率计算,到时候会用到这节课的配置。所以,这节课的内容一定要亲手敲一遍代码,别光看。