资讯动态

RoboMasterC型开发板接收大疆DT7遥控器&DR16接收机DBUS通信协议:一种巧妙的单缓冲区方案

发布时间:2026/10/8 8:08:58 来源:尧图企业网站定制
目录一、引言1.传统的单缓冲区和双缓冲区面临的问题2.基于单缓冲区的定长帧循环快照法二、准备工作1.大疆DT7遥控器DR16接收机2.DBUS协议3.DR16数据帧解析三、开发过程1.cubemx配置2.代码实现和调试四、结语定长帧循环快照法的适用范围一、引言1.传统的单缓冲区和双缓冲区面临的问题在 RoboMaster 电控开发中读取遥控器数据是最高频、最基础的操作之一。DT7 遥控器通过 DR16 接收机输出 DBUS 协议信号STM32 需要以 100kbps 的波特率、每帧 18 字节、约 14ms 一帧的速度持续接收数据。如果处理不当这个看似简单的任务就会给CPU 带来沉重负担甚至接收不到数据。在编者的学习过程中接触过两种DBUS接收实现方式但在我看来它们都存在一些问题首先是传统的单缓冲区方案它采用了“DMA 空闲中断”的思路定义一个18字节的数组用来接收DR16 发完一帧数据18字节触发空闲中断此时失能DMA停止接收的同时解析数据解析完成后清空数组再使能DMA开启接收。这个方案看似逻辑严密它为防止新数据覆盖正在解析的一帧选择关闭DMA停止接收数据解析完成后再重新接收。但它存在一个致命问题数据的发送并不会因为DMA的关闭而停止。在数据解析的时间里若干个字节的数据将会丢失。而由于数组的大小仅仅正好是一帧数据的大小后续收到的每一帧数据都会错位。这将意味着这种方案在一次丢帧后将永远不会再收到完整的一帧。双缓冲区方案解决了上述问题。它的核心思路是用两个缓冲区轮流接客一个给 DMA 写一个给 CPU 读交替使用互不干扰。DMA不停数据也将被完整的接收。但编者认为它仍然存在一些问题1.双缓冲区方案本质上是硬件解决方法对硬件资源要求高部分芯片不支持。这意味着它的移植性一般。2.内存占用大。3.只能缓存两帧一旦数据错位甚至无法回溯容错小4.调试维护难度大2.基于单缓冲区的定长帧循环快照法综上所述本文提供一种巧妙的新方案。它只需一个缓冲区且能做到不停用DMA。编者将它命名为“定长帧循环快照法下面简述本方案逻辑用一块能容纳 5 帧 DBUS 数据的大缓冲区作为 “循环跑道”DMA 硬件全程不停歇地循环往里写数据每收到完整一帧就触发串口空闲中断我们通过 DMA 硬件计数器精准定位刚写完的帧位置把这 18 字节完整 “快照拷贝” 出来再解析经编者查阅memcpy拷贝的执行速度是微秒级不影响数据接收的实时性。全程 DMA 不停车、无接收空档用极简单的逻辑实现了高可靠的单缓冲接收。二、准备工作1.大疆DT7遥控器DR16接收机DT7 遥控器是一款工作于2.4 GHz 频段的无线电通信设备该遥控器仅能与DR16 接收机配合使用。DR16 接收机是一款工作频率为2.4 GHz的16通道接收机可配合DT7遥控器使用。配对方法为开启需要配对的遥控器靠近正常工作的DR16长按对频按键5s左右然后松开当指示灯常亮绿灯时便代表连接成功。连接时确保周围没有其它开启的遥控器防止误连2.DBUS协议接收机与接收机之间采用DBUS协议进行通信信号电平为TTL电平但是和UART是反相的但是可以直接连接 RobomasterC型开发板上的DBUS接口即USART3因为接口内置了反相器。DBUS与UART之间电平标准为反相关系因而不能随意连接到其他串口。当我们后续配置cubemx串口参数时按照DBUS通信参数进行配置3.DR16数据帧解析三、开发过程1.cubemx配置芯片选择C板使用的STM32F407IGH6本文略去常规配置操作仅展示关键步骤DBUS接口即USART3串口模式选择异步模式参数对照上文DBUS通信参数填入100k8E1开启中断为串口3添加一个DMA通道注意是接收通道rx而不是发送模式设置为轮询模式优先级设置为高2.代码实现和调试我们先处理头文件大致了解一下框架宏定义。这一步我们给串口定义了用户标签定义了一帧为18定义了缓冲区大小为5帧#defineDBUS_HUARThuart3#defineDBUS_BUFLEN18U// 标准DBUS帧长#defineDBUS_BUF_SIZE90U// 循环缓冲区总大小可存5帧50ms容错窗口定义一个结构体并把它初始化保证每个通道上电时默认是0由上文“DR16数据帧解析”可知左右摇杆是11bit左右拨杆是2bit。所以我们摇杆通道使用int16_t拨杆通道使用uint8_t。留有一定内存余量但不至于浪费typedefstruct{int16_tch0;// 右摇杆水平int16_tch1;// 右摇杆垂直int16_tch2;// 左摇杆水平int16_tch3;// 左摇杆垂直uint8_tsw1;// 右拨杆uint8_tsw2;// 左拨杆}rc_info_t;#definerc_Init{0,0,0,0,0,0}声明两个我们后续要用的变量externrc_info_trc;externvolatileuint8_tdbus_rx_flag;// 一帧接收完成标志声明两个我们后续要调用的函数voiddbus_uart_init(void);voiduart_receive_handler(UART_HandleTypeDef*huart);接下来我们处理源文件把接收缓冲区初始化为0初始化结构体rcdbus_rx_flag是同步标志位空闲中断里解析完一帧数据后置1主循环检测到1就消费数据并清零uint8_tdbus_rx_buf[DBUS_BUF_SIZE]{0};rc_info_trcrc_Init;volatileuint8_tdbus_rx_flag0U;DBUS 串口初始化函数voiddbus_uart_init(void){// 清除空闲标志避免上电误触发__HAL_UART_CLEAR_IDLEFLAG(DBUS_HUART);// 开启空闲中断__HAL_UART_ENABLE_IT(DBUS_HUART,UART_IT_IDLE);// 用HAL标准函数启动DMA循环接收DMA全程不停HAL_UART_Receive_DMA(DBUS_HUART,dbus_rx_buf,DBUS_BUF_SIZE);}DBUS协议解析函数。其中复杂解算过程直接复制粘贴即可staticvoidrc_callback_handler(rc_info_t*rc,uint8_t*buff){// 摇杆通道解析11位有效位中点1024rc-ch0(int16_t)(((uint16_t)buff[0]|((uint16_t)buff[1]8))0x07FFU)-1024;rc-ch1(int16_t)(((uint16_t)(buff[1]3)|((uint16_t)buff[2]5))0x07FFU)-1024;rc-ch2(int16_t)(((uint16_t)(buff[2]6)|((uint16_t)buff[3]2)|((uint16_t)buff[4]10))0x07FFU)-1024;rc-ch3(int16_t)(((uint16_t)(buff[4]1)|((uint16_t)buff[5]7))0x07FFU)-1024;rc-sw1(buff[5]6)0x03U;// bit6~bit7rc-sw2(buff[5]4)0x03U;// bit4~bit5// 异常校验DR16满杆约±660超出则判定为数据错乱if((abs(rc-ch0)660)||(abs(rc-ch1)660)||(abs(rc-ch2)660)||(abs(rc-ch3)660)){memset(rc,0,sizeof(rc_info_t));//清零}}核心代码空闲中断回调函数1.第一件事一定要清除空闲标志若不清除硬件持续触发中断主循环无法运行2.串口过滤判断是不是UART3若不是需过滤掉以免影响DBUS解析3.计算写指针位置DMA 硬件有一个NDTR寄存器记录 “剩余可写字节数”。用缓冲区总长度减去剩余数量就能算出 DMA 当前写到了缓冲区的第几个字节这个位置就是刚写完的那一帧的末尾。4.快照取完整帧分正常情况和回卷情况两种5.调用协议解析函数然后把dbus_rx_flag置为1通知主循环新一帧的数据已就绪staticvoiduart_rx_idle_callback(UART_HandleTypeDef*huart){__HAL_UART_CLEAR_IDLEFLAG(huart);//第一件事先清除串口空闲标志if(huart!DBUS_HUART){return;}// 计算当前写指针位置// NDTR 剩余可写字节数总大小 - 剩余 已写入位置uint16_twrite_posDBUS_BUF_SIZE-__HAL_DMA_GET_COUNTER(huart-hdmarx);uint8_tframe_buf[DBUS_BUFLEN];//从循环缓冲区快照出完整一帧if(write_posDBUS_BUFLEN){// 正常情况帧未跨缓冲区首尾直接倒推18字节拷贝memcpy(frame_buf,dbus_rx_buf[write_pos-DBUS_BUFLEN],DBUS_BUFLEN);}else{// 回卷情况帧跨了缓冲区结尾和开头分两段拼接uint8_ttail_lenwrite_pos;uint8_thead_lenDBUS_BUFLEN-tail_len;// 先拷缓冲区尾部的部分memcpy(frame_buf,dbus_rx_buf[DBUS_BUF_SIZE-head_len],head_len);// 再拷缓冲区开头的部分memcpy(frame_bufhead_len,dbus_rx_buf,tail_len);}// 帧长校验 解析// 空闲中断触发一帧结束正常帧长必为18字节校验通过才解析// 这里额外校验是防止干扰、误触发导致的错误解析rc_callback_handler(rc,frame_buf);dbus_rx_flag1U;}中断入口分发函数。检查是否是有效的中断空闲事件避免其他串口中断事件触发voiduart_receive_handler(UART_HandleTypeDef*huart){// 同时检查标志位和中断使能确保是有效空闲中断if(__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE)__HAL_UART_GET_IT_SOURCE(huart,UART_IT_IDLE)){uart_rx_idle_callback(huart);}}源文件处理完毕不要忘了挂接串口中断打开stm32f4xx_it.c找到 USART3_IRQHandler 函数做如下修改同时别忘了引用我们前文写的头文件最后处理主函数main.c记得引用头文件调用DBUS串口初始化函数启动接收主循环处理如下先清零、后处理防止处理期间新来的帧被覆盖丢失至此代码实现部分结束调试指南实验现象监视结构体“rc”推动摇杆对应通道数值同步变化若排查中断工作监视dbus_rx_flag数值在0和1之间快速跳转即中断正常触发恒为0说明中断未能触发恒为1说明主循环未能正常消费可能是主循环卡死若排查接收线路监视原始数组dbus_rx_buf十六进制查看若有数据被接收说明数据解算部分有问题若没有数据继续往初始配置和硬件方面排查波特率、校验位、DMA、接线、设备工作状态等等四、结语本文提供的定长帧循环快照法以循环 DMA 串口空闲中断为核心用一块大缓冲区实现 5 帧数据容错DMA全程不停解决了漏接数据的问题非常适合DBUS这类定长帧协议。同时逻辑简单非常易于理解调试也很简易。但是定长帧循环快照法仍然不适用于以下几种场景1.不适合实时性要求极高的场景。定长帧循环快照法在回卷情况下需要拷贝两次持续占用CPU时间。而双缓冲区方案无需面临这一问题2.不适合不定长协议兼容性远弱于双缓冲方案。一旦一帧的长度不固定本方案完全失效3.不适用串口需双向通信的场景编者rm电控小登一枚能力有限请多多批评指正

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑