Files
HW-Debug-Tools/I2C-Lab/firmware/main.c
T

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);
}
}