张高兴的 .NET Core IoT 入门指南:(五)PWM 信号输出
什么是 pwm
在解释 pwm 之前首先来了解一下电路中信号的概念,其中包括模拟信号和数字信号。模拟信号是一种连续的信号,与连续函数类似,在图形上表现为一条不间断的连续曲线。数字信号为只能取有限个数值的信号,比如计算机中的高电平(1)和低电平(0)。
pwm(pulse width modulation)即脉冲宽度调制,简称脉宽调制,通过对一系列的脉冲的宽度进行调制,从而等效出所需要的模拟信号。如图 1 所示,蓝色波形为调制的一系列脉冲,红色波形为模拟的正弦样信号。在模拟电路中,模拟信号的值可以连续进行变化,而数字电路是在高电平和低电平中取值,所以电压或电流会以脉冲的形式出现。通过使用 pwm 技术,我们可以在数字电路中模拟出电信号的连续变化。
图1:pwm 示意图
提示
看完上面的如果你还不明白,那么可以看看下面这个生动的解释,这个解释来源于:
“简单的说,比如你有5v电源,要控制一台灯的亮度,有一个传统办法,就是串联一个可调电阻,改变电阻,灯的亮度就会改变。还有一个办法,就是pwm调节。不用串联电阻,而是串联一个开关。假设在1秒内,有0.5秒的时间开关是打开的,0.5秒关闭,那么灯就亮0.5秒,灭0.5秒。这样持续下去,灯就会闪烁。如果把频率调高一点,比如是1毫秒,0.5毫秒开,0.5毫秒灭,那么灯的闪烁频率就很高。我们知道,闪烁频率超过一定值,人眼就会感觉不到。所以,这时你看不到灯的闪烁,只看到灯的亮度只有原来的一半。同理,如果1毫秒内,0.1毫秒开,0.9毫秒灭,那么,灯的亮度就只有原来的10分之一。”
使用 pwm 需要了解占空比(duty cycle)和频率(frequency)的概念。占空比即 pwm 信号在一个周期内处于高电平的时间与整个周期的时间的比值。在 5v 电源的情况下,想要产生一个 3v 的信号,可以使用占空比为 60% 的 pwm。图 2 从波形的角度解释了 pwm。频率是 pwm 信号在 1 秒内完成一个周期的次数,单位是 hz。如果输出的频率够高并保持一定的占空比,就可以模拟出恒定电压。图 3 对比了小灯亮度的变化与占空比的变化,通过观察图右侧的 pwm 波形可以看到占空比越高小灯越亮。
图2:占空比示意图
图3:小灯亮度变化与占空比变化对比
raspberry pi 上提供了硬件 pwm 功能,一共包括 2 个通道,引出了 4 个 gpio 引脚。其中 gpio 12 和 gpio 18 属于通道 0,gpio 13 和 gpio 19 属于通道 1。但有意思的是只有通道 0 的 gpio 18 引脚的默认功能为 pwm,其他的不是被音频处理所占用,就是引脚另有它用。启用这些引脚需要进行一些特殊配置甚至内核编程。
提示
如何启用 raspberry pi 上的 pwm ?
修改 /boot/config.txt ,添加 dtoverlay=pwm 。
启用 pwm 通道 1 请参考:https://github.com/raspberrypi/firmware/issues/1178
修改 gpio 引脚功能请参考:https://www.dummies.com/computers/raspberry-pi/raspberry-pi-gpio-pin-alternate-functions 和 http://abyz.me.uk/rpi/pigpio/pigs.html
相关类
pwm 操作的相关类位于 system.device.pwm 命名空间下。
pwmchannel
public class pwmchannel : idisposable { // 创建 pwmchannel 对象 // chip 为 pwm 芯片编号,linux 下位于 /sys/class/pwm 文件夹下 // channel 为 通道编号 public static pwmchannel create(int chip, int channel, int frequency = 400, double dutycycle = 0.5); // 占空比,取值为 0.0 - 1.0 public double dutycycle { get; set; } // 频率,单位为 hz public int frequency { get; set; } // 打开和关闭 pwm 通道 public void start(); public void stop(); }
pwm 的使用步骤
- 实例化一个 pwmchannel 对象
pwmchannel pwm = pwmchannel.create(chip: 0, channel: 0, frequency: 400, dutycycle: 0);
- 打开 pwm 通道
pwm.start();
- 设置占空比/频率改变输出的 pwm 信号
pwm.dutycycle = 0.5;
- 关闭 pwm 通道
pwm.stop();
使用硬件 pwm 控制 led 的亮度
硬件需求
名称 | 数量 |
---|---|
led | x1 |
220 ω 电阻 | x1 |
杜邦线 | 若干 |
电路
- led 正极 - gpio 18 (pin 12)
- led 负极 - gnd
使用 docker 运行示例
示例地址:https://github.com/zhanggaoxing/dotnet-core-iot-demo/tree/master/src/pwmled
docker build -t pwm-led-sample -f dockerfile . docker run --rm -it -v=/sys/class/pwm:/sys/class/pwm --privileged=true pwm-led-sample
代码
- 打开 visual studio ,新建一个 .net core 控制台应用程序,项目名称为“pwmled”。
- 引入 system.device.gpio nuget 包。
- 在 program.cs 中,将主函数代码替换如下:
static void main(string[] args) { int brightness = 0; using pwmchannel pwm = pwmchannel.create(chip: 0, channel: 0, frequency: 400, dutycycle: 0); pwm.start(); while (brightness != 255) { pwm.dutycycle = brightness / 255d; brightness++; thread.sleep(10); } while (brightness != 0) { pwm.dutycycle = brightness / 255d; brightness--; thread.sleep(10); } pwm.stop(); }
- 发布、拷贝、更改权限、运行
效果图
使用软件 pwm 控制 rgb led
上面提到 raspberry pi 中默认只有 gpio 18 这一个引脚可以使用 pwm,要控制 rgb led 则至少需要使用 3 个 pwm,这显然是不够用的。在 iot.device.bindings
这个 nuget 包中为我们提供了使用 gpio 模拟的软件 pwm 类 softwarepwmchannel
。软件 pwm 的使用效果并没有硬件 pwm 的那种“顺滑”,因为其精度完全取决于 gpio 的速度。
提示
rgb led 有三种颜色,但通常只有 4 个引脚,而三种单色 led 却有 6 个引脚,为什么会少了 2 个引脚?rgb led 分为共阳极和共阴极。如果少的两个引脚为阳极,则为共阳极 rgb led,三个单色 led 共用一个阳极,剩下的三个引脚为各自的阴极。共阴极 rgb led 则相反。两种 led 在使用上类似,但程序相反,比如共阴极时占空比越高 led 越亮,而共阳极时,占空比越高则 led 越暗。
硬件需求
名称 | 数量 |
---|---|
rgb led | x1 |
220 ω 电阻 | x3 |
杜邦线 | 若干 |
电路
- led r - gpio 18 (pin 12)
- led g - gpio 23 (pin 16)
- led b - gpio 24 (pin 18)
- led 阴极 - gnd
使用 docker 运行示例
示例地址:https://github.com/zhanggaoxing/dotnet-core-iot-demo/tree/master/src/pwmrgb
docker build -t pwm-rgb-sample -f dockerfile . docker run --rm -it --device /dev/gpiomem pwm-rgb-sample
代码
- 打开 visual studio ,新建一个 .net core 控制台应用程序,项目名称为“pwmrgb”。
- 引入 iot.device.bindings nuget 包。
- 在 program.cs 中,将主函数代码替换如下:
static void main(string[] args) { using pwmchannel red = new softwarepwmchannel(pinnumber: 18, frequency: 400, dutycycle: 0); using pwmchannel green = new softwarepwmchannel(pinnumber: 23, frequency: 400, dutycycle: 0); using pwmchannel blue = new softwarepwmchannel(pinnumber: 24, frequency: 400, dutycycle: 0); red.start(); green.start(); blue.start(); breath(red, green, blue); red.stop(); green.stop(); blue.stop(); } public static void breath(pwmchannel red, pwmchannel green, pwmchannel blue) { int r = 255, g = 0, b = 0; while (r != 0 && g != 255) { red.dutycycle = r / 255d; green.dutycycle = g / 255d; r--; g++; thread.sleep(10); } while (g != 0 && b != 255) { green.dutycycle = g / 255d; blue.dutycycle = b / 255d; g--; b++; thread.sleep(10); } while (b != 0 && r != 255) { blue.dutycycle = b / 255d; red.dutycycle = r / 255d; b--; r++; thread.sleep(10); } }
- 发布、拷贝、更改权限、运行
效果图
供参考
- pulse-width modulation - wikipedia:https://en.wikipedia.org/wiki/pulse-width_modulation
- rpi4 : pwm0 & pwm1 alternate pins - github:
- raspberry pi gpio pin alternate functions:
- pwm source code:https://github.com/dotnet/iot/tree/master/src/system.device.gpio/system/device/pwm
- 脉冲宽度调制 - 百度百科:
上一篇: 雍正皇帝问沈县令自己有没有贪过 为什么他还说肯定有
下一篇: 云南名吃有哪些,让你用舌尖感受云南