You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Thonny/MicroPython环境下同步两台Raspberry Pi Pico时钟?

Raspberry Pi Pico 跨设备光通信时钟同步问题

我用两台Raspberry Pi Pico做光通信:一台通过LED闪烁发送比特,另一台用光电二极管读取电压接收比特。

  • 同一台Pico上收发时,每比特60μs的速率能正常工作;
  • 跨两台Pico收发时,只有每比特0.1s的速率能准确传输,降到0.01s就会丢数据。

我怀疑问题出在两台Pico的时钟或采样率存在微小差异,想知道在Thonny/MicroPython环境下能不能同步它们的时钟?


Pico #1 原发送代码

led=Pin(13,Pin.OUT)        # 用于闪烁的LED
SaqVoltage = machine.ADC(28)      # 读取输入信号,超过阈值时发送比特数据

# 省略部分代码:获取初始低电压值,判断是否收到高电压触发信号,以及LED/光电二极管校准逻辑

hi = "0110100001101001"
while True:
    SaqVoltage_value = SaqVoltage.read_u16()*3.3 / 65536    # 读取输入信号判断是否触发发送
    finalBitString = ""
   
    if SaqVoltage_value > saQCutOff: # 检测到高电压,发送"hi"序列
        finalBitString = # hi比特串加上起始/终止序列
        for i in range (0, len(finalBitString)): 
            if finalBitString[i]=="1":
                led(1)  
            elif finalBitString[i]=="0":
                led(0)
            utime.sleep(.01)

Pico #2 原接收代码

SaqDiode = machine.ADC(28)   # 读取光电二极管信号
# 省略LED/光电二极管校准代码

startSeqSaq = []
wordBits=[]
while True:
    utime.sleep(.01)   # 每0.01秒采样一次二极管
    SaqDiode_value = SaqDiode.read_u16()*3.3 / 65536
    if (saqDiodeHi - saqCutOff <= SaqDiode_value):      # 判断当前比特是1还是0
        bit=1  
    elif (SaqDiode_value <= saqDiodeLo + saqCutOff):
        bit=0
    if len(startSeqSaq)==10:    # 记录最近10个比特,检测起始序列
        startSeqSaq.pop(0)
        startSeqSaq.append(bit)
    elif len(startSeqSaq)<10:
        startSeqSaq.append(bit)

    if startSeqSaq == startSeq: # 检测到起始序列,开始接收数据比特
        while True:
            utime.sleep(.01)
            SaqDiode_value = SaqDiode.read_u16()*3.3 / 65536
            if (saqDiodeHi - saqCutOff <= SaqDiode_value):
                bit=1
                wordBits.append(bit)
            elif (SaqDiode_value < saqDiodeLo + saqCutOff):
                bit=0
                wordBits.append(bit)
        
            if len(wordBits)>10:   # 检测终止序列
                last14=wordBits[-10:]
            else:
                last14 = []
            if last14==endSeq:
                char = frombits(wordBits[:-10])
                wordBits=[]
                print("Function Generator Reset Signal Time in ms from Start: ", char)
                break

解决方案

1. 时钟同步的可行性

MicroPython环境下无法直接同步两台Pico的硬件时钟——RP2040芯片没有内置RTC硬件同步机制,也没有原生的跨设备时钟同步协议支持。但可以通过软件自适应采样或帧同步的方式规避时钟偏差问题,比硬同步更实用。

2. 代码核心优化方向

你当前用固定utime.sleep(0.01)控制比特时长和采样间隔,这种方式对时钟偏差极度敏感:哪怕两台Pico时钟只有0.1%的误差,100个比特后就会差出1个比特的时长,直接导致丢数据。

发送端优化

  • 放弃utime.sleep(),改用循环计数或硬件定时器生成精准比特周期,避免系统调度带来的延时误差;
  • 在发送帧开头加入同步前导码(比如连续的01交替比特),让接收端能校准采样时机。

简化优化后的发送逻辑:

import machine
import utime

led = machine.Pin(13, machine.Pin.OUT)
SaqVoltage = machine.ADC(28)
saQCutOff = 1.5  # 替换为你的实际校准阈值
hi = "0110100001101001"
preamble = "0101010101"  # 5组01交替的同步前导码
stop_seq = "11110000"

def send_bit(bit, duration_us):
    led.value(int(bit))
    # 用微秒级循环计数替代sleep,精度更高
    start = utime.ticks_us()
    while utime.ticks_diff(utime.ticks_us(), start) < duration_us:
        pass

while True:
    SaqVoltage_value = SaqVoltage.read_u16() * 3.3 / 65536
    if SaqVoltage_value > saQCutOff:
        finalBitString = preamble + hi + stop_seq
        for bit in finalBitString:
            send_bit(bit, 10000)  # 10ms每比特,可按需调整为1000us(1ms)甚至更小

接收端优化

  • 放弃固定间隔采样,改用边缘检测+动态采样:先捕获LED电平变化的边缘(上升/下降沿),以此为基准在比特周期的中间点采样,自动抵消时钟偏差;
  • 收到同步前导码后,计算实际比特周期长度,动态调整采样间隔。

简化优化后的接收逻辑:

import machine
import utime

SaqDiode = machine.ADC(28)
saqDiodeHi = 3.0  # 替换为你的实际高电平校准值
saqDiodeLo = 0.3  # 替换为你的实际低电平校准值
saqCutOff = 0.5
startSeq = [0,1,0,1,0,1,0,1,0,1]  # 和发送端前导码对应
endSeq = [1,1,1,1,0,0,0,0]

def read_bit():
    val = SaqDiode.read_u16() * 3.3 / 65536
    return 1 if val >= (saqDiodeHi - saqCutOff) else 0

def wait_for_edge():
    # 等待电平变化边缘,用于校准比特周期
    last_val = read_bit()
    while True:
        current_val = read_bit()
        if current_val != last_val:
            return current_val, utime.ticks_us()
        utime.sleep_us(10)

startSeqSaq = []
wordBits = []

while True:
    bit, edge_time = wait_for_edge()
    startSeqSaq.append(bit)
    if len(startSeqSaq) > 10:
        startSeqSaq.pop(0)
    
    if startSeqSaq == startSeq:
        # 计算实际比特周期:前导码是01交替,相邻边缘的时间差就是比特周期
        _, next_edge = wait_for_edge()
        bit_duration = utime.ticks_diff(next_edge, edge_time)
        print(f"校准比特周期:{bit_duration}us")
        
        wordBits = []
        while True:
            # 在比特周期中间点采样,抵消时钟偏差
            utime.sleep_us(bit_duration // 2)
            current_bit = read_bit()
            wordBits.append(current_bit)
            utime.sleep_us(bit_duration // 2)
            
            # 检测终止序列
            if len(wordBits) >= len(endSeq):
                last_bits = wordBits[-len(endSeq):]
                if last_bits == endSeq:
                    # 比特转字符逻辑
                    def frombits(bits):
                        chars = []
                        for b in range(0, len(bits), 8):
                            byte = bits[b:b+8]
                            chars.append(chr(int(''.join(map(str, byte)), 2)))
                        return ''.join(chars)
                    char = frombits(wordBits[:-len(endSeq)])
                    print(f"收到数据:{char}")
                    wordBits = []
                    break

3. 额外注意事项

  • 光电二极管电路需加滤波电容减少噪声,确保高低电平差值足够大,避免误判;
  • 更高速率需求下,可尝试用Pico的PWM模块发送LED信号,比手动切换电平更稳定;
  • 若追求极致速率,可考虑UART转光模块方案,但纯软件实现的话,自适应采样是最优解。

内容的提问来源于stack exchange,提问作者nerd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 13:54:38