海纳百客

海纳百客

树莓派 Pico 实现交通灯控制程序

吃瓜阿阳

友情提示点击顶部放大镜 可以使用站内搜索 记住我们的地址 www.hainabaike.com

微控制器(MCU)几乎存在于你日常使用的所有电子产品中,例如交通灯。交通灯在控制器的作用下,控制器确保整个交通网络保持顺畅。

虽然构建大型交通管理系统是一个非常高级的项目,但构建由树莓派 Pico 驱动的微型模拟器本身就很简单。通过这个项目,你将看到如何控制多个 LED,设置不同的时 间,以及如何在程序的其余部分继续运行时监控一个按钮输入,使用一种称为线程的技术。

搭建这个项目,你需要:
– 树莓派 Pico
– 面包板
– 红色、黄色、绿色的 LED
– 330Ω 电阻 3 个
– 一个有源蜂鸣器
– 一组公对公跳线
– microUSB 数据线

将 Pico 连接到树莓派或其他运行 Thonny MicroPython IDE 的计算机。

简易的交通灯

首先用面包板搭建一个简单的红绿灯系统电路,如图所示。拿起你的红色LED灯,把红色 LED 插到面包板上,让它跨过中间的分割线。使用一个 330Ω 电阻和跳线串连到 Pico 上。其中,LED 较长的脚和电阻连接,较短的脚和 Pico 的 GND 引脚通过跳线连通。

黄色和绿色的 LED 也类似处理,但使用的 Pico 引脚不同,参考图片所示。

启动 Thonny。创建一个新的程序,然后开始导入 machine 库,以便你可以控制你的 GPIO 引脚:

import machine

你还需要导入 utime 库,以便你可以在亮灯与灭灯之间添加延迟:

import utime

在你控制你 Pico 的 GPIO 口的任何程序里,在使用前都你将需要对它进行设置:

led_red = machine.Pin(15, machine.Pin.OUT)
led_amber = machine.Pin(14, machine.Pin.OUT)
led_green = machine.Pin(13, machine.Pin.OUT)

这些线将 GP15、GP14 和 GP13 引脚设置为输出,每个引脚都有一个述性的名称,以便于代码通俗易懂。

真正的红绿灯不会一闪而过然后停下来,它们会不停地开着,即使那里不塞车,但为了让你 的程序实现类似的效果,你需要建立一个无限循环:

while True: 

下面的每一行都需要缩进四个空格,这样 MicroPython 就知道它们是循环的一部分:

led_red.value(1)
utime.sleep(5)
led_amber.value(1)
utime.sleep(2)
led_red.value(0)
led_amber.value(0)
led_green.value(1)
utime.sleep(5)
led_green.value(0)
led_amber.value(1)
utime.sleep(5)
led_amber.value(0)

单击 Run 并将程序保存到 Pico 中,文件名为 Traffic_Lights.py。观察 LED,首先红色的 LED 灯会亮起来,告诉交通停止;接下来,黄色 LED 将会亮起,警告司机信号灯即将改变;接下来,两个 LED 都关闭,绿色 LED 亮起,让车辆知道它可以通过;然后绿色的 LED 熄灭,黄色的 LED 亮起来,警告司机信号灯又要变了;最后,黄色 LED 熄灭,回路从开始重新启动,红色 LED 亮起。

这个显示状态会一直循环直到你按下停止按钮,因为它形成了一个无限循环。它是基于现实 世界中英国交通控制系统中使用的交通灯模式。

然而,真正的红绿灯不仅仅是道路车辆的红绿灯,它们也在那里保护行人,让他们有机会安全 地穿过繁忙的道路。在英国,最常见的类型这些灯被称为行人操作用户友好型智能交叉口。行人要过马路的时候手动按下按钮,蜂鸣器发出提示让行人过马路。

要实现这个功能,需要两样东西:一个按钮开关,这样行人就可以要求车灯让他们过马路;一个蜂鸣器,让行人知道什么时候轮到他们穿越马路。将这些连接到你的面包板,如图所示。

开关通过面包板连接 Pico 的 GP16 和 3V3 上,蜂鸣器则连接 GP12 和 GND 上。

如果你再次运行你的程序,你会发现按钮和蜂鸣器没有什么反应。那是因为你还没有告诉你的 程序如何使用它们。在 Thonny 中,请返回编辑控制 LED 的行,并在下面添加以下两条新行:

button = machine.Pin(16, machine.Pin.IN, machine.Pin.PULL_DOWN)
buzzer = machine.Pin(12, machine.Pin.OUT)

程序将引脚 GP16 上的按钮设置为输入模式,控制蜂鸣器引脚的引脚 GP12 上的设置为输出。请记住,Pico 有内置的可编程电阻的输入,我们为本书中的项目设置为下拉模式。这 意味着引脚的电压被拉到 0V(它的逻辑电平是0),除非它连接到 3.3V 电源(在这种情况下,它 的逻辑电平将是1,直到断开)。

接下来,你需要一种方法来让程序不断监控按钮的价值。以前,你的所有程序都通过一系列说 明一步一步地工作,一次只做一件事。你的红绿灯程序同样,当它运行时,MicroPython 会一步一步地浏览你的指示,打开和关闭 LED。

对于一套基本的红绿灯,这就足够了。不过,对于设置了行人手动按钮的交叉口,你的程序需要能够记录按钮是否以不中断红绿灯的方式按下。要做到这一点,你需要使用到一个新的库:_thread。返回到程序的开始部分,在那里你导入 machine 和 utime 库,并导入 _thread 库:

import _thread

线程实际上是一个小的、部分独立的程序。你可以将前面编写的控制灯的循环视为程序的主线程,并使用 _thread 库创建一个同时运行的额外线程。

可视化线程的简单方法是将每个线程都想象成厨房里的独立人员,当厨师准备主菜时,其 他人正在制作酱汁。目前,你的程序只有一个线程:控制红绿灯的线程。然而,为 Pico 提供算力的 RP2040 微控制器有两个处理核心,意思是,就像厨房中的厨师和副厨师一样,你可以同时运行两个线程来完成更多的工作。

在制作另一个线程之前,你需要一种方法让新线程将信息传回主线程,并且你可以使用全局 变量做到这一点。在此之前,你一直在处理的变量称为局部变量,并且仅在程序的一个部分工作:一个全局变量在任何地方都有效,这意味着一个线程可以更改值,另一个线程可以检查它是否已更改。

首先,你需要创建一个全局变量。在 buzzer = 这行下面,添加以下内容:

global button_pressed
button_pressed = False

这将把 button_pressed 设置为一个全局变量,并给它一个默认值 False,意思是当程序启动时,按钮还没有被按下。下一步是定义线程,通过添加下面的行让程序更具有可读性:

def button_reader_thread():
	global button_pressed
	while True:
		if button.value() == 1:
			button_pressed = True

添加的第一行定义了线程,并给它一个名称来述它的用途:读取按钮输入的线程。就像在编写 循环时,MicroPython 需要将线程中包含的所有内容缩进 4 个空格,这样它就知道线程从哪里开始和结束。

下一行让 MicroPython 知道你将更改全局 button_pressed 变量的值。如果你只想检查该值,则不需要这一行—但是没有这一行,你就不能对变量进行任何更改。

接下来,你设置了一个新的循环,这意味着接下来需要一个新的四空格缩进,总共是八个缩进,这样 MicroPython 就知道循环是线程的一部分,下面的代码也是循环的一部分。这个嵌套代码在多个水平 MicroPython 缩进是很常见的。

下一行是一个条件语句,用于检查按钮的值是否为 1。因为 Pico 使用一个内部下拉电 阻,当按钮没有被按下时,读取的值是 0,表示在条件下的代码永远不会运行。只有当按钮被按下时,线程的最后一行才会运行:这一行将 button_pressed 变量设置为 True,让程序的其余部分知道按钮已被按下。

你可能注意到,在线程中没有任何东西可以将 button_pressed 变量重置回当按钮被按下后 释放时为 False。这是有原因的,虽然你可以在交通灯周期的任何时候按下过马路的按钮,但它只有在交通灯变红、你可以安全过马路时才会生效。你的新线程需要做的就是在按钮被按下时改变变量;当行人安全过马路时,主线程会将其重置为 False。

定义一个线程并不会使它自动运行,可以在程序中的任何地方启动一个线程,并且需要明确地告诉 _thread 库何时启动线程。与运行普通代码不同,运行线程不会停止程序的其余部分,当线程启动时,MicroPython 将继续运行程序的下一行。

在你的线程下面新建一行,删除所有 Thonny 为你自动添加的缩进,如下所示:

_thread.start_new_thread(button_reader_thread, ())

这告诉 _thread 库启动前面定义的线程。此时,线程将开始运行,并迅速进入循环:每秒检查按钮数千次,看它是否被按下。与此同时,主线程将继续执行程序的主要部分。

现在点击 Run 按钮。你会看到交通灯和以前一样,没有延迟或停顿。但是,如果你按下按钮,什么也不会发生,因为你还没有添加代码来实际响应按钮。

转到你的主循环的开始,在 True: 的正下方,添加以下代码:记住要注意嵌套缩进:

if button_pressed == True:
	led_red.value(1)
	for i in range(10):
		buzzer.value(1)
		utime.sleep(0.2)
		buzzer.value(0)
		utime.sleep(0.2)
	global button_pressed
	button_pressed = False

这段代码检查 button_pressed 全局变量,以查看自上次循环运行以来,按钮开关是否在任何时候被按下。如果有,就像你之前的按钮阅读线程报告的那样,它开始运行一段代码,首先打开红色 LED 灯来阻止交通,然后蜂鸣器响十次——让行人知道该过马路了。

最后,最后两行将 button_pressed 变量重置为 False,因此下次循环运行时,除非再次按下按钮,否则不会触发行人过马路代码。你将看到,在条件语句中不需要使用 global button_pressed 来检查变量的状态;只有当你想要更改变量并使该更改影响程序的其他部 分时,才需要使用它。

最终程序如下:

import machine
import utime
import _thread

led_red = machine.Pin(15, machine.Pin.OUT)
led_amber = machine.Pin(14, machine.Pin.OUT)
led_green = machine.Pin(13, machine.Pin.OUT)
button = machine.Pin(16, machine.Pin.IN, machine.Pin.PULL_DOWN)
buzzer = machine.Pin(12, machine.Pin.OUT)

global button_pressed
button_pressed = False

def button_reader_thread():
	global button_pressed
	while True:
		if button.value() == 1:
			button_pressed = True

_thread.start_new_thread(button_reader_thread, ())

while True:
	if button_pressed == True:
		led_red.value(1)
		for i in range(10):
			buzzer.value(1)
			utime.sleep(0.2)
			buzzer.value(0)
			utime.sleep(0.2)

		global button_pressed
		button_pressed = False

led_red.value(1)
utime.sleep(5)
led_amber.value(1)
utime.sleep(2)
led_red.value(0)
led_amber.value(0)
led_green.value(1)
utime.sleep(5)
led_green.value(0)
led_amber.value(1)
utime.sleep(5)
led_amber.value(0)

点击 Run 图标。首先,程序将正常运行,交通灯将按照通常的模式亮和关。按下按钮开关:如果程序当前在它的循环过程中,在到达终点并再次循环之前不会发生任何事情,这时红灯会变亮,蜂鸣器会提示你可以在道路上安全通过了。

过马路的时候按下按钮,红灯保持点亮,蜂鸣器响 10 次。之后蜂鸣器不再响了,红灯仍然亮着,所以任何在蜂鸣器发出时开始过马路的人 都有时间在车辆允许通行之前到达另一边。

恭喜你!你已经建立了你自己的海雀交叉口!

    标签:

    发布评论 条评论)

    评论列表