目录

用按键控制LED灯

我们开始将基本的电子元件连接到你的树莓派Pico上,并编写程序来控制和感知它们,就像我们的大脑可以控制我们的四肢,通过感知器官获取外界的反应。树莓派Pico的RP2040微控制器在设计时就充分考虑到了对外部硬件器件的连接、感知和控制,它有许多通用输入/输出(GPIO)引脚来同各种元器件进行连接和通信,这样我们可以构建多种不同的项目,从最简单的点亮LED到记录关于你周围世界的数据。

1. 你好,LED!

就像在屏幕上打印“Hello, World!”是学习编程语言的第一步一样,在电路板上通过编程点亮LED灯是学习物理计算的经典入门方式(FPGA的学习也是如此)。树莓派Pico上已经有一个贴装(SMD)的LED,即便仅用树莓派Pico核心板,你也不需要任何额外的元器件就可以开始编程了。在我们的Pico学习板上,还有4个单独的、不同颜色的LED - R(红色)、G(绿色)、B(蓝色)、Y(黄色),前面三个用于常用的RGB组合,R、G、Y在一起是交通灯的组合,可以通过这4个LED灯做一些组合和状态切换相关的项目和编程学习。

学习板上的4个LED的使用方式和Pico核心板上的是一样的,只是连接到不同的GPIO管脚上而已:

LED所在的板卡对应的RP2040的GPIO管脚
蓝色LEDPico核心板25
红色LEDPico学习板26
绿色LEDPico学习板22
蓝色LEDPico学习板21
黄色LEDPico学习板20

我们可以先考虑使用Pico核心板上的LED。

首先找到LED,它是位于板顶Micro USB插座左侧的小矩形元件(如下图),上面标有“LED”字样。

树莓派Pico核心板上的LED位置

这个LED的工作原理和其它任何LED一样:

板上的这颗LED连接到RP2040的通用输入/输出引脚之一:GP25,正因为这个管脚已经用于在板上连接LED,所以在Pico核心板的板边输入输出管脚上就没有这个GP25了。虽然板上的这颗LED外,我们不能将任何其它外部硬件再连接到该管脚,但它可以像我们程序中的任何其它GPIO管脚一样处理,并且无需任何额外的元件就可以添加输出到程序中。

运行Thonny,配置它连接到Pico,点击进入脚本区域,然后写下下面的一行代码:

import machine

这很短的一行代码对于在Pico上使用MicroPython是非常关键的,它加载或导入被称为“库”的MicroPython代码集合,在本例中是“machine”库,这个“machine”库包含了MicroPython与Pico和其它MicroPython兼容设备通信所需的所有指令,扩展了用于物理计算的语言。如果没有这一行命令,我们是无法控制Pico的任何GPIO管脚的,自然也无法使板载的LED点亮。

有选择地导入(import)

在MicroPython和Python中都可以只导入库的一部分,而不是整个库,以节省内存,并有可能混合和匹配来自不同库的函数。在我们的示例中导入了整个库,在其它地方,我们可能会看到程序中有这么写的:

from machine import Pin

这告诉MicroPython只从'machine'库中导入'Pin'函数,而不是整个库。

“machine”库开放了所谓的应用程序编程接口(API)。这个名称听起来很复杂,但它准确地描述了它的作用: 为我们的程序或应用程序提供了一种通过接口与Pico通信的方法。

程序的下一行就提供了一个“machine”库API的示例:

led_onboard = machine.Pin(25, machine.Pin.OUT)

这一行定义了一个名为led onboard的对象,并起了一个读起来非常容易理解的名字(比较友好),我们可以在稍后的程序中使用它来引用核心板上的LED。从技术上讲,我们可以使用任何名称,比如susan、gupta或fish-sandwich,但最好坚持使用描述变量目的的名字,以使程序更容易阅读和理解。

该行的第二部分调用machine库中的Pin函数。这个函数是专为处理我们的Pico的GPIO引脚。目前还没有GPIO管脚(包括连接到板载LED的GP25管脚)知道它们应该做什么。第一个参数,25,是我们要设置的管脚的编号; 第二个 machine.Pin.Out 是告诉Pico引脚应该用作输出而不是输入。

这行代码只是完成了引脚的设置,但它还不能点亮LED,要点亮LED,我们需要通过程序告诉Pico把管脚打开,接着输入下面的一行代码:

led_onboard.value(1)

这一行代码也使用了machine库的API。前面的行创建了对象led onboard,作为GP25引脚上的输出; 这一行将该对象的值设定为1(二进制中的高电平),用于'on',它也可以将值设置为0,用于'off'。

管脚编号

当谈到树莓派Pico上的GPIO管脚时,通常使用它们的全名: 例如,GP25是指连接到板上LED的管脚。但是,在MicroPython中,字母G和P被删除了, 所以在程序中写的是'25'而不是'GP25',否则它不会工作!

点击Thonny的“Run”按钮,将程序保存为Blink.py, 我们会看到LED灯亮了起来。祝贺你!你已经编写了第一个物理计算程序!

然而,我们会注意到,LED一直亮着, 那是因为我们的程序告诉Pico打开它,但从来没有告诉它关闭它。如何关闭呢?我们可以在程序后面再添加一行:

led_onboard.value(0)

然而,这一次运行这个程序,LED似乎永远不会亮起来,这是因为Pico的工作速度非常非常快,远远快于我们用肉眼看到的速度。LED亮了一下,在下一条指令就让它灭掉,亮的时间非常短暂,短暂到我们的眼睛无法看到。要解决这个问题,我们需要通过引入延迟来降低程序的执行速度进而延长LED亮的时间。

回到程序的顶部, 在第一行的末尾单击并按回车键以插入新的第二行。在这一行输入:

import utime

像导入“machine”库一样,这一行将一个新库 - 'utime'库导入到MicroPython。这个库处理与时间有关的所有事情,从测量时间到在程序中插入延迟。

到程序的底部,单击行led_onboard.value(1)的末尾,然后按回车以插入新行。键入:

utime.sleep(5)

它将调用utime库中的sleep函数,这将使程序暂停我们输入的秒数 — 在本例中是5秒。

再次单击Run按钮。这一次,我们会看到Pico上的板载LED灯亮了,保持灯亮5秒钟(我们可以试着数一数)然后再熄灭。

UTIME vs TIME

如果你以前用Python编写过程序,你将习惯于使用“time”的库。utime库是为像Pico这样的微控制器设计的版本——“u”代表“μ”,希腊字母“mu”,是“micro”的缩写。如果我们忘记并使用了导入时间,不要担心, MicroPython将自动使用utime库代替。

终于,我们可以让LED闪烁了。为此,我们需要创建一个循环,重写我们的程序如下:

import machine
import utime
 
led_onboard = machine.Pin(25, machine.Pin.OUT)
 
while True: 
    led_onboard.value(1)
    utime.sleep(5)
    led_onboard.value(0)
    utime.sleep(5)
 

记住,循环中的行需要缩进四个空格,这样MicroPython就知道它们构成了循环。

再次点击“Run”图标,我们会看到LED开关打开5秒、关闭5秒、再打开,不断重复在无限循环。LED将继续闪烁,直到我们点击停止图标取消我们的程序和重置我们的Pico。

还有另一种方法可以处理相同的工作:使用toggle(切换),而不是显式地将LED的输出设置为0或1。删除程序的最后四行并替换它们,使其看起来像这样:

import machine
import utime
 
led_onboard = machine.Pin(25, machine.Pin.OUT)
 
while True: 
    led_onboard.toggle() 
    utime.sleep(5)

再次运行程序,我们会看到和之前一样的活动:板载LED会亮5秒、熄灭5秒,然后在无限循环中再次亮起来。我们会发现这一次的程序短了两行:我们对它进行了优化。所有数字输出引脚都可以使用toggle()来简单地在开、关状态之间切换:

挑战: 让LED点亮的时间更长

如何改变我们的程序来让LED灯亮更久? 多休息一会儿怎么样? 在LED开关打开和关闭的情况下,我们能使用的最小延迟是多少?

2. 控制学习板上的LED

到目前为止,我们一直在使用树莓派Pico核心板,在其RP2040微控制器上运行MicroPython程序,改变板载LED的点亮和熄灭的状态。微控制器通常与更多的外部元器件一起使用,也正是为此,我们设计了这个基于Pico的学习板。在MicroPython中控制学习板上的LED与控制Pico核心板内部的LED没有什么不同,只是管脚的编号发生了变化而已。

打开Thonny,加载前面写好的的Blink.py程序。找到:

led_onboard = machine.Pin(25, machine.Pin.OUT)

修改引脚编号,将它从连接到Pico核心板内部LED的引脚25更改为连接到学习板上红色LED的引脚26。这个LED的名称也最好随之修改,比如改为led_red。程序中所有用到该LED的地方,都要把名字修改过来:

import machine
import utime
 
led_red = machine.Pin(26, machine.Pin.OUT)
 
while True: 
    led_red.toggle() 
    utime.sleep(5)

挑战: 同时控制多个发光二极管

修改程序同时点亮Pico核心板和学习板上的led; 编写一个程序,当学习板上的LED关闭时,点亮核心板上的LED,反之亦然

3. 输入: 读取按钮开关

前面的LED都是利用了GPIO的输出特性,其实RP2040GPIO既可以做输出控制,也可以做输入状态监测使用。在树莓派Pico核心板上没有放置任何输入按键,在我们的学习板上特地放了两个轻触按键:

按键GPIO管脚编号
KEY112
KEY213

隐藏起来的阻抗

与LED不同的是,按钮开关不需要限流电阻。但它仍需要一个电阻器,用于上拉或下拉,取决于你的电路如何工作。没有上拉或下拉电阻,输入被称为浮动 - 这意味着它有一个“噪声”信号,即使你不按按钮,它也会触发。电路中的电阻在哪里? 藏在Pico的微控制器RP2040里,可以为每一个GPIO管脚编程电阻,根据电路的需要在MicroPython中设置为下拉电阻或上拉电阻。

有什么区别呢?一个下拉电阻连接引脚接地,意思是当按钮没有按下时,输入是0。一个上拉电阻将引脚连接到3V3,这意味着当按钮没有被按下时,输入将是1。所有的电路在这本书中使用可编程电阻在下拉模式。

加载Thonny,然后用通常的代码行启动一个新程序:

import machine

接下来,您需要使用machine的API将其中一个按键KEY1管脚设置为输入,而不是输出:

key1 = machine.Pin(12, machine.Pin.IN, machine.Pin.PULL_DOWN)

这与LED项目的工作方式相同: 创建一个名为“key1”的对象,其中包括引脚号,在本例中为GP12,并将其配置为电阻设置为下拉的输入。然而,创建对象并不意味着它会自己做任何事情, 就像之前创建LED对象并不会让LED亮起来一样。

要真正读取按键的状态,需要再次使用machine API,这次使用value函数来读取,而不是设置“管脚”的值。输入下面的代码:

print(key1.value())

点击Run图标并将你的程序保存为key1.py,记住它保存在树莓派Pico上。此程序将打印出一个数字: GP12上的输入值。

因为输入使用了一个下拉电阻,这个值将是0,让你知道按钮没有被按下。

用手指按住按钮,再按一次Run图标。这一次,您将看到值“1”打印到Shell: 按下按钮就完成了电路并改变了从引脚读取的值。

要连续读取按钮,需要在程序中添加一个循环。编辑程序,使其如下所示:

import machine
import utime
 
key1 = machine.Pin(12, machine.Pin.IN, machine.Pin.PULL_UP)
while True:
    if key1.value() == 0:
        print("You pressed the button!")
        utime.sleep(2)

再次单击Run按钮。这一次,除非你按下按钮,否则不会发生任何事情; 当你这样做时,你会看到一条打印到Shell区域的消息。与此同时,延迟是很重要的: 记住,你的Pico运行的速度比你可以阅读的速度快得多,没有延迟,即使是简单的按下按钮也可以打印数百条信息到Shell!

每次按下按钮,你都会看到打印的消息。如果你按下按钮的时间超过两秒,它将每两秒打印一次信息,直到你松开按钮。

4. 将输入和输出放在一起

大多数电路都有不止一个组件,这就是为什么你的Pico有这么多的GPIO管脚。现在是时候把你所学到的东西放在一起来构建一个更复杂的电路了 - 用按键开关来控制LED的状态。 实际上,这个电路将前两个电路合并成一个,你可能记得你使用引脚GP19驱动外学习板上红色的LED,并用引脚GP12读取学习板上的一个按键的状态,现在我们将输入按键和输出LED一起联动工作。

在Thonny中启动一个新程序,并开始导入程序需要的两个库:

import machine
import utime

接下来,设置输入和输出引脚:

led_red = machine.Pin(26, machine.Pin.OUT)
key1 = machine.Pin(12, machine.Pin.IN, machine.Pin.PULL_UP)

然后创建一个循环读取按钮:

while True:
    if key1.value() == 0:

不过,这次你将根据输入引脚的值切换输出引脚和连接到它的LED,而不是将消息打印到Shell。输入以下内容,记住它需要缩进8个空格,当你按下上面这行末尾的回车键时,Thonny应该会自动处理:

led_led.value(1)
utime.sleep(2)

这足以打开LED,但当按钮没有被按下时,你还需要再次关闭它。添加以下新行,使用BACKSPACE键删除8个空格中的4个——这意味着该行将不属于if语句,而是构成无限循环的一部分:

led_red.value(0)

你完成的程序应该是这样的:

import machine
import utime
led_red = machine.Pin(19, machine.Pin.OUT)
key1 = machine.Pin(12, machine.Pin.IN, machine.Pin.PULL_UP)
while True:
    if key1.value() == 0:
        led_red.value(1)
        utime.sleep(2)
    led_red.value(0)


单击Run图标并将程序保存为Pico上的key_led.py。起初,什么都不会发生,然而,按下按钮你会看到LED灯亮了起来。松开按钮2秒后LED再次熄灭,直到再次按下按钮。

祝贺你,你已经建立了你的第一个电路,它基于从另一个输入控制一个引脚,一个更大的东西的构建块!

挑战: 修改你的程序,使它既点亮LED、又向Shell打印状态消息

思考一下你需要改变什么来让LED在没有按下按钮时保持亮着,在按下按钮时关闭? 你能在电路中添加更多的按钮和led吗?

以下部分为其它与LED控制相关的参考案例。

5. 案例汇总

5.1 通过循环来控制4个LED灯(R、G、B、Y)的变化

# 树莓派Pico Multiple LED Blink
# rgb-blink.py
 
# RED LED - Pico GPIO 26
# GREEN LED - Pico GPIO 22
# BLUE LED - Pico GPIO 21
# YELLOW LED - Pico GPIO 20  
# EETree Info & Tech 2021
# https://www.eetree.cn
 
 
import machine
import utime
 
led_red = machine.Pin(26, machine.Pin.OUT)
led_green = machine.Pin(22, machine.Pin.OUT)
led_blue = machine.Pin(21, machine.Pin.OUT)
led_yellow = machine.Pin(20,machine.Pin.OUT)
 
 
while True:
 
    led_red.value(1)
    led_green.value(0)
    led_blue.value(0)  
    led_yellow.value(1)  
    utime.sleep(2)
 
    led_red.value(0)
    led_green.value(1)
    led_blue.value(0)  
    led_yellow.value(1)  
    utime.sleep(2)
 
    led_red.value(0)
    led_green.value(0)
    led_blue.value(1)  
    utime.sleep(2)
 
    led_red.value(1)
    led_green.value(1)
    led_blue.value(0)  
    led_yellow.value(1)  
    utime.sleep(2)
 
    led_red.value(1)
    led_green.value(0)
    led_blue.value(1)  
    utime.sleep(2)
 
    led_red.value(0)
    led_green.value(1)
    led_blue.value(1)  
    led_yellow.value(0)  
    utime.sleep(3)
 
    led_red.value(1)
    led_green.value(1)
    led_blue.value(1)  
    led_yellow.value(1)  
    utime.sleep(2)
 
    print("End of Loop")
 
    led_red.value(0)
    led_green.value(0)
    led_blue.value(0)  
    led_yellow.value(0)  
    utime.sleep(2)

5.2 按键测试

# 树莓派 Pico Switch Test
# switchtest.py
 
# KEY1 - Pico GPIO 12
# KEY2 - Pico GPIO 13
 
# EETree Info & Tech 2021
# https://www.eetree.cn
 
import machine
import utime
 
key1 = machine.Pin(12, machine.Pin.IN, machine.Pin.PULL_UP)
key2 = machine.Pin(13, machine.Pin.IN, machine.Pin.PULL_UP)
 
while True:
    if key1.value() == 0:
        print("key1 pressed")
 
    if key2.value() == 0:
        print("key2 pressed")
 
    utime.sleep(0.25)

5.3 加入中断的按键控制

# 树莓派 Pico Interrupt & Toggle Demo
# interrrupt-toggle-demo.py
 
# RED LED - Pico GPIO 26
# GREEN LED - Pico GPIO 22
 
# KEY1 - Pico GPIO 12
 
# EETree Info & Tech 2021
# https://www.eetree.cn
 
import machine
import utime
 
led_red = machine.Pin(26, machine.Pin.OUT)
led_green = machine.Pin(22, machine.Pin.OUT)
 
led_red.value(0)
led_green.value(0)
 
key1 = machine.Pin(12, machine.Pin.IN, machine.Pin.PULL_UP)
 
def int_handler(pin):
    key1.irq(handler=None)
    print("Interrupt Detected!")
    led_red.value(1)
    led_green.value(0)
    utime.sleep(4)
    led_red.value(0)
    key1.irq(handler=int_handler)
 
key1.irq(trigger=machine.Pin.IRQ_FALLING , handler=int_handler)
 
while True:
 
     led_green.toggle()
     utime.sleep(2)

5.4 开关和LED演示

# 树莓派 Pico Switch & RGB LED Demo
# switch-led-demo.py
 
# RED LED - Pico GPIO 26
# GREEN LED - Pico GPIO 22
# BLUE LED - Pico GPIO 21
 
# BLACK BUTTON - Pico GPIO 2 - Pin 4
 
# EETree Info & Tech 2021
# https://www.eetree.cn
 
import machine
import utime
 
led_red = machine.Pin(26, machine.Pin.OUT)
led_green = machine.Pin(22, machine.Pin.OUT)
led_blue = machine.Pin(21, machine.Pin.OUT)
 
led_red.value(0)
led_green.value(0)
led_blue.value(0)
 
key1 = machine.Pin(12, machine.Pin.IN, machine.Pin.PULL_UP)
 
while True:
    if key1.value() == 0:
        led_red.value(1)
        led_green.value(0)
        led_blue.value(0)  
        utime.sleep(1)
 
        led_red.value(0)
        led_green.value(1)
        led_blue.value(0)  
        utime.sleep(1)
 
        led_red.value(0)
        led_green.value(0)
        led_blue.value(1)  
        utime.sleep(1)
 
        led_red.value(0)
        led_green.value(0)
        led_blue.value(0)  

点亮Pico板上的一颗LED

from machine import Pin,Timer
from time import sleep_us
 
led = Pin(25, Pin.OUT)
tim = Timer()
 
print("Flash LED.")
 
def tick(timer):
    global led
    led.toggle()
 
tim.init(freq=2, mode=Timer.PERIODIC, callback=tick)