引言

PWM(脉宽调制)是嵌入式系统中最常用的输出手段,从电机调速到LED调光,其精度直接影响控制质量。很多开发者习惯用RTOS的软件定时器(如FreeRTOS的vTimerCreate)来生成PWM,却忽略了它和硬件定时器在时序确定性上的巨大鸿沟。本文将深入分析两者在PWM场景下的差异,并给出清晰的选型边界。

1. 原理对比:软件定时器 vs 硬件定时器

1.1 硬件定时器:硬件级的确定性

硬件定时器由芯片内部独立时钟驱动,计数过程不依赖CPU。当计数器达到比较值时,硬件自动翻转GPIO引脚(如STM32的TIMx_CHx),整个过程零CPU干预。其精度由时钟源决定,例如STM32F407的TIM2挂载在84MHz总线上,可实现11.9ns的计数分辨率。

1.2 RTOS软件定时器:基于Tick的软实现

软件定时器本质是RTOS内核维护的定时器列表,依赖系统Tick中断(通常1ms)进行递减计数。当定时器到期时,回调函数在TimerTask上下文中执行,但GPIO翻转仍需通过软件操作完成。这意味着:

  • 分辨率受Tick周期限制:无法实现小于1ms的精确延时
  • 抖动不可避免:回调执行受任务调度影响,可能被高优先级任务抢占
  • CPU占用:每个周期都需要CPU参与GPIO操作

2. PWM输出精度的量化差异

2.1 抖动(Jitter)对比

硬件定时器PWM的周期抖动通常在纳秒级,而软件定时器PWM的抖动可达数百微秒。以下是一个实测场景(FreeRTOS + STM32F103,Tick=1ms):

// 软件定时器PWM:周期10ms,占空比50%
void vPwmCallback(TimerHandle_t xTimer) {
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
}
// 创建定时器
TimerHandle_t xPwmTimer = xTimerCreate("PWM", pdMS_TO_TICKS(10), pdTRUE, NULL, vPwmCallback);
xTimerStart(xPwmTimer, 0);

当系统同时运行UART接收和LCD刷新任务时,示波器显示周期抖动达±0.8ms,占空比误差±8%。而硬件定时器PWM(TIM3_CH1)在相同负载下,抖动<1μs。

2.2 分辨率限制

软件定时器的最小脉宽等于Tick周期(如1ms),无法生成更高频率的PWM。例如要输出20kHz PWM(周期50μs),软件定时器完全不可行。硬件定时器则可轻松达到MHz级。

3. 选型边界:何时用软件定时器,何时用硬件定时器

3.1 软件定时器的适用场景

  • 低频信号:频率低于100Hz,且占空比精度要求不高(如LED呼吸灯)
  • 非实时控制:如状态机超时、按键消抖
  • 多路输出:硬件定时器资源不足时,用软件模拟多个低速PWM

3.2 硬件定时器的强制场景

  • 高频PWM:>1kHz,如电机驱动、开关电源
  • 高精度占空比:如PID调节输出,需要微秒级分辨率
  • 实时性要求:如电流环控制,抖动必须控制在微秒内

4. 实战:STM32硬件定时器PWM配置

以STM32F407输出1kHz、50%占空比PWM为例(TIM4_CH1,PA6):

// 初始化GPIO
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// 初始化TIM4
__HAL_RCC_TIM4_CLK_ENABLE();
TIM_HandleTypeDef htim4 = {0};
htim4.Instance = TIM4;
htim4.Init.Prescaler = 84-1;  // 84MHz/84=1MHz
htim4.Init.Period = 1000-1;   // 1MHz/1000=1kHz
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&htim4);

// 配置PWM通道
TIM_OC_InitTypeDef sConfigOC = {0};
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500;  // 50%占空比
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_1);

// 启动PWM
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1);

5. 混合策略:软件定时器辅助硬件定时器

实际项目中,常用硬件定时器输出主PWM,软件定时器做动态调参。例如:

// 软件定时器每100ms调整一次占空比
void vDutyUpdateCallback(TimerHandle_t xTimer) {
    static uint16_t duty = 100;
    duty += 10;
    if (duty > 900) duty = 100;
    __HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_1, duty);
}

这样既保证了PWM波形质量,又实现了灵活的调参逻辑。

6. 注意事项与常见陷阱

  • Tick频率影响:提高Tick频率(如1kHz→10kHz)可改善软件定时器精度,但会增加CPU开销,需权衡。
  • 回调函数中禁止阻塞:软件定时器回调运行在TimerTask,若执行vTaskDelay或长循环,会阻塞所有软件定时器。
  • 硬件定时器资源有限:STM32F1系列只有7个定时器,规划多路PWM时需提前分配。
  • 时钟树配置:硬件定时器时钟源可能来自APB1/APB2,需确认分频系数,否则频率计算错误。
  • 调试影响:使用JTAG调试时,硬件定时器可能因断点暂停,导致PWM输出中断,需注意。

结语

软件定时器与硬件定时器并非对立,而是互补。理解其底层机制后,开发者应根据频率、精度、实时性要求做出理性选择。对于PWM输出,除非是极低频且非关键的场景,否则强烈建议使用硬件定时器。希望本文能帮助你在嵌入式设计中少走弯路。