diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/cmcng.h b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/cmcng.h index 62dd7f9..7605573 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/cmcng.h +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/cmcng.h @@ -154,4 +154,13 @@ void uart_srv(void); void UART2_SendString(uint8_t *buf, uint16_t len); void UART1_SendString(uint8_t *buf, uint16_t len); +/* ---- 4G 通道: UART1 <-> Air780 (协议 §6.1 纯字节流透传) ---- */ +void uart1_dma_init(void); /* 初始化 (DEBUG!=0 时为空实现) */ +void uart1_dma_poll(void); /* 主循环轮询: 消费 DMA 缓冲 -> 分流 */ +extern uint32_t g_uart1_fwd_to_air; /* 上行转发帧数 (UART2->UART1) */ +extern uint32_t g_uart1_fwd_to_loop; /* 下行转发帧数 (UART1->UART2, 0x7F) */ +extern uint32_t g_uart1_local_cnt; /* 下行本地处理帧数 (0x8F) */ +extern uint32_t g_uart1_badchk; /* 下行校验失败帧数 */ +extern uint32_t g_uart1_drop; /* 下行 DMA 溢出丢弃次数 */ + #endif /* INCLUDE_CMCNG_H_ */ diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/peripheral_main.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/peripheral_main.c index 82191ed..66e35a7 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/peripheral_main.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/peripheral_main.c @@ -275,6 +275,7 @@ void Main_Circulation(void) FAULT_MARKER(MK_UART_SRV_IN); uart_srv(); FAULT_MARKER(MK_UART_SRV_OUT); + uart1_dma_poll(); /* 4G 通道下行收帧 (协议 §6.1): 置于 fault marker 之外, 不改变 MK_UART_SRV 语义 */ ota_poll(); /* OTA 刷写状态机 (V1.08): 非阻塞 tick 驱动, 先消费 UART2 ACK 再推进 */ snap_flush(); /* 快照落盘: 无条件挂主循环 (原在 iot_enable 分支, TCP 模式不落盘) */ FAULT_MARKER(MK_POLL_BLE_IN); diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/usart_biz.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/usart_biz.c index 374cd77..97b38e6 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/usart_biz.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/usart_biz.c @@ -8,6 +8,7 @@ #include "config.h" #include "cmcng.h" +#include "debug.h" /* DEBUG 宏: 4G 版本以 -DDEBUG=0 编译, 此时 printf 不占用 USART1 */ #include "loop_uart_proto.h" #include #include "dbn_ble_srv.h" @@ -113,6 +114,238 @@ void uart2_dma_poll(void) } } + +/*==================== UART1 <-> Air780 4G 通道 (协议 §6.1) ==================== + * 定位: 4G 通道中 vd960DBN 只做"字节流透传", 零业务转换 —— + * 上行: Loop 0x7F 帧(UART2) --校验通过后原样转发--> UART1 --> Air780 解析转 JSON + * 下行: Air780 帧(UART1) --魔数分流--> 0x7F: 转发 UART2(给 vd960Loop) + * 0x8F: DBN 本地处理 + * + * 硬约束: + * 1) 只转发 lup_verify_checksum() 通过的完整帧。Air780 侧靠 loop_state 变化沿判车, + * 转发一个残缺帧 = 凭空多判一台车。 + * 2) 接收必须走 DMA (与 UART2 方案A 同理): PRINT 临界区 / BLE 栈回调 / SPI 擦除等 + * 长阻塞窗口会屏蔽 RXNE 中断 -> 丢字节 -> 校验失败。本通道"不丢帧"是硬要求。 + * 3) 帧装配器独立于 g_lup_parser: 后者专供 UART2(Loop) 通道, 两条流的字节绝不能 + * 混进同一个状态机 (混了就是互相残杀)。 + * + * 帧格式 (与 Loop/BLE 协议完全一致): + * [7F][Addr][LEN][CMD][Value: LEN-1B][XOR][SUM] 总长 = LEN + 5 + * 校验覆盖 Addr..Value (不含 magic), 复用 lup_verify_checksum() + * + * 编译开关: + * 调试版 (DEBUG != 0): printf 占用 USART1/PB6, 本通道不参与编译 -> 走空实现 + * 4G 版 (DEBUG == 0): -DDEBUG=0 编译即自动启用 (PB6/PB7 由本文件接管) + * 亦可 -DUART1_AIR780_EN=1/0 强制覆盖 + *===========================================================================*/ +#ifndef UART1_AIR780_EN +#define UART1_AIR780_EN (DEBUG == 0) +#endif + +#define UART1_MAGIC_LOOP LUP_MAGIC /* 0x7F: 转发给 vd960Loop */ +#define UART1_MAGIC_LOCAL 0x8F /* 0x8F: DBN 本地处理 (BLE 侧同名魔数) */ +#define UART1_DMA_BUF_LEN 512 + +#if (UART1_AIR780_EN) + +static uint8_t uart1_dma_buf[UART1_DMA_BUF_LEN] __attribute__((aligned(4))); +static uint16_t uart1_dma_last = 0; /* 主循环消费位置 (DMA 写指针由硬件维护) */ + +/* 下行帧装配器 (独立实例, 不与 g_lup_parser 共享) */ +static uint8_t uart1_rx_frame[LUP_MAX_PKG_LEN]; +static uint16_t uart1_rx_idx = 0; /* 已收字节数 */ +static uint16_t uart1_rx_need = 0; /* 本帧总长, 0 = 尚未定长 */ + +/* 通道计数: 现场核对"上行转发数 == Air780 实收数", 比翻日志可靠 */ +uint32_t g_uart1_fwd_to_air = 0; /* 上行: UART2 -> UART1 转发帧数 */ +uint32_t g_uart1_fwd_to_loop = 0; /* 下行: UART1 -> UART2 转发帧数 */ +uint32_t g_uart1_local_cnt = 0; /* 下行: 本地处理帧数 (0x8F) */ +uint32_t g_uart1_badchk = 0; /* 下行: 校验失败帧数 */ +uint32_t g_uart1_drop = 0; /* 下行: DMA 溢出丢弃次数 */ + +/* ---- 下行分流 ---- */ +static void uart1_dispatch_frame(uint8_t *pkg, uint16_t len) +{ + if (pkg[0] == UART1_MAGIC_LOOP) { + /* 0x7F -> 原样转发给 vd960Loop */ + g_uart1_fwd_to_loop++; + UART2_SendString(pkg, len); + } else { + /* 0x8F -> DBN 本地处理 (预留: 具体行为待协议细化, 先只计数) */ + g_uart1_local_cnt++; + } +} + +/* ---- 坏帧回找魔数: 把 [1..len) 里第一个魔数起的残余搬到头部继续装配 ---- + * 防"单帧损坏 -> 后续帧全部失步" */ +static void uart1_resync(const uint8_t *buf, uint16_t len) +{ + uint16_t i; + for (i = 1; i < len; i++) { + if (buf[i] == UART1_MAGIC_LOOP || buf[i] == UART1_MAGIC_LOCAL) { + uint16_t k, n = 0; + for (k = i; k < len; k++) { + uart1_rx_frame[n++] = buf[k]; + } + uart1_rx_idx = n; + uart1_rx_need = 0; + if (n >= 3) { + uint8_t lf = uart1_rx_frame[2]; + uint16_t need = (uint16_t)lf + 5; + if (lf >= 1 && need <= LUP_MAX_PKG_LEN) { + uart1_rx_need = need; + } else { + uart1_rx_idx = 0; /* LEN 非法: 残余作废 */ + } + } + return; + } + } + /* 残余中没有魔数 -> 全部丢弃 */ +} + +/* ---- 定长收满 -> 校验 -> 分流 ---- */ +static void uart1_frame_ready(void) +{ + uint16_t len = uart1_rx_idx; + uart1_rx_idx = 0; + uart1_rx_need = 0; + if (lup_verify_checksum(uart1_rx_frame, len) == 0) { + uart1_dispatch_frame(uart1_rx_frame, len); + } else { + g_uart1_badchk++; + uart1_resync(uart1_rx_frame, len); + } +} + +/* ---- UART1 字节流装配 ---- */ +static void uart1_feed_byte(uint8_t b) +{ + uint8_t guard = 0; + + if (uart1_rx_idx == 0) { + if (b == UART1_MAGIC_LOOP || b == UART1_MAGIC_LOCAL) { + uart1_rx_frame[0] = b; + uart1_rx_idx = 1; + } + return; /* 非魔数字节: 帧间空闲, 直接丢 */ + } + + if (uart1_rx_idx >= LUP_MAX_PKG_LEN) { /* 防御: 正常流程到不了 */ + uart1_rx_idx = 0; + uart1_rx_need = 0; + return; + } + + uart1_rx_frame[uart1_rx_idx++] = b; + + if (uart1_rx_idx == 3) { /* [7F][Addr][LEN] 到手 -> 定长 */ + uint8_t lf = uart1_rx_frame[2]; + uint16_t need = (uint16_t)lf + 5; + if (lf < 1 || need > LUP_MAX_PKG_LEN) { + uart1_rx_idx = 0; /* LEN 非法 -> 重新找魔数 */ + uart1_rx_need = 0; + return; + } + uart1_rx_need = need; + } + + /* 校验失败后 resync 可能让残余已够长, 故用 while (guard 防跑飞) */ + while (uart1_rx_need != 0 && uart1_rx_idx >= uart1_rx_need && guard++ < 8) { + uart1_frame_ready(); + } +} + +void uart1_dma_init(void) +{ + GPIO_InitTypeDef GPIO_InitStructure = {0}; + USART_InitTypeDef USART_InitStructure = {0}; + DMA_InitTypeDef DMA_InitStructure; + + /* PB6 = USART1_TX / PB7 = USART1_RX (USART1 重映射) */ + RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); + RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); + + GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE); + + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; /* Tx */ + GPIO_Init(GPIOB, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; /* Rx */ + GPIO_Init(GPIOB, &GPIO_InitStructure); + + USART_InitStructure.USART_BaudRate = 115200; /* 与 Air780 侧 uart1 一致 */ + USART_InitStructure.USART_WordLength = USART_WordLength_8b; + USART_InitStructure.USART_StopBits = USART_StopBits_1; + USART_InitStructure.USART_Parity = USART_Parity_No; + USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; + USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; + USART_Init(USART1, &USART_InitStructure); + USART_Cmd(USART1, ENABLE); + + /* RX 用 DMA1_Ch5 循环缓冲 (UART2 占用 Ch6/Ch7, 通道不冲突) */ + DMA_DeInit(DMA1_Channel5); + DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART1->DATAR); + DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)uart1_dma_buf; + DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; + DMA_InitStructure.DMA_BufferSize = UART1_DMA_BUF_LEN; + DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; + DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; + DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; + DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; + DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; + DMA_InitStructure.DMA_Priority = DMA_Priority_High; + DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; + DMA_Init(DMA1_Channel5, &DMA_InitStructure); + + USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); + DMA_Cmd(DMA1_Channel5, ENABLE); + USART_ITConfig(USART1, USART_IT_RXNE, DISABLE); /* DMA 接管, 逐字节中断不再需要 */ + + uart1_dma_last = 0; + uart1_rx_idx = 0; + uart1_rx_need = 0; +} + +/* 主循环每轮调用: 消费 DMA 环形缓冲新字节 -> 装配 -> 分流 */ +void uart1_dma_poll(void) +{ + uint16_t cur = (uint16_t)(UART1_DMA_BUF_LEN - DMA_GetCurrDataCounter(DMA1_Channel5)); + uint16_t n = (uint16_t)((cur + UART1_DMA_BUF_LEN - uart1_dma_last) & (UART1_DMA_BUF_LEN - 1)); + + if (n == 0) return; + + /* 溢出保护: 未消费超过半缓冲 -> 已被 DMA 覆盖, 复位装配器重同步 */ + if (n > (UART1_DMA_BUF_LEN / 2)) { + g_uart1_drop++; + uart1_rx_idx = 0; + uart1_rx_need = 0; + uart1_dma_last = cur; + return; + } + + while (n--) { + uint8_t b = uart1_dma_buf[uart1_dma_last]; + uart1_dma_last = (uint16_t)((uart1_dma_last + 1) & (UART1_DMA_BUF_LEN - 1)); + uart1_feed_byte(b); + } +} + +#else /* !UART1_AIR780_EN: 调试版 printf 占用 USART1 -> 空实现, 保证链接一致 */ + +void uart1_dma_init(void) { } +void uart1_dma_poll(void) { } +uint32_t g_uart1_fwd_to_air = 0; +uint32_t g_uart1_fwd_to_loop = 0; +uint32_t g_uart1_local_cnt = 0; +uint32_t g_uart1_badchk = 0; +uint32_t g_uart1_drop = 0; + +#endif /* UART1_AIR780_EN */ + void uart_init(void){ GPIO_InitTypeDef GPIO_InitStructure = {0}; USART_InitTypeDef USART_InitStructure = {0}; @@ -152,6 +385,7 @@ void uart_init(void){ USART_Cmd(USART2, ENABLE); uart2_dma_init(); /* DMA 循环接收接管 (2026-08-17) */ + uart1_dma_init(); /* 4G 通道 UART1↔Air780 初始化 (协议 §6.1); DEBUG!=0 时为空实现 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; @@ -247,6 +481,14 @@ void uart_srv(void) // 其他: 校验后匹配挂起命令 lup_process_frame(g_pkg_uart_2.pkg, g_pkg_uart_2.offset); + /* --- 4G 通道上行转发 (协议 §6.1) --- + 校验通过的完整帧原样送 Air780; 必须置于下方 InitPkgUart 消费之前。 + 只转发 verify 通过的帧: Air780 靠 loop_state 变化沿判车, 残缺帧 = 多判一台车 */ + if (lup_verify_checksum(g_pkg_uart_2.pkg, g_pkg_uart_2.offset) == 0) { + g_uart1_fwd_to_air++; + UART1_SendString(g_pkg_uart_2.pkg, g_pkg_uart_2.offset); + } + // --- 传感器上报 (0xC0) 分流 --- // 回调已处理 TCP 推送,此处仅处理 BLE 转发 if(cmd == LUP_CMD_SENSOR_REPORT) diff --git a/vd960DBN/docs/devlog.md b/vd960DBN/docs/devlog.md index 7726df7..b642f7c 100644 --- a/vd960DBN/docs/devlog.md +++ b/vd960DBN/docs/devlog.md @@ -4,6 +4,80 @@ > > 项目定位: DLD960 通信板 — BLE 配网、TCP JSON 协议服务、Loop MCU 串口桥接 +## 2026-09-10 — vd960DBN UART1 ↔ Air780 4G 通道实现(协议 §6.1 纯透传) + +> 用户需求 2026-09-10:把 vd960DBN 与 vd960Air(Air780) 两侧的串口对接逻辑做出来 —— DBN 侧 UART1(PB6/PB7) 通道 + UART2↔UART1 双向透传。 +> **关闭**:V1.12 条目"vd960DBN 侧开发计划"第 1 条;V1.11 条目第 1、2 条(UART1 通道 / 双向透传)。 + +### 实现 + +| 文件 | 改动 | +|------|------| +| `APP/usart_biz.c` | +242 行:UART1 通道整段(118-347);上行转发插入 `uart_srv()`(484-490) | +| `APP/include/cmcng.h` | 声明 `uart1_dma_init/poll` + 5 个通道计数 | +| `APP/peripheral_main.c` | 主循环加 `uart1_dma_poll()`(置于 `MK_UART_SRV` fault marker **之外**,不改变既有 marker 语义) | + +**上行**(Loop → Air780):`uart_srv()` 内 `lup_process_frame()` 之后、**`InitPkgUart` 消费之前**插入转发, +且**只转发 `lup_verify_checksum()==0` 的完整帧**。插入点必须在 BLE 分支改写 `pkg[0]=0x8F` 之前 —— +否则会把已改魔数的帧发给 Air780。 + +**下行**(Air780 → DBN):UART1 收帧 → **魔数分流**:`0x7F` → `UART2_SendString()` 给 Loop;`0x8F` → DBN 本地处理(预留,当前仅计数)。 + +### 三个关键设计决定(含理由) + +1. **接收走 DMA(DMA1_Ch5 循环 + 512B),不用 RXNE 中断。** 理由与 UART2 方案A(2026-08-17) 完全相同: + `PRINT` 临界区关中断 ~7.4ms / BLE 栈回调 / SPI 擦除 45ms 等长阻塞窗口会屏蔽 RXNE → 丢字节 → 校验失败。 + 本通道"不丢帧"是 Air780 沿检测的硬前提。UART2 占 DMA1_Ch6/Ch7,**Ch5 空闲,无冲突**。 +2. **帧装配器独立于 `g_lup_parser`。** `lup_feed_byte()` 硬编码操作全局 `g_lup_parser`(专供 UART2/Loop 通道), + 两条字节流混进同一状态机必然互相残杀。故 UART1 用独立装配器,只复用纯函数 `lup_verify_checksum()`。 +3. **坏帧回找魔数(resync)。** 校验失败时在已收缓冲里从 idx=1 起回找下一个 `0x7F/0x8F`,残余搬到头部继续装配, + 防"单帧损坏 → 后续帧全部失步、链路长时间瞎掉"。 + +### 与 printf 共用 USART1 的处理(按用户指示) + +用户 2026-09-10 指示:**printf 对应串口不动;不会同时使用 Debug 打印和 4G 通信,4G 通信时禁用 debug 即可。** + +- 现状:`debug.c` 的 `USART_Printf_Init()` 把 USART1 重映射到 PB6 做 printf,且 `PRINT` 走 `__disable_irq()`。 +- 落地:本通道用编译开关 `UART1_AIR780_EN`(默认 `(DEBUG == 0)`)—— **4G 版本以 `-DDEBUG=0` 编译即自动启用**。 + 此时 `#if(DEBUG)` 为假:`PRINT` 是空宏,且 `USART_Printf_Init()` 里 USART1 的配置分支整段不参与编译 + (已核对 `USART_Init/USART_Cmd` 均在 `#if(DEBUG == DEBUG_UART1)` 内)→ USART1 干净地交给本文件。 +- 调试版(DEBUG!=0)本通道编译为空实现,**`debug.h`/`debug.c` 一行未改**,printf 行为完全不变。 +- ⚠ 遗留:`net_srv.c:125` 有一处裸 `printf()`(错误路径),`DEBUG=0` 时仍会调用,建议改为 `PRINT`(列入待办)。 + +### 验证 + +- **host 单测**(本机无 RISC-V 工具链,无法编译固件):从 `usart_biz.c` **原样抽取**装配器代码 + 逐字复刻 + `loop_uart_proto.c` 的 XOR/SUM 校验,gcc 编译(`-Wall -Wextra -Werror` **零警告**)跑 13 组用例 **全部通过**: + 单帧 `0x7F`/`0x8F` 分流、带数据帧逐字节一致、70B 最大帧、校验和破坏不转发、 + **坏帧后紧跟好帧仍送达**、**截断帧后只交付 1 帧完整好帧**、乱码前缀、`LEN=0` 非法、`LEN` 超长、三帧连发、 + `0x9F`(Loop OTA 魔数) 忽略、上行闸门只放行 verify 通过的帧。 +- **待板级验证**:需 MounRiver 工具链编译 + 实机(本机无 riscv 工具链,固件编译尚未跑过)。 + +### 现场核对手段(比翻日志可靠) + +新增 5 个全局计数(`cmcng.h` 已声明,可调试器 Watch): +`g_uart1_fwd_to_air`(上行转发帧数)/ `g_uart1_fwd_to_loop`(下行 0x7F)/ `g_uart1_local_cnt`(下行 0x8F)/ +`g_uart1_badchk`(校验失败)/ `g_uart1_drop`(DMA 溢出)。 + +**判据:跑一段时间后 `g_uart1_fwd_to_air` 与 Air780 侧实收帧数必须相等。** + +### 接线(供硬件核对) + +- DBN `USART1` 重映射:**PB6 = TX / PB7 = RX**,115200 8N1 +- 与 Air780 必须**交叉**:`DBN_PB6(TX) → Air780_RX`,`DBN_PB7(RX) ← Air780_TX` +- 波特率两侧一致:115200(Air780 侧日志 `Uart_ChangeBR uart1, 115200`) + +### 仍待办 + +- [ ] `0x8F` 下行的本地处理行为(当前仅计数;协议未细化具体动作) +- [ ] `0x7D` 握手/配置同步帧(V1.11 曾提,未进正式协议,暂预留) +- [ ] BLE 设置服务器/topic 时同步下发 Air780 配置(V1.12 第 2 条) +- [ ] 通道切换策略(V1.12 第 3 条,待讨论;暂定默认 4G) +- [ ] `net_srv.c:125` 裸 `printf` → `PRINT` +- [ ] 板级联调:编译烧录 + 帧计数对齐 + 断线重连 + +--- + ## 2026-08-31 — MQTT 协议 V1.13:initialize extra_info 增加 imei/iccid 可选字段 > 用户需求 2026-08-31:initialize 指令的 `extra_info` 增加可选字段 `imei` / `iccid`(4G 模块 IMEI / 流量卡 ICCID)。