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:
@@ -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)
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
@@ -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})
|
||||
Reference in New Issue
Block a user