树莓派多线程“双控”红绿灯
2022-08-31牟晓东
牟晓东
在计算机编程中,“多线程”属于一种并发的执行机制,它的引入也并非为了提高运行效率,其主要作用是追求“小时间片的轮询”式同步以完成多项任务。以开源硬件编程中的“双控”红绿灯为例,十字路口的红绿灯在正常情况下会每隔一段均匀的时间进行颜色的平均切换,当遇到“紧急情况”需要某个方向(水平或竖直)立刻切换为绿灯通行状态时,不必等到另外方向的绿色通行时段结束就马上为紧急通道让路,处理完紧急情况后再自动切换至“自动”模式,实现对红绿灯的“双控”。此时,比较好的选择就是使用多线程编程的方式来实现。
实验器材包括树莓派和古德微扩展板各一块,摇杆模块一个,ADS1115模数转换器一个,红色和绿色LED灯各四支,330Ω电阻四个,小型面包板一个,杜邦线若干。
首先,将扩展板正确安装于树莓派上;接着,将模数转换器按照标注插入扩展板的Up引脚列(或反向插入Down引脚列),再使用杜邦线将摇杆模块的+5V和GND端分别连接至扩展板的+5V和GND接地端,摇杆模块的VRX和VRY端分别连接至扩展板的A0和A1模拟数据输入端口(保持其按钮的SW端是悬空不用状态);然后,在面包板上模拟十字路口的红绿灯——为了实现两种颜色的LED灯在同时亮起时有大致一样的亮度,需要为每支红色LED灯先串联一个330Ω电阻(绿色LED灯直接插入面包板即可),要注意处于同一方向上的两支同色LED灯是并联的,这样可以使用一个信号来同时驱动其发光或熄灭;四组同色的LED灯连接好之后,分别再通过杜邦线连接至扩展板的5号、6号、12号和16号引脚(如图1)。
最后,给树莓派连接数据线,通电启动操作系统。
通过浏览器访问古德微网站(http://www.gdwrobot.cn/robot_system/#/home/carcontrol),登录自己的账号后进入图形化编程界面。
为了实现水平和竖直两个方向各自“亮绿灯”的通行状态,同时也对应另一方向“亮红灯”的禁行状态,需要先编写“水平方向通行”和“竖直方向通行”两个函数——前者实现5号和12号小灯“亮”、6号和16号小灯“灭”,后者实现5号和12号小灯“灭”、6号和16号小灯“亮”,同时也加入调试信息的显示输出(比如“水平方向通行”)和等待5秒的亮灯(灭灯)延时。接着,再来建立一个名为“自动红绿灯”的函数,将“水平方向通行”和“竖直方向通行”两个子函数添加进来即可——不区分前后次序(如图2)。
从左侧“线程”处添加子线程,对应调用的线程函数是“自动红绿灯”(注意名称要正确对应);然后再建立一个“重复当‘真’”的循环结构,执行的动作即为手动操控摇杆时对“自动红绿灯”进行中断,执行某方向红绿灯的临时通行——建立名为“摇杆X轴”和“摇杆Y轴”的两个变量,分别赋值为从模拟端口A0和A1进行数据的读取,并且通过两个输出调试信息模块进行数据的提示输出;建立一个“如果…执行…否则如果…执行…”双分支选择结构,分别对应调用“水平方向通行”和“竖直方向通行”函数,各自的判断条件是摇杆对应方向是否有拨动的动作。经过测试后发现,摇杆在水平方向上向左和向右拨动到极限时,变量“摇杆X轴”的值大约分别是22和32767(中间状态的数据值是18774左右),因此构建“‘摇杆X轴<=22’或‘摇杆X轴>=32767’”作为判断摇杆是否发生了左右拨动的条件,然后就会调用“水平方向通行”函数,控制水平方向的两处绿色LED灯发光,同时竖直方向的两处红色LED灯也发光,并且持续5秒钟。同理,第二个条件判断对应摇杆在竖直方向是否有拨动的动作;最后,添加一个等待0.01秒的等待模块(如图3)。
也就是说,主程序其实就是两个子线程在并行,一个(子线程)负责红绿灯每隔5秒钟就进行一次红灯和绿灯不同方向的切换;另一个(循环结构)负责每隔0.01秒钟就对摇杆进行一次检测,若有某个方向的拨动动作发生时,则迅速将自动红绿灯切换为该方向绿灯通行、对应方向红灯禁行的状态,持续5秒钟后若没有检测到摇杆有拨动动作,则恢复为之前的自动红绿灯切换状态。
程序编写完毕后保存为“多线程‘双控’红绿灯”,然后点击“连接设备”与树莓派进行连接,最后点击“运行”进行程序的测试,实现了预期的多线程“双控”红绿灯效果(如图4)。
运行Windows的“遠程桌面连接”,输入对应的IP地址后登录进入树莓派操作系统,通过菜单命令打开IDE开始进行Python代码编程。
导入RPi.GPIO模块:“import RPi.GPIO as GPIO”,导入time模块:“import time”;为了对模数转换器进行数据读取,还需要导入模块:“import Adafruit_ADS1x15”;为了进行线程方面的操作,再导入threading模块:“import threading”。
接着,通过“GPIO.setwarnings(False)”将错误警告提示信息关闭,并且通过“GPIO.setmode(GPIO.BCM)”将工作模式设置为BCM模式;然后将5号、6号、12号和16号引脚均设置为OUT输出状态:“GPIO.setup(5,GPIO.OUT)”、“GPIO.setup(6,GPIO.OUT)”、“GPIO.setup(12,GPIO.OUT)”、“GPIO.setup(16,GPIO.OUT)”;最后,通过“adc = Adafruit_ADS1x15.ADS1115()”来生成模数转换器的具体实例对象(如图5)。
先来编写Horizontal_Go()和Vertical_Go()两个子函数,分别对应实现水平方向通行和竖直方向通行的亮灯与灭灯功能。其中,主要是将LED所连接的引脚端口号设置为HIGH高电平(亮灯)和LOW低电平(灭灯),比如“GPIO.output(5,GPIO.HIGH)”、“GPIO.output(6,GPIO.LOW)”等等,后面的“time.sleep(5)”作用是控制亮灯和灭灯进行5秒钟的延时。
再来编写Auto_RedGreen()函数,对应图形化编程中的“自动红绿灯”函数,直接建立一个“while True”循环结构,对Horizontal_Go()和Vertical_Go()两个子函数进行顺序调用即可。
最后再编写摇杆My_Rocker()函数,实现对摇杆是否有左右或上下拨动动作的判断及做出某方向的通行响应。仍然也是在“while True”循环结构中,先建立My_X和My_Y两个变量,分别为其赋值为“adc.read_adc(0,gain=1)”和“adc.read_adc(0, gain=1)”,对应从模拟端口A0和A1进行数据的读取(其中的参数gain是增益);接着使用“if…elif…”双分支选择结构分别对My_X和My_Y两个变量的数据进行大小判断,条件成立的话则分别执行Horizontal_Go()和Vertical_Go()两个子函数的亮灯和灭灯动作;最后,再添加延時0.01秒的等待命令:“time.sleep(0.01)”(如图6)。
在主程序中建立t1和t2两个线程,其值分别为“threading.Thread(target=Auto_RedGreen)”和“threading.Thread(target=My_Rocker)”,通过target参数来控制调用自动红绿灯Auto_RedGreen()函数和摇杆My_Rocker ()函数;接着,启动两个子线程:“t1.start()”、“t2.start()”,执行对应的线程代码;最后,添加“while 1:pass”代码,将程序保存为“多线程’双控’红绿灯.py”(如图7)。
点击Run按钮运行程序,与图形化编程所实现的多线程“双控”红绿灯效果完全一致(如图8)。