diff --git a/armsrc/bwm_forward.c b/armsrc/bwm_forward.c index f49bda28a..12765654f 100644 --- a/armsrc/bwm_forward.c +++ b/armsrc/bwm_forward.c @@ -14,13 +14,15 @@ #include "bwm_forward.h" #include "bwm_uart_at32.h" -#include "pm3_cmd.h" +#include "pm3_cmd.h" // PM3_CMD_DATA_SIZE, PM3_* return codes #include "string.h" #ifndef MIN #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #endif +// CRC-16/CCITT-FALSE, byte-identical to the BWM firmware's crc16_ccitt() +// (poly 0x1021, init 0xFFFF, MSB-first, no reflection, no xorout). static uint16_t bwm_crc16(const uint8_t *data, size_t len, uint16_t crc) { for (size_t i = 0; i < len; i++) { crc ^= (uint16_t)data[i] << 8; @@ -35,28 +37,43 @@ static uint16_t bwm_crc16(const uint8_t *data, size_t len, uint16_t crc) { return crc; } +// --------------------------------------------------------------------------- +// TX: wrap one reply frame into a SEND_FORWARD_DATA app_com frame. +// A full NG/OLD frame is <= PM3_CMD_DATA_SIZE + a small header/postamble, well +// under the BWM 4096-byte payload cap, so a single frame always suffices. +// --------------------------------------------------------------------------- #define BWM_TX_OVERHEAD (2 + 2 + 2 + 2) // hdr + cmd + len + crc #define BWM_TX_MAX_PAYLOAD (PM3_CMD_DATA_SIZE + 64) // NG/OLD frame ceiling #define BWM_TX_BUFSZ (BWM_TX_OVERHEAD + BWM_TX_MAX_PAYLOAD) -static void bwm_pump(void); +static void bwm_pump(void); // fwd decl: TX gate pumps RX to collect forward-frame acks +// --- Flow control (ack window) --------------------------------------------- +// s_fwd_inflight: forward frames sent but not yet acked by the ESP. Bumped on +// send, decremented when a SLAVE_RESP echoing cmd=SEND_FORWARD_DATA arrives. +// We may send while it is below BWM_FC_WINDOW; at the cap we wait for an ack. static volatile int16_t s_fwd_inflight = 0; int bwm_fwd_writebuffer_sync(const uint8_t *data, size_t len) { - static uint8_t frame[BWM_TX_BUFSZ]; + static uint8_t frame[BWM_TX_BUFSZ]; // single-threaded bare-metal: static OK if (len > BWM_TX_MAX_PAYLOAD) { - len = BWM_TX_MAX_PAYLOAD; + len = BWM_TX_MAX_PAYLOAD; // defensive; should never trigger } size_t idx = 0; + // Flow control: block while the in-flight window is full, waiting for the + // ESP to ack an earlier forward frame. bwm_pump() drains the IRQ-filled RX + // ring, so acks are collected even while we sit inside a tight download loop + // (the reply_old firehose). The spin cap is a safety valve so a dead or + // disconnected ESP can't hard-hang us. A window >= 1 means single command + // replies never block - only sustained bursts hit the cap. { uint32_t spins = 0; while (s_fwd_inflight >= BWM_FC_WINDOW) { bwm_pump(); if (++spins > BWM_FC_ACK_TIMEOUT_SPINS) { - s_fwd_inflight = 0; + s_fwd_inflight = 0; // best-effort: assume the pipe cleared break; } } @@ -77,10 +94,16 @@ int bwm_fwd_writebuffer_sync(const uint8_t *data, size_t len) { frame[idx++] = (uint8_t)((crc >> 8) & 0xFF); int wr = bwm_uart_write(frame, idx); - s_fwd_inflight++; + s_fwd_inflight++; // one more forward frame awaiting its ack return wr; } +// --------------------------------------------------------------------------- +// RX: persistent app_com de-framer. Feeds raw FPC bytes through a state machine +// and pushes the payloads of valid DATA_FORWARD (0xD2 0xD3 / cmd 8089) frames +// into a byte FIFO that bwm_read_ng() drains. Non-DATA_FORWARD frames (slave +// responses, forwarded logs, cmd-error reports) are validated and discarded. +// --------------------------------------------------------------------------- #define BWM_DEFIFO_SZ 2048 // >= one full NG frame's payload #define BWM_RXFRAME_MAX (PM3_CMD_DATA_SIZE + 64) @@ -91,27 +114,28 @@ typedef enum { typedef struct { bwm_state_t state; uint8_t hdr1; - bool is_bcast; + bool is_bcast; // header pair is 0xD2 0xD3 uint16_t cmd; uint16_t len; - uint16_t got; - uint16_t crc_calc; + uint16_t got; // payload bytes received + uint16_t crc_calc; // running CRC over hdr..payload uint16_t crc_recv; uint8_t payload[BWM_RXFRAME_MAX]; } bwm_parser_t; static bwm_parser_t s_p = { .state = S_IDLE }; +// De-framed payload ring static uint8_t s_fifo[BWM_DEFIFO_SZ]; -static volatile uint16_t s_fifo_head = 0; -static volatile uint16_t s_fifo_tail = 0; +static volatile uint16_t s_fifo_head = 0; // write +static volatile uint16_t s_fifo_tail = 0; // read static uint16_t fifo_count(void) { return (uint16_t)((s_fifo_head - s_fifo_tail) & (BWM_DEFIFO_SZ - 1)); } static void fifo_push(uint8_t b) { uint16_t next = (uint16_t)((s_fifo_head + 1) & (BWM_DEFIFO_SZ - 1)); - if (next != s_fifo_tail) { + if (next != s_fifo_tail) { // drop on overflow rather than corrupt s_fifo[s_fifo_head] = b; s_fifo_head = next; } @@ -126,6 +150,8 @@ static void bwm_reset_frame(bwm_parser_t *p) { p->state = S_IDLE; } +// Update running CRC one byte at a time (mirrors the streaming update in the +// BWM firmware parser). static void crc_step(bwm_parser_t *p, uint8_t byte) { p->crc_calc = bwm_crc16(&byte, 1, p->crc_calc); } @@ -138,12 +164,14 @@ static void bwm_feed_byte(bwm_parser_t *p, uint8_t byte) { } else if (byte == BWM_HDR_SLAVE_RESP_1) { p->hdr1 = byte; p->is_bcast = false; p->state = S_HDR2; } + // any other byte: stay idle (resync) break; case S_HDR2: { bool ok = (p->is_bcast && byte == BWM_HDR_SLAVE_BCAST_2) || (!p->is_bcast && byte == BWM_HDR_SLAVE_RESP_2); if (!ok) { + // header mismatch: reset and re-examine this byte as a potential SOF p->state = S_IDLE; bwm_feed_byte(p, byte); return; @@ -161,7 +189,7 @@ static void bwm_feed_byte(bwm_parser_t *p, uint8_t byte) { p->len |= (uint16_t)byte << 8; crc_step(p, byte); p->got = 0; - if (p->len > BWM_RXFRAME_MAX) { + if (p->len > BWM_RXFRAME_MAX) { // oversized -> drop frame bwm_reset_frame(p); } else { p->state = (p->len == 0) ? S_CRC_LO : S_PAYLOAD; @@ -185,11 +213,13 @@ static void bwm_feed_byte(bwm_parser_t *p, uint8_t byte) { fifo_push(p->payload[i]); } } else if ((p->is_bcast == false) && p->cmd == BWM_CMD_SEND_FORWARD_DATA) { + // SLAVE_RESP ack for a forward frame -> one slot freed if (s_fwd_inflight > 0) { s_fwd_inflight--; } } } + // valid non-DATA_FORWARD frames and CRC failures alike: just resync bwm_reset_frame(p); break; @@ -199,6 +229,7 @@ static void bwm_feed_byte(bwm_parser_t *p, uint8_t byte) { } } +// Pull whatever raw framed bytes are waiting and run them through the parser. static void bwm_pump(void) { uint8_t scratch[64]; uint16_t avail = bwm_uart_rx_available(); @@ -218,6 +249,8 @@ uint16_t bwm_fwd_rxdata_available(void) { if (fifo_count() > 0) { return fifo_count(); } + // No de-framed payload yet, but raw frame bytes may be waiting; pump once so + // receive_ng()'s gate reflects real forward data. bwm_pump(); return fifo_count(); } @@ -227,6 +260,8 @@ uint32_t bwm_read_ng(uint8_t *data, size_t len) { return 0; } + // Same bounded-retry budget shape as bwm_uart_read(); USART_SLOW_LINK (set + // for the BWM/BLE link) widens it so a slow round-trip doesn't time out. uint32_t tryconstant = 0; #ifdef USART_SLOW_LINK tryconstant = 50000;