/** * 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 * flags: '-' stop | 'r' repeated-START 收尾 | 'c' 流截断 * | 'p' 半字节断, 可追加 'L' = 长事务截断(>56 字节) * acks : n+1 个 'A'/'N'(首个为地址应答) * W 边沿快照行(pins bit0=SDA bit1=SCL), 'W.' 结束 * '=' 开头为命令应答, '!' 开头为告警 * * 命令: ? | v | mon start|stop | stats | wave [n] | mon led ... * 时钟: 250MHz(overclock, vreg 1.15V), USB 独立 PLL 不受影响。 */ #include #include #include #include #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 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); } }