文章目录
- DBUS协议
- C板上的DBUS
- DR16 接收机
- 机器人控制协议
- 配置遥控器传输协议
- 添加官方函数库
- 代码编写
- 程序流程
- 1.初始化发送DMA和接收DMA
- 2.编写USART3的串口中断函数及调用解码函数
- 3.主函数的循环中调用串口实现的函数
DBUS协议
- DBUS通信协议和串口类似
- DBUS的传输速率(波特率)为100kbit/s,传输的单位数据长度为8位
- 奇偶校验位为偶校验,结束位1位
- DBUS使用的电平标准与串口相反(在DBUS协议中0表示高电平,1表示低电平)
- 使用串口进行接收时需要在接收电路上添加一个反相器
C板上的DBUS
在C板的原理图中可以找到DBUS
该DBUS通过一个反相器后与UART3的接收端相连
使用DBUS接收的遥控器数据,一帧数据的长度为18字节,一共144位
DR16 接收机
DR16接收机输出的信号为标准的DBUS协议数据
DR16 接收机是一款工作频率为2.4 GHz 的16 通道接收机,配合DT7 遥控器使用。
机器人控制协议
当遥控器与接收机建立连接后,接收机每隔14ms通过DBUS发送一帧18字节数据
除了通过遥控器直接控制之外,还可以通过PC进行控制,控制链如下:
配置遥控器传输协议
先将Connecitivity下的USART3打开,将mode设置为 Asynchronous(异步通讯方式)
波特率设置为100000,数据帧设置为8位数据位,无校验位,1位停止位
然后开启USART3的DMA,添加USART_RX,配置为最高的优先级
配置为循环模式
开启USART3的全局中断
然后可以自动生成代码
添加官方函数库
我们不需要学会如何使用寄存器的方法去做遥控器数据的串口接收中断和位运算的方法去做遥控器的数据解析
我们只需要学会调用官方函数库并使用官方代码即可
将官方样例代码“9.remote_control_dma”中的“applications”和“bsp”文件夹复制到工程中
在keil中按如下操作
先新建applications和bsp组
点击新创建的组,在file栏点击Add添加我们刚刚复制的源文件
然后将复制的代码全部添加到我们创建的组中
接着打开魔法棒,选择C/C++,选择Include Paths的最右侧的三个点
双击空白,选择三个点,将刚刚的文件夹路径添加到include路径中
再将bsp文件夹中的boards文件夹添加到路径中即可在本地文件中调用我们复制进来的源文件中
的函数
代码编写
程序流程
- 先将USART1的DMA发送初始化,和USART3的DMA接收初始化
- 然后在USART3的串口接收中断中使用DMA接收遥控器的数据,并使用官方解码函数进行解码
- 然后在主函数的循环中调用串口实现的函数,将解码完成的遥控器函数通过USART1的DMA发送功能发送出来
1.初始化发送DMA和接收DMA
将USART1的DMA发送初始化,和USART3的DMA接收初始化
a. 实现 C 语言中的 printf
(利用 stdarg.h 下的 va_start 函数和 vsprintf 函数再配合串口的 DMA 发送功能)
将要发送的内容存储在tx_buf中,将要发送的数据长度存储在len变量中,然后将tx_buf的首地址和数据长度len传递给DMA发送函数完成DMA的数据发送
//初始化USART1的DMA
//要用头文件#include<stdarg.h>
const RC_ctrl_t *local_rc_ctrl;
void usart_printf(const char *fmt,...)
{
static uint8_t tx_buf[256] = {0};
static va_list ap;//保存参数起始地址
static uint16_t len;
va_start(ap, fmt);//找出这个函数在栈中排列的一堆参数的起始地址,然后直接浏览栈中参数
//return length of string
//返回字符串长度
len = vsprintf((char *)tx_buf, fmt, ap);// vsprintf() 实现格式化字符串的读取
va_end(ap);//释放ap,就像释放指针一样
usart1_tx_dma_enable(tx_buf, len);
}
vsprintf 函数参考:(2条消息) C语言printf()、sprintf()、vsprintf() 的区别与联系_哈士奇超帅的博客-CSDN博客_vsprintf sprintf区别
b. 串口的DMA接收与发送配置
使用 USART3 的 DMA 接收功能来接收遥控器数据。
(通过函数 remote_control_init实现)
在初始化时,使能 DMA串口接收和空闲中断,配置当外设数据到达之后的存储的缓冲区,在这里开启了双缓冲区功能,每一帧 sbus 数据为 18 字节,而开启的双缓冲区总大小为 36 字节,这样可以避免 DMA传输越界
//初始化USART3的DMA
void RC_init(uint8_t *rx1_buf, uint8_t *rx2_buf, uint16_t dma_buf_num)
{
//enable the DMA transfer for the receiver request
//使能 DMA 串口接收
SET_BIT(huart3.Instance->CR3, USART_CR3_DMAR);
//enalbe idle interrupt
//使能空闲中断
__HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE);
//disable DMA
//失效 DMA
__HAL_DMA_DISABLE(&hdma_usart3_rx);
while(hdma_usart3_rx.Instance->CR & DMA_SxCR_EN)
{
__HAL_DMA_DISABLE(&hdma_usart3_rx);
}
hdma_usart3_rx.Instance->PAR = (uint32_t) & (USART3->DR);
//memory buffer 1
//内存缓冲区 1
hdma_usart3_rx.Instance->M0AR = (uint32_t)(rx1_buf);
//memory buffer 2
//内存缓冲区 2
hdma_usart3_rx.Instance->M1AR = (uint32_t)(rx2_buf);
//data length
//数据长度
hdma_usart3_rx.Instance->NDTR = dma_buf_num;
//enable double memory buffer
//使能双缓冲区
SET_BIT(hdma_usart3_rx.Instance->CR, DMA_SxCR_DBM);
//enable DMA
//使能 DMA
__HAL_DMA_ENABLE(&hdma_usart3_rx);
}
在完成了USART1的DMA初始化和USART3的DMA初始化
2.编写USART3的串口中断函数及调用解码函数
完成初始化后,当USART3产生空闲中断时就会进入中断函数进行处理
在USART3的中断函数里面我们需要清除标志物,并进行判断
判断进行接收的缓冲区是1号缓冲区还是2号缓冲区
然后使用设定的长度减去剩余的长度,获取DMA接收到的数据长度
判断该数据长度是否与一帧数据长度相等,如果相等就调用官方函数进行解码
void USART3_IRQHandler(void)
{
if(huart3.Instance->SR & UART_FLAG_RXNE)//接收到数据
{
__HAL_UART_CLEAR_PEFLAG(&huart3);//清除标志物
}
else if(USART3->SR & UART_FLAG_IDLE)
{
static uint16_t this_time_rx_len = 0;
__HAL_UART_CLEAR_PEFLAG(&huart3);//清除标志物
if ((hdma_usart3_rx.Instance->CR & DMA_SxCR_CT) == RESET)
{
/* Current memory buffer used is Memory 0 */
//disable DMA
//失效 DMA
__HAL_DMA_DISABLE(&hdma_usart3_rx);
//get receive data length, length = set_data_length - remain_length
//获取接收数据长度,长度 = 设定长度 - 剩余长度
this_time_rx_len = SBUS_RX_BUF_NUM - hdma_usart3_rx.Instance->NDTR;
//reset set_data_lenght
//重新设定数据长度
hdma_usart3_rx.Instance->NDTR = SBUS_RX_BUF_NUM;
//set memory buffer 1
//设定缓冲区 1
hdma_usart3_rx.Instance->CR |= DMA_SxCR_CT;
//enable DMA
//使能 DMA
__HAL_DMA_ENABLE(&hdma_usart3_rx);
if(this_time_rx_len == RC_FRAME_LENGTH)
{
//处理遥控器数据
sbus_to_rc(sbus_rx_buf[0], &rc_ctrl);//调用官方解码函数
}
}
else
{
/* Current memory buffer used is Memory 1 */
//disable DMA
//失效 DMA
__HAL_DMA_DISABLE(&hdma_usart3_rx);
//get receive data length, length = set_data_length - remain_length
//获取接收数据长度,长度 = 设定长度 - 剩余长度
this_time_rx_len = SBUS_RX_BUF_NUM - hdma_usart3_rx.Instance->NDTR;
//reset set_data_lenght
//重新设定数据长度
hdma_usart3_rx.Instance->NDTR = SBUS_RX_BUF_NUM;
//set memory buffer 2
//设定缓冲区 2
DMA1_Stream1->CR &= ~(DMA_SxCR_CT);
//enable DMA
//使能 DMA
__HAL_DMA_ENABLE(&hdma_usart3_rx);
if(this_time_rx_len == RC_FRAME_LENGTH)
{
//处理遥控器数据
sbus_to_rc(sbus_rx_buf[1], &rc_ctrl);//调用官方解码函数
}
}
}
}
3.主函数的循环中调用串口实现的函数
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
usart_printf(
"**********\r\n\
ch0:%d\r\n\
ch1:%d\r\n\
ch2:%d\r\n\
ch3:%d\r\n\
ch4:%d\r\n\
s1:%d\r\n\
s2:%d\r\n\
mouse_x:%d\r\n\
mouse_y:%d\r\n\
press_l:%d\r\n\
press_r:%d\r\n\
key:%d\r\n\
**********\r\n",
local_rc_ctrl->rc.ch[0], local_rc_ctrl->rc.ch[1], local_rc_ctrl->rc.ch[2], local_rc_ctrl->rc.ch[3], local_rc_ctrl->rc.ch[4],
local_rc_ctrl->rc.s[0], local_rc_ctrl->rc.s[1],
local_rc_ctrl->mouse.x, local_rc_ctrl->mouse.y,local_rc_ctrl->mouse.z, local_rc_ctrl->mouse.press_l, local_rc_ctrl->mouse.press_r,
local_rc_ctrl->key.v);
HAL_Delay(1000);
}
/* USER CODE END 3 */
}
然后就可以编译烧录