3、直流有刷电机开环控制:PWM生成、死区时间设置、电机正反转、速度开环控制实验

好,我们正式开始直流有刷电机的开环控制。这一章是电机控制的「敲门砖」。你想想看,如果连让电机转起来都搞不定,后面那些花里胡哨的算法就全是空中楼阁。

我个人习惯,学任何新东西,先让它动起来再说。哪怕只是嗡嗡响,也比纸上谈兵强一百倍。今天我们就来搞定三件事:怎么让电机转、怎么让它反转、怎么控制它转多快

3.1 PWM生成:电机的「油门」

直流有刷电机,说白了就是一个线圈加一个换向器。给它通上电,它就转。但问题来了——怎么控制速度?

你可能会想:「调电压呗!」没错,但调电压不是让你真的去拧一个电位器。在嵌入式世界里,我们用的是PWM(脉宽调制)

PWM的原理很简单:
把电源当成一个开关。开关一直开着,电机全速跑;开关一直关着,电机停。那如果开关开一会儿、关一会儿呢?电机就会「半速」跑。

这个「开」的时间占整个周期的比例,就叫占空比。占空比50%,电机大概跑一半的速度。当然,实际转速和占空比不是严格的线性关系,这个我们后面再聊。

核心公式:

占空比 = Ton / (Ton + Toff) × 100%

电机平均电压 = 电源电压 × 占空比

我在项目中遇到过一个问题:用STM32的定时器输出PWM,结果电机一启动就「吱吱」叫。后来发现是PWM频率太低了,落在了人耳可听范围内。嗯,这里要注意——PWM频率一般选在10kHz以上,既听不到噪音,又能保证电机响应够快。

下面是一个典型的PWM初始化代码(以STM32为例):

// 定时器2,通道1输出PWM,频率20kHz
void PWM_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;

    // 使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    // PA0配置为复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 定时器配置:72MHz / (72 * 50) = 20kHz
    TIM_TimeBaseStructure.TIM_Period = 999;        // 自动重装值
    TIM_TimeBaseStructure.TIM_Prescaler = 3;       // 预分频
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

    // PWM模式1,占空比初始为0
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);

    TIM_Cmd(TIM2, ENABLE);
}

代码写完了,怎么调占空比?很简单:

// 设置占空比为50%
TIM_SetCompare1(TIM2, 500);  // 500 / 1000 = 50%

小技巧:我习惯把占空比用「千分比」来表示。比如500就是50%,1000就是100%。这样计算方便,而且精度够用。你如果喜欢用百分比,也可以自己封装一个函数。

3.2 死区时间设置:别让上下管「打架」

如果你用的是H桥驱动电路(比如L298N、TB6612),那死区时间就是个绕不开的话题。

什么是死区时间?
H桥有四个开关管,上管和下管不能同时导通。一旦同时导通,电源直接短路,电流瞬间飙升——管子烧了,板子冒烟了,项目凉了。

我曾经就吃过这个亏。调试一个电机驱动板,手一抖,上下管同时开了0.5微秒。就这0.5微秒,MOS管直接炸了,还带走了旁边的电容。嗯,从那以后,我再也不敢忽视死区时间了。

死区时间的设置,说白了就是在切换上下管时,插入一段「都关断」的时间。比如上管关掉后,等1微秒,再打开下管。这1微秒就是死区时间。

死区时间怎么设?

  • 太短:有短路风险,管子可能烧
  • 太长:电机换向不流畅,效率降低,甚至产生噪音

一般建议:死区时间 = 开关管关断时间 × 1.5 ~ 2倍。比如你的MOS管关断时间是200ns,那死区时间设300~400ns就够。

在STM32的高级定时器里,死区时间是这样配置的:

// 死区时间设置:100个时钟周期
// 假设定时器时钟72MHz,100个周期 ≈ 1.39微秒
TIM_BreakDeadTimeConfig(TIM1, 0xFF, 0x0D, TIM_Break_Enable, 
                        TIM_AutomaticOutput_Enable, TIM_OSSRIState_Enable, 
                        TIM_OSSRState_Enable);

警告:如果你用的是现成的驱动模块(比如L298N),它内部已经处理了死区时间,你不需要额外设置。但如果你是自己搭H桥,死区时间必须自己算清楚。别问我怎么知道的——我烧过三块板子才记住这个教训。

3.3 电机正反转:H桥的「换向」逻辑

直流有刷电机要反转,其实就一句话:把电源的正负极对调

在H桥里,这个操作通过控制四个开关管来实现:

模式 Q1(左上) Q2(右上) Q3(左下) Q4(右下)
正转 ON OFF OFF ON
反转 OFF ON ON OFF
刹车 OFF OFF OFF OFF
滑行 OFF OFF OFF OFF

注意看,正转和反转其实就是两组对角管交替导通。而刹车和滑行看起来一样,但实际效果不同——刹车是让电机端子短路,利用反电动势快速制动;滑行是让电机自由旋转,靠摩擦力慢慢停。

代码实现也很直接:

// 电机正转
void Motor_Forward(void) {
    GPIO_SetBits(GPIOB, GPIO_Pin_0);  // IN1 = 1
    GPIO_ResetBits(GPIOB, GPIO_Pin_1); // IN2 = 0
}

// 电机反转
void Motor_Reverse(void) {
    GPIO_ResetBits(GPIOB, GPIO_Pin_0); // IN1 = 0
    GPIO_SetBits(GPIOB, GPIO_Pin_1);   // IN2 = 1
}

// 电机刹车
void Motor_Brake(void) {
    GPIO_SetBits(GPIOB, GPIO_Pin_0);   // IN1 = 1
    GPIO_SetBits(GPIOB, GPIO_Pin_1);   // IN2 = 1
}

// 电机滑行
void Motor_Coast(void) {
    GPIO_ResetBits(GPIOB, GPIO_Pin_0); // IN1 = 0
    GPIO_ResetBits(GPIOB, GPIO_Pin_1); // IN2 = 0
}

个人经验:我建议你在切换正反转时,先让电机刹车50ms,再切换方向。为什么?因为电机有惯性,直接反向通电会产生很大的冲击电流,容易烧驱动。先刹车让电机停下来,再反转,电流就平滑多了。

3.4 速度开环控制实验:让电机「听话」

好了,前面都是铺垫。现在我们来做一个完整的实验:让电机以指定的速度旋转

开环控制是什么意思?就是只发指令,不检测实际转速。你给50%占空比,电机就跑50%的速度——至于它到底跑了多少转,我们不管。

实验步骤很简单:

  1. 初始化PWM和GPIO
  2. 设置占空比(比如50%)
  3. 设置方向(正转)
  4. 等待一段时间
  5. 改变占空比或方向

完整代码示例:

int main(void) {
    // 初始化
    PWM_Init();
    GPIO_Init();
    
    while(1) {
        // 正转,50%速度,持续3秒
        Motor_Forward();
        TIM_SetCompare1(TIM2, 500);
        Delay_ms(3000);
        
        // 正转,100%速度,持续3秒
        TIM_SetCompare1(TIM2, 1000);
        Delay_ms(3000);
        
        // 刹车,停1秒
        Motor_Brake();
        TIM_SetCompare1(TIM2, 0);
        Delay_ms(1000);
        
        // 反转,75%速度,持续3秒
        Motor_Reverse();
        TIM_SetCompare1(TIM2, 750);
        Delay_ms(3000);
        
        // 滑行,停2秒
        Motor_Coast();
        TIM_SetCompare1(TIM2, 0);
        Delay_ms(2000);
    }
}

你运行这个程序,会看到电机先慢速正转,再快速正转,然后刹车停住,接着反转,最后滑行停下。

实验要点:

  • 占空比从0到1000,对应0%到100%
  • 正反转切换前,建议先刹车50ms
  • 滑行和刹车的区别:滑行是自由停止,刹车是强制停止
  • 开环控制下,电机实际转速受负载影响很大——你用手捏住轴,转速就会下降

说到负载影响,我当年做第一个电机项目时就踩过坑。我设了50%占空比,空载时转速很稳。结果一装上轮子,转速直接掉到30%。我当时还以为是代码写错了,查了半天。后来才明白——开环控制不抗负载扰动。这就是为什么后面我们要学闭环控制。

3.5 本章小结

这一章我们干了三件事:

  • PWM生成:用定时器输出PWM波,控制电机平均电压
  • 死区时间设置:防止H桥上下管直通,保护硬件
  • 正反转控制:通过H桥切换电流方向,实现电机换向
  • 开环速度控制:用占空比控制速度,简单但精度有限

下一章,我们会引入编码器,开始做闭环速度控制。到时候你会发现,有了反馈,电机才能真正「听话」。

嗯,今天就到这里。去动手试试吧——让电机转起来,比看十遍文章都管用。