4. 定时器与PWM:SysTick定时器、通用定时器配置、PWM输出原理、风扇调速实现

各位同学,欢迎来到第四章。这一章我们聊聊定时器和PWM。说实话,在暖通空调主控里,定时器和PWM是真正干活的家伙。你想想看,风扇要转多快、阀门要开多大、压缩机要不要启动——这些动作背后,全是定时器和PWM在驱动。

我个人习惯把定时器比作MCU的「心跳」。没有它,整个系统就像没了节奏的乐队,各吹各的号。而PWM呢,说白了就是通过调节脉冲宽度来模拟模拟量输出。嗯,这里要注意,很多新手容易把PWM和真正的DAC搞混,后面我会细说。

4.1 SysTick定时器:系统的心跳

SysTick是ARM Cortex-M内核自带的一个24位递减计数器。它最大的特点就是——简单、可靠、所有Cortex-M芯片都有。我在项目中遇到过客户要求换MCU型号,但SysTick的代码几乎不用改,这就是它的优势。

SysTick主要用来做什么?

  • 操作系统的心跳:FreeRTOS、uCOS这些RTOS,全靠SysTick来产生时间片中断。
  • 精准延时:比如你要等100微秒再读传感器,用SysTick最合适。
  • 软件定时器:在裸机编程里,我经常用SysTick来维护一个全局的毫秒计数器。

配置SysTick其实就几步:

// SysTick配置示例:产生1ms中断
void SysTick_Init(void)
{
    // 1. 设置重装载值
    // 假设系统时钟为72MHz,1ms需要72000个时钟周期
    SysTick->LOAD = 72000 - 1;
    
    // 2. 清零当前值
    SysTick->VAL = 0;
    
    // 3. 配置控制寄存器
    // 使能SysTick、使能中断、使用系统时钟
    SysTick->CTRL = 0x07;
}

// 中断服务函数
void SysTick_Handler(void)
{
    static uint32_t tick_ms = 0;
    tick_ms++;
    
    // 每1000ms翻转一次LED
    if(tick_ms % 1000 == 0)
    {
        GPIO_Toggle(LED_PIN);
    }
}
我的小技巧:SysTick的优先级默认是最低的。如果你有实时性要求高的中断,记得在NVIC里把SysTick优先级调高一点。我曾经因为没注意这个,导致系统在高速中断时时间跑偏了。

4.2 通用定时器:真正的多功能选手

如果说SysTick是「基本款」,那通用定时器就是「豪华版」。以STM32为例,通用定时器(TIM2-TIM5)支持:

  • 向上/向下计数模式
  • 输入捕获(测量脉冲宽度)
  • 输出比较(产生精确延时)
  • PWM生成
  • 编码器接口

配置通用定时器,我一般按这个流程走:

// 通用定时器配置示例:TIM2产生100Hz定时中断
void TIM2_Init(void)
{
    // 1. 使能定时器时钟
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
    
    // 2. 设置预分频器
    // 72MHz / 7200 = 10kHz
    TIM2->PSC = 7200 - 1;
    
    // 3. 设置自动重装载值
    // 10kHz / 100 = 100Hz
    TIM2->ARR = 100 - 1;
    
    // 4. 更新事件产生中断
    TIM2->DIER |= TIM_DIER_UIE;
    
    // 5. 使能定时器
    TIM2->CR1 |= TIM_CR1_CEN;
    
    // 6. 配置NVIC
    NVIC_EnableIRQ(TIM2_IRQn);
}
关键点:定时器的频率计算公式:
定时频率 = 系统时钟 / (PSC + 1) / (ARR + 1)
记住这个公式,你就能算出任何想要的频率。

4.3 PWM输出原理:说白了就是占空比

PWM,全称Pulse Width Modulation,脉冲宽度调制。你想想看,一个方波信号,高电平占整个周期的比例,就是占空比。占空比从0%到100%,对应的等效电压就从0V到VCC。

为什么会这样?因为人眼和电机都有惯性。LED闪烁太快你看不出来,电机电压变化太快转子也反应不过来。所以PWM本质上是在「骗」负载——用高频开关来模拟连续电压。

在暖通空调里,PWM的应用场景:

  • 风扇调速:改变占空比,风扇转速跟着变
  • 阀门控制:比例阀的开度控制
  • 加热器功率:电加热的功率调节
  • 压缩机变频:通过PWM控制逆变器

配置PWM输出,以TIM3的通道1为例:

// PWM输出配置示例:TIM3_CH1输出50Hz PWM
void PWM_Init(void)
{
    // 1. 使能时钟
    RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    
    // 2. 配置GPIO为复用功能
    GPIOA->MODER |= GPIO_MODER_MODER6_1;  // AF模式
    GPIOA->AFR[0] |= 0x02 << 24;         // AF2 = TIM3_CH1
    
    // 3. 配置定时器
    TIM3->PSC = 7200 - 1;    // 72MHz/7200 = 10kHz
    TIM3->ARR = 200 - 1;     // 10kHz/200 = 50Hz
    
    // 4. 配置PWM模式
    TIM3->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2;  // PWM模式1
    TIM3->CCMR1 |= TIM_CCMR1_OC1PE;  // 预装载使能
    
    // 5. 设置初始占空比50%
    TIM3->CCR1 = 100;
    
    // 6. 使能输出
    TIM3->CCER |= TIM_CCER_CC1E;
    
    // 7. 使能定时器
    TIM3->CR1 |= TIM_CR1_CEN;
}

// 设置占空比函数
void PWM_SetDuty(uint16_t duty)
{
    if(duty > 200) duty = 200;  // 限幅
    TIM3->CCR1 = duty;
}
我曾经踩过的坑:PWM频率的选择很关键。风扇调速一般用20kHz以上,否则你会听到「滋滋」的啸叫声。我第一次做风扇项目时用了1kHz,结果客户投诉说风扇有噪音。后来改成25kHz,问题就解决了。人耳对20kHz以下的声音比较敏感,所以PWM频率要避开这个范围。

4.4 风扇调速实现:从理论到实战

好了,前面铺垫了这么多,终于到实战环节了。暖通空调里的风扇,常见的有两种:

  • 直流无刷风扇:4线制(电源、地、PWM输入、转速反馈)
  • 交流风扇:通过可控硅或继电器控制

这里我们重点讲直流无刷风扇的PWM调速。这种风扇在暖通空调里用得最多,比如室内机的贯流风扇、室外机的轴流风扇。

完整的调速实现代码:

// 风扇控制结构体
typedef struct {
    uint16_t target_speed;    // 目标转速,单位RPM
    uint16_t current_speed;   // 当前转速
    uint8_t duty_cycle;       // 当前占空比 0-100%
    uint8_t fan_status;       // 风扇状态
} Fan_Control_t;

// 风扇初始化
void Fan_Init(void)
{
    PWM_Init();  // 初始化PWM输出
    // 配置转速反馈引脚为输入捕获
    TIM4_IC_Init();
    
    // 设置初始占空比为0,风扇停止
    PWM_SetDuty(0);
}

// 风扇调速函数
void Fan_SetSpeed(uint16_t target_rpm)
{
    uint8_t duty;
    
    // 根据目标转速计算占空比
    // 假设风扇额定转速3000RPM对应100%占空比
    duty = (target_rpm * 100) / 3000;
    
    // 限幅处理
    if(duty > 100) duty = 100;
    if(duty < 20) duty = 20;  // 最低启动占空比
    
    // 设置PWM占空比
    PWM_SetDuty((duty * 200) / 100);  // 转换为CCR值
    
    // 记录目标转速
    fan_control.target_speed = target_rpm;
    fan_control.duty_cycle = duty;
}

// 转速反馈处理(在输入捕获中断中调用)
void Fan_Speed_Feedback(uint16_t period_us)
{
    // 计算转速
    // 假设风扇每转产生2个脉冲
    // 转速 = 60 / (period_us * 2 / 1000000)
    uint16_t rpm = 30000000 / period_us;
    
    fan_control.current_speed = rpm;
    
    // 闭环控制:如果实际转速与目标偏差太大,调整占空比
    if(abs(rpm - fan_control.target_speed) > 50)
    {
        // 简单的PID调节
        int16_t error = fan_control.target_speed - rpm;
        int16_t adjust = error / 10;  // 比例系数
        fan_control.duty_cycle += adjust;
        
        // 限幅
        if(fan_control.duty_cycle > 100) fan_control.duty_cycle = 100;
        if(fan_control.duty_cycle < 20) fan_control.duty_cycle = 20;
        
        // 更新PWM
        PWM_SetDuty((fan_control.duty_cycle * 200) / 100);
    }
}
实战经验总结:
参数 推荐值 说明
PWM频率 20-25kHz 避免人耳可听噪声
最低占空比 20-30% 低于此值风扇可能不启动
调速分辨率 8位(0-255) 足够平滑调速
反馈采样周期 100ms 太频繁会增加CPU负担
避坑指南:我曾经在调试风扇时发现,风扇在低占空比下会抖动。后来查资料才知道,直流无刷风扇需要一定的启动电压。我的解决方案是:启动时先给一个较高的占空比(比如50%),等风扇转起来后再降到目标值。这个「软启动」策略在暖通空调里很常用。

好了,这一章的内容就到这里。定时器和PWM是嵌入式开发的基石,掌握了它们,你就能控制暖通空调里的大部分执行器。下一章我们会讲ADC和传感器采集,到时候你会看到定时器如何与ADC配合,实现精准的采样时序。

记住,多动手写代码,多拿示波器看波形。纸上得来终觉浅,绝知此事要躬行。