课程目录
2 PWM与电机控制
2.1 PWM&呼吸灯
在上一讲中我们学习了GPIO接口,实现了高电平、低电平的输出。通过高电平和低电平可以控制小灯的亮和灭。然而生活中,我们可能不仅仅要控制电气设备的工作与否,可能还需要控制它工作的状态,比如控制灯的亮度和电机的转速。
在高中物理中,我们学习过,如果想要调节小灯泡(白炽灯)的亮度可以通过调节灯泡两端的电压来调节。然而,我们的微处理器它可以很方便的输出高低电平信号,但是很难输出电压连续可变的信号。那我们要如何来调节灯泡的亮度呢?
我们可以来分析一下。假设我们把电灯的亮度变化曲线按单位时间均匀分割,如下图所示。

可以看到在单位时间内,小灯功率曲线下的面积是小灯在单位时间内对外输出的能量。如果我以单位时间为周期,在单位时间已开始让小灯打开,当小灯发出的能量达到一定数值之后就关闭直到单位时间结束,下一周期再控制开和关,以此类推,从而实现每个周期内小灯发出的光能量和之前一致。

由于我们在单位周期内让小灯发出的能量是一致的,而我们人类的眼睛有视觉暂留效应,我们感受到光的亮度也是和之前一致。而这种通过调节脉冲信号宽度的方式就是所谓的PWM。
2.1.1PWM信号是什么?
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,PWM是一种通过对模拟信号电平进行数字编码的方法,通过改变脉冲的宽度来控制电路的开关状态,从而实现对模拟信号的调节。

接下来聊聊与其相关的几个概念
(1)PWM频率(f):是指1秒钟内信号从高电平到低电平再回到高电平的次数(一个周期);也就是说一秒钟 PWM有多少个周期 单位: Hz 表示方式: 50Hz 100Hz
(2)PWM的周期(T):信号从高电平到低电平再回到高电平的时间
T=1/f周期=1/频率50Hz = 20ms 一个周期
如果频率为50Hz ,也就是说一个周期是20ms 那么一秒钟就有 50次PWM周期
(3)占空比:是一个脉冲周期内,高电平的时间与整个周期时间的比例 单位: % (0%-100%)

2.1.2PWM用数字信号模拟模拟信号

占空比的不断变化可以使PWM从宏观上实现类似正弦波等不同的波形信号;将变化的PWM信号接在小灯泡上也就能实现小灯的渐亮渐暗的效果(呼吸灯)
2.2PWM实现呼吸灯
2.2.1拿出所需的器材并接线
- ESP32开发板
- 面包板
- 红绿灯
- 杜邦线
- 跳线
- Type-C数据线
- 一台装好PlatformIO环境的电脑

2.2.2软件程序设计
1.analogWrite() 函数实现呼吸灯效果
想要通过 Arduino 输出 PWM 有两种方法,第一种就是使用 Arduino 自带的 analogWrite(pin, value)
函数,其中的两个参数:
pin
:要写入的 Arduino 引脚。允许的数据类型:int.value
:占空比:介于 0(始终关闭)和 255(始终开启)之间。允许的数据类型:int.
#include <Arduino.h>
// 宏定义 GPIO 输出引脚
#define LED_PIN 12
void setup() {
// 配置 GPIO 输出引脚
pinMode(LED_PIN, OUTPUT);
}
void loop() {
// 实现渐亮效果
for(int i=0;i<256;i++) {
// 设置亮度模拟值
analogWrite(LED_PIN, i);
// 延时 10ms
delay(10);
}
// 实现渐灭效果
for(int i=255;i>=0;i--) {
// 设置亮度模拟值
analogWrite(LED_PIN, i);
// 延时 10ms
delay(10);
}
}
(1)这里用到的analogWrite
函数的语法非常简单,只需要指定要控制的引脚和输出电压即可。例如,analogWrite(9, 127)就表示将数字引脚9的输出电压设置为50%的占空比,即2.5V。在Arduino编程中,输出电压的范围是0~255,对应的电压值是0~5V。因此,analogWrite函数可以实现对模拟电路的精确控制。
(2)宏定义(#define)是可以将一对文本进行替换,在编译器读到需要被替换的文本的时候,会将这些文本全部替换成我们给定的文本。在这里,我们将 “LED_PIN” 使用宏定义,定义为 12,在以后的语句中,一旦编译器读取到 “LED_PIN” 就会直接将该位置的 “LED_PIN” 替换为对应的数字文本。
(3)for循环一般形式为
for (初始化表达式; 条件表达式; 更新表达式) {
// 循环体
};
执行流程:第一步先执行初始化表达式。
然后执行条件表达式。若其为真,则执行 for 语句中循环体和更新表达式
若其为假,则结束循环。
2.LEDC外设输出PWM信号实现呼吸灯效果
第二种就是使用LEDC控制函数产生PWM信号,接下来我来介绍LEDC的有关控制函数
- ledcSetup:这个函数的作用是设置LEDC通道【0 ~ 15一共有16路通道,高速通道是0-7由80兆赫兹时钟驱动,低速通道是8-16由1兆赫兹时钟驱动。(初学者这个不理解也ok,我们主要学习该如何产生这个PWM信号)】对应的频率和分辨率(分辨率:把一个周期平均分成多少2的多少次方,分辨率越高,分辨率可以实现的占空比变化的越精细,从而可以更好地满足不同的控制需求)这个函数需要三个参数分别是通道号,频率和分辨率(0 ~ 10)。函数最后返回频率
uint32_t ledcSetup(uint8_t channel, uint32_t freq, uint8_t resolution_bits);
- ledcAttachPin:这个函数的作用是 将 GPIO 口与 LEDC 通道关联以实现输出;两个参数:引脚号和通道号
void ledcAttachPin(uint8_t pin, uint8_t channel);
- ledcWrite:这个函数作用是指定通道输出一定占空比波形;两个参数:通道号和占空比
void ledcWrite(uint8_t channel, uint32_t duty);
- ledcDetachPin:这个函数作用是解除 GPIO 口与 LEDC 通道的关联;一个参数:引脚号
void ledcDetachPin(uint8_t pin);
使用 LEDC 外设的时候需要遵循以下步骤:
- 使用
ledcSetup()
函数建立 LEDC 通道; - 通过
ledcAttachPin()
将 GPIO 口与 LEDC 通道关联; - 通过
ledcWrite()
设置频率; - 通过
ledcDetachPin()
解除 GPIO 口与 LEDC 通道的关联
所有我们可以通过以下代码,实现呼吸灯效果:(pow是指数函数)
#include <Arduino.h>
#define FREQ 2000 // 频率
#define CHANNEL 0 // 通道
#define RESOLUTION 8 // 分辨率
#define LED 11 // LED 引脚
void setup()
{
ledcSetup(CHANNEL, FREQ, RESOLUTION); // 设置通道
ledcAttachPin(LED, CHANNEL); // 将通道与对应的引脚连接
}
void loop()
{
// 逐渐变亮
for (int i=0;i<pow(2, RESOLUTION); i++)
{
ledcWrite(CHANNEL, i); // 输出PWM
delay(5);
}
// 逐渐变暗
for (int i=pow(2, RESOLUTION)-1;i>=0;i--)
{
ledcWrite(CHANNEL, i); // 输出PWM
delay(5);
}
}
2.3PWM实现电机驱动
2.3.1电机介绍
直流电动机是依靠直流电驱动的电动机,它有两个电极,一端接正极,一端接负极。电压越大转速越快。将正负极反过来,电机还可以反转。(不要直接将其接到面包板上,这种电机的驱动一般需要几百毫安的电流,单片机承受不住,会烧坏。这时候就需要电机驱动芯片)

2.3.2电机驱动芯片(tb6612)
TB6612FNG可以驱动两个电机,每一个驱动都有两个逻辑输入引脚,两个输出引脚和一个PWM引脚。可以通过给两个逻辑输入引脚不同的电平来控制电机的运行状态,通过PWM输入引脚实现电机调速。

1.tb6612引脚说明

2.tb6612模式说明

制动是刹车,停止/待机是滑行。
总结:
①PWM 信号控制输出电压大小从而达到控制转速的作用
②AIN1 与 ANI2 信号一起控制电机的正反转以及停止状态
2.3.3代码实现电机驱动
(1)拿出所需器材并接线
- ESP32开发板
- 面包板
- tb6612电机驱动模块
- 电机
- 杜邦线
- 跳线
- Type-C数据线
- 一台装好PlatformIO环境的电脑

#include <Arduino.h>
#define AIN1 10
#define AIN2 9
#define FREQ 2000 // 频率
#define CHANNEL 0 // 通道
#define RESOLUTION 8 // 分辨率
#define PWM 12 // PWM引脚
void setup() {
pinMode(9,OUTPUT);
pinMode(10,OUTPUT);
ledcSetup(CHANNEL, FREQ, RESOLUTION); // 设置通道
ledcAttachPin(PWM, CHANNEL); // 将通道与对应的引脚连接
}
void loop() {
ledcWrite(CHANNEL, 255);
digitalWrite(AIN1,HIGH);
digitalWrite(AIN2,LOW);
}
2.3.4代码实现电机变速驱动
#include <Arduino.h>
#define AIN1 10
#define AIN2 9
#define FREQ 2000 // 频率
#define CHANNEL 0 // 通道
#define RESOLUTION 8 // 分辨率
#define PWM 12 // PWM引脚
void setup() {
pinMode(9,OUTPUT);
pinMode(10,OUTPUT);
ledcSetup(CHANNEL, FREQ, RESOLUTION); // 设置通道
ledcAttachPin(PWM, CHANNEL); // 将通道与对应的引脚连接
}
void loop() {
//逐渐加速
for (int i=0;i<pow(2, RESOLUTION); i++)
{
ledcWrite(CHANNEL, i); // 输出PWM
delay(10);
}
// 逐渐减速
for (int i=pow(2, RESOLUTION)-1;i>=0;i--)
{
ledcWrite(CHANNEL, i); // 输出PWM
delay(10);
}
digitalWrite(AIN1,HIGH);
digitalWrite(AIN2,LOW);
}
2.4开环与闭环的区别
2.4.1开环的介绍
开环的英文名是open-loop。开环相对于闭环而言,也叫开环控制系统。意思就是不将控制的结果反馈回来影响当前控制的系统。开环就相当于单向操作,我们给控制器一个值,控制器就按这个值操作控制。也就是只控制输出,不计后果的控制。这种系统比较简单,容易掌握使用,工作稳定,但精度和速度的提高受到限制,所以一般仅用于不考虑外界影响,或惯性小、精度要求不高的一些系统。打个比方,开环相当于开水龙头,你拧到什么位置,水龙头就出多少大小的水,没有反馈信号。
2.4.2闭环的介绍
闭环控制是指控制器输出信号不仅作用于执行器,同时也通过传感器等设备采集系统的实际输出信号,与设定值进行比较,并根据比较结果调整控制器输出信号的大小和时间,以使系统输出信号逐渐接近设定值。在闭环控制中,系统反馈信号的使用和处理可以使控制系统更加稳定和精确。