明白了:
- 设备 A(本地)——你手里正在跑代码、带键盘/屏幕的那块板子;
- 设备 B(远端)——只引出 UART-TX/RX 的裸机 MicroPython 板;
- 双方都已经烧好同版本 MicroPython,波特率统一 115200;
- 目标:在 A 上敲命令 → 把 B 的 REPL 当“远程 shell”用,并且能把 B 上的文件拉回 A 编辑,再写回去。
下面给出“双端都是 MicroPython”的最简实现,全部用 pyboard.py 核心思路——也就是 raw-REPL(Ctrl-A) 模式:
- 发代码时一次性把脚本贴进去;
- 远端在 raw-REPL 里执行完后把 stdout+stderr 统一回传;
- 文件内容用 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
思路:
- 远端分块读取 → base64 → print;
- 本地收到后解码 → 写 SD/Flash;
- 最后远端发一行
###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 上玩。