feat(I2C-Inject): 归档 RP2040 固件到 mcu-rp2040 并补齐说明

This commit is contained in:
2026-09-03 17:33:17 +08:00
parent d0cefeebf3
commit 736b43efe4
5 changed files with 85 additions and 6 deletions
+192
View File
@@ -0,0 +1,192 @@
# LT8619C I2C 注入器 — BPI-Pico-RP2040 / MicroPython
# ============================================================
# 用途: 在 demo 板(LT8619C, 7bit 地址 0x32)的 I2C 总线上择机注入寄存器写入,
# 避开 demo 板 MCU 的 ~500ms 轮询: 等一轮活动结束 + 静置 15ms 后快速
# "切bank→写→读回校验", 失败自动重试, 完成后立即释放总线回监听。
# 接线: GP8(物理脚11)=SDA, GP9(物理脚12)=SCL, GND 共地; 不接 VCC, 不加上拉。
# 串口: USB CDC, 命令均十六进制, 一行一回:
# w <bank> <reg> <val> 择机写 + 读回校验, 例: w 60 6E 80 (B通道镜像)
# r <bank> <reg> 择机读, 例: r 60 6E
# s 总线边沿计数统计
# ? 帮助
# 返回行以 '=' 开头为正常结果, '!' 为告警/失败。
import machine, time, sys, select, array, micropython
SDA, SCL = 8, 9
I2C_ADDR = 0x32
FREQ = 100_000
QUIET_MS = 50 # 静置判定窗口(总线空闲该时长后才注入)
ARM_TIMEOUT_MS = 1500 # 武装后等下一轮总线活动的超时(超过则硬闯并告警)
RETRY = 3
CAP_EDGES = 8000 # 嗅探最大边沿数(内存 ~40KB)
# ---- 嗅探: 紧循环读 GPIO_IN(0xD0000004) 位8/9, 变化即记录时间戳(TIMERAWL, us) ----
# val 编码: bit0=SDA(GP8), bit1=SCL(GP9)
# 性能: 字节码 machine.mem32 ~12us/读(混叠), viper 裸指针 ~0.27us/读(~3MHz 采样) —— 必须用 viper
def _cap_edges_bc(ts, vv, max_n, timeout_us):
n = 0
last = (machine.mem32[0xD0000004] >> 8) & 3
tend = machine.mem32[0x40054028] + timeout_us
while n < max_n:
v = (machine.mem32[0xD0000004] >> 8) & 3
if v != last:
ts[n] = machine.mem32[0x40054028]
vv[n] = v
n += 1
last = v
elif machine.mem32[0x40054028] >= tend:
break
return n
def _cap_edges_viper(ts, vv, max_n: int, timeout_us: int) -> int:
p_gpio = ptr32(0xD0000004)
p_timer = ptr32(0x40054028)
p_ts = ptr32(ts)
p_vv = ptr8(vv)
last = int(p_gpio[0]) >> 8 & 3
tend = int(p_timer[0]) + timeout_us
n = 0
while n < max_n:
v = int(p_gpio[0]) >> 8 & 3
if v != last:
p_ts[n] = int(p_timer[0])
p_vv[n] = v
n += 1
last = v
elif int(p_timer[0]) >= tend:
break
return n
_cap_edges = _cap_edges_bc
try:
_cap_edges_viper(array.array('i', bytes(4)), array.array('b', bytes(1)), 0, 0)
_cap_edges = _cap_edges_viper # viper 可用则用(~3MHz 采样)
except Exception:
pass # 退回字节码(仅够解码 <15kHz 的总线)
last_edge_us = time.ticks_us()
edges = 0
LED = machine.Pin(25, machine.Pin.OUT) # 板载 LED: 常亮 = 注入器监听中
def _on_edge(_pin):
global last_edge_us, edges
last_edge_us = time.ticks_us()
edges += 1
def bus_listen():
"""两脚浮空输入 + 双沿中断(只听不驱)"""
for p in (SDA, SCL):
pin = machine.Pin(p, machine.Pin.IN, None)
pin.irq(trigger=machine.Pin.IRQ_RISING | machine.Pin.IRQ_FALLING,
handler=_on_edge)
def wait_window():
"""等一轮总线活动结束再静置 QUIET_MS; 返回 True=正常等到, False=超时硬闯"""
saw = edges
t0 = time.ticks_ms()
while edges == saw: # 等下一轮活动出现(demo ~500ms 一轮)
if time.ticks_diff(time.ticks_ms(), t0) > ARM_TIMEOUT_MS:
print('! 无总线活动 %.0fms, 直接注入' % ARM_TIMEOUT_MS)
return False
time.sleep_ms(2)
t0 = time.ticks_ms()
while True: # 等静置
if time.ticks_diff(time.ticks_us(), last_edge_us) >= QUIET_MS * 1000:
return True
if time.ticks_diff(time.ticks_ms(), t0) > 2000:
print('! 静置超时, 直接注入')
return False
time.sleep_us(200)
def inject(bank, reg, val=None):
"""注入并校验; val=None 只读。返回 (ok, readback)"""
for attempt in range(1, RETRY + 1):
wait_window()
i2c = machine.I2C(0, sda=machine.Pin(SDA), scl=machine.Pin(SCL), freq=FREQ)
try:
if val is not None:
i2c.writeto(I2C_ADDR, bytes([0xFF, bank])) # 切 bank
i2c.writeto(I2C_ADDR, bytes([reg, val])) # 写寄存器
rb = i2c.readfrom_mem(I2C_ADDR, reg, 1)[0] # repeated-start 读回
except OSError as e:
print('! 第%d次总线错误 %s' % (attempt, e))
rb = None
finally:
bus_listen() # 立即释放总线
if rb is None:
continue
if val is None or rb == val:
return True, rb
print('! 第%d次校验不符: 写%02X%02X' % (attempt, val, rb))
return False, None
def main():
bus_listen()
for _ in range(3): # 上电自检: 快闪 3 次
LED.on(); time.sleep_ms(80); LED.off(); time.sleep_ms(80)
LED.on() # 常亮 = 监听中
print('LT8619C 注入器就绪 GP8=SDA GP9=SCL %dkHz addr 0x%02X' % (FREQ // 1000, I2C_ADDR))
poll = select.poll()
poll.register(sys.stdin, select.POLLIN)
buf = b''
while True:
try:
if poll.poll(50):
buf += sys.stdin.read(1)
if buf[-1:] not in (b'\n', b'\r'):
continue
line = buf.strip().decode()
buf = b''
parts = line.split()
if not parts:
continue
cmd = parts[0].lower()
if cmd == 'w' and len(parts) == 4:
bank, reg, val = (int(x, 16) for x in parts[1:])
ok, rb = inject(bank, reg, val)
print('= w %02X %02X %02X : %s 读回=%s' %
(bank, reg, val, 'OK' if ok else 'FAIL',
'%02X' % rb if rb is not None else '--'))
elif cmd == 'r' and len(parts) == 3:
bank, reg = (int(x, 16) for x in parts[1:])
ok, rb = inject(bank, reg)
print('= r %02X %02X : %s %s' %
(bank, reg, 'OK' if ok else 'FAIL',
'%02X' % rb if rb is not None else '--'))
elif cmd == 's':
print('= 边沿计数 %d' % edges)
elif cmd == 'c':
ms = int(parts[1]) if len(parts) > 1 else 700
for p in (SDA, SCL): # 捕获期间暂停监听中断
machine.Pin(p, machine.Pin.IN, None).irq(handler=None)
ts = array.array('i', bytes(4 * CAP_EDGES))
vv = array.array('b', bytes(CAP_EDGES))
n = _cap_edges(ts, vv, CAP_EDGES, ms * 1000)
bus_listen()
print('= capture %d edges / %d ms' % (n, ms))
t0 = ts[0] if n else 0
out = []
for i in range(n):
out.append('%d %d' % (ts[i] - t0, vv[i]))
if len(out) >= 100:
print('C ' + '\nC '.join(out))
out = []
if out:
print('C ' + '\nC '.join(out))
print('C.')
else:
print('? w bank reg val | r bank reg | s | c [ms]')
except KeyboardInterrupt:
break
print('退出')
main()