389 lines
14 KiB
C
389 lines
14 KiB
C
/**
|
|
* 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);
|
|
}
|
|
}
|