跳到主要内容

【PWM】呼吸灯

PWM简介

PWM 波形

PWM 波形是一种方波信号,是高/低电平不断切换的结果,其波形如图所示,这是 3 种占空比不同的波形:

PWM波形

PWM 的几个关键参数为:

  • 频率(Frequency):即高低电平切换的速度,切换的速度越快则频率越高,1000Hz 的 PWM 波意味着 1 秒钟有 1000 个脉冲

  • 占空比(Duty Cycle):即每个周期内,高电平所占的宽度

    • 例如图中 50% duty cycle,即高/低电平的时间各占 50%

    • 图中的75% duty cycle高电平占 75%,低电平占 25%

    • 图中的25% duty cycle高电平占 25%,低电平占 75%

PWM 实现呼吸灯

如果使用 PWM 信号控制 LED 的亮/灭,那么占空比越高,灯点亮的时间就越长

因此,当 PWM频率足够高,以至于人眼无法分辨时,PWM 的占空比就可以控制 LED 灯的亮度

也就是说,占空比越高,LED 看起来越亮

硬件电路设计

LED 的正极接开发板的 D12 引脚,并串联一个电阻,负极接 GND,如下图:

PWM电路
提示

一定要接电阻,不然会由于电流过大,烧坏 LED

软件程序设计

1. analogWrite() 函数实现呼吸灯效果

想要通过 Arduino 输出 PWM 有两种方法,第一种就是使用 Arduino 自带的 analogWrite(pin, value) 函数,
其中的两个参数:

  • pin:要写入的 Arduino 引脚。允许的数据类型:int

  • value:占空比:介于 0(始终关闭)和 255(始终开启)之间。允许的数据类型:int

代码

// 宏定义 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);
}
}

2. 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 的所有控制函数:

// 设置 LEDC 通道对应的频率和计数位数(占空比分辨率),返回最终频率
// 分辨率的意思就是把一个周期分成 2 的 resolution_bits 份。
uint32_t ledcSetup(uint8_t channel, uint32_t freq, uint8_t resolution_bits);

// 指定通道输出一定占空比波形
void ledcWrite(uint8_t channel, uint32_t duty);

// 类似于 arduino 的 tone ,当外接无源蜂鸣器的时候可以发出某个声音(根据频率不同而不同)
uint32_t ledcWriteTone(uint8_t channel, uint32_t freq);

// 该方法是上面方法的进一步封装,可以直接输出指定调式和音阶声音的信号
uint32_t ledcWriteNote(uint8_t channel, note_t note, uint8_t octave);

// 返回指定通道占空比的值
uint32_t ledcRead(uint8_t channel);

// 返回指定通道当前频率(如果当前占空比为0 则该方法返回0)
uint32_t ledcReadFreq(uint8_t channel);

// 将 LEDC 通道绑定到指定 IO 口上以实现输出
void ledcAttachPin(uint8_t pin, uint8_t channel);

// 解除 IO 口的 LEDC 功能
void ledcDetachPin(uint8_t pin);

使用 LEDC 外设的时候需要遵循以下步骤:

  • 使用 ledcSetup() 函数建立 LEDC 通道;

  • 通过 ledcAttachPin() 将 GPIO 口与 LEDC 通道关联;

  • 通过 ledcWrite()、ledcWriteTone()、ledcWriteNote()设置频率、设置蜂鸣器音调等等

  • 通过 ledcDetachPin() 解除 GPIO 口与 LEDC 通道的关联

所有我们可以通过以下代码,实现呼吸灯效果:

#define FREQ        2000    // 频率
#define CHANNEL 0 // 通道
#define RESOLUTION 8 // 分辨率
#define LED 12 // 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);
}
}