PLAN for AI UART 两Micropython设备操作

明白了:

  • 设备 A(本地)——你手里正在跑代码、带键盘/屏幕的那块板子;
  • 设备 B(远端)——只引出 UART-TX/RX 的裸机 MicroPython 板;
  • 双方都已经烧好同版本 MicroPython,波特率统一 115200;
  • 目标:在 A 上敲命令 → 把 B 的 REPL 当“远程 shell”用,并且能把 B 上的文件拉回 A 编辑,再写回去。

下面给出“双端都是 MicroPython”的最简实现,全部用 pyboard.py 核心思路——也就是 raw-REPL(Ctrl-A) 模式:

  1. 发代码时一次性把脚本贴进去;
  2. 远端在 raw-REPL 里执行完后把 stdout+stderr 统一回传;
  3. 文件内容用 uzlib + base64 流式打包,避免内存炸,也兼容二进制。

一、硬件连线

A.TX → B.RX
A.RX → B.TX
GND 共地

(如果两块 3.3 V 直接连即可;5 V 板子请串 1 kΩ 或加电平转换)


二、公用底层:raw_REPL 会话函数

把下面代码存到 A 的 /lib/uart_repl.py,后面所有功能都调它:

import time, binascii, uzlib, gc

class UartRepl:
    def __init__(self, uart, timeout=3):
        self.u = uart
        self.u.read()          # 清空缓冲
        self.timeout = timeout

    def _wait(self, want):
        t = time.ticks_ms()
        buf = b''
        while time.ticks_diff(time.ticks_ms(), t) < self.timeout*1000:
            if self.u.any():
                buf += self.u.read(1)
                if buf.endswith(want):
                    return buf
        raise OSError('uart repl timeout')

    def enter_raw_repl(self):
        self.u.write(b'\r\x03\x03')  # 两次 Ctrl-C 打断
        time.sleep_ms(200)
        self.u.write(b'\x01')        # Ctrl-A 进 raw-REPL
        self._wait(b'raw REPL; CTRL-B to exit\r\n>')

    def exit_raw_repl(self):
        self.u.write(b'\x02')        # Ctrl-B 回到友好 REPL

    def exec_(self, src):
        src = src.strip()
        self.u.write(bytes(src, 'utf-8'))
        self.u.write(b'\x04')        # Ctrl-D 执行
        self._wait(b'OK')
        # 读回输出,直到出现 '\x04' 或 '\x04\x04' 错误标记
        out = self._wait(b'\x04')
        if out.endswith(b'\x04\x04'):
            raise Exception('remote exception:\n' + out[:-2].decode())
        return out[:-1].decode()     # 去掉结尾 \x04

三、功能 1:远程“命令行”

from machine import UART
from uart_repl import UartRepl
u = UART(2, 115200)
r = UartRepl(u)
r.enter_raw_repl()

while True:
    cmd = input('B# ')       # 在 A 端敲命令
    if cmd == 'exit':
        break
    print( r.exec_(cmd) )
r.exit_raw_repl()

四、功能 2:把 B 的文件拉回 A

思路:

  1. 远端分块读取 → base64 → print;
  2. 本地收到后解码 → 写 SD/Flash;
  3. 最后远端发一行 ###EOF### 当结束标记。
def pull(remote, local, chunk=512):
    script = f'''
import ubinascii, gc
with open('{remote}','rb') as f:
    while 1:
        blk=f.read({chunk})
        if not blk:break
        print(ubinascii.b2a_base64(blk),end='')
print('###EOF###')
'''
    r.enter_raw_repl()
    data = r.exec_(script)
    r.exit_raw_repl()
    # 本地还原
    with open(local,'wb') as fout:
        for line in data.splitlines():
            if line=='###EOF###':break
            fout.write(binascii.a2b_base64(line))
    print('pull done', local)

用例:

>>> pull('main.py', '/sd/main_remote.py')

五、功能 3:把 A 的文件推到 B

推之前先读本地文件 → base64 分块 → 发脚本 → 远端解码写盘。

def push(local, remote, chunk=512):
    import ubinascii
    r.enter_raw_repl()
    with open(local,'rb') as f:
        while 1:
            blk = f.read(chunk)
            if not blk: break
            b64 = binascii.b2a_base64(blk).strip()
            r.exec_(f"""
import ubinascii, gc
with open('{remote}','ab') as fo:
    fo.write(ubinascii.a2b_base64({repr(b64)}))
""")
    # 文件开头先 truncate
    r.exec_(f"import uos; uos.remove('{remote}') if '{remote}' in uos.listdir() else 0")
    r.exec_(f"import uos; uos.rename('{remote}.tmp','{remote}')")  # 原子替换
    r.exit_raw_repl()
    print('push done', remote)

用例:

>>> push('/sd/new_boot.py', 'boot.py')

六、做成 A 上的“远程文件管理”命令行

把上面函数再包一层交互:

if __name__ == '__main__':
    import sys, machine, uart_repl
    u = machine.UART(2, 115200)
    r = uart_repl.UartRepl(u)
    cmd = sys.argv[1]
    if cmd == 'pull':
        pull(sys.argv[2], sys.argv[3])
    elif cmd == 'push':
        push(sys.argv[2], sys.argv[3])
    elif cmd == 'sh':
        shell_mode(r)   # 前面 while True 那个函数

A 的 REPL 下就能:

>>> import uart_fs
>>> uart_fs.pull('data.csv', '/sd/data.csv')
>>> uart_fs.push('/sd/data_new.csv', 'data.csv')

七、二进制/大文件注意事项

  • chunk 设 512 字节时,RAM 峰值 < 2 kB,ESP32/RP2040 都无压力;
  • 若文件几百 kB,可在远端 gc.collect() 每次写完一块;
  • 如果两端都带硬件 flow-control(RTS/CTS),可把波特率直接拉到 921600,传输 100 kB 只需几秒。

八、一句话回顾

“本地 MicroPython” 用 raw-REPL 把 “远端 MicroPython” 当脚本引擎:

  • 读文件 = 远端 print(base64(chunk)) → 本地解码;
  • 写文件 = 本地 base64(chunk) → 远端 write(decode)
    只要一条 UART,就能把 B 的整个文件系统挂载到 A 上玩。

发表回复