教程:VENTUNO Q/Bridge 与 RPC
概述
本指南介绍 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="
" 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="
" alt="创建新 App" />
- 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 发送一个布尔值。
- 此 Python 脚本在 Dragonwing™ QCS8275 上运行,每秒调用一次 MCU 暴露的名为
- 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="
" 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 应用中很有用。
先决条件:
- 烧录 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); }
- 安装 Python 依赖
- 使用系统包管理器安装 msgpack 库:
sudo apt install python3-msgpack
- 创建 Python 脚本
- 创建名为 msgpack_test.py 的新文件:
nano msgpack_test.py
- 添加脚本内容
- 复制并粘贴以下代码。该脚本手动连接到 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="
" alt="自定义 Python 到 Router 示例" />
原文:https://docs.arduino.cc/tutorials/ventuno-q/bridge/ 本页为 Arduino 官方教程的中文翻译,供内部查阅。命令与代码保持原文。