如何在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
相关产品推荐
相关产品推荐

