2.语音板的LED灯编程
0x00 语音板LED灯介绍
在语音板上,我们预留了一个可以编程控制的LED灯。这样大家可以自己编程做出各种各样的灯光效果,例如想做出呼吸灯效果、闪烁灯效果都是可以的。这颗LED灯可以配合着语音交互过程,做出各样反馈效果,这样语音板就显的更好玩一点。这颗LED灯,我用的是一颗蓝色的LED灯,具体位置如下图所示:
除了要知道这颗LED灯的物理位置,还需要知道这颗LED灯连接到GPIO哪个引脚上,这样我们才好编程控制GPIO引脚从而可以控制LED灯。LED灯连接的GPIO引脚如下图所示:
接触树莓派不多的同学可能对gpio这个命令不熟悉,gpio这个工具是wiringPi这个软件包中的。那这个wiringPi是什么东西呢?它可以理解为arduino版本中的API打包版本,使用wiringPi非常简单。它的语法规则基本上跟arduino上编程是一样的,使用C语言编程,基本上都是调用现成的API就可以了。
当使用gpio readall命令时,就可以将树莓派上排针的位置、功能、编号都给列出来,非常的实用。下面对上图进行下简单的解释,中间的两列是物理引脚,就是我们看到树莓派上的那两排排针。靠近板子外侧的编号都是偶数,靠近内侧的编号都是奇数。但是靠这个物理编号,我们还是没办法编程的,所以这里就用到了我们提到的wiringPi,使用它进行编程的时候我们需要注意各引脚的编号会跟物理引脚不同。例如物理引脚编号32,对应着wiringPi上的26号引脚。那这里又会发现旁边竟然还有一个BCM编号,这个是什么呢?
这个BCM编号是根据CPU上的GPIO寄存器来命名的,其实总体来说无论使用wiringPi的编号还是BCM的编号,其实对应的物理引脚都是一样的。无非是在编程的时候,我们需要使用不同的API来调用不同的IO口。
当然这里除了种方式来操作树莓派的引脚。那就是使用python GPIO,这是树莓派官方推荐的一种方式。因为这种方式在使用引脚的时候,我们只要数一下物理引脚就可以来编程了。这里来看看这两种编程方式的区别:
(1).RPi.GPIO:编程语言:python,树莓派官方资料中推荐且容易上手。它是一个小型的python库,可以帮助用户完成相关IO口操作,但是它还没有支持SPI、I2C或者1-wire等总线接口。
(2).wiringPi:编程语言:C,wiringPi适合那些具有C语言基础,在接触树莓派之前已经接触过单片机或者嵌入式开发的人群。wiringPi的API函数和arduino非常相似,这也使得它广受欢迎。可以操作包括UART设备,I2C设备和SPI设备。
0x01 使用python GPIO方式编程
这里我们来写代码让LED灯出现亮-灭-亮-灭的效果,其实就跟arduino中的闪烁一样了,代码如下:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import RPi.GPIO as GPIO
import time
#led灯的物理引脚
channel=32
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
GPIO.setup(channel, GPIO.OUT, initial=GPIO.HIGH)
try:
while True:
#led灯GPIO置高-灭
GPIO.output(channel, GPIO.HIGH)
print("X")
time.sleep(1)
#led灯GPIO置低-亮
GPIO.output(channel, GPIO.LOW)
print("O")
time.sleep(1)
except KeyboardInterrupt:
pass
#GPIO资源清理
GPIO.cleanup()
这里的代码进行下简单的解析,首先使用import导入我们需要的RPi.GPIO。然后我们定义下led连接的物理引脚,通过上一节的图中得知led连接在物理引脚32上。下面的代码都可以使用channel来代替32了,这样方便我们编代码。下面的GPIO.setmode(),这里的输入可以是GPIO.BOARD就是使用物理引脚号,我们直接数排针1-40就行了。你的Led接在哪个物理引脚上就用哪个,这里的输入参数还可以是GPIO.BCM,这样输入的channel就不是物理引脚编号了,就是BCM编号了。
下面的GPIO.setup()就是为了配置相应的引脚,输出模式还是输入模式。由于我们接的led灯,输出高低电平才能控制灯的亮灭,所以这里设置的是GPIO.OUT,后面还有一个参数就是为了设置这个引脚初始状态的,这里我们置为LOW。在后面的while循环就是重点了,我们控制灯亮,直接就是GPIO.ouput(channel, GPIO.HIGH)就可以了。就是设置相应的引脚高电平,那么灯就亮,想让灯灭,那就设置相应引脚低电平就可以了。
将该代码放在树莓派中,将源码命名为python_led_blink.py,执行起来效果如下:
我们还可以做出一个呼吸灯的效果,就是让led灯慢慢的变亮,然后又慢慢的变暗,然后又慢慢的变亮,如此循环。代码也很简单,参考代码如下:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import RPi.GPIO as GPIO
import time
#led灯的物理引脚
channel=32
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
GPIO.setup(channel, GPIO.OUT, initial=GPIO.LOW)
pwm = GPIO.PWM(channel, 50)
pwm.start(0)
try:
while True:
#led灯慢慢变亮
for duty in range(100, 0, -4):
pwm.ChangeDutyCycle(duty)
time.sleep(0.1)
#led灯慢慢变暗
for duty in range(0, 100, 4):
pwm.ChangeDutyCycle(duty)
time.sleep(0.1)
except KeyboardInterrupt:
pass
#GPIO资源清理
pwm.stop()
GPIO.cleanup()
下面我们来对代码进行简单的解析,这里我们使用了一个新API。那就是GPIO.PWM(),这个PWM是什么呢?脉冲宽度调制,PWM是一种对模拟信号电平进行数字编码的方法,由于计算机不能输出模拟电压,只能输出0 或5V 的的数字电压值,我们就通过使用高分辨率计数器,利用方波的占空比被调制的方法来对一个具体模拟信号的电平进行编码。PWM 信号仍然是数字的,因为在给定的任何时刻,满幅值的直流供电要么是5V(ON),要么是0V(OFF)。电压或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去的。通的时候即是直流供电被加到负载上的时候,断的时候即是供电被断开的时候。只要带宽足够,任何模拟值都可以使用PWM 进行编码。输出的电压值是通过通和断的时间进行计算的。
输出电压=(接通时间/脉冲时间)*最大电压值。
其实这个PWM可以简单形象的描述出来,大家可以想象一下骑自行车的场景。假如现在我们按1分钟为一个周期,1秒钟可以脚蹬一圈来看看我们自行车当前的速度,若你要想自行车以最大的速度前进,那么是不是在1min内,你脚蹬自行车肯定就不能停,1秒蹬一圈,自行车的速度肯定是最快的。
那现在你想控制自己自行车的速度为最大速度的一半,那该怎么操作呢?很简单了,你1分钟之内,只要前半分钟蹬车就可以了,剩下的半分钟休息就行了。那如果你想控制自行车速度为最大速度的1/4,那是不是就应该在前15秒蹬就行了,剩下的45秒休息,那么自行车的速度肯定就是最大速度的1/4了。听起来是不是挺简单的,pwm原理差不多就是这样的。
这个PWM应用场景很多的,我们制作的ROS小车在控制电机转动时候也是用到PWM来控制电机转速的。所以理解和掌握pwm对我们后面学习是很有帮助的。
现在返回来继续介绍这个代码,GPIO.PWM(channel, freq)就是设置led灯GPIO的pwm频率,就是设置下周期是多少,这里设置为50是什么意思呢?那就是一个周期为20ms,因为频率=1/周期。接下来pwm.start(0),开始启动pwm输出,默认从0开始就是低电平。接下来进入while循环,我们首先将led灯从暗逐渐调整到最亮,那就是把占空比从0调整到100。需要注意这里的占空比最大是100,意思是全周期的高电平输出,这是个百分比的数字。再加上延时,我们就可以看出来灯光亮度在不断的升高,因为占空比也在不断的增加。那最终的运行效果,如下所示:
0x02 wiringPi编程控制led灯
使用wiringPi库来编程,我感觉应该是最简单、亲切的,因为这个编程基本上跟arduino上编程一样的了。大部分人可能都喜欢使用wiringPi编程,使用wiringPi编程控制led灯blink的代码如下:
#include <wiringPi.h>
#include <stdio.h>
#define LED_PIN 26
#define DELY_MS 1000
int main(void)
{
wiringPiSetup();
pinMode(LED_PIN, OUTPUT);
while(1)
{
//led GPIO引脚置高电平,灯灭
digitalWrite(LED_PIN, HIGH);
printf("X\n");
delay(DELY_MS);
//led GPIO引脚置低电平,灯亮
digitalWrite(LED_PIN, LOW);
printf("O\n");
delay(DELY_MS);
}
return 0;
}
这份代码就比较简单了,这里就不再做解析,因为跟arduino代码基本上一样,无非就是加了个c的main函数。这里要说明下如何来编译该源文件,首先命名该源码为wiringpi_led_blink.c,将该源码编译的命令如下:
gcc wiringpi_led_blink.c -o led_blink -Wall -lwiringPi
-Wall:这个参数是告诉gcc编译器,在编译源码的时候把Warning all输出。
-lwiringPi:这个参数告诉gcc编译器,在编译源码的时候需要动态链接wiringPi.so这个库。最前面的是lib的首字母,表明链接的是动态库。
当编译完成后,会生成一个可执行文件led_blink,我们只要执行这个文件就可以看出来led灯的blink效果了。这里就不再看最终的效果了,大家可以自行编译去查看效果。
当然这里我们实现了blink的代码,我们也可以实现fade的效果的。这里需要注意的是树莓派的排针上有硬件PWM,其实所有的GPIO都可以使用PWM功能,只不过没有硬件pwm的只能软件模拟,这样就是比较消耗CPU资源而已。对于树莓派4来说,共有三个硬件PWM,如下图所示:
可以发现我们的led灯正好在物理引脚32上,这个引脚也具备硬件输出的PWM功能。那接下来我们就可以来写代码了,代码如下:
#include <wiringPi.h>
#include <stdio.h>
#define LED_PIN 26
#define DELY_MS 10
int main(void)
{
int duty = 0;
wiringPiSetup();
pinMode(LED_PIN, PWM_OUTPUT);
while(1)
{
//led 灭->亮
for(duty=1024; duty>=0; duty-=4)
{
pwmWrite(LED_PIN, duty);
delay(DELY_MS);
}
//led 亮-灭
for(duty=0; duty<=1024; duty+=4)
{
pwmWrite(LED_PIN, duty);
delay(DELY_MS);
}
}
return 0;
}
对于这份代码的编译与上面blink代码的编译命令是一样的,只不过这里当生成了执行文件后,在执行的时候,需要加上sudo权限才能执行。因为这里需要用到硬件pwm输出功能,必须是root权限才行。
0x03 源码下载
本次课程设计的源码,我都会上传到语音板的代码仓库中。大家可以查看下源码目录下的example文件下的四个文件:
至于如何下载该代码仓库是比较简单的,该项目的代码仓库源码地址如下:
https://code.corvin.cn/corvin_zhang/AIVoiceSystem
0x04 参考资料
[1].树莓派开发系列教程9-树莓派GPIO控制. https://blog.csdn.net/xdw1985829/article/details/39580401/
[2].逗比学树莓派之GPIO. https://blog.csdn.net/u013431550/article/details/40516939
[3].raspberry-gpio-python官方主页. https://sourceforge.net/p/raspberry-gpio-python/wiki/Home/
[4].使用 RPI.GPIO 模块的脉宽调制(PWM)功能. https://www.jianshu.com/p/4e9e0ecfa6aa
[5].Windows on Device项目实践1-PWM调光灯制作. https://www.cnblogs.com/dearsj001/p/WindowsOnDevice_1.html?utm_source=tuicool
0x05 问题反馈
大家在按照教程操作过程中有任何问题,可以直接在文章末尾给我留言,或者关注ROS小课堂的官方微信公众号,在公众号中给我发消息反馈问题也行。我基本上每天都会处理公众号中的留言!当然如果你要是顺便给ROS小课堂打个赏,我也会感激不尽的,打赏30块还会被邀请进ROS小课堂的微信群,与更多志同道合的小伙伴一起学习和交流!
[wshop_reward]