diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/loop_uart_proto.h b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/loop_uart_proto.h index 07be97d..24ff326 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/loop_uart_proto.h +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/loop_uart_proto.h @@ -212,7 +212,11 @@ typedef enum { LUP_FRAME_STATE_HEADER, // 接收 Header (Addr, LEN, CMD) LUP_FRAME_STATE_VALUE, // 接收 Value LUP_FRAME_STATE_CHECK, // 接收 Checksum - LUP_FRAME_STATE_COMPLETE // 帧完成 + LUP_FRAME_STATE_COMPLETE, // 帧完成 + /* 0x9F OTA 专用状态 (2026-08-19): 帧格式 9F SubL SubH LEN CMD DATA CHECK */ + LUP_FRAME_STATE_OTA_LEN, // 0x9F: 收 LEN (含 CMD) + LUP_FRAME_STATE_OTA_CMD, // 0x9F: 收 CMD + LUP_FRAME_STATE_OTA_CHECK // 0x9F: 收 CHECK (仅 SUM, 无 XOR) } LUP_FrameState; typedef struct { @@ -228,6 +232,11 @@ extern LUP_FrameParser g_lup_parser; /* 喂一个字节给帧解析器,返回 1 表示帧接收完成 */ int lup_feed_byte(uint8_t byte); +/* 0x9F OTA 帧解析器 (OTA 模式专用, g_flag_counter_ota.flag=1 时使用) + * 帧格式: 9F SubL SubH LEN CMD DATA(LEN-1) CHECK(SUM) + * 与 0x7F 共用 g_lup_parser 缓冲/索引, 状态值独立 (LUP_FRAME_STATE_OTA_*) */ +int lup_feed_byte_ota(uint8_t byte); + /* 取完整帧的指针和数据长度 */ const uint8_t *lup_frame_data(void); uint16_t lup_frame_len(void); diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/loop_uart_proto.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/loop_uart_proto.c index 321a88a..f609740 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/loop_uart_proto.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/loop_uart_proto.c @@ -655,6 +655,110 @@ int lup_feed_byte(uint8_t byte) // 不应该到达这里,调用方应在 COMPLETE 后 reset lup_frame_reset(); break; + default: + /* 未知状态 (含 0x9F OTA 专用状态值): 防御性复位, 防状态机跑飞 */ + lup_frame_reset(); + break; + } + + return 0; +} + +/*=========================================================================== + * 0x9F OTA 帧解析器 (OTA 模式专用, 2026-08-19) + * + * 帧格式 (DLD960LoopBootloader 源码确认): + * [9F] [SubL] [SubH] [LEN] [CMD] [DATA: LEN-1] [CHECK: SUM] + * - SubL/SubH: 分包序号, 2 字节小端 (低字节在前) + * - LEN: 含 CMD 的字节数, 故 DATA = LEN - 1 + * - CHECK: 仅 SUM 校验 (无 XOR), 从 SubL 累加到 DATA 最后字节 + * 总帧长 = LEN + 5 (与 0x7F 相同) + * + * 状态机复用 g_lup_parser (缓冲/索引), 状态值独立 (LUP_FRAME_STATE_OTA_*) + * 调用方 (uart2_dma_poll) 在 OTA 模式切换时负责 lup_frame_reset() + *===========================================================================*/ +int lup_feed_byte_ota(uint8_t byte) +{ + LUP_FrameParser *p = &g_lup_parser; + + switch (p->state) { + case LUP_FRAME_STATE_IDLE: + if (byte == LUP_MAGIC_OTA) { + p->buf[0] = byte; + p->idx = 1; + p->state = LUP_FRAME_STATE_HEADER; /* 复用 HEADER: 收 SubL/SubH */ + } + break; + + case LUP_FRAME_STATE_HEADER: + p->buf[p->idx++] = byte; + if (p->idx == 3) { /* SubL + SubH 收完 */ + p->state = LUP_FRAME_STATE_OTA_LEN; + } + break; + + case LUP_FRAME_STATE_OTA_LEN: + p->buf[p->idx++] = byte; + { + uint8_t len_field = p->buf[3]; + uint16_t data_bytes; + if (len_field < 1) { + data_bytes = 0; /* LEN<1 非法, 按无数据直接到 CHECK */ + } else { + data_bytes = len_field - 1; /* LEN 含 CMD */ + } + if (data_bytes > LUP_MAX_VALUE_LEN) { + lup_frame_reset(); /* 非法长度, 丢弃 */ + break; + } + p->value_len = data_bytes; + p->value_idx = 0; + } + p->state = LUP_FRAME_STATE_OTA_CMD; + break; + + case LUP_FRAME_STATE_OTA_CMD: + p->buf[p->idx++] = byte; + if (p->value_len > 0) { + p->state = LUP_FRAME_STATE_VALUE; /* 复用 VALUE: 收 DATA */ + } else { + p->state = LUP_FRAME_STATE_OTA_CHECK; + } + break; + + case LUP_FRAME_STATE_VALUE: + p->buf[p->idx++] = byte; + p->value_idx++; + if (p->value_idx >= p->value_len) { + p->state = LUP_FRAME_STATE_OTA_CHECK; + } + break; + + case LUP_FRAME_STATE_OTA_CHECK: + p->buf[p->idx++] = byte; + { + /* SUM = SubL+SubH+LEN+CMD+DATA (不含 magic 0x9F, 不含本 CHECK) */ + uint8_t sum = 0; + uint16_t i; + for (i = 1; i < (uint16_t)(p->idx - 1); i++) { + sum += p->buf[i]; + } + if (sum == p->buf[p->idx - 1]) { + p->state = LUP_FRAME_STATE_COMPLETE; + return 1; /* 帧完成 */ + } + lup_frame_reset(); /* 校验失败, 丢弃 */ + } + break; + + case LUP_FRAME_STATE_COMPLETE: + // 不应该到达这里,调用方应在 COMPLETE 后 reset + lup_frame_reset(); + break; + + default: + lup_frame_reset(); + break; } return 0; diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/usart_biz.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/usart_biz.c index 5925f6b..5c5aad3 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/usart_biz.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/usart_biz.c @@ -28,6 +28,7 @@ void USART2_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); static uint8_t uart2_dma_buf[UART2_DMA_BUF_LEN] __attribute__((aligned(4))); static uint16_t uart2_dma_last = 0; /* 主循环消费位置 (DMA 写指针由硬件维护) */ static uint32_t uart2_dma_drop = 0; /* 溢出丢弃计数 (主循环消费不及时) */ +static uint8_t uart2_dma_ota_mode = 0xFF; /* 上次 OTA 模式 (0xFF=未初始化, 首轮强制 reset) */ void uart2_dma_init(void) { @@ -67,19 +68,33 @@ void uart2_dma_poll(void) if (n == 0) return; - /* 溢出保护: 未消费 > 半缓冲 (256B) → 消费太慢被 DMA 覆盖, 重置解析器丢帧计数 */ - if (n > (UART2_DMA_BUF_LEN / 2)) { - uart2_dma_drop++; + /* 溢出保护: 未消费 > 半缓冲 (256B) → 消费太慢被 DMA 覆盖, 重置解析器丢帧计数 + 2026-08-19: OTA 模式下 0x9F ACK 帧仅 7B, 停等协议同一时刻 Loop 至多回 1 帧, + 放宽阈值到整缓冲 — 256B 阈值在 TX 阻塞窗口可能误杀 ACK */ + { + uint16_t drop_thr = g_flag_counter_ota.flag ? UART2_DMA_BUF_LEN : (UART2_DMA_BUF_LEN / 2); + if (n > drop_thr) { + uart2_dma_drop++; + lup_frame_reset(); + uart2_dma_last = cur; + return; + } + } + + /* OTA 模式切换 (0x7F ↔ 0x9F) 时重置帧解析器, 防旧模式残留状态污染 */ + if (g_flag_counter_ota.flag != uart2_dma_ota_mode) { lup_frame_reset(); - uart2_dma_last = cur; - return; + uart2_dma_ota_mode = g_flag_counter_ota.flag; } while (n--) { uint8_t b = uart2_dma_buf[uart2_dma_last]; uart2_dma_last = (uint16_t)((uart2_dma_last + 1) & (UART2_DMA_BUF_LEN - 1)); - if (lup_feed_byte(b)) { + /* OTA 模式走 0x9F 帧解析 (bootloader 的 pre_ok/addr_ok/data ACK), + 非 OTA 保持 0x7F 协议解析 */ + int done = g_flag_counter_ota.flag ? lup_feed_byte_ota(b) : lup_feed_byte(b); + if (done) { /* 帧接收完成 → 复制到 g_pkg_uart_2 (与中断版同逻辑, 主循环无竞争) */ const uint8_t *frame = lup_frame_data(); uint16_t frame_len = lup_frame_len(); @@ -90,6 +105,9 @@ void uart2_dma_poll(void) g_pkg_uart_2.tick = 0; } lup_frame_reset(); + } else if (g_lup_parser.state != LUP_FRAME_STATE_IDLE) { + /* 收帧中 → tick 归零 (对齐中断版行为, 防 TIM3 半帧兜底误判) */ + g_pkg_uart_2.tick = 0; } } } @@ -273,7 +291,14 @@ void uart_srv(void) } } else{ - // OTA 模式 — 忽略 Loop MCU 数据 + /* OTA 模式 (2026-08-19): Loop bootloader 回的 0x9F 帧 + (back_9F_pre_ok / addr_ok / data ACK) 透传回 BLE 工具, + 否则停等协议 (0xA7 WITH_BACK) 永远等不到 ACK */ + if(g_flag_bt_state){ + g_flag_notify_temp = set_response_tran_to_notify(g_pkg_uart_2.pkg, g_pkg_uart_2.offset, &g_notify_buftemp); + } + /* OTA 模式同样清 flag — 残留帧会在 OTA 结束后被误处理 */ + InitPkgUart(&g_pkg_uart_2); } diff --git a/vd960DBN/docs/devlog.md b/vd960DBN/docs/devlog.md index 6e1817c..0fcebc3 100644 --- a/vd960DBN/docs/devlog.md +++ b/vd960DBN/docs/devlog.md @@ -4,6 +4,47 @@ > > 项目定位: DLD960 通信板 — BLE 配网、TCP JSON 协议服务、Loop MCU 串口桥接 +## 2026-08-19 — UART2 RX DMA 下 BLE→Loop OTA 失效修复:0x9F 帧透传回 BLE + +### 背景 + +DMA 改造(fed4335)后通过 BLE 给地感(Loop MCU)OTA 升级失效。代码排查 + DLD960LoopBootloader 源码确认根因。 + +### 根因 + +1. **`lup_feed_byte()` IDLE 态只认 `0x7F`**,Loop bootloader 回的 0x9F 响应帧(pre_ok/addr_ok/data ACK)第一个字节就被丢弃 +2. **OTA 是停等协议**:`0xA7` (TRAN_PKG_WITH_BACK) 每收一块必须回 `back_9F_data()` ACK,工具等不到 ACK 不发下一块 → DBN 吞掉 ACK = 升级必然卡死 +3. **`uart_srv` OTA 分支"忽略 Loop MCU 数据"**——即使解析成功也不透传回 BLE +4. DMA 版相对中断版丢失"收帧中 tick 归零";溢出保护 256B 阈值在 TX 阻塞窗口可能误杀 ACK + +### 0x9F 帧格式(DLD960LoopBootloader 源码确认) + +| 方向 | 帧格式 | 说明 | +|------|--------|------| +| 下行 | `9F SubL SubH LEN CMD DATA(LEN-1) CHECK` | CHECK = SUM(SubL..DATA),**无 XOR**;LEN 含 CMD | +| 上行 | `9F SubL SubH 02 CMD Status CHECK` | 例:pre_ok = `9F 01 00 02 A5 00 A8` | +| CMD | `A5` READY / `A6` ADDR_START / `A7` TRAN_WITH_BACK(回ACK) / `A8` TRAN_WITHOUT_BACK(不回) | 总帧长 = LEN+5 与 0x7F 相同 | + +### 变更 + +| 文件 | 变更 | +|------|------| +| `loop_uart_proto.h` | `LUP_FrameState` 追加 OTA 专用状态(OTA_LEN/OTA_CMD/OTA_CHECK);声明 `lup_feed_byte_ota()` | +| `loop_uart_proto.c` | 实现 `lup_feed_byte_ota()`:0x9F 帧状态机(SUM 校验,复用 `g_lup_parser` 缓冲);`lup_feed_byte` 加 default 防御性 reset | +| `usart_biz.c` | `uart2_dma_poll`:OTA 模式切换时 reset 解析器 + 按 `g_flag_counter_ota.flag` 选 0x9F/0x7F 解析器 + OTA 模式溢出阈值放宽到整缓冲(0x9F ACK 仅 7B,停等协议同刻至多 1 帧)+ 收帧中 tick 归零(对齐中断版);`uart_srv` OTA 分支:0x9F 帧 `set_response_tran_to_notify` 透传回 BLE + `InitPkgUart` 清 flag | + +### 验证 + +- gcc 隔离单测 `tests/test_lup_ota_parser.c`:**8 断言全过**(bootloader 真实帧向量 pre_ok/addr_ok/addr_err/data ACK、错误校验拒绝并复位、0x7F 帧隔离、两帧粘包独立解析) +- `gcc -fsyntax-only`:`loop_uart_proto.c`/`usart_biz.c` 无新错误(仅 RISC-V interrupt attribute 假阳性 + 原有 unused warning) + +### 遗留 + +- **`g_flag_counter_ota.flag` 无退出机制**:升级完成后 DBN 仍卡 OTA 透传态,只能断电重启恢复 0x7F 通信——待定加"OTA 超时/完成帧自动退出" +- 待板级:MRS 编译 + 真机 BLE OTA 全流程回归 + +--- + ## 2026-08-18 — log_query 改为 hex 原始字节上报(协议 V1.03/V1.07 修订) ### 背景 @@ -1370,6 +1411,7 @@ TCP 超时要等 ~2 分钟才触发 `SINT_STAT_TIM_OUT`, | 版本 | 时间 | 说明 | |------|------|------| +| V4.4 | 2026-08-19 | UART2 DMA 下 BLE→Loop OTA 修复: lup_feed_byte_ota 0x9F 帧解析+透传回 BLE (停等 ACK), DMA poll OTA 模式切换/溢出阈值/tick 归零, 单测 8 例 | | V4.3 | 2026-08-18 | log_query 改 hex 原始字节上报 (协议 V1.03/V1.07 修订): offlog_evt_to_hex/snap_rec_to_hex, 2 条快照 406B<800B, 快照 count 恢复 2 | | V4.2 | 2026-08-18 | MQTT 命令集补齐: ssc_net_query/iot_net_query/iot_topic_query 只读组包 (协议 V1.02 对齐), 工具禁用未实现按钮 | | V4.1 | 2026-08-18 | TCP/MQTT 脱机日志快照流: log_stat/log_query/log_clear 支持 stream=snapshot (协议 V1.03/V1.07), snap_rec_to_json 序列化, 事件流 count=0 按上限 | diff --git a/vd960DBN/tests/test_lup_ota_parser.c b/vd960DBN/tests/test_lup_ota_parser.c new file mode 100644 index 0000000..7c40637 --- /dev/null +++ b/vd960DBN/tests/test_lup_ota_parser.c @@ -0,0 +1,262 @@ +/* + * test_lup_ota_parser.c — 验证 lup_feed_byte_ota 0x9F 帧解析器 + * + * 测试向量来自 DLD960LoopBootloader 源码 (back_9F_pre_ok/addr_ok/data): + * pre_ok : 9F 01 00 02 A5 00 A8 (CHECK = 01+00+02+A5+00 = A8) + * addr_ok: 9F 01 00 02 A6 00 A9 + * addr_err:9F 01 00 02 A6 01 AA + * data : 9F 02 A7 + * + * 编译: gcc -I../BLE/OnlyUpdateApp_Peripheral/APP/include -o test_lup_ota_parser test_lup_ota_parser.c + */ +#include +#include +#include + +/* ---- mock: 从 loop_uart_proto.h 复制的最小依赖 ---- */ +#define LUP_MAGIC 0x7F +#define LUP_MAGIC_OTA 0x9F +#define LUP_MAX_VALUE_LEN 64 +#define LUP_MAX_PKG_LEN (1 + 3 + LUP_MAX_VALUE_LEN + 2) + +typedef enum { + LUP_FRAME_STATE_IDLE = 0, + LUP_FRAME_STATE_HEADER, + LUP_FRAME_STATE_VALUE, + LUP_FRAME_STATE_CHECK, + LUP_FRAME_STATE_COMPLETE, + LUP_FRAME_STATE_OTA_LEN, + LUP_FRAME_STATE_OTA_CMD, + LUP_FRAME_STATE_OTA_CHECK +} LUP_FrameState; + +typedef struct { + LUP_FrameState state; + uint8_t buf[LUP_MAX_PKG_LEN]; + uint16_t idx; + uint16_t value_len; + uint16_t value_idx; +} LUP_FrameParser; + +static LUP_FrameParser g_lup_parser; + +static void lup_frame_reset(void) +{ + g_lup_parser.state = LUP_FRAME_STATE_IDLE; + g_lup_parser.idx = 0; + g_lup_parser.value_len = 0; + g_lup_parser.value_idx = 0; + memset(g_lup_parser.buf, 0, sizeof(g_lup_parser.buf)); +} + +static const uint8_t *lup_frame_data(void) { return g_lup_parser.buf; } +static uint16_t lup_frame_len(void) { return g_lup_parser.idx; } + +/* ---- 嵌入被测函数体 (从 loop_uart_proto.c 提取) ---- */ +int lup_feed_byte_ota(uint8_t byte) +{ + LUP_FrameParser *p = &g_lup_parser; + + switch (p->state) { + case LUP_FRAME_STATE_IDLE: + if (byte == LUP_MAGIC_OTA) { + p->buf[0] = byte; + p->idx = 1; + p->state = LUP_FRAME_STATE_HEADER; + } + break; + + case LUP_FRAME_STATE_HEADER: + p->buf[p->idx++] = byte; + if (p->idx == 3) { + p->state = LUP_FRAME_STATE_OTA_LEN; + } + break; + + case LUP_FRAME_STATE_OTA_LEN: + p->buf[p->idx++] = byte; + { + uint8_t len_field = p->buf[3]; + uint16_t data_bytes; + if (len_field < 1) { + data_bytes = 0; + } else { + data_bytes = len_field - 1; + } + if (data_bytes > LUP_MAX_VALUE_LEN) { + lup_frame_reset(); + break; + } + p->value_len = data_bytes; + p->value_idx = 0; + } + p->state = LUP_FRAME_STATE_OTA_CMD; + break; + + case LUP_FRAME_STATE_OTA_CMD: + p->buf[p->idx++] = byte; + if (p->value_len > 0) { + p->state = LUP_FRAME_STATE_VALUE; + } else { + p->state = LUP_FRAME_STATE_OTA_CHECK; + } + break; + + case LUP_FRAME_STATE_VALUE: + p->buf[p->idx++] = byte; + p->value_idx++; + if (p->value_idx >= p->value_len) { + p->state = LUP_FRAME_STATE_OTA_CHECK; + } + break; + + case LUP_FRAME_STATE_OTA_CHECK: + p->buf[p->idx++] = byte; + { + uint8_t sum = 0; + uint16_t i; + for (i = 1; i < (uint16_t)(p->idx - 1); i++) { + sum += p->buf[i]; + } + if (sum == p->buf[p->idx - 1]) { + p->state = LUP_FRAME_STATE_COMPLETE; + return 1; + } + lup_frame_reset(); + } + break; + + case LUP_FRAME_STATE_COMPLETE: + lup_frame_reset(); + break; + + default: + lup_frame_reset(); + break; + } + + return 0; +} + +/* ---- 测试辅助 ---- */ +static int fails = 0; + +/* 喂完整帧, 断言: 帧完成 且 len 正确 */ +static void feed_frame(const uint8_t *frame, int len, const char *name) +{ + int i, done = 0; + lup_frame_reset(); + for (i = 0; i < len; i++) { + if (lup_feed_byte_ota(frame[i])) { + done = 1; + break; + } + } + if (!done) { + printf("FAIL %s: frame not completed\n", name); + fails++; + return; + } + if ((int)lup_frame_len() != len) { + printf("FAIL %s: len=%d expected %d\n", name, (int)lup_frame_len(), len); + fails++; + return; + } + if (memcmp(lup_frame_data(), frame, len) != 0) { + printf("FAIL %s: content mismatch\n", name); + fails++; + return; + } + printf("PASS %s\n", name); +} + +/* 喂错误校验帧, 断言: 解析失败 (reset 回 IDLE) */ +static void feed_bad(const uint8_t *frame, int len, const char *name) +{ + int i, done = 0; + lup_frame_reset(); + for (i = 0; i < len; i++) { + if (lup_feed_byte_ota(frame[i])) { + done = 1; + break; + } + } + if (done) { + printf("FAIL %s: bad checksum accepted\n", name); + fails++; + return; + } + if (g_lup_parser.state != LUP_FRAME_STATE_IDLE) { + printf("FAIL %s: parser not reset to IDLE (state=%d)\n", name, g_lup_parser.state); + fails++; + return; + } + printf("PASS %s\n", name); +} + +int main(void) +{ + uint8_t f1[] = {0x9F, 0x01, 0x00, 0x02, 0xA5, 0x00, 0xA8}; /* pre_ok */ + uint8_t f2[] = {0x9F, 0x01, 0x00, 0x02, 0xA6, 0x00, 0xA9}; /* addr_ok */ + uint8_t f3[] = {0x9F, 0x01, 0x00, 0x02, 0xA6, 0x01, 0xAA}; /* addr_err */ + uint8_t f4[] = {0x9F, 0x05, 0x00, 0x02, 0xA7, 0x00, 0xAE}; /* data ACK sub=5 ok */ + uint8_t f5[] = {0x9F, 0x34, 0x12, 0x02, 0xA7, 0x01, 0xF0}; /* data ACK sub=0x1234 err */ + uint8_t bad[] = {0x9F, 0x01, 0x00, 0x02, 0xA5, 0x00, 0xA9}; /* CHECK 错 */ + uint8_t v7f[] = {0x7F, 0x00, 0x04, 0x4A, 0x00, 0x4E, 0x4E}; /* 0x7F 帧不应被解析 */ + + feed_frame(f1, sizeof(f1), "pre_ok (9F 01 00 02 A5 00 A8)"); + feed_frame(f2, sizeof(f2), "addr_ok (9F 01 00 02 A6 00 A9)"); + feed_frame(f3, sizeof(f3), "addr_err(9F 01 00 02 A6 01 AA)"); + feed_frame(f4, sizeof(f4), "data ack sub=5 (9F 05 00 02 A7 00 AE)"); + feed_frame(f5, sizeof(f5), "data ack sub=0x1234 err (9F 34 12 02 A7 01 F0)"); + + feed_bad(bad, sizeof(bad), "bad checksum rejected"); + + /* 0x7F 帧: IDLE 不认 → 应无帧完成, 状态回 IDLE */ + { + int i, done = 0; + lup_frame_reset(); + for (i = 0; i < (int)sizeof(v7f); i++) { + if (lup_feed_byte_ota(v7f[i])) { done = 1; break; } + } + if (done) { + printf("FAIL 0x7F frame parsed by ota parser\n"); + fails++; + } else if (g_lup_parser.state != LUP_FRAME_STATE_IDLE) { + printf("FAIL 0x7F frame left parser non-IDLE (state=%d)\n", g_lup_parser.state); + fails++; + } else { + printf("PASS 0x7F frame ignored by ota parser\n"); + } + } + + /* 粘帧: 两帧连续 → 各自解析成功 */ + { + uint8_t glue[14]; + int i, done_cnt = 0; + memcpy(glue, f1, 7); + memcpy(glue + 7, f2, 7); + lup_frame_reset(); + for (i = 0; i < 14; i++) { + if (lup_feed_byte_ota(glue[i])) { + done_cnt++; + if (done_cnt == 1 && memcmp(lup_frame_data(), f1, 7) != 0) { + printf("FAIL glue frame1 mismatch\n"); fails++; + } + if (done_cnt == 2 && memcmp(lup_frame_data(), f2, 7) != 0) { + printf("FAIL glue frame2 mismatch\n"); fails++; + } + lup_frame_reset(); + } + } + if (done_cnt != 2) { + printf("FAIL glue: %d frames completed, expected 2\n", done_cnt); + fails++; + } else { + printf("PASS glued frames parsed independently\n"); + } + } + + printf("\n%s (%d failures)\n", fails ? "FAILED" : "ALL PASS", fails); + return fails ? 1 : 0; +}