最近折腾一个调光显示屏,厂商把调光的编程接口暴露成了PWM,所以就折腾了一下树莓派的PWM输出能力。这里面有一些散落在不同文档中的内容,还有一些不知道从什么文档里查到的东西,归纳在这里,以备后来者之需。
树莓派的40 pin通用引脚可以输出PWM信号,用于表示一个连续量以操作电机控制器、调光灯等设备。他的PWM输出有两种:软件PWM其实就是普通的GPIO输出,在一个线程里定时开关,优点是所有的GPIO引脚都可以做,缺点是工作频率、精度都比较低(在新的pigpio库里使用高精度时钟改善了软件PWM的精度,以前老的python库实现超过几百Hz就完全不准了)。而硬件PWM是使用树莓派CPU内置的PWM硬件,将它的信号导出到40 pin引脚,优点是精度和工作频率都很高,缺点是可用数量少,只有两路。
硬件PWM的配置
想要使用硬件PWM,首先显然是确认有哪些引脚可以输出硬件PWM。参考/boot/overlays/README
的内容,只有GPIO18是在全部树莓派平台上都能作为硬件PWM输出脚的。其它引脚是否可用于此,可以使用raspi-gpio
程序,例如在我的树莓派3B上面:
$ raspi-gpio funcs 12
GPIO, DEFAULT PULL, ALT0, ALT1, ALT2, ALT3, ALT4, ALT5
12, DOWN, PWM0, SD4, DPI_D8, AVEOUT_VID8, AVEIN_VID8, ARM_TMS
$ raspi-gpio funcs 41
GPIO, DEFAULT PULL, ALT0, ALT1, ALT2, ALT3, ALT4, ALT5
41, DOWN, PWM1, SD5, TE0, SD1_DAT5, SPI2_MOSI, RXD1
可以看到GPIO12的第零号替代功能是PWM0,而GPIO41的第零号替代功能是PWM1。
然后需要在树莓派的启动配置文件/boot/config.txt
里面加载对应的设备树overlay,并设置参数,比如:
dtoverlay=pwm,pin=12,func=4
就会将PWM开启在12号GPIO上。注意此处的func号必须与引脚的alt功能序号匹配,有个很奇怪的顺序:
Func 0 = Input
Func 1 = Output
Func 2 = Alt 5
Func 3 = Alt 4
Func 4 = Alt 0
Func 5 = Alt 1
Func 6 = Alt 2
Func 7 = Alt 3
所以这里func设为4,对应alt0,因为raspi-gpio
告诉我们12号GPIO的PWM功能在alt0上。
硬件PWM的使用
Linux内核通过sysfs支持硬件PWM,所以这个部分的内容不仅限于树莓派,实际上所有实现了对应驱动的开发板都一样。
树莓派的raspbian系统映像已经提供了对应的驱动,可以直接使用。修改并保存/boot/config.txt
之后重启设备,如果设置正确,可以在目录/sys/class/pwm
中看到一些东西,比如:
$ ls /sys/class/pwm
pwmchip0
$ ls /sys/class/pwm/pwmchip0
device export npwm power pwm0 subsystem uevent unexport
这些伪文件就是Linux内核PWM驱动提供的操纵接口,在shell里可以通过cat
读,通过echo
重定向写。在任意编程语言里也可以通过读写文件的接口进行同样的操作。
首先创建一个PWM的导出,向export
写几,就会创建对应的目录在pwmchipX里面:
$ echo 0 >/sys/class/pwm/pwmchip0/export
$ ls /sys/class/pwm/pwmchip0/pwm0
capture duty_cycle enable period polarity power uevent
这里面,period是以纳秒计数的PWM周期,duty_cycle是以纳秒计数的每周期高电平时间。比如我想要一个20KHz的PWM,占空比为80%,那我就应当:
$ cd /sys/class/pwm/pwmchip0/pwm0
$ echo 50000 >period # 两万Hz的时长是五万纳秒
$ echo 10000 >duty_cycle # 占空比80%,那么20%的时长就是一万纳秒
然后向enable写0或者1进行开关。
$ echo 1 >enable # 立即开启
关闭之后不会清空原有设置,再次打开会以之前设置的参数运行PWM。
如果需要释放资源,向/sys/clas/pwm/pwmchipX/unexport
写对应的序号,会清空对应的PWM导出目录,并且删除配置。