PWM实验
上期回顾
上一小节结尾时,我们让大家尝试优化代码,使用宏定义的方法完成实验,以下是示例代码:
#define LED_PIN 13
#define BUTTON_PIN 7
// 定义 LED 逻辑值
int led_logic = 0;
// 判断 LED 的状态是否改变过
bool status = false;
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLDOWN);
}
void loop() {
// 按键消抖
if (digitalRead(BUTTON_PIN)) {
// 睡眠 10ms,如果依然为高电平,说明抖动已消失。
delay(10);
if (digitalRead(BUTTON_PIN) && !status) {
led_logic = !led_logic;
digitalWrite(LED_PIN, led_logic);
// led 的状态发生了变化,即使我持续按着按键,LED 的状态也不应该改变。
status = !status;
} else if (!digitalRead(BUTTON_PIN)) {
status = false;
}
}
}
这节课,我们学习制作呼吸灯,通过 LED 灯的亮度变化来验证 PWM 不同电压的输出。
一、实验目的
通过 LED 灯的亮度变化来验证 PWM 不同电压的输出,制作一个呼吸灯效果,模拟人类呼吸的节奏。
二、实验原理
脉冲宽度调制(PWM,Pulse Width Modulation)是英文 Pulse Width Modulation
的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在测量、通信到功率控制与变换的许多领域中。PWM 通过调节输出不同频率(频率是指 1 秒钟内信号从高电平到低电平再回到高电平的次数(一个周期))、占空比(一个周期内高电平出现时间占总时间比例)的方波,以实现固定频率或平均电压输出。频率固定,改变占空比可改变输出电压,如下所示:
三、硬件电路设计
(一)物料清单(BOM 表)
(二)电路连接
本次实验中,将单片机与PC直连即可;
- 如果身边有直插式 LED和阻值在
1kΩ
左右的电阻可以按照下图接线:
LED 的正极接开发板的引脚,然后串联一个电阻,负极接 GND。
注意:一定要接电阻,不然会由于电流过大,烧坏 LED。
四、软件程序设计
(一)使用 analogWrite()
函数实现呼吸灯效果
想要通过 Arduino 输出 PWM 有两种方法,第一种就是使用 Arduino 自带的 analogWrite(pin, value)
函数,其中的两个参数:
pin
:要写入的 Arduino 引脚。允许的数据类型:int。
value
:占空比,介于 0(始终关闭)和 255(始终开启)之间。允许的数据类型:int。
示例代码
// 宏定义 GPIO 输出引脚
#define LED_PIN 13
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);
}
}
(二)使用 LEDC 输出 PWM 信号
第二种是使用 ESP32 的 LEDC 外设,在 ESP32 上有一个 LEDC 外设模块专用于输出 PWM 波形。LED PWM 控制器可以生成 16 路通道(0 ~ 15),波形的周期和占空比可配置。分为高低速两组,高速通道(0 ~ 7)由 80MHz 时钟驱动,低速通道(8 ~ 15)由 1MHz 时钟驱动。
另外,每路 LED PWM 支持自动步进式地增加或减少占空比,可以用于 LED RGB 彩色梯度发生器。作为刚入门的学习者,上面这段概念不理解也不影响我们后续的学习,我们需要了解的是 LEDC 的控制函数以及 PWM 信号的产生流程。
其实在esp32_hal_led.h
文件中,有 LEDC 的所有控制函数:
ledcSetup(uint8_t channel, uint32_t freq, uint8_t resolution_bits)
:设置 LEDC 通道对应的频率和计数位数(占空比分辨率),返回最终频率。
ledcWrite(uint8_t channel, uint32_t duty)
:指定通道输出一定占空比波形。
ledcWriteTone(uint8_t channel, uint32_t freq)
:类似于 Arduino 的 tone,当外接无源蜂鸣器的时候可以发出某个声音(根据频率不同而不同)。
ledcWriteNote(uint8_t channel, note_t note, uint8_t octave)
:该方法是上面方法的进一步封装,可以直接输出指定调式和音阶声音的信号。
ledcRead(uint8_t channel)
:返回指定通道占空比的值。
ledcReadFreq(uint8_t channel)
:返回指定通道当前频率(如果当前占空比为0 则该方法返回0)。
ledcAttachPin(uint8_t pin, uint8_t channel)
:将 LEDC 通道绑定到指定 IO 口上以实现输出。
ledcDetachPin(uint8_t pin)
:解除 IO 口的 LEDC 功能。
使用 LEDC 外设的时候需要遵循以下步骤:
- 使用
ledcSetup()
函数建立 LEDC 通道;
- 通过
ledcAttachPin()
将 GPIO 口与 LEDC 通道关联;
- 通过
ledcWrite()、ledcWriteTone()、ledcWriteNote()
设置频率、设置蜂鸣器音调等等;
- 通过
ledcDetachPin()
解除 GPIO 口与 LEDC 通道的关联。
所以我们可以通过以下代码,实现呼吸灯效果:
#define FREQ 2000 // 频率
#define CHANNEL 0 // 通道
#define RESOLUTION 8 // 分辨率
#define LED 13 // 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);
}
}
这次实验现象应该和上一种使用 analogWrite()
函数的方法效果一样;
最后大家可以尝试修改RESOLUTION
的值,看看实验现象有何不同,为什么?