一起来撸F3——SP Racing F3 豪华版飞控作为开发板——第四章 串口的DMA驱动遥控器S.Bus信号解析

2019-07-20 01:34发布

本帖最后由 Talons 于 2018-8-23 09:09 编辑

串口的应用是继PWM输出之后,第二个飞控真必须用到的外设,不论是飞控与数传,OSD的通讯,还是遥控器接收机的S.Bus信号,都要用到串口。STM32串口有很多高级设置,尤其是电平反向,数据反向,大小端设定,等等,对于特殊协议很友好,基本不用做硬件上的改变就可以直接连接使用。
先做一个简单的输入输出实验。用DMA驱动串口1。(实测,当波特率为1382400时,STM32串口输出用逻辑分析仪看没有问题,但是XCOM串口助手已经是乱码了,可能是板子用的CP2102不支持,于是波特率设置为460800)
串口1的初始化和DMA初始化大家可以在附件的工程中自己看,不贴在这里了。
在main中定义两个数组。
uint8_t aRxBuffer1[5];
uint8_t aTxBuffer1B[7] = "xxxxx ";
用于储存接受和发送数据,在接收中断void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
for(uint8_t i = 5; i != 0; i--)
{
    aTxBuffer1B[i-1] = aRxBuffer1[i-1];
}
HAL_UART_Transmit_DMA(&huart1, aTxBuffer1B, 7);  

也就是将收到的在发出去,尾巴的“ ”时换行的意思。
实验现象如下:
串口返回实验现象.JPG
注意一定要发送5个字符(或者自己在启动串口DMA时定义好的字符数),否则多出来的字符会被排在下一个数组的前面字节。
关于串口DMA接收模式我喜欢用循环,只需要在开始时设置一次DMA开始就行,不然每次传输结束后还要重新开始,飞控系统收发数据一般是定长数据包,只要做好第一帧检测就不会出错,反而单次模式如果接收一帧后不及时开启下一次可能会丢失数据。


下面进行S.Bus解析测试,首先来了解一下S.Bus。自行参考https://blog.csdn.net/wsptr/article/details/53795458
这位博主讲的很详细,关于电平取反部分,STM32无需取反电路,芯片内部可以软件设置。
初始化中,波特率100000,2位停止位,偶校验,9位数据(这里强调一下,很多地方校验位不算在数据位中,但是STM32必须加进去,否则校验位会覆盖最后一位数据位,造成数据出错)
接下来时关键的,电平取反设置:

huart3.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_TXINVERT_INIT|UART_ADVFEATURE_RXINVERT_INIT;
//开启高级特征

huart3.AdvancedInit.TxPinLevelInvert = UART_ADVFEATURE_TXINV_ENABLE;
//输出电平取反

huart3.AdvancedInit.RxPinLevelInvert = UART_ADVFEATURE_RXINV_ENABLE;
//输入电平取反

必要的是要输入取反即可,我是为了测试看输出有没有问题两个都改了。


在接收中断中,因为开启了两个接收中断,就要利用传入的句柄“UART_HandleTypeDef *huart”进行判断是哪个串口的。
接着判断该数据帧是否有效。为了方式半帧开始,我利用S.Bus一帧3ms,间隔7ms的特点:
HAL_UART_Receive(&huart3,&S_BUS_Receive,1,0xFFFFFFFF);//利用串口超时等待任意一个串口数据。
HAL_Delay(5);//这里的5ms延时时必要的,可以防止DMA传送从半帧开始,造成解算错误。
S_BUS_READY = 1;//令标记为1。
MX_USART3_UART_Init();
//这句不一定需要。

MX_DMA_Init();
HAL_UART_Receive_DMA(&huart3,aRxBuffer3,25);
//重现初始化串口并开始DMA模式。

先用一句超时方式等到任意一个字节,说明现在的时间是正在传输某一帧数据,延时5ms,保证开启DMA的瞬间串口处于空闲状态。
虽然也可以用串口中断进行起始字节0x0F和结束字节0x00的定位,但是过于简单,中间的数据位很可能形成相同的假象,综合判断延时法最保险。

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{

if(huart == &huart1)
{

  if(S_BUS_READY != 0)
  {
   for(uint8_t i = 5; i != 0; i--)
   {
    aTxBuffer1B[i-1] = aRxBuffer1[i-1];
   }
   HAL_UART_Transmit_DMA(&huart1, aTxBuffer1B, 7);              //测试S.Bus解算时请勿发送串口测试命令,防止数据覆盖。

  }

}

if(huart == &huart3)
{

  if(S_BUS_READY != 0)
  {
   for(uint8_t i = 25; i != 0; i--)
   {
    aTxBuffer3[i-1] = aRxBuffer3[i-1];
   }
   S_BUS_READY = 2;
   HAL_UART_Transmit_DMA(&huart3, aTxBuffer3, 25);
  }

}
}

标红的就是句柄判断。然后再对应的在接收中断里标记“S_BUS_READY = 2”。
我还随便写了一句把收到的再发出去,用于调试观察是否存在半帧开始的情况,证明延时法确实可靠。

在主函数中当当标记为2时做解析,并发送到串口1(飞控上有CP2102,USB连接电脑串口助手)解析结束再赋值为1。
while (1)
  {
  if(S_BUS_READY == 2)
  {
   HAL_GPIO_TogglePin(Test_IO_GPIO_Port, Test_IO_Pin);
   RC_Translate();                                                                                                         //在这个功能函数中解析。
   for(uint8_t i = 0;i != 10;i++)
   {
    aTxBuffer1[i*10+4] = RC_Data / 1000 + 48;
    aTxBuffer1[i*10+5] = (RC_Data % 1000) / 100 + 48;
    aTxBuffer1[i*10+6] = (RC_Data % 100) / 10 + 48;
    aTxBuffer1[i*10+7] = RC_Data % 10 + 48;
   }
   HAL_GPIO_TogglePin(Test_IO_GPIO_Port, Test_IO_Pin);                                              //利用IO反转时间测试解算用时约为9us。
   HAL_UART_Transmit_DMA(&huart1, aTxBuffer1, TX1_BUFFER_LENGTH);                        //测试串口返回数据时要么注释本句。
   S_BUS_READY = 1;
  }
  }


void RC_Translate(void)
{
if((aRxBuffer3[0] != 0x0F) || (aRxBuffer3[24] != 0x00))
  return;
RC_Data[0] = ((0x0000 + (aRxBuffer3[2] & 0x07)) << 8) + aRxBuffer3[1];
RC_Data[1] = (((0x0000 + (aRxBuffer3[3] & 0x3F)) << 8) + aRxBuffer3[2]) >> 3;
RC_Data[2] = (((((0x0000 + (aRxBuffer3[5] & 0x01)) << 8) + aRxBuffer3[4]) << 8) + aRxBuffer3[5]) >> 6;
RC_Data[3] = (((0x0000 + (aRxBuffer3[6] & 0x0F)) << 8) + aRxBuffer3[5]) >> 1;
RC_Data[4] = (((0x0000 + (aRxBuffer3[7] & 0x7F)) << 8) + aRxBuffer3[6]) >> 4;
RC_Data[5] = (((((0x0000 + (aRxBuffer3[9] & 0x03)) << 8) + aRxBuffer3[8]) << 8) + aRxBuffer3[7]) >> 7;
RC_Data[6] = (((0x0000 + (aRxBuffer3[10] & 0x1F)) << 8) + aRxBuffer3[6]) >> 2;
RC_Data[7] = (((0x0000 + (aRxBuffer3[11] & 0xFF)) << 8) + aRxBuffer3[10]) >> 5;
RC_Data[8] = (((0x0000 + (aRxBuffer3[13] & 0x07)) << 8) + aRxBuffer3[12]) >> 0;
RC_Data[9] = (((0x0000 + (aRxBuffer3[14] & 0x3F)) << 8) + aRxBuffer3[13]) >> 3;
}


首先判断首尾,如果不正确说明传输有误,直接返回。如果正确则继续。
这里我只写了前十通道的解析,因为我的乐迪AT9只有10通道,使用更高数通道的可以自行解析后面通道。
在这里说一下第24字节,也就是“RC_Data[23]”这个字节的4、5位标识故障,若为1表示接收机与遥控式连接失败,输出值为遥控器上设置好的失控保护值。
下面时实验现象:
S.Bus解析逻辑分析.JPG
S.Bus解析串口结果.JPG

从串口1返回的数据看,解析成功。


工程文件:
链接:https://pan.baidu.com/s/14KNjlXlEuslxUE84K4vIUg 密码:10yf






友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。