Bootstrap

【四轴】利用PWM输出驱动无刷电机

1. 基本原理

1.1 什么是PWM波

简单的来讲,PWM是一种只由高低电平组成的波,可见图1.1.1

图1.1.1 PWM波示例

PWM波由两个非常关键的元素,一个叫周期,一个叫占空比

1.1.1 PWM的周期是什么意思

定性的来说,一个周期就是PWM的信号由高电平到低电平再回到高电平所需要的时间。即图1.1.1.1所示

图1.1.1.1 PWM的一个周期

周期的倒数叫频率,单位是hz,100hz就表示一秒钟有100个周期,一个周期的时间是0.01s

1.1.2 PWM的占空比是什么意思

简单的来讲,占空比就是一个周期内,高电平所持续的时间与整个周期的比值,公式如下

比如占空比为0.5,就说明在这个周期内有一般的时间都是高电平。

1.2 如何利用PWM输出驱动无刷电机

简单来说,我们将发送特定频率(50HZ)的PWM给无刷电机,发的PWM占空比越大,电机转速越快;占空比越小,转速越小。

Q:为什么是50HZ呢?

A:对于大多数电机和舵机而言,50HZ(即周期为2ms)的PWM波都是比较合适的频率。具体的还得去看电机的说明书

Q:占空比多大才叫大?多小才叫小?

A:实测对于新西达2212(1000kv)电机,占空比在0.05-0.10这个区间时,电机转动的速度由0到最大。

1.3 落实到代码,我们究竟需要写什么?

看了1.2,我们很容易想到如果我们能实现一个函数,可以自由地调节输出不同占空比的PWM波,让这个波传给电机,不就行了吗?

思路是正确的,但是落实起来却发现有了下面的QA:

Q:我哪来的PWM波?凭空生成吗?

A:STM32上有许多定时器(TIM),这些定时器有的可以用来专门生成特定频率的PWM波,还可以依赖HAL库的函数方便地修改其占空比。

所以,我们要做的很简单,首先在STM32CUBEMX上配置好需要用来做PWM输出的定时器(比如我拿的TIM3),设定频率为50hz,具体可以看下面这个视频中的教程:

定时器-PWM输出_哔哩哔哩_bilibili

要记得:频率 = 主频 / (预分频系数 + 1) / (重装载值 + 1),比如我的STM32F401ret6的主频设定为了84MHZ,那么如果我想要得到50HZ的PWM波,就将预分频系数设定成了168 - 1,重装载值设定成了10000 - 1。

2. STM32CUBEMX配置

参考视频:

定时器-PWM输出_哔哩哔哩_bilibili

我的配置:

3. 实现代码

3.1 源文件

#include "Motor.h"
#include "tim.h"
#include "MySerial.h"

/**
 * @brief 初始化电机 PWM 输出
 * 
 * 此函数开启四个通道的 PWM 输出,用于驱动舵机或电机。
 * 在调用此函数前,需确保定时器(TIM3)已通过 HAL 库初始化。
 */
void Motor_Init(void) 
{
    // 开启 TIM3 的 4 个 PWM 通道
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);
    // 电机预启动,否则发现遥控不了
		Motor_SetPulse(1,0.05);
		HAL_Delay(1000);
		Motor_SetPulse(2,0.05);
		HAL_Delay(1000);
		Motor_SetPulse(3,0.05);
		HAL_Delay(1000);
		Motor_SetPulse(4,0.05);
		HAL_Delay(1000);
}

/**
 * @brief 设置指定通道的 PWM 占空比
 * 
 * @param channel PWM 通道号(1 ~ 4)
 * @param Pulse 占空比百分比(0.0 ~ 1.0),表示 PWM 高电平所占比例。
 *              - 0.0:完全低电平
 *              - 1.0:完全高电平
 *              - 其他值:高低电平按比例分配
 * 
 * 该函数会将占空比转换为定时器比较寄存器的值。
 */
void Motor_SetPulse(int channel, float Pulse)
{
    // 确保占空比在有效范围内(0.0 ~ 1.0)
    if (Pulse < 0.0f) Pulse = 0.0f;
    if (Pulse > 1.0f) Pulse = 1.0f;

    // 根据占空比计算计数值
    int duty = (int)(ARR_VAL * Pulse);  // ARR_VAL 是自动重装载值

    // 根据通道号设置对应的比较值
    switch(channel) {
        case 1: 
            __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, duty);
            break;
        case 2: 
            __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_2, duty);
            break;
        case 3: 
            __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_3, duty);
            break;
        case 4: 
            __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_4, duty);
            break;
        default:
            // 无效通道号,忽略设置
            break;
    }
}

3.2 头文件

#ifndef __MOTOR_H
#define __MOTOR_H
#include "main.h"
#define ARR_VAL 10000 // 定义自动重装载值(ARR)
void Motor_Init(void);// 初始化电机 PWM 模块
void Motor_SetPulse(int channel, float Pulse);// 设置 PWM 占空比

#endif

一些解释

由于HAL库函数的封装性有点高,导致很多人单纯会用,但是对于其内核缺少了解。 拿我代码中的__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, duty)举例,这句代码的含义是将定时器3的通道1的捕获/比较寄存器(CCR)值设定为duty。

出现了个不知道的东西:CCR,其实,仔细观察我的代码,在这个地方还有个陌生的东西:

ARR_VAL,它是我定义的宏,值为定时器3的通道1的重装载值(ARR),下面,我将介绍它们俩的含义:

首先说ARR,它叫做重装载值寄存器,存的值就是重装载值,也叫做最大计数器时,它表示计数器在一个周期内所能达到的最大值。

再来说CCR,它是捕获/比较寄存器,存的值就是当前的计数器捕获值。

CCR的值,可以代表高电平持续的时间。ARR、CRR与占空比有如下关系:

占空比 = CCR / ARR

举个例子,如果我的ARR在初始化时设定为了10000,那如果此时我让某个通道的PWM波的CCR为5000,就可以让这个波的占空比等于5000 / 10000 = 0.5,即可以实现输出占空比为50%的PWM波。

所以,对于函数__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, duty),duty的取值是需要根据自己的ARR来的,比如你的ARR初始化为了5000,这时你想要输出20%占空比的PWM波,那你的duty取值就应该为0.2 * 5000 = 1000;

ps:ARR的初始化、预分频系数的初始化,都在STM32CUBEMX中做了,具体的看上面那个视频就能明白。

参考链接

PWM原理 PWM频率与占空比详解-CSDN博客

PWM常见输出方法及避坑指南-CSDN博客