返回

浏览器中的树莓派 4 和 5:基础模式与 Linux 模式,分步指南

Velxio 中的树莓派是画布上的一块开发板,它针对你围绕它搭建的电路运行 Python 脚本。无需编译。你编写脚本,按下运行,两个引擎之一便会执行它:

本文构建两个项目,每种模式各一个,并展示过程中的每一次点击。第一个是树莓派 5 上用 Grove 模块搭建的报警站:输入端有运动传感器、振动开关和 DHT11,输出端有蜂鸣器、两个 LED 以及一个带风扇的继电器。第二个是树莓派 4 上带有三个 I2C 设备的传感器装置。两个脚本都使用 Raspberry Pi OS 自带的库,因此无需修改即可在真实开发板上运行。

开始之前,关于访问权限有两点说明。任何人都可以在画布上放置树莓派并围绕它搭建电路。运行它则需要已登录的账户:每个免费账户在树莓派系列上可获得三次 15 分钟的试用会话,两种模式均可,任何付费方案都会取消该限制。详情见文末的费用说明部分。

运行同一脚本的两种方式

基础模式Linux 模式
脚本运行位置浏览器标签页内的 Python 解释器Velxio 服务器上 QEMU 下的 Alpine Linux 客户机
启动时间几秒钟约一分钟
GPIORPi.GPIO、gpiozero、Adafruit Blinka相同,外加 /dev/gpiochip0 和 libgpiod 工具
I2C、SPI、UART、1-Wire通过模拟设备文件使用真正的 smbus2、spidev、pyserial 和 w1thermsensor通过内核真实设备节点使用相同的库
shell 和文件系统无有,位于串口监视器中
第三方包来自 requirements.txt来自 requirements.txt,在客户机启动前离线解析
最适合几乎所有场景shell 工具、子进程、文件,以及任何教程中从终端执行的操作

引擎会为你选择。如果脚本请求只有 Linux 才能做到的事情,例如 subprocess,它会被发送到 Linux 模式,工具栏中的标签会告诉你原因。你也可以自行强制使用 Linux 模式,第二个项目正是如此。

项目 1:树莓派 5 上的 Grove 报警站

Grove 模块是给树莓派接传感器最快的方式:每个模块都有相同的四针插座,在 Velxio 中每个模块都用三根线接到排针上——SIG、VCC 和 GND。这个报警站用 PIR 运动传感器和振动开关监视房间,用 DHT11 读取空气数据,并通过蜂鸣器、“一切正常”的绿色 LED、“报警”的蓝色 LED,以及一个在房间变热时驱动风扇的继电器来做出响应。

第 1 步:在树莓派 5 上启动项目

打开 velxio.dev/editor。Start a new project 对话框会自动弹出。滚动到 Raspberry Pi 部分,点击 Raspberry Pi 5 卡片。Raspberry Pi 4B 卡片的工作方式完全相同:两块板共享 40 针排针和相同的库,因此下面的一切对两者都适用。

Start a new project 对话框中的 Raspberry Pi 部分;Raspberry Pi 5 卡片被高亮显示

第 2 步:添加元件

点击工具栏最右侧蓝色的 Add 按钮。它会打开元件目录。

工具栏中 Add 按钮被高亮显示

在搜索框中输入名称,然后点击卡片将元件放到画布上。Grove 模块都以 Seeed 的名称列出,因此搜索 “Grove PIR” 或 “Grove relay” 就能找到它们。依次添加以下八个元件:

  1. Grove - PIR Motion Sensor
  2. Grove - Vibration Sensor (SW-420)
  3. Grove - Temperature & Humidity Sensor (DHT11)
  4. Grove - Piezo Buzzer
  5. Grove - Green LED
  6. Grove - Blue LED
  7. Grove - Relay
  8. DC Motor / Fan

Add Component 对话框中搜索框里输入了 Grove PIR,PIR 运动传感器卡片被高亮显示

元件可以拖到画布上的任意位置。把树莓派放在底部、模块放在其排针上方,可以让连线保持简短。

第 3 步:接线

要画一根线,先点击一个引脚,再点击另一端的引脚。悬停在元件上时会显示引脚名称。每个 Grove 模块都接相同的三根线:SIG 接到某个 GPIO,VCC 接到 3V3,GND 接到 GND。继电器在供电侧是个例外,它的线圈需要 5 V。

模块SIGVCCGND
PIR 运动传感器GPIO173V3GND
振动传感器 (SW-420)GPIO273V3GND
DHT11GPIO43V3GND
蜂鸣器GPIO243V3GND
绿色 LEDGPIO53V3GND
蓝色 LEDGPIO63V3GND
继电器GPIO235VGND

风扇本身没有信号线。它接在继电器的开关触点上,就像实验台上的负载一样:电源进入 COM,只有在继电器线圈通电时才会从 NO 输出。

从到
Raspberry Pi 5VRelay COM
Relay NOFan +
Fan -Raspberry Pi GND

接线完成的报警站:树莓派 5 在底部,PIR、振动传感器、DHT11 和蜂鸣器在排针上方排成一排,两个 LED 以及带风扇的继电器在它们下方,每个模块都有标注

第 4 步:脚本

左侧的文件浏览器中已经有一个名为 script.py 的文件,里面是一段简短的闪烁脚本。保留这个名称。基础模式会运行它找到的第一个 .py 文件,但 Linux 模式会专门运行 script.py,因此主文件保持这个名称的项目在两种模式下都能工作。

代码编辑器中文件浏览器里选中了 script.py

将其内容替换为以下脚本:

"""Grove alarm station on a Raspberry Pi 5.

A PIR sensor and a vibration switch watch the room, a DHT11 reads the air,
and the actuators answer: a buzzer and a blue LED for the alarm, a green LED
while all is quiet, and a relay that runs a fan when the room gets warm.
Every module hangs off the header with a Grove cable, one signal pin each.

Wiring, BCM numbers (VCC and GND of each module to 3V3 and GND):
    PIR motion sensor   SIG GPIO17   HIGH while motion is seen
    vibration SW-420    SIG GPIO27   LOW while shaking, HIGH at rest
    DHT11               SIG GPIO4
    buzzer              SIG GPIO24
    green LED GPIO5, blue LED GPIO6
    relay               SIG GPIO23, VCC on 5V; the contacts switch the fan:
                        5V to COM, NO to the fan's +, the fan's - to GND
"""
import time

import adafruit_dht
import board
from gpiozero import LED, Button, Buzzer, MotionSensor

FAN_ON_AT = 28   # degrees C
HOLD = 2.5       # seconds the alarm light stays on after the beeps

pir = MotionSensor(17)
shaker = Button(27, pull_up=True)   # the SW-420 pulls SIG low while it vibrates
dht = adafruit_dht.DHT11(board.D4, use_pulseio=False)
buzzer = Buzzer(24)
quiet_led = LED(5)
alarm_led = LED(6)
fan = LED(23)                        # the relay coil: on() closes COM to NO


def alarm(reason, beeps, beep_time):
    print(f"ALARM: {reason}")
    quiet_led.off()
    alarm_led.on()
    buzzer.beep(on_time=beep_time, off_time=0.1, n=beeps, background=False)
    time.sleep(HOLD)
    alarm_led.off()
    quiet_led.on()


quiet_led.on()
motion_seen = False
shaking = False
next_reading = 0
print("Station armed")

while True:
    if pir.motion_detected and not motion_seen:
        alarm("motion in the room", beeps=3, beep_time=0.15)
    motion_seen = pir.motion_detected

    if shaker.is_pressed and not shaking:
        alarm("vibration on the door", beeps=1, beep_time=0.6)
    shaking = shaker.is_pressed

    if time.monotonic() >= next_reading:
        next_reading = time.monotonic() + 2
        try:
            temperature, humidity = dht.temperature, dht.humidity
        except RuntimeError as err:   # a DHT11 misses a reading now and then
            print(f"DHT11: {err}")
        else:
            fan.value = temperature >= FAN_ON_AT
            print(f"{temperature:.0f} C  {humidity:.0f} %RH  fan {'on' if fan.value else 'off'}")

    time.sleep(0.1)

其中有三个地方值得注意,因为它们在真实硬件上也是如此:

第 5 步:运行

在工具栏上,点击绿色三角形来运行项目。底部的 Serial Monitor 会打开并报告引擎正在做什么:它加载 Python 运行时、安装 GPIO 库,并打印一行类似 [Velxio] ready in 6068 ms 的信息,后面跟着它正在运行的脚本名称。从那时起,脚本中的每个 print() 都会输出到那里。

工具栏中 Run 按钮被高亮显示

脚本会打印 Station armed,然后每两秒打印一行温度和湿度。画布上的 DHT11 起始温度为 31 C,因此继电器会立即闭合,风扇开始旋转:电机会显示方向箭头和以 rpm 为单位的速度,继电器自身的指示灯也会亮起。现在与传感器交互:

基础模式下运行的报警站:风扇在继电器后方旋转,点击 PIR 半球罩后蓝色 LED 亮起,Serial Monitor 显示读数和 ALARM 行

Run 按钮旁边的红色方块会停止脚本。这就是基础模式的全部:脚本在你的标签页中运行,引脚直接连接到画布,从点击 Run 到电路做出反应的整个循环只需几秒钟。

项目 2:树莓派 4 上的 I2C 传感器装置,然后切换到 Linux 模式

第二个项目把三个 I2C 设备接到排针总线上,并使用真正的 smbus2 与它们通信:一个带两个电位器的 Grove ADS1115 模数转换器、一个 Grove SHT31 温湿度传感器,以及一个 SSD1306 OLED。再加上一个接在普通 GPIO 上的按钮和一个 LED 就完整了。同样的电路在树莓派 5 上无需改动任何一根线或一行代码即可运行:排针是一样的。

ADS1115 的存在有一个让很多人踩坑的原因:树莓派没有模拟输入。无论是真实板子还是 Velxio,直接把电位器接到 GPIO 上读到的永远只有高或低,所以旋钮要经过 ADC。

电路

在 Raspberry Pi 4B 卡片上新建一个项目,然后从元件目录中添加以下部件:

然后接线。三个 I2C 设备共用 GPIO2(SDA)和 GPIO3(SCL):

从(树莓派 4)到
GPIO2 (SDA)ADS1115、SHT31 和 OLED 的 SDA
GPIO3 (SCL)ADS1115、SHT31 和 OLED 的 SCL
3V3ADS1115、SHT31、OLED 和两个电位器的 VCC
GNDADS1115、SHT31、OLED 和两个电位器的 GND
GPIO27LED 阳极;LED 阴极接 220 Ω,再接 GND
GPIO22按钮;按钮另一端接 GND
从到
电位器 1 SIGADS1115 A0
电位器 2 SIGADS1115 A1

接好线的传感器装置:树莓派 4 在底部,ADS1115 和两个电位器、OLED 和 SHT31 在它上方,LED 和按钮在右侧,I2C 引脚和每个设备上都有标注

脚本

将其保存为 script.py。它首先询问三个地址是否有设备应答,然后循环:两个 ADC 通道、温度和湿度、按钮状态控制 LED,以及通过 luma.oled 在 OLED 上显示读数。

"""Sensor rig check for a Raspberry Pi 4.

Probes the three I2C devices, then loops: two knobs through the ADS1115,
temperature and humidity from the SHT31, the readings on the OLED, and a
button that lights the LED. Everything here is what Raspberry Pi OS ships:
smbus2 for the bus, gpiozero for the LED and the button, luma.oled for
the display.

Wiring, on the 40-pin header:
    I2C   : ADS1115, SHT31 and OLED on GPIO2 (SDA) and GPIO3 (SCL), 3V3, GND
    ADC   : one potentiometer on ADS1115 A0, another on A1
    GPIO  : LED through 220 ohm on GPIO27, button between GPIO22 and GND
"""
import time

import smbus2
from gpiozero import LED, Button
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306

ADS1115, SHT31, OLED = 0x48, 0x44, 0x3C
NAMES = {ADS1115: "ADS1115", SHT31: "SHT31", OLED: "SSD1306 OLED"}

# ADS1115 config register, field by field (datasheet, table 8)
ADS_START = 0x8000       # OS: begin one conversion
ADS_MUX_SINGLE = 0x4000  # AINx against GND; the channel goes in bits 12 and 13
ADS_PGA_4V = 0x0200      # +/-4.096 V full scale
ADS_ONE_SHOT = 0x0100    # power down between conversions
ADS_128_SPS = 0x0080     # 128 samples per second
ADS_NO_COMP = 0x0003     # comparator off
ADS_LSB_VOLTS = 4.096 / 32768

bus = smbus2.SMBus(1)
led = LED(27)
button = Button(22, pull_up=True)


def present(address):
    """True when a device acknowledges its address on bus 1."""
    try:
        bus.write_quick(address)
        return True
    except OSError:
        return False


def read_ads1115(channel):
    """One single-shot conversion on AIN0..AIN3, returned in volts."""
    config = (ADS_START | ADS_MUX_SINGLE | (channel << 12)
              | ADS_PGA_4V | ADS_ONE_SHOT | ADS_128_SPS | ADS_NO_COMP)
    bus.write_i2c_block_data(ADS1115, 0x01, [config >> 8, config & 0xFF])
    time.sleep(0.01)
    hi, lo = bus.read_i2c_block_data(ADS1115, 0x00, 2)
    raw = (hi << 8) | lo
    if raw & 0x8000:
        raw -= 0x10000
    return raw * ADS_LSB_VOLTS


def read_sht31():
    """Single shot, high repeatability, no clock stretching (command 0x2400)."""
    bus.i2c_rdwr(smbus2.i2c_msg.write(SHT31, [0x24, 0x00]))
    time.sleep(0.02)
    reply = smbus2.i2c_msg.read(SHT31, 6)
    bus.i2c_rdwr(reply)
    d = list(reply)
    temperature = -45 + 175 * ((d[0] << 8) | d[1]) / 65535
    humidity = 100 * ((d[3] << 8) | d[4]) / 65535
    return temperature, humidity


print("I2C bus 1:")
for address, name in NAMES.items():
    print(f"  0x{address:02X}  {name}: {'found' if present(address) else 'missing'}")
print()

oled = ssd1306(i2c(port=1, address=OLED))

while True:
    knob_a = read_ads1115(0)
    knob_b = read_ads1115(1)
    temperature, humidity = read_sht31()
    pressed = button.is_pressed
    led.value = pressed
    print(f"A0 {knob_a:5.3f} V   A1 {knob_b:5.3f} V   {temperature:4.1f} C  {humidity:3.0f} %RH"
          + ("   button pressed" if pressed else ""))
    with canvas(oled) as draw:
        draw.text((0, 0), "Pi 4 sensor rig", fill="white")
        draw.text((0, 16), f"A0 {knob_a:.2f}V  A1 {knob_b:.2f}V", fill="white")
        draw.text((0, 32), f"{temperature:.1f} C   {humidity:.0f} %RH", fill="white")
        draw.text((0, 48), "button " + ("down" if pressed else "up"), fill="white")
    time.sleep(2)

有三个细节值得一看。ADS1115 是按数据手册中配置寄存器的字段逐一配置的,因此每个常量都可以对照该文档的表 8 进行核对。SHT31 使用 i2c_rdwr 读取,因为它的测量命令是两个字节且没有寄存器地址,普通的 SMBus 调用无法表达。而 luma.oled 正是树莓派教程中用 pip 安装的那个显示库;这里它已预装,就像 smbus2 和 gpiozero 一样。

先在基础模式下运行

点击 Run。探测会找到三个芯片,循环会打印读数,OLED 会显示它们。转动电位器,A0 或 A1 的电压会随之变化;点击 SHT31,你可以设置它报告的温度和湿度;按住按钮,LED 就会亮起。

基础模式下运行的传感器装置:OLED 显示两个电压、温度和湿度以及按钮状态,串口监视器显示找到的三个设备和读数

到目前为止一切都在浏览器中运行。接下来的步骤会在 Linux 机器上运行完全相同的项目。

切换到 Linux 模式

  1. 如果串口监视器未打开,请点击工具栏上的 Serial。
  2. 在串口监视器标题栏的右侧,点击 Linux terminal。

串口监视器标题栏,Linux terminal 按钮已高亮

  1. 一个名为 Advanced Linux mode 的对话框会说明即将发生的事情:项目会重启,启动大约需要一分钟,而且一切运行都比浏览器中慢。点击 Start Linux mode。

Advanced Linux mode 对话框,Start Linux mode 按钮已高亮

客户机在串口监视器中启动,此时它已变成一个真正的终端。启动期间,工具栏中板子选择器旁边的芯片显示 Linux,其工具提示说明此项目已开启 Linux 模式。

工具栏中显示 Linux 的引擎芯片已高亮

当提示符出现时,Velxio 会将 script.py 复制到客户机的主目录并启动它。在空闲的服务器上,提示符大约 20 秒后出现,首次读数在你点击按钮后约一分钟出现。输出与之前相同,OLED 由同样的 luma.oled 调用驱动,现在是从客户机内部通过内核的 /dev/i2c-1 进行。

Linux 模式下的传感器装置:终端显示找到的三个设备和读数,OLED 显示相同的值

使用 shell

这正是 Linux 模式的用途。在终端内点击,按 Ctrl+C 停止脚本,你就处于 Alpine Linux 系统的 root 提示符下。可以尝试以下几行:

cat /etc/os-release | head -2
ls /dev/i2c* /dev/spidev* /dev/gpiochip*
gpioset gpiochip0 27=1
ls -la /home/pi

第二行将排针总线列为设备节点:/dev/i2c-0、/dev/i2c-1、/dev/spidev0.0、/dev/spidev0.1 和 /dev/gpiochip0。它们响应与树莓派上相同的系统调用,因此自行打开它们的库、C 程序或你自己的 ioctl 代码都能与画布上的部件通信。

第三行是从 shell 驱动引脚的现代方式,使用 Raspberry Pi OS Bookworm 附带的 libgpiod 工具。你按下 Enter 的那一刻,画布上 GPIO27 的 LED 就会亮起。客户机使用的是 libgpiod 版本 1,因此芯片是位置参数,如上所写,而版本 2 中带 --chip 的写法无法识别。

脚本被中断后的 Linux shell:gpioset 命令已高亮,画布上的 LED 已点亮

要手动再次运行脚本,请输入 python3 script.py。要返回浏览器引擎,请点击同一标题栏中的 Exit Linux terminal。该选择不会随项目保存:明天重新打开它,Run 会回到基础模式。

Linux 模式下的串口监视器标题栏,Exit Linux terminal 按钮已高亮

各模式下可用的功能

下表是文档中树莓派页面的简要版本,该页面是逐模块对照实际产品测量得出的。

模块基础模式Linux 模式
RPi.GPIO、gpiozero已预装已预装
smbus2、spidev已预装,真实库已预装,通过内核节点使用真实库
serial(pyserial)仅树莓派 UART 路径真实 tty 上的真实 pyserial,/dev/serial0
adafruit_dht、Adafruit_DHT已预装已预装
w1thermsensor、1-Wire sysfs 树已预装通过 requirements.txt;sysfs 树可用
luma.oled、RPLCD、ST7789、Adafruit Blinka已预装已预装
gpiod 及 gpio* 工具浏览器中无字符设备已预装
cv2(OpenCV)支持客户机无对应构建
picamera2支持,通过你的网络摄像头不支持;请使用 rpicam-jpeg 及其同类工具
requests、urllib通过带允许列表的出站代理客户机内无网络
PyPI 上的其他任何包通过 requirements.txt通过 requirements.txt,前提是 PyPI 有 musl aarch64 的 wheel

两种模式都做不到的事情,免得你花一次试用会话才发现:

两个习惯能节省时间。将主文件命名为 script.py,这样两种模式运行的是同一个文件。并把第三方包放在旁边的 requirements.txt 中:当脚本导入一个缺失的包时,运行控制台会给出该行,并有一个按钮为你写入。

费用

任何人都可以无需账户就接线树莓派电路。运行则受限:

未登录访客看到的工具栏,其中 Free trial、登录芯片被高亮显示

两种引擎上的试用会话相同,因此只需基础模式的脚本应留在基础模式:它几秒即可启动,而不是一分钟,而 Linux 客户机是留给只有 Linux 才能做的事情的。

接下来去哪里

总结

两个项目,两个引擎。Grove 报警站在树莓派 5 上以基础模式运行,从空白画布到电路做出反应只用了几分钟,使用的是树莓派教程中会用的 gpiozero 和 Adafruit DHT 代码。传感器装置先在树莓派 4 上以基础模式运行,然后切换到 Linux 模式,在那里同一个脚本通过内核的 /dev/i2c-1 与 ADS1115、SHT31 和 OLED 通信,而在提示符下输入的一条 gpioset 命令点亮了画布上的一个 LED。基础模式涵盖了树莓派教程所需的大部分内容;Linux 模式则为终端、文件和系统工具而存在,代价是一分钟的启动时间。


分享到:
David Montero

作者

David Montero

Creator of Velxio, the open-source circuit and Arduino simulator.

GitHub velxio.dev

相关文章


下一篇
《恶魔城:月下夜想曲》在 ESP32-S3 上原生运行