跳转至

PWM与呼吸灯

概要

  • 脉宽调制技术的原理与属性(占空比,频率)

  • MicroPython-ESP32 PWM部分的API文档

  • 通过PWM脉宽调节技术控制LED的亮度的演示实例。

keywords : 占空比 PWM LED 呼吸灯

提出问题

之前我们一直在使用数字信号来控制LED灯的亮灭,那我们该如何使用数字信号控制小灯的亮度呢?

这就不得不提及我们的PWM脉宽调制技术了。

PWM脉宽调制技术

PWM的全称为Pulse Width Modulation,翻译成中文是脉冲宽度调节,是把模拟信号调制成脉波的技术。

如果控制LED,亮1s,然后灭1s, 往复循环, 那我们可以看到LED在闪烁。如果我们把这个周期缩小到200ms,亮100ms,然后灭100ms,往复循环, 可以看到LED灯在高频闪烁。这个周期持续缩小,持续缩小,总有一个临界值,我们的人眼分辨不出来LED在频闪,而此时LED的亮度处在灭与亮之间亮度的中间值, 达到了1/2亮度。

我们可以调节一个周期内,LED亮与灭的比例, 通过调节比例,就可以达到控制LED亮度的目的。在一个周期内,高电平时间占总体周期的比例,称之为占空比 (duty)

pwm

例如PWM的控制周期为100ms,其中20ms为高电平,80ms为低电平,则占空比就是 20/100 = 20%。

注意有时候占空比有时候在嵌入式并不是百分比,而是参考其分辨率。有的单片机例如Arduino,它的占空比取值为0-255。

ESP32的duty取值范围为

0 <= duty <= 1023

分辨率越高,也就意味着你可以调节的亮度的档位也就越高,引脚输出的平均电压处于0-3.3v之间 划分成1024份,你可以取其任意一个。

PWM的第二个属性就是频率, 频率为控制周期T的倒数。在上面这个例子里面,100ms就是控制周期,那频率就是

1s / 0.1s = 10HZ

频率的取值范围由硬件决定,ESP32的PWM频率范围为0 < freq <= 78125

PWM-常用API

PWM可在所有输出引脚上启用。但其存在局限:须全部为同一频率,且仅有8个通道。频率须位于1Hz和78125Hz之间.

在引脚上使用PWM,您须首先创建一个引脚对象,例如:

这里用的是GPIO2 安信可的NodeMCU-32S开发板的板载LED

>>> from machine import Pin,PWM
>>> led_pin = Pin(2, Pin.OUT)

使用以下指令创建PWM对象:

# 把Pin对象传入PWM的构造器中
>>> led_pwm = PWM(led_pin)
# 初始化PWM 频率=500, 占空比=512
>>> led_pwm.init(500, 512)

或者初始化的时候,一步到位

>>> led_pwm = PWM(led_pin, freq=500, duty=512)

您也可使用以下方法设置频率与占空比:

>>> led_pwm.freq(500)
>>> led_pwm.duty(512)

注意:占空比介于0至1023间,其中512为50%。若您打印PWM对象,则该对象将告知您其当前配置:

>>> led_pwm
PWM(12, freq=500, duty=512)

您也可调用没有参数的freq()duty()方法以获取其当前值。

引脚将继续保持在PWM模式,直至您使用以下指令取消此模式:

>>> led_pwm.deinit()

注意: pwm使用完了之后,需要销毁,注意deinit

使用PWM来控制LED的亮度

相信聪明的你已经从以上的API讲解中明白,只要我们更改了控制LED的引脚上的PWM输出的占空比,即可完成对亮度的控制。

让我们编写一个叫做Switch的类,传入一个Pin对象,和PWM输出的占空比。

from machine import PWM
from machine import Pin

class Switch():
    """
    创建一个开关类
    """
    def __init__(self, pin, freq=1000):
        """
        初始化绑定一个引脚,设置默认的PWM频率为1000
        """
        self.pwm = PWM(pin,freq=freq)

    def change_duty(self, duty):
        """
        改变占空比
        """
        if 0 <= duty and duty <= 1023:
            self.pwm.duty(duty)
        else:
            print('警告:占空比只能为 [0-1023] ')

    def deinit(self):
        """
        销毁
        """
        self.pwm.deinit()

我们创建一个switch.py将以上的代码放入其中:

现在你可以通过创建一个Switch对象来控制某个管脚的输出了。

>>> switch = Switch(Pin(2)) #创建一个switch对象,控制我们的板载led
>>> switch.change_duty(0) #不亮
>>> switch.change_duty(100) #很微弱
>>> switch.change_duty(500) #接近一半的亮度
>>> switch.change_duty(1000) #几乎全亮

# 释放pwm资源
>>> switch.deinit()

测试完毕,记得释放资源,因为PWM资源的限制,当你不再使用的时候, 需要释放PWM资源,执行deinit()函数释放资源。 注意,在MicroPython里面,自定义的__del__函数,并不会执行。所以我们需要自己定义一个函数deinit,用来释放对象的资源, 这也是MicroPython的规范, 经常释放资源是一个好习惯。

PWM呼吸灯

上面的例程,效果并不是很容易展示,所以笔者打算以PWM输出来模拟一个呼吸灯的效果,并展示给大家看。

import machine
import utime, math
from switch import Switch
from machine import Pin

switch_led = Switch(Pin(2))

def pulse(switch, period, gears):
    # 呼吸灯核心代码
    # 借用sin正弦函数,将PWM范围控制在 23 - 1023范围内
    # switch 开关对象
    # period 呼吸一次的周期 单位/毫秒
    # gears 呼吸过程中经历的亮度档位数

    for i in range(2 * gears):
        switch.change_duty(int(math.sin(i / gears * math.pi) * 500) + 523)
        # 延时
        utime.sleep_ms(int(period / (2 * gears)))

# 呼吸十次
for i in range(10):
    pulse(switch_led, 2000, 100)

# 释放资源
switch_led.deinit()

效果图如下

参考文献

经典科普:为什么电影24帧就行,但游戏要60帧?

人类显示器的黑历史

定义PWM-upycraft文档