3. PWM驱动开发:定时器初始化、占空比调节函数、频率切换实现、多通道同步

好,咱们进入第三章。PWM驱动开发,这可以说是按摩仪振动反馈的灵魂所在。你想想看,按摩仪好不好用,很大程度上就取决于这个PWM波形控制得够不够细腻。我最早做这个项目的时候,也踩过不少坑,今天就把这些经验掰开揉碎了讲给你听。

3.1 定时器初始化——打好地基

做PWM的第一步,就是初始化定时器。说白了,定时器就是PWM的心脏,它负责产生那个周期性的方波。我个人习惯,在初始化之前,先想清楚几个关键参数:频率、占空比范围、还有精度。

拿我们常用的STM32来举例,一般用通用定时器(比如TIM2、TIM3)来做PWM输出。初始化代码大致长这样:

void PWM_Timer_Init(void)
{
    // 1. 使能定时器时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    
    // 2. 配置定时器时基单元
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_TimeBaseStructure.TIM_Prescaler = 71;      // 预分频,72MHz / (71+1) = 1MHz
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period = 999;         // 自动重装载值,1MHz / (999+1) = 1kHz
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    // 3. 配置PWM模式
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;              // 初始占空比为0
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);
    
    // 4. 使能定时器
    TIM_Cmd(TIM3, ENABLE);
}

嗯,这里要注意一点:预分频器和自动重装载值的计算。我曾经在项目里因为算错了预分频,导致PWM频率差了十万八千里,查了半天才发现是分频系数没减1。记住公式:PWM频率 = 定时器时钟 / ((预分频+1) * (自动重装载+1))

核心要点:定时器初始化时,预分频和自动重装载值决定了PWM的频率和分辨率。频率越高,分辨率越低,这是个权衡。

3.2 占空比调节函数——让振动有强有弱

占空比调节,说白了就是控制按摩仪的振动强度。占空比越大,电机转得越快,振动就越强。我建议把占空比调节封装成一个独立的函数,方便上层调用。

void PWM_SetDutyCycle(uint8_t channel, uint16_t duty)
{
    // 限制占空比范围,防止溢出
    if(duty > 999) duty = 999;
    
    switch(channel)
    {
        case 1:
            TIM_SetCompare1(TIM3, duty);
            break;
        case 2:
            TIM_SetCompare2(TIM3, duty);
            break;
        case 3:
            TIM_SetCompare3(TIM3, duty);
            break;
        case 4:
            TIM_SetCompare4(TIM3, duty);
            break;
        default:
            break;
    }
}

你可能会问,为什么占空比最大值是999?因为我们的自动重装载值设的是999,所以占空比范围就是0到999。0表示完全关闭,999表示全速运转。我在项目中遇到过一个问题:如果占空比设得太大,电机启动瞬间电流会很大,容易导致电源电压跌落。所以后来我加了一个软启动逻辑,占空比从0慢慢增加到目标值。

小技巧:占空比调节不要一步到位,尤其是从0跳到很大的值。可以做一个渐变函数,每次增加或减少一小步,这样电机运行更平滑,用户体验也更好。

3.3 频率切换实现——不同模式不同节奏

按摩仪通常有好几种模式:比如连续振动、脉冲振动、波浪振动等等。这些模式本质上就是不同的PWM频率和占空比组合。频率切换,就是改变定时器的自动重装载值。

我个人习惯的做法是,先停止定时器,修改参数,再重新启动。为什么?因为如果在运行中直接改自动重装载值,可能会产生一个不完整的脉冲,导致电机抖动一下。

void PWM_SetFrequency(uint16_t freq_hz)
{
    uint16_t period;
    
    // 根据目标频率计算自动重装载值
    // 假设定时器时钟为1MHz(预分频后)
    period = 1000000 / freq_hz - 1;
    
    // 停止定时器
    TIM_Cmd(TIM3, DISABLE);
    
    // 修改自动重装载值
    TIM_SetAutoreload(TIM3, period);
    
    // 重新使能定时器
    TIM_Cmd(TIM3, ENABLE);
}

这里有个坑:频率切换后,占空比也要跟着调整。比如原来1kHz时占空比500,对应50%;切换到2kHz后,自动重装载值变成499,那占空比500就超过100%了。所以每次切换频率,最好重新计算一下占空比。我曾经因为没注意这个,导致切换频率后电机直接满速运转,把用户吓了一跳。

警告:频率切换时,务必同步更新占空比。建议封装一个统一的接口函数,同时传入频率和占空比两个参数,内部自动计算并设置。

3.4 多通道同步——让多个电机步调一致

按摩仪通常不止一个振动电机,可能有4个、6个甚至更多。多通道同步,就是让这些电机按照同一个节奏工作。比如波浪模式,从第一个电机到最后一个电机依次启动,形成一种流动感。

实现多通道同步,关键是要用同一个定时器的多个通道。STM32的定时器一般有4个通道,每个通道可以独立设置占空比,但共享同一个时基(频率)。这样天然就是同步的。

void PWM_MultiChannel_Sync(uint16_t *duty_array, uint8_t len)
{
    uint8_t i;
    
    // 假设最多4个通道
    for(i = 0; i < len && i < 4; i++)
    {
        PWM_SetDutyCycle(i+1, duty_array[i]);
    }
}

如果你需要更多通道,可以用多个定时器,但要注意同步问题。我的做法是:用一个主定时器产生同步信号,其他从定时器都跟着主定时器走。具体来说,就是把主定时器的更新事件(UEV)连接到从定时器的触发输入,实现硬件同步。

同步方式 优点 缺点 适用场景
同一定时器多通道 天然同步,无需额外配置 通道数有限(最多4个) 4个电机以内的按摩仪
主从定时器级联 通道数可扩展 配置稍复杂,有微小延迟 4-8个电机的按摩仪
软件同步 灵活,任意通道数 CPU占用高,实时性差 对同步要求不高的场景

嗯,这里要提醒一下:多通道同步不仅仅是频率同步,相位也很重要。比如波浪模式,每个通道的相位要依次错开。实现相位偏移,可以通过设置每个通道的比较值来实现。举个例子,4个通道的相位依次相差90度,那它们的比较值就分别设为0、250、500、750(假设周期为1000)。

实战经验:我在做一款四头按摩仪时,发现如果所有电机同时启动,瞬间电流太大,电源扛不住。后来我用了相位偏移,让四个电机依次启动,电流峰值降低了60%,用户体验反而更好了。

好了,这一章的内容就到这里。PWM驱动开发,说白了就是控制好频率和占空比,再让多个通道协同工作。下一章我们会讲ADC采样与反馈控制,到时候就能让按摩仪根据用户的实际使用情况自动调节了。