add DMA remote control

This commit is contained in:
TuxMonkey
2026-07-14 21:58:07 +08:00
parent f4c0424c45
commit 47f5e4ddab

View File

@@ -0,0 +1,144 @@
# SBUS 遥控接收修复说明
这份记录用来说明这次从“遥控器收不到数据 / `rc_ctrl` 不变化”一路排查到当前稳定版本的主要修改点。写得偏简略重点放在为什么改、DMA 怎么配、哪些文件动过,以及最后对当前 `git diff` 的复核结论。
## 1. 一开始的问题和定位思路
最开始的现象是:遥控器链路没有让 `rc_ctrl` 更新,后面又观察到 `rc_debug.rx_event_count` 也不增长,说明问题不只是协议解析,而是 UART/DMA 接收事件本身没有稳定进入。
排查时按链路分层看:
1. **初始化链路**:发现遥控器模块没有在 `RobotInit()` 里真正初始化,所以补了 `RemoteControlInit(&huart5)`
2. **UART 协议配置**:用户确认 SBUS 硬件已经做了反向,所以保持 UART5 为 `100000 / 8E2`,也就是 HAL 里的 `UART_WORDLENGTH_9B + UART_PARITY_EVEN + UART_STOPBITS_2`。当前没有启用软件 `RXINV`
3. **GPIO 电平配置**UART5 RX 是 PD2当前保持 `GPIO_NOPULL`,没有继续保留“内部上拉”的实验配置。
4. **DMA 内存可访问性**H7 上 DMA1/DMA2 不能访问 DTCM。原来接收对象如果落到 DTCM就可能导致 DMA 没法真正写入数据。现在把 USART 实例池和接收 buffer 放进 `.dma_buffer` 段,并链接到 RAM_D1。
5. **DMA 接收方式**:原来的 UART5 RX DMA 是 `DMA_CIRCULAR`,但这里使用的是 `HAL_UARTEx_ReceiveToIdle_DMA()`,为了按实际一帧 25 字节触发并重启,改成 `DMA_NORMAL + ReceiveToIdle`
6. **异常恢复**:如果 UART/DMA 出错或重启失败,不能只在中断里死等。现在加了 `USARTServiceTask()`,在 daemon 任务上下文里做 stream 级恢复。
7. **SBUS 解析**:只接受完整 25 字节帧,检查帧头 `0x0F`,尾字节允许 `0x00/0x04/0x14/0x24/0x34`,并处理 failsafe/offline。
后面临时试过的 `RXINV`、PD2 上拉、50 字节接收、滑动同步、`rc_debug` 等实验代码已经撤掉,当前版本回到“硬件反相 + 正常 SBUS 帧解析”的方案。
## 2. DMA 当前怎么配置
UART5 RX 的 DMA 配置在 `Core/Src/usart.c``TronOneH7_Scaffold.ioc` 中保持一致:
- UART`UART5`
- RX 引脚:`PD2`
- DMA stream`DMA1_Stream5`
- Request`DMA_REQUEST_UART5_RX`
- 方向:`DMA_PERIPH_TO_MEMORY`
- 外设地址不自增:`DMA_PINC_DISABLE`
- 内存地址自增:`DMA_MINC_ENABLE`
- 外设/内存数据宽度:`BYTE`
- 模式:`DMA_NORMAL`
- 优先级:`DMA_PRIORITY_VERY_HIGH`
- FIFO关闭
接收 buffer 的关键点:
- `USART_Instance usart_instance_pool[DEVICE_USART_CNT]` 放在 `User_Code/bsp/usart/bsp_usart.c`
- 这个池加了 `__attribute__((section(".dma_buffer"), aligned(32)))`
- `STM32H723XG_FLASH.ld` 新增 `.dma_buffer (NOLOAD)` 段,并放到 `RAM_D1`,避免 DMA 访问 DTCM 失败。
- `USART_Instance.recv_buff` 也做了 32 字节对齐。
启动和重启方式:
- `USARTServiceInit()` 调用 `HAL_UARTEx_ReceiveToIdle_DMA()` 启动接收。
- 启动成功后关闭 DMA 半传输中断 `DMA_IT_HT`,避免半包回调干扰。
- `HAL_UARTEx_RxEventCallback()` 中记录 `rx_event_count``last_rx_size`,把实际收到的 `Size` 传给遥控器解析回调,然后重新启动接收。
- `HAL_UART_ErrorCallback()` 中记录 `uart_error_count``last_uart_error`,并尝试重启接收。
- 如果中断里重启失败,会置位/累计错误,后续由 `USARTServiceTask()` 在任务上下文里恢复 UART/DMA。
## 3. 主要加了什么,在哪里
- `User_Code/application/robot.c`
- 定义全局 `volatile RobotMode_t RobotMode`
-`RobotInit()` 中调用 `RemoteControlInit(&huart5)`
- `Core/Src/usart.c`
- UART5 保持 `100000 / 8E2`
- UART5 `AdvancedInit` 保持 `UART_ADVFEATURE_NO_INIT`,没有软件 RXINV。
- PD2 保持 `GPIO_NOPULL`
- UART5 RX DMA 从 `DMA_CIRCULAR` 改为 `DMA_NORMAL`
- `TronOneH7_Scaffold.ioc`
- 同步把 `Dma.UART5_RX.13.Mode` 改为 `DMA_NORMAL`
- `STM32H723XG_FLASH.ld`
- 新增 `.dma_buffer` 段,放入 `RAM_D1`
- `User_Code/bsp/usart/bsp_usart.c/.h`
- USART 实例不再 `malloc`,改用静态实例池,并放入 `.dma_buffer`
- 模块回调改为携带 `(USART_Instance *instance, const uint8_t *recv_data, uint16_t recv_size)`
- 加入 `rx_event_count``last_rx_size``uart_error_count``last_uart_error``rx_restart_error_count` 等接收状态字段。
- 加入 `USARTServiceTask()` 做 UART/DMA 恢复。
- `USARTServiceInit()` 改为返回 `HAL_StatusTypeDef`
- `User_Code/module/periph/remote_control/rc.c/.h`
- `RemoteControlInit()` 注册 UART5并显式启动 USART 接收服务。
- SBUS 解析只接受完整 25 字节帧。
- 加入帧头、尾字节、failsafe 校验。
- 加入 `RemoteControlReadSnapshot()`,用于原子读取当前遥控器快照。
- 修复 `RemoteControlIsOnline()` 和失控清零的竞态。
- 修复 SWB/SWC 通道、开关边沿标志。
- `Core/Src/freertos.c`
- daemon task 中调用 `USARTServiceTask()`,让 UART/DMA 异常恢复发生在任务上下文。
- `User_Code/module/software/daemon/daemon.c/.h`
- 修正 `temp_count/init_count` 初始化逻辑。
- `DaemonReload()``DaemonIsOnline()``Daemon_Update()` 加了简单临界区保护。
- `temp_count` 改为 `volatile`
- `User_Code/application/indicator_app/ws2812status.c`
- 不再用局部 `RobotMode` 遮蔽全局状态。
- LED 显示时结合 `RemoteControlIsOnline()` 判断遥控器离线。
- `User_Code/module/paramdef/robot_def.h`
- `RobotMode` 声明改为 `extern volatile RobotMode_t RobotMode`
## 4. 当前建议上板观察项
上板后重点看这些量:
- `rx_event_count` 是否持续增长。
- `last_rx_size` 是否稳定为 `25`
- `rc_valid_frame_count` 是否持续增长。
- `uart_error_count``rx_restart_error_count` 是否不持续增长。
- 原始帧是否类似 `0F ... 00/04/14/24/34`
注意:当前稳定版本已经没有 `rc_debug` 这个临时调试结构。`rx_event_count``last_rx_size``USART_Instance` 里,`rc_valid_frame_count` 等在 `rc.c` 内部是 `static volatile`。如果后续想长期在调试器里直接 watch 一个固定结构,可以再专门加一个轻量 getter 或 debug struct当前为了回到干净版本没有保留那套临时代码。
## 5. Git 变更复核和可疑点
已读取当前 `git status --short``git diff --stat``git diff --check` 和关键文件 diff。当前工作区共有 16 个已修改文件,其中 SBUS 接收链路相关的是:
- `Core/Src/freertos.c`
- `Core/Src/usart.c`
- `STM32H723XG_FLASH.ld`
- `TronOneH7_Scaffold.ioc`
- `User_Code/application/indicator_app/ws2812status.c`
- `User_Code/application/robot.c`
- `User_Code/bsp/usart/bsp_usart.c`
- `User_Code/bsp/usart/bsp_usart.h`
- `User_Code/module/paramdef/robot_def.h`
- `User_Code/module/periph/remote_control/rc.c`
- `User_Code/module/periph/remote_control/rc.h`
- `User_Code/module/software/daemon/daemon.c`
- `User_Code/module/software/daemon/daemon.h`
复核结论:
- `git diff --check` 没有发现空白错误,只提示这些文件下次被 Git 处理时 LF 可能转 CRLF。
- 没有发现 `rc_debug``rc_ctrl_debug``RemoteControlDebugPoll``RXINV``RxPinLevelInvert`、50 字节 buffer、滑动同步等实验代码残留。
- UART5 当前确实是 `100000 / 8E2`,没有软件 RXINVPD2 也是 `GPIO_NOPULL`
- UART5 RX DMA 在 `.ioc` 和生成代码里都已经是 `DMA_NORMAL`,没有一边改一边没同步的问题。
- `RobotMode` 目前只有一个定义,在 `robot.c`;其它地方通过 `extern volatile` 使用,没看到重复定义。
需要特别注意的可疑/无关变更:
- `User_Code/module/periph/buzzer/buzzer.cpp` 只改了两行注释空格,和 SBUS 修复无关,建议不要混进本次提交。
- `ozonedeb/windebnewestux.jdebug``ozonedeb/windebnewestux.jdebug.user` 是 Ozone/J-Link 调试器本地状态变化,包括探针序列号、打开窗口、布局、打开文件等,和代码逻辑无关,建议不要混进本次提交。
- `rc_valid_frame_count` 等计数是 `static volatile`,调试符号里一般能看到,但 C 代码外部不能直接引用;这不是接收链路问题,只是“是否方便 watch”的问题。
总体看SBUS 主链路相关 diff 没看到明显可疑残留;最需要清理的是 `buzzer.cpp``ozonedeb/*.jdebug*` 这些无关 dirty 文件。