1. 使用PWM控制sysfs接口
直接使用ls /sys/class/pwm
命令得到的结果为空
因此要想使用硬件PWM,首先得确认哪些引脚可以输出硬件PWM。在/boot/overlays/README中提到,只有GPIO18是在全树莓派平台上都能作为硬件PWM输出脚的。其他引脚是否可用于PWM输出,可以使用raspi-gpio查看。例如:
pi@raspberrypi:~ $ 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
pi@raspberrypi:~ $ 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上。
1.1 硬件PWM的使用
Linux内核通过sysfs支持硬件PWM,所以这个部分的内容不仅限于树莓派,实际上所有实现了对应驱动的开发板都一样。
树莓派的raspbian系统映像已经提供了对应的驱动,可以直接使用。修改并保存/boot/config.txt
之后重启设备,如果设置正确,可以在目录/sys/class/pwm
中看到一些东西,比如:
pi@raspberrypi:~ $ ls /sys/class/pwm
pwmchip0
pi@raspberrypi:~ $ ls /sys/class/pwm//pwmchip0
device export npwm power subsystem uevent unexport
这些伪文件就是Linux内核PWM驱动提供的操纵接口,在shell里可以通过cat
读,通过echo
重定向写。在任意编程语言里也可以通过读写文件的接口进行同样的操作。
每个控制器的通道数可以在 npwm 中读取(只读)
pi@raspberrypi:~ $ cat /sys/class/pwm//pwmchip0/npwm
2
通过在“export”中写入相应的数字来导出每个通道(请求 sysfs 激活)
首先创建一个PWM的导出,向export
写几,就会创建对应的目录在pwmchipX里面:
pi@raspberrypi:~ $ echo 0 > /sys/class/pwm/pwmchip0/export
pi@raspberrypi:~ $ ls /sys/class/pwm/pwmchip0/
device export npwm power pwm0 subsystem uevent unexport
pi@raspberrypi:~ $ ls /sys/class/pwm/pwmchip0/pwm0
capture duty_cycle enable period polarity power uevent
这里面,period是以纳秒计数的PWM周期,duty_cycle是以纳秒计数的每周期高电平时间。比如我想要一个20KHz的PWM,占空比为80%,那我就应当:
pi@raspberrypi:~ $ echo 50000 > /sys/class/pwm/pwmchip0/pwm0/period # 两万Hz的时长是五万纳秒
pi@raspberrypi:~ $ echo 10000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle # 占空比80%,那么20%的时长就是一万纳秒
然后向enable写0或者1进行开关。
pi@raspberrypi:~ $ echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable
关闭之后不会清空原有设置,再次打开会以之前设置的参数运行PWM。
如果需要释放资源,向/sys/clas/pwm/pwmchipX/unexport
写对应的序号,会清空对应的PWM导出目录,并且删除配置。
参考链接:raspberry-pi - 树莓派硬件PWM的使用方法 - 个人文章 - SegmentFault 思否
2. WringPi库-硬件PWM接口函数
使用硬件PWM接口需要包含头文件:#include <wiringPi.h>
树莓派3B的WiringPi映射表
BCM引脚图:
int wiringPiSetup(void)
:- 初始化树莓派引脚,使用的是wiringPi引脚编号表,引脚的编号为0~16
- 需要root权限
-
- 返回-1表示失败
int wiringPiSetupGpio(void)
:- 初始化树莓派引脚,使用BCM GPIO引脚编号表
- 需要root权限
- 返回-1表示失败
pwmSetClock(int divisor)
- divisor: 设置PWM时钟的分频。范围为2~4095
注:PWM基础时钟19.2MHz,WiringPi库在初始化时,默认divisor值是32,因此默认PWM时钟就为PWMfreq=19.2×1000×1000 / 32 = 600KHz。
- divisor: 设置PWM时钟的分频。范围为2~4095
pwmSetMode(int mode)
- mode:设置PWM的不同工作模式,其中PWM_MODE_BAL(Balanced模式), PWM_MODE_MS(Mark:Space模式(占空比模式))
- Mark:Space模式是传统PWM模式,树莓派默认PWM工作在Balanced模式下。需要重新设置占空比,就要设置为Mark:Space模式。
pwmSetRange(int range)
- 用来设置PWM的周期,默认值为1024.计算方式:比如600KHz的PWM时钟,
range = (600 x 1000Hz) / PWMfreq
。
- 用来设置PWM的周期,默认值为1024.计算方式:比如600KHz的PWM时钟,
pwmWrite(int pin, int value)
- pin: 硬件PWM引脚编号(在WiringPi中的编号),将在该引脚上产生PWM波。
- value:设置占空比,value取值范围:0~range,默认范围:0~1023。因为一个周期分为range等份,所以占空比范围为0~range。
3. 使用硬件PWM驱动无源蜂鸣器
使用PWM驱动无源蜂鸣器发出有旋律的响声。
接线图:
将无源蜂鸣器的I/O接口接在树莓派的12引脚上,因为树莓派的12引脚对应WiringPi接口中的GPIO 1,所以在代码中的引脚编号为1
pi@raspberrypi:~ $ vim pwm.c
/* 无源蜂鸣器 */
#include <softPwm.h>
#include <wiringPi.h>
#include <softTone.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define PIN 1
// 音谱定义
// 低C音符的频率
#define Tone_CL1 131
#define Tone_CL2 147
#define Tone_CL3 165
#define Tone_CL4 175
#define Tone_CL5 196
#define Tone_CL6 221
#define Tone_CL7 248
// 中C音符的频率
#define Tone_CM1 262
#define Tone_CM2 294
#define Tone_CM3 330
#define Tone_CM4 350
#define Tone_CM5 393
#define Tone_CM6 441
#define Tone_CM7 495
// 高C音符的频率
#define Tone_CH1 525
#define Tone_CH2 589
#define Tone_CH3 661
#define Tone_CH4 700
#define Tone_CH5 786
#define Tone_CH6 882
#define Tone_CH7 990
// 第一首歌音谱
int makerobo_song_1[] = {Tone_CM3, Tone_CM5, Tone_CM6, Tone_CM3, Tone_CM2, Tone_CM3, Tone_CM5, Tone_CM6, Tone_CH1, Tone_CM6, Tone_CM5, Tone_CM1, Tone_CM3, Tone_CM2, Tone_CM2, Tone_CM3, Tone_CM5, Tone_CM2, Tone_CM3, Tone_CM3, Tone_CL6, Tone_CL6, Tone_CL6, Tone_CM1, Tone_CM2, Tone_CM3, Tone_CM2, Tone_CL7, Tone_CL6, Tone_CM1, Tone_CL5};
// 节拍
int makerobo_beat_1[] = {1, 1, 3, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 3};
int g_stop = 1;
void sig_handler(int signum)
{
switch( signum )
{
case SIGINT:
case SIGTERM:
g_stop = 0;
default:
break;
}
return;
}
int main()
{
int i;
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
if( wiringPiSetup() == -1 )
{
printf("makerobo setup wirinngPi falied: %s\n", strerror(errno));
return -1;
}
pinMode(PIN, PWM_OUTPUT);
pwmSetRange(1024);
pwmSetClock(75);
while( g_stop )
{
printf("makerobo music is being played...\n");
for( i=0; i<sizeof(makerobo_song_1)/4; i++ )
{
pwmWrite(PIN, makerobo_song_1[i]);
delay(makerobo_beat_1[i] * 500);
}
}
printf("Exit this program!\n");
pwmWrite(PIN, 0);
return 0;
}
运行:
pi@raspberrypi:~ $ gcc pwm.c -o pwm -lwiringPi
pi@raspberrypi:~ $ sudo ./pwm