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,具体可以看下面这个视频中的教程:
要记得:频率 = 主频 / (预分频系数 + 1) / (重装载值 + 1),比如我的STM32F401ret6的主频设定为了84MHZ,那么如果我想要得到50HZ的PWM波,就将预分频系数设定成了168 - 1,重装载值设定成了10000 - 1。
2. STM32CUBEMX配置
参考视频:
我的配置:
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中做了,具体的看上面那个视频就能明白。