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,其实就三步。我习惯这么记:
- 使能时钟 —— GPIO时钟和定时器时钟都得打开
- 配置GPIO模式 —— 设为复用功能,并指定复用到的外设
- 配置定时器 —— 设置通道为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输出正常?
代码写完了,怎么验证?我一般这么做:
- 看寄存器 —— 读定时器的CCR1寄存器,确认写入的占空比值正确。
- 看引脚电平 —— 用万用表测引脚电压。50%占空比时,如果PWM频率够高,万用表会显示约VCC/2的电压。
- 看波形 —— 最靠谱的方法是用示波器。看频率对不对,占空比对不对,波形有没有毛刺。
我记得有一次,示波器显示频率是2kHz,但我配的是1kHz。查了半天,发现是定时器时钟源选错了——用了内部低速时钟。这种问题,光看代码很难发现,必须上示波器。
4.7 小结
GPIO复用配置PWM输出,是嵌入式驱动开发的基础操作。说白了就是三步:开时钟、配引脚、配定时器。但每一步都有细节,漏一个都不行。
我个人建议,刚开始学的时候,先拿一个定时器、一个通道、一个引脚,把流程跑通。别一上来就搞多通道、多定时器。基础打牢了,后面自然就快了。
下一讲,咱们会深入PWM的占空比和频率计算,到时候会用到这节课的配置。所以,这节课的内容一定要亲手敲一遍代码,别光看。