2、硬件平台介绍:常用MCU(STM32、ESP32)的PWM定时器资源、PWM输出引脚与复用功能

做PWM调试,第一步得搞清楚你手头的MCU到底有多少“家底”。说白了,就是定时器资源和引脚分布。我见过不少新手,代码写得飞起,结果发现引脚不对、定时器不够用,最后全得重来。今天咱们就把STM32和ESP32这两大主流平台的PWM家底翻个底朝天。

2.1 STM32的PWM定时器资源

STM32的定时器体系,我习惯把它分成三个梯队:

  • 高级定时器(TIM1/TIM8):能输出6路互补PWM,带死区插入、刹车功能。做电机控制、逆变器,首选它们。
  • 通用定时器(TIM2~TIM5,TIM9~TIM14):最常用的PWM发生器。4路独立通道,支持向上/向下/中央对齐计数。
  • 基本定时器(TIM6/TIM7):不能输出PWM,只能做定时或触发DAC。别在这上面浪费时间找PWM。

举个例子,STM32F103C8T6这颗“小蓝板”上的资源:

定时器 类型 通道数 PWM输出引脚 备注
TIM1 高级 4(互补6路) PA8~PA11 16位,支持死区
TIM2 通用 4 PA0~PA3 16位,最常用
TIM3 通用 4 PA6, PA7, PB0, PB1 16位
TIM4 通用 4 PB6~PB9 16位
我的习惯:做项目前先画一张“定时器-引脚”映射表。把要用到的PWM通道、对应引脚、复用功能号(AF1/AF2)全列出来。省得焊好板子才发现引脚冲突。

2.2 STM32的引脚复用功能

STM32的引脚不是“一根筋”。同一个引脚,可能同时属于GPIO、USART、SPI、定时器通道。你得通过配置GPIO的复用功能(AF)来“告诉”芯片:这个引脚现在归定时器管。

以STM32F1系列为例,配置PWM输出时,GPIO要设成复用推挽输出(AF_PP),速度建议设到50MHz以上。我踩过坑:速度设成2MHz,PWM频率高了之后波形直接变形。

// STM32F1 TIM2_CH1 PWM输出配置示例
GPIO_InitTypeDef GPIO_InitStruct = {0};

__HAL_RCC_TIM2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();

GPIO_InitStruct.Pin = GPIO_PIN_0;          // PA0 = TIM2_CH1
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;    // 复用推挽输出
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;  // 50MHz
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
我曾经犯过的错:在STM32G0系列上,复用功能号从AF0到AF7,TIM1的CH1可能对应AF2。没查数据手册直接抄F1的代码,结果PWM死活不出来。后来发现G0的复用映射表和F1完全不同。切记:每个系列的数据手册都要看“Alternate function mapping”那张表。

2.3 ESP32的PWM资源

ESP32的PWM体系和STM32差别很大。它没有传统意义上的“定时器通道”,而是用LED PWM控制器(LEDC)来实现。说白了,这是一个专门为PWM设计的硬件模块,和LED驱动无关,名字容易误导人。

ESP32的LEDC资源:

  • 8个通道:分成两组,每组4个通道(0~3,4~7)
  • 2个定时器:每组共享一个定时器,但每个通道可以独立设置占空比
  • 16位分辨率:占空比精度0~65535
  • 频率范围:从1Hz到40MHz,但分辨率越高,最高频率越低

你想想看,8个通道听起来不少,但如果你要控制8个舵机,每个舵机需要不同的频率,那就麻烦了——因为同一组的4个通道共享一个定时器,频率必须相同。

LEDC组 定时器 通道 可输出引脚 特点
组0 定时器0 0~3 任意GPIO 频率共享,占空比独立
组1 定时器1 4~7 任意GPIO 频率共享,占空比独立
ESP32的优势:PWM输出可以映射到任意GPIO,不像STM32那样固定引脚。这给PCB布局带来极大便利。但代价是:同一组通道频率必须一致,灵活性不如STM32的独立定时器。

2.4 ESP32的PWM配置实战

ESP32用Arduino框架配置PWM很简单,但底层逻辑得清楚:

// ESP32 LEDC PWM配置示例
// 设置LEDC通道0,频率5000Hz,分辨率8位
ledcSetup(0, 5000, 8);
// 将通道0绑定到GPIO 18
ledcAttachPin(18, 0);
// 输出50%占空比(8位分辨率下,255的一半≈127)
ledcWrite(0, 127);

这里有个细节:ledcSetup的第三个参数是分辨率位数。8位分辨率下,占空比范围0~255;10位就是0~1023。分辨率越高,频率上限越低。我做过测试:16位分辨率下,最高频率只能到几kHz,做电机控制还行,做LED调光完全够用。

我的建议:做呼吸灯、LED调光用8位分辨率就够了。做舵机控制用10~12位。做高精度模拟输出(比如音频),才需要14~16位。别盲目追求高分辨率,频率上不去反而坏事。

2.5 两张图看懂PWM资源体系

下面这张图,我画的是STM32和ESP32的PWM资源对比框架。你看一眼就能明白两者的设计思路差异:

STM32 vs ESP32 PWM资源体系对比 STM32 PWM体系 高级定时器 TIM1/TIM8 通用定时器 TIM2~TIM5 6路互补PWM+死区 4路独立PWM 引脚固定:需查复用功能映射表 ESP32 PWM体系 LEDC组0(通道0~3) LEDC组1(通道4~7) 共享定时器0频率 共享定时器1频率 引脚自由:任意GPIO均可输出 STM32:定时器独立、引脚固定、适合多频率场景 ESP32:通道共享频率、引脚自由、适合同频率多路输出

2.6 选型建议:什么时候用谁?

我个人的经验总结:

  • 做电机控制、逆变器、开关电源:选STM32的高级定时器。死区控制、刹车功能、互补输出,这些是硬需求。
  • 做多路LED调光、舵机群控:ESP32更合适。引脚任意分配,省去PCB布线烦恼。
  • 做音频PWM(Class-D功放):STM32的定时器精度更高,频率更稳定。ESP32的LEDC在高频下抖动偏大。
  • 做IoT设备,需要WiFi+少量PWM:ESP32一把梭。省一颗MCU,省一堆麻烦。
注意:ESP32的LEDC在深度睡眠模式下会停止工作。如果你需要PWM在低功耗状态下持续输出(比如呼吸灯唤醒指示),得用RTC定时器+外部电路实现。这个坑我踩过,项目差点因为这个延期。

嗯,硬件平台这部分就聊到这儿。记住一句话:PWM调试,七分在硬件规划,三分在代码实现。把定时器资源和引脚映射搞清楚,后面调试才能顺风顺水。