feat: I2C-Lab v0.1.0 代码入库——RP2040 PIO 被动监控 probe 固件(pico-sdk C, 250MHz 双核) + Fluent 上位机(事务流/波形快照/CSV/演示模式);release/ 交付物(exe/UF2/BIN/zip)不入库、本地保留待真机验收发布;根 README 工具表与 agent.md 登记;I2C-Inject README 标注后续并入 I2C-Lab;移除误登记的 OBI-1/ui-style-demo 相关内容

This commit is contained in:
2026-10-13 15:41:29 +08:00
parent 74469cfe66
commit 42ff258b35
25 changed files with 3292 additions and 20 deletions
+27
View File
@@ -0,0 +1,27 @@
# I2C-Lab probe 固件(RP2040 / pico-sdk)
# 构建(见 ../README.md §固件):
# cmake -B build -G Ninja -DPICO_SDK_PATH=<sdk> -DPICO_TOOLCHAIN_PATH=<arm-gcc>
# cmake --build build
# 注: 不用 pico_add_extra_outputs(它会 FetchContent 拉 github 上的 picotool,
# 本机网络不通)。UF2 由 make_uf2.py 从 .bin 生成(带往返自检)。
cmake_minimum_required(VERSION 3.16)
include(pico_sdk_import.cmake) # 由 pico-sdk/external/ 复制
project(i2c_lab_fw C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(i2c_lab main.c)
pico_generate_pio_header(i2c_lab ${CMAKE_CURRENT_LIST_DIR}/bus_watch.pio)
target_link_libraries(i2c_lab
pico_stdlib
pico_multicore
hardware_pio
hardware_clocks
hardware_vreg
)
pico_enable_stdio_usb(i2c_lab 1)
pico_enable_stdio_uart(i2c_lab 0)
+31
View File
@@ -0,0 +1,31 @@
;
; bus_watch.pio — I2C 总线被动边沿捕获(RP2040 PIO)
; ==================================================
; 思路同 pico-examples 的 logic_analyser: 硬件只做最难的"确定性捕获"
; (电平变化即压入 FIFO), 协议解码(I2C 状态机)留给 CPU——与 PC 端
; i2cdec.py 共享同一套算法语义(源自 I2C-Inject 真机验证的
; i2c_sniff_decode.py)。
;
; 接线约定(沿用 I2C-Inject): GP8=SDA, GP9=SCL, 共地, 不加上拉,
; 纯输入不驱动(开漏总线零干扰)。
; 引脚映射: in_base = GP8 → ISR/X/Y 的 bit0=SDA bit1=SCL。
;
; 每个 FIFO 字 = 新电平值(0..3)。时间戳由 CPU 取(边沿间隔 ≥ 500ns,
; 核1 专职轮询, 抖动远小于 1us)。
; 环形轮询 5 条指令 @250MHz = 20ns/圈, 可分辨 ~40ns 毛刺;
; push noblock: FIFO 满则丢当前沿(Y 已更新, 后续沿仍连续可解,
; CPU 侧以"双位同变"启发式统计疑似丢沿)。
.program bus_watch
top:
mov isr, null ; ISR 清零(低 2 位待填)
in pins, 2 ; ISR[1:0] = SCL<<1 | SDA
mov x, isr
jmp x != y, changed ; 电平有变 -> 送出
jmp top ; 无变 -> 继续盯
changed:
mov y, x ; 先更新基线(即使 push 被丢也不失步)
push noblock ; 送出新电平(FIFO 满则丢弃)
jmp top
+388
View File
@@ -0,0 +1,388 @@
/**
* I2C-Lab probe — RP2040 I2C 总线被动监控固件 v0.1.0
* ==================================================================
* 硬件: BPI-Pico-RP2040(与 Raspberry Pi Pico 引脚兼容)
* 接线: GP8=SDA, GP9=SCL, GND 共地; 纯被动(不加上拉/不驱动)。
*
* 架构(双核分工):
* core1 硬实时: 轮询 PIO bus_watch SM 的 RX FIFO, 取 µs 时间戳,
* 跑 I2C 解码状态机(语义与 PC 端 i2cdec.py 一致) →
* 事务环形缓冲(核0 消费) + 边沿环形缓冲(波形快照用);
* core0 USB CDC 文本命令行(全仓 `=`/`!` 回显约定), T 行事务流,
* W 行边沿快照, LED 指示。
*
* 监控行协议(与 i2cdec.py 的 to_tline/parse_tline 对齐):
* T <t0_us> <dur_us> <addr7:02X> <R|W> <flags> <acks> <n> <b1..bn>
* flags: '-' stop | 'r' repeated-START 收尾 | 'c' 流截断
* | 'p' 半字节断, 可追加 'L' = 长事务截断(>56 字节)
* acks : n+1 个 'A'/'N'(首个为地址应答)
* W <t_us> <pins> 边沿快照行(pins bit0=SDA bit1=SCL), 'W.' 结束
* '=' 开头为命令应答, '!' 开头为告警
*
* 命令: ? | v | mon start|stop | stats | wave [n] | mon led ...
* 时钟: 250MHz(overclock, vreg 1.15V), USB 独立 PLL 不受影响。
*/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "pico/bootrom.h"
#include "pico/stdio_usb.h"
#include "hardware/pio.h"
#include "hardware/clocks.h"
#include "hardware/vreg.h"
#include "bus_watch.pio.h"
#define FW_VERSION "0.1.0"
#define PIN_SDA 8
#define PIN_SCL 9
#define PIN_LED 25
#define PIO_BUS pio0
/* 长事务只保留前 DATA_CAP 个数据字节(n 总数照计, flags 加 'L') */
#define DATA_CAP 56
#define TRANS_RING_SIZE 256u /* 事务环形缓冲条数(2 的幂) */
#define EDGE_RING_SIZE (16u * 1024u) /* 边沿环形缓冲条数(2 的幂), 64KB */
#define EDGE_DT_MAX 0xFFFFFEu /* Δt 压缩上限(>16.7s 的间隙按此记) */
enum { END_STOP = 0, END_RSTART, END_CUT, END_PARTIAL };
typedef struct {
uint32_t t0;
uint32_t dur;
uint8_t addr7;
uint8_t rw; /* 0=W 1=R */
uint8_t ended; /* END_* */
uint8_t truncated; /* 长事务截断 */
uint8_t n; /* 记录的数据字节数 ≤ DATA_CAP */
uint8_t ackn; /* 记录的应答数 = n+1 */
uint8_t data[DATA_CAP];
uint64_t acks; /* bit i(i<ackn): 1=ACK(SDA=0) */
} trans_rec_t;
/* ---------------- 共享状态(核1 生产 / 核0 消费) ---------------- */
typedef struct {
uint32_t edges; /* 捕获到的边沿总数 */
uint32_t trans; /* 解码完成的事务总数 */
uint32_t trans_drop; /* 事务环形覆盖丢数(消费不及) */
uint32_t susp; /* 疑似丢沿(双位同变) */
uint32_t ring_ovf; /* 边沿环覆盖次数 */
uint32_t last_edge_t; /* 最后边沿的绝对时间戳(us) */
} bus_stats_t;
volatile bus_stats_t stats = {0};
volatile trans_rec_t trans_ring[TRANS_RING_SIZE];
volatile uint32_t trans_head = 0; /* 核1 写 */
volatile uint32_t trans_tail_pub = 0;/* 核1 维护(覆盖时前推); 核0 参考 */
volatile uint32_t edge_ring[EDGE_RING_SIZE];
volatile uint32_t edge_head = 0; /* 核1 写 */
volatile uint32_t edge_newest_t = 0; /* 最新边沿绝对时间(us) */
volatile uint32_t edge_hold = 0; /* 核0 置位: 暂停边沿入环(wave 快照) */
/* ---------------- core1: 捕获 + 解码 ---------------- */
static uint bus_sm; /* SM 号(core0 声明, 双核共用) */
static struct {
int in_txn;
uint32_t t0;
uint8_t bytes[1 + DATA_CAP]; /* [0]=地址字节 */
uint8_t nbits, shift;
int nbytes_stored; /* 已存字节(含地址) */
int ntotal; /* 总字节(含地址, 含未存) */
uint64_t acks;
int ackn;
} d;
static uint32_t prev_sda, prev_scl, have_prev;
static uint32_t ring_last_t;
static void push_trans(uint32_t t1, int reason) {
if (!d.in_txn) return;
int ended = reason;
if (reason == END_CUT && d.nbits != 0) ended = END_PARTIAL;
trans_rec_t r;
memset(&r, 0, sizeof r);
r.t0 = d.t0;
r.dur = t1 - d.t0;
uint8_t addr_byte = (d.nbytes_stored > 0) ? d.bytes[0] : 0;
r.addr7 = addr_byte >> 1;
r.rw = addr_byte & 1;
r.ended = (uint8_t)ended;
int ndata_total = (d.ntotal > 0) ? d.ntotal - 1 : 0;
r.truncated = ndata_total > DATA_CAP;
int n_listed = d.nbytes_stored - 1;
if (n_listed > DATA_CAP) n_listed = DATA_CAP;
if (n_listed < 0) n_listed = 0;
r.n = (uint8_t)n_listed;
int ackn_listed = d.ackn < (n_listed + 1) ? d.ackn : n_listed + 1;
r.ackn = (uint8_t)ackn_listed;
memcpy(r.data, d.bytes + 1, (size_t)n_listed);
r.acks = d.acks; /* 高位未置位的应答位视为 N/A, 打印按 ackn */
uint32_t h = trans_head;
if (h - trans_tail_pub >= TRANS_RING_SIZE) { /* 覆盖最旧 */
stats.trans_drop++;
trans_tail_pub = h - TRANS_RING_SIZE + 1;
}
trans_ring[h & (TRANS_RING_SIZE - 1)] = r;
trans_head = h + 1;
stats.trans++;
d.in_txn = 0;
}
static void open_txn(uint32_t t) {
d.in_txn = 1;
d.t0 = t;
d.nbits = d.shift = 0;
d.nbytes_stored = 0;
d.ntotal = 0;
d.acks = 0;
d.ackn = 0;
}
static void store_byte(uint8_t v) {
d.ntotal++;
if (d.nbytes_stored < (int)(sizeof d.bytes)) d.bytes[d.nbytes_stored++] = v;
}
/* 一个 FIFO 字(新电平)的处理: 解码状态机 + 边沿入环 */
static void feed_word(uint32_t pins, uint32_t t) {
uint32_t sda = pins & 1, scl = (pins >> 1) & 1;
if (have_prev) {
if (scl && prev_scl) { /* SCL 高电平期 SDA 变化 */
if (prev_sda && !sda) { /* START / repeated START */
push_trans(t, END_RSTART);
open_txn(t);
} else if (!prev_sda && sda) { /* STOP */
push_trans(t, END_STOP);
}
} else if (scl && !prev_scl) { /* SCL 上升沿采样 */
if (d.in_txn) {
if (d.nbits < 8) {
d.shift = (uint8_t)((d.shift << 1) | sda);
if (++d.nbits == 8) store_byte(d.shift);
} else { /* 第 9 位 = 应答 */
if (d.ackn < 64 && !sda)
d.acks |= 1ull << d.ackn;
d.ackn++;
d.nbits = 0;
d.shift = 0;
}
}
}
if ((sda != prev_sda) && (scl != prev_scl))
stats.susp++; /* 合法 I2C 不应双位同变 */
}
prev_sda = sda;
prev_scl = scl;
have_prev = 1;
stats.edges++;
stats.last_edge_t = t;
if (!edge_hold) { /* 边沿环(波形快照源) */
uint32_t dt = t - ring_last_t;
if (dt > EDGE_DT_MAX) dt = EDGE_DT_MAX;
uint32_t h = edge_head;
if (h && (h & (EDGE_RING_SIZE - 1)) == 0)
stats.ring_ovf++; /* 每绕环覆盖一圈记一次 */
edge_ring[h & (EDGE_RING_SIZE - 1)] = (dt << 2) | pins;
edge_head = h + 1;
edge_newest_t = t;
}
ring_last_t = t;
}
static void core1_entry(void) {
uint sm = bus_sm; /* 由 core0 声明并初始化 */
while (true) {
while (!pio_sm_is_rx_fifo_empty(PIO_BUS, sm)) {
uint32_t w = pio_sm_get(PIO_BUS, sm) & 3;
feed_word(w, time_us_32());
}
tight_loop_contents();
}
}
/* ---------------- core0: 命令行 + 事务流 ---------------- */
static volatile bool mon_active = false;
static void cmd_help(void) {
printf("= 命令: ? 帮助 | v 版本 | mon start|stop 监控流开关"
" | stats 计数 | wave [n] 最近 n 个边沿快照(默认1024, 上限%u)"
" | boot 重进 BOOTSEL\n", EDGE_RING_SIZE);
}
static void cmd_stats(void) {
uint32_t now = time_us_32();
uint32_t ago = now - stats.last_edge_t;
printf("= stats edges=%u trans=%u trans_drop=%u susp=%u ring_ovf=%u "
"last_edge_ago_us=%u mon=%d\n",
stats.edges, stats.trans, stats.trans_drop, stats.susp,
stats.ring_ovf, ago, mon_active ? 1 : 0);
}
static void cmd_wave(uint32_t n) {
if (n == 0) n = 1024;
if (n > EDGE_RING_SIZE) n = EDGE_RING_SIZE;
edge_hold = 1;
sleep_ms(2); /* 等核1 让出环写 */
uint32_t h = edge_head;
uint32_t stored = h < EDGE_RING_SIZE ? h : EDGE_RING_SIZE;
uint32_t m = n < stored ? n : stored;
if (m == 0) {
printf("! wave 无边沿可倒出\n");
edge_hold = 0;
return;
}
/* 边沿环存 Δt: 先求区间内 Δ 和, 反推最旧一条的绝对时间 */
uint32_t sum_dt = 0;
for (uint32_t i = h - m + 1; i < h; i++)
sum_dt += edge_ring[i & (EDGE_RING_SIZE - 1)] >> 2;
uint32_t t = edge_newest_t - sum_dt;
printf("= wave %u\n", m);
for (uint32_t i = h - m; i < h; i++) {
uint32_t e = edge_ring[i & (EDGE_RING_SIZE - 1)];
printf("W %u %u\n", t, e & 3);
t += e >> 2;
}
printf("W.\n");
edge_hold = 0; /* 快照期间新边沿有缺口, stats.susp 可见 */
}
static void emit_trans(volatile trans_rec_t *r) {
char flags[4] = "-rcp";
char f[3] = {flags[r->ended], 0, 0};
if (r->truncated) { f[1] = 'L'; f[2] = 0; }
char acks[DATA_CAP + 2];
for (int i = 0; i < r->ackn; i++)
acks[i] = (r->acks & (1ull << i)) ? 'A' : 'N';
acks[r->ackn] = 0;
printf("T %u %u %02X %c %s %s %u", r->t0, r->dur, r->addr7,
r->rw ? 'R' : 'W', f, acks, r->n);
for (int i = 0; i < r->n; i++)
printf(" %02X", r->data[i]);
printf("\n");
}
/* 消费事务环: 监控开则打印, 关则丢弃计数 */
static void drain_trans(void) {
static uint32_t tail = 0; /* 核0 本地 */
uint32_t budget = 128; /* 每轮上限, 保证命令行响应 */
while (budget--) {
if (tail < trans_tail_pub) tail = trans_tail_pub;
if (tail >= trans_head) break;
volatile trans_rec_t *r = &trans_ring[tail & (TRANS_RING_SIZE - 1)];
if (mon_active && stdio_usb_connected())
emit_trans(r);
tail++;
}
}
static void handle_line(char *line) {
/* 就地去 CR/LF 与空白 */
size_t len = strlen(line);
while (len && (line[len - 1] == '\r' || line[len - 1] == '\n'
|| isspace((unsigned char)line[len - 1])))
line[--len] = 0;
char *save = NULL;
char *cmd = strtok_r(line, " \t", &save);
if (!cmd) return;
if (!strcmp(cmd, "?") || !strcmp(cmd, "help")) {
cmd_help();
} else if (!strcmp(cmd, "v")) {
printf("= i2c-lab probe v%s GP8=SDA GP9=SCL sys=%uMHz "
"(被动监控, T/W 行协议)\n", FW_VERSION,
(unsigned)(clock_get_hz(clk_sys) / 1000000));
} else if (!strcmp(cmd, "mon")) {
char *sub = strtok_r(NULL, " \t", &save);
if (sub && !strcmp(sub, "start")) {
mon_active = true;
printf("= mon start (T 行流开启)\n");
} else if (sub && !strcmp(sub, "stop")) {
mon_active = false;
printf("= mon stop\n");
} else {
printf("! mon start|stop\n");
}
} else if (!strcmp(cmd, "stats")) {
cmd_stats();
} else if (!strcmp(cmd, "wave")) {
char *arg = strtok_r(NULL, " \t", &save);
cmd_wave(arg ? (uint32_t)strtoul(arg, NULL, 0) : 1024);
} else if (!strcmp(cmd, "boot")) {
printf("= 重进 BOOTSEL\n");
sleep_ms(50);
reset_usb_boot(0, 0);
} else {
printf("! 未知命令 '%s', '?' 看帮助\n", cmd);
}
}
int main(void) {
vreg_set_voltage(VREG_VOLTAGE_1_15);
set_sys_clock_khz(250000, false); /* 超频提高捕获余量; 失败回落默认 */
stdio_init_all();
/* PIO: 总线被动监视(纯输入, 关上下拉) */
uint off = pio_add_program(PIO_BUS, &bus_watch_program);
bus_sm = pio_claim_unused_sm(PIO_BUS, true);
pio_sm_config c = bus_watch_program_get_default_config(off);
sm_config_set_in_pins(&c, PIN_SDA); /* in_base=8 → bit0=SDA bit1=SCL */
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
pio_sm_init(PIO_BUS, bus_sm, off, &c);
pio_gpio_init(PIO_BUS, PIN_SDA);
pio_gpio_init(PIO_BUS, PIN_SCL);
gpio_disable_pulls(PIN_SDA);
gpio_disable_pulls(PIN_SCL);
/* 纯输入: 不设 pindir, 程序也从不驱动 — 对开漏总线零干扰 */
gpio_init(PIN_LED);
gpio_set_dir(PIN_LED, GPIO_OUT);
multicore_launch_core1(core1_entry);
for (int i = 0; i < 3; i++) { /* 上电自检快闪 3 次(同 I2C-Inject) */
gpio_put(PIN_LED, 1); sleep_ms(80);
gpio_put(PIN_LED, 0); sleep_ms(80);
}
printf("= i2c-lab probe v%s ready GP8=SDA GP9=SCL ('?' 帮助)\n",
FW_VERSION);
char buf[128];
size_t bi = 0;
bool bannered = false;
while (true) {
/* 命令行: 非阻塞收字符组行 */
int ch = getchar_timeout_us(0);
if (ch != PICO_ERROR_TIMEOUT) {
if (ch == '\r' || ch == '\n') {
if (bi < sizeof buf) buf[bi] = 0;
if (bi) handle_line(buf);
bi = 0;
} else if (bi < sizeof buf - 1) {
buf[bi++] = (char)ch;
}
}
/* USB 枚举完成后补发横幅(插入即见) */
if (!bannered && stdio_usb_connected()) {
bannered = true;
printf("= i2c-lab probe v%s connected GP8=SDA GP9=SCL\n",
FW_VERSION);
}
drain_trans();
gpio_put(PIN_LED, mon_active); /* 常亮 = 监控中(同注入器约定) */
sleep_ms(1);
}
}
+133
View File
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
make_uf2.py — 最小 UF2 封装器(RP2040)
==================================================
背景: pico-sdk 2.x 生成 UF2 依赖 picotool(FetchContent 拉 github), 本机
网络对 github 不通时走本脚本替代: elf 先经 arm-none-eabi-objcopy 转 bin,
再按 UF2 规范(Microsoft UF2 spec, family 变体)打包。
用法:
python make_uf2.py <app.bin> <out.uf2> [base_addr_hex]
python make_uf2.py --test # 内部往返自检(解包回来比对 payload)
UF2 块格式(512B/块): magic0 0x0A324655, magic1 0x9E5D5157,
flags 0x00002000(family), addr, len(≤476, 本工具固定 256), size, blockNo,
numBlocks, familyId=0xE48BFF56(RP2040), data[476], magicEnd 0x0AB16F30。
"""
import struct
import sys
MAGIC0 = 0x0A324655
MAGIC1 = 0x9E5D5157
MAGIC_END = 0x0AB16F30
FLAG_FAMILY = 0x00002000
FAMILY_RP2040 = 0xE48BFF56
RP2040_FLASH_BASE = 0x10000000
PAYLOAD = 256 # RP2040 flash 页大小, 256 对齐最稳
def bin_to_uf2(data: bytes, base: int = RP2040_FLASH_BASE,
family: int = FAMILY_RP2040) -> bytes:
"""块布局按官方 uf2conv 实现(经真机 UF2 逐块核对):
0:magic0 4:magic1 8:flags 12:targetAddr 16:payloadSize
20:blockNo 24:numBlocks 28:familyID 32:data[476] 508:magicEnd"""
if len(data) % PAYLOAD: # 补齐到整页
data = data + b'\x00' * (PAYLOAD - len(data) % PAYLOAD)
total = len(data) // PAYLOAD
out = bytearray()
for i in range(total):
chunk = data[i * PAYLOAD:(i + 1) * PAYLOAD]
block = struct.pack(
'<IIIIIIII',
MAGIC0, MAGIC1, FLAG_FAMILY,
base + i * PAYLOAD, # targetAddr
PAYLOAD, # payloadSize
i, total, # blockNo / numBlocks
family, # fileSize 槽位 = familyID
)
block += chunk
block += b'\x00' * (476 - PAYLOAD) # data 区补齐到 476
block += struct.pack('<I', MAGIC_END)
assert len(block) == 512
out += block
return bytes(out)
def uf2_to_payload(uf2: bytes):
"""解包自检用: 还原 (base, payload bytes); 非法块抛 ValueError"""
if len(uf2) % 512:
raise ValueError(f'uf2 长度 {len(uf2)} 不是 512 的倍数')
blocks = [uf2[i:i + 512] for i in range(0, len(uf2), 512)]
base = None
total = None
chunks = {}
for n, b in enumerate(blocks):
(m0, m1, flags, addr, nb, no, nt, fam) = struct.unpack_from('<8I', b)
if m0 != MAGIC0 or m1 != MAGIC1:
raise ValueError(f'块 {n} magic 错')
if struct.unpack_from('<I', b, 508)[0] != MAGIC_END:
raise ValueError(f'块 {n} 尾 magic 错')
if flags & FLAG_FAMILY and fam != FAMILY_RP2040:
raise ValueError(f'块 {n} family 非 RP2040: 0x{fam:08X}')
if nb > 476:
raise ValueError(f'块 {n} payloadSize 超限')
if base is None:
base, total = addr, nt
if no != n or nt != total:
raise ValueError(f'块 {n} 序号/总数错')
chunks[no] = (addr, b[32:32 + nb])
payload = bytearray()
expect = base
for i in range(total):
if i not in chunks:
raise ValueError(f'缺块 {i}')
addr, c = chunks[i]
if addr != expect:
raise ValueError(f'块 {i} 地址不连续: {addr:#x} != {expect:#x}')
payload += c
expect += len(c)
return base, bytes(payload)
def _selftest():
import random
rng = random.Random(7)
for size in (0, 1, 255, 256, 257, 4096, 4097, 65536):
blob = bytes(rng.randrange(256) for _ in range(size))
if size == 0:
continue # 空镜像无意义(0 块)
uf2 = bin_to_uf2(blob, base=0x10000000)
base, back = uf2_to_payload(uf2)
pad = (-size) % PAYLOAD
assert base == 0x10000000
assert back[:size] == blob and len(back) == size + pad, \
f'size={size} 往返不一致'
print(f'PASS make_uf2 往返自检 ({(size + (-size) % PAYLOAD) // PAYLOAD} 块)')
return 0
def main(argv):
if len(argv) >= 2 and argv[1] == '--test':
return _selftest()
if len(argv) < 3:
print(__doc__)
return 2
with open(argv[1], 'rb') as f:
data = f.read()
base = int(argv[3], 16) if len(argv) > 3 else RP2040_FLASH_BASE
uf2 = bin_to_uf2(data, base=base)
with open(argv[2], 'wb') as f:
f.write(uf2)
print(f'UF2: {len(uf2)} B, {len(uf2) // 512} 块, '
f'base=0x{base:08X}, payload={len(data)} B -> {argv[2]}')
# 写完即自检
with open(argv[2], 'rb') as f:
b, p = uf2_to_payload(f.read())
assert b == base and p[:len(data)] == data
print('PASS 写出文件解包往返一致')
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))
+121
View File
@@ -0,0 +1,121 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
# following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
endif ()
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
endif()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
)
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
GIT_SUBMODULES_RECURSE FALSE
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
else ()
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
endif ()
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})