教程:VENTUNO Q/Bridge 与 RPC

来自WindwizPi wiki
Windwiz​(留言 | 贡献)2026年9月28日 (一) 05:58的版本 (导入 VENTUNO Q 中文教程)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
此页面具有访问限制。如果您看见此消息,则说明您没有权限访问此页面。

概述

本指南介绍 Bridge RPC library,它可在 Arduino® VENTUNO™ Q 的 Linux 侧(Qualcomm® QCS8275)与实时 STM32H5F5 微控制器之间实现通信。

Bridge 库已预装在 VENTUNO Q 的软件中,无需额外安装。

硬件与软件要求

硬件要求

软件要求

说明:

你也可以使用 Arduino IDE 2+ 仅对 VENTUNO Q 的微控制器侧进行编程。

Bridge - Remote Procedure Call (RPC) Library

VENTUNO Q 使用 RPC(Remote Procedure Call,远程过程调用)在 Linux(Qualcomm MPU)侧与实时 STM32 MCU 之间交换数据。该机制允许在一个处理器上运行的函数从另一个处理器透明地调用,就像本地调用一样。

<img src="ventuno-rpc.png" alt="VENTUNO Q RPC" />

概述

Bridge 库在 Arduino_RPClite 框架之上提供通信层。它管理 SoC 与 MCU 之间的双向 RPC 流量,处理方法绑定、请求转发与异步响应。

  • Linux 侧(Qualcomm® QCS8275):运行更高层服务,并可远程调用 MCU 函数。
  • MCU 侧(STM32,Zephyr RTOS):处理时间关键任务,并通过 RPC 向 Linux 处理器暴露函数。

Arduino Router(基础设施)

在底层,通信由名为 Arduino Router(arduino-router)的后台 Linux 服务管理。

你在代码中使用的是 Bridge 库,而 Router 才是使这一切成为可能的流量控制器。它使用 MessagePack RPC 实现 星型拓扑(Star Topology) 网络。

主要特性:

  • 多点通信: 与通常点对点的简单串口通信不同,Router 允许多个 Linux 进程同时与 MCU 通信(以及彼此通信)。
Linux ↔ MCU: 多个 Linux 进程可同时与 MCU 交互(例如,一个 Python® 脚本在读取传感器,同时另一个独立的 C++ 应用在控制电机)。
Linux ↔ Linux: 你可以用 Router 桥接运行在 Linux 系统上的不同应用。例如,一个 Python 脚本可以暴露一个 RPC 函数,供另一个 Python® 或 C++ 应用直接调用,从而在完全不涉及 MCU 的情况下让服务交换数据。
  • 服务发现: 客户端(如你的 Python® 脚本或 MCU Sketch)会“注册”它们想要暴露的函数。Router 维护这些函数的目录,并将调用路由到正确的目标。

源代码:

系统配置与硬件接口

Router 管理两个处理器之间的物理连接。了解哪些硬件资源被 Router 占用很重要,以避免与你自己的应用发生冲突。

  • Linux 侧(Dragonwing™ QCS8275): Router 占用串口设备 /dev/ttyHS1。
  • MCU 侧(STM32H5F5): Router 占用硬件串口 Serial1。

警告:

⚠️ 警告:保留资源:请勿在自己的代码中尝试打开 /dev/ttyHS1(Linux)或 Serial1(Arduino/Zephyr)。这些接口由 arduino-router 服务独占锁定。直接访问它们将导致 Bridge 失败。

管理 Router 服务

arduino-router 作为系统服务自动运行。大多数情况下,你无需直接与其交互。但如果你在调试高级问题,或需要重启通信栈,可通过 Linux 终端控制它:

检查状态,查看 router 是否正在运行并已连接:

systemctl status arduino-router

重启服务:如果通信似乎卡住,可以在不重启开发板的情况下重启 router:

sudo systemctl restart arduino-router

查看日志,以查看实时日志进行调试(例如,查看 RPC 消息是否被拒绝,或客户端是否已断开):

journalctl -u arduino-router -f

要在日志中捕获更详细的信息,可在 systemd 服务配置中追加 --verbose 参数。

  • 打开服务文件进行编辑:
sudo nano /etc/systemd/system/arduino-router.service
  • 找到以 ExecStart= 开头的行,并在命令末尾追加 --verbose。更新后的服务文件应如下所示:
[Unit]
Description=Arduino Router Service
After=network-online.target
Wants=network-online.target
Requires=

[Service]
# Put the micro in a ready state.
ExecStartPre=-/usr/bin/gpioset -c /dev/gpiochip1 -t0 37=0
ExecStart=/usr/bin/arduino-router --unix-port /var/run/arduino-router.sock --serial-port /dev/ttyHS1 --serial-baudrate 115200 --verbose # <--- ADD THIS
# End the boot animation after the router is started.
ExecStartPost=/usr/bin/gpioset -c /dev/gpiochip1 -t0 70=1
StandardOutput=journal
StandardError=journal
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
  • 必须重新加载 systemd 守护进程,配置更改才会生效。
sudo systemctl daemon-reload
  • 重启 Router:
sudo systemctl restart arduino-router
  • 查看详细日志:
journalctl -u arduino-router -f

核心组件

BridgeClass 是管理 RPC 客户端与服务器的主类。

  • begin():初始化 bridge 与内部串口传输。
  • call(method, args...):在 Linux 侧调用函数并等待结果。
  • notify(method, args...):在 Linux 侧调用函数但不等待响应(即发即弃)。
  • provide(name, function):将本地 MCU 函数暴露给 Linux。注意:该函数在高优先级后台 RPC 线程中执行。请保持这些函数简短且线程安全。
  • provide_safe(name, function):暴露本地 MCU 函数,但确保它在主 loop() 上下文中执行。如果你的函数会与标准 Arduino API(如 digitalWrite 或 Serial)交互,请使用此方法,以避免并发崩溃。

警告:

警告: 请勿在 provide() 函数内部使用 Bridge.call() 或 Monitor.print()。在响应一个通信的同时发起新的通信会导致系统死锁。

RpcCall

  • 表示异步 RPC 的辅助类。若调用其 .result 方法,它会等待响应、提取返回值,并在需要时传播错误码。

Monitor

  • 该库包含一个预定义的 Monitor 对象。它允许 Linux 侧通过 RPC 方法 mon/write 向 MCU 发送文本流(充当虚拟 Serial Monitor)。

线程与安全性

  • Bridge 使用 Zephyr 互斥锁(k_mutex)保证在传输层读写时的安全并发访问。更新由持续轮询请求的后台线程处理。
  • 传入更新: 由专用后台线程(updateEntryPoint)处理,该线程持续轮询请求。
  • 安全执行: provide_safe 机制挂接到主循环(__loopHook),以便在处理器空闲时安全执行用户回调。

使用示例(Arduino App Lab)

本示例展示 Linux 侧(Dragonwing™ QCS8275) 如何通过 Bridge 调用远程函数,切换 MCU(STM32) 上的 LED。

在 Arduino App Lab 中创建新 App,然后将下面的示例分别复制粘贴到新 App 的 “Python” 和 “sketch” 部分。

<img src="ventuno-create-app-small.png" alt="创建新 App" />

  1. Linux(Dragonwing™ QCS8275)调用远程 MCU 函数的示例
    此 Python 脚本在 Dragonwing™ QCS8275 上运行,每秒调用一次 MCU 暴露的名为 set_led_state 的 RPC:
    # main.py (Linux side)
    from arduino.app_utils import *
    import time
    
    led_state = False
    
    def loop():
        global led_state
        time.sleep(1)
        led_state = not led_state
        Bridge.call("set_led_state", led_state)
    
    App.run(user_loop=loop)
    
    这会使用 Bridge.call("set_led_state", <bool>) 每秒向 MCU 发送一个布尔值。
  2. MCU(STM32)设置:包含 Bridge 并启动它
    此 sketch 包含 Bridge 库并配置 LED 引脚。
    #include "Arduino_RouterBridge.h"
    
    void setup() {
        pinMode(LED_BUILTIN, OUTPUT);
    
        Bridge.begin();
        Bridge.provide("set_led_state", set_led_state);
    }
    
    void loop() {
    }
    
    void set_led_state(bool state) {
        // LOW state means LED is ON
        digitalWrite(LED_BUILTIN, state ? LOW : HIGH);
    }
    
    这会将本地 MCU 函数 set_led_state 注册为名为 "set_led_state" 的 RPC 服务,从而使 Linux(Dragonwing™ QCS8275)侧可以使用 Bridge.provide("set_led_state", set_led_state); 像调用本地函数一样远程调用它。

说明:

你也可以反过来做:Python 函数可以提供给 MCU sketch,以便在本地使用。

将 Python 脚本粘贴到 App 的 Python 文件、将 Arduino 代码粘贴到 sketch 后,即可运行 App,并观察到 LED #1 每秒以红色闪烁一次。

<img src="ventuno-blink.gif" alt="红色 LED 闪烁" />

说明:

Bridge RPC 库中还有更多高级方法,可通过在 Arduino App Lab 中测试不同的内置示例来探索。

通过 Unix Socket 交互(高级)

Linux 进程使用位于以下路径的 Unix Domain Socket 与 Router 通信:

/var/run/arduino-router.sock

虽然 Bridge 库会自动处理这些,但你也可以手动连接到该 socket,使用任何支持 MessagePack RPC 的语言(例如 Python、C++、Rust、Go)与 MCU 或其他 Linux 服务交互。

使用示例(自定义 Python 客户端)

以下示例演示如何使用 msgpack 库,从标准 Python 脚本控制 MCU 函数(set_led_state),而无需使用 Arduino App Lab 辅助类。这对于将 Arduino 函数集成到现有 Linux 应用中很有用。

先决条件:

  1. 烧录 MCU Sketch
    使用 Arduino IDE 或 Arduino App Lab 上传以下代码。这将注册我们要调用的函数。
    #include "Arduino_RouterBridge.h"
    
    void setup() {
      pinMode(LED_BUILTIN, OUTPUT);
    
      Bridge.begin();
      // We use provide_safe to ensure the hardware call runs in the main loop context
      Bridge.provide_safe("set_led_state", set_led_state);
    }
    
    void loop() {
    }
    
    void set_led_state(bool state) {
      digitalWrite(LED_BUILTIN, state ? LOW : HIGH);
    }
    
  2. 安装 Python 依赖
    使用系统包管理器安装 msgpack 库:
    sudo apt install python3-msgpack
    
  3. 创建 Python 脚本
    创建名为 msgpack_test.py 的新文件:
    nano msgpack_test.py
    
  4. 添加脚本内容
    复制并粘贴以下代码。该脚本手动连接到 Router 的 Unix socket,并发送原始 RPC 请求。
    import socket
    import msgpack
    import sys
    
    # 1. Define the connection to the Router's Unix Socket
    SOCKET_PATH = "/var/run/arduino-router.sock"
    
    # 2. Parse command line arguments
    # Default to turning LED ON (True) if no argument is provided
    led_state = True
    
    if len(sys.argv) > 1:
        arg = sys.argv[1]
        if arg == "1":
            led_state = True
        elif arg == "0":
            led_state = False
        else:
            print("Usage: python3 msgpack_test.py [1|0]")
            sys.exit(1)
    
    print(f"Sending request to set LED: {led_state}")
    
    # 3. Create the MessagePack RPC Request
    # Format: [type=0 (Request), msgid=1, method="set_led_state", params=[led_state]]
    request = [0, 1, "set_led_state", [led_state]]
    packed_req = msgpack.packb(request)
    
    # 4. Send the request
    try:
        with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client:
            client.connect(SOCKET_PATH)
            client.sendall(packed_req)
    
            # 5. Receive the response
            response_data = client.recv(1024)
            response = msgpack.unpackb(response_data)
    
            # Response Format: [type=1 (Response), msgid=1, error=None, result=None]
            print(f"Router Response: {response}")
    
    except Exception as e:
        print(f"Connection failed: {e}")
    

运行示例

现在你可以从终端运行脚本并传入 1(开)或 0(关)来测试连接:

python3 msgpack_test.py 1 # to turn on the LED
# or
python3 msgpack_test.py 0 # to turn off the LED

<img src="ventuno-custom-python-rpc.png" alt="自定义 Python 到 Router 示例" />


原文:https://docs.arduino.cc/tutorials/ventuno-q/bridge/ 本页为 Arduino 官方教程的中文翻译,供内部查阅。命令与代码保持原文。