本文主要是介绍基于STM32实现sBus协议数据获取、解析并转为CAN协议数据,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
本文目录:
1、功能实现情况
2、实现路线
3、关于sBus共地问题的注意事项
4、结语
1、功能实现情况
目前该装置可以实现将航模接收机输出的sBus协议数据进行获取并根据sBus协议进行解析,最终经CAN总线将解析之后的数据传输出去。目前为功能实现阶段,暂时将解析之后的11位数据(在stm32中,数据的存储类型为uint16_t)舍弃掉部分位之后转为8位数据(在stm32中数据的存储类型为uint8_t)经CAN总线传输出去。
2、实现路线
图1为数据的传输方向示意图,也为装置的实现路线图。其中遥控器和接收机使用的型号为云卓T10,有10个通道,接收机5V供电,接收机输出信号选择sBus协议信号。
图1:实现路线
其中从遥控器发送数据到接收机以及硬件取反电路组成实现路线第一段;在实现路线第一段基础上加上STM32组成实现路线第二段,最后在实现路线第二段的基础上加上CAN总线收发器模块组成实现路线第三段,至此,输出的数据为CAN协议数据。
至于为什么在接收机和STM32之间加上硬件取反电路是由sBus协议内容要求的,关于sBus协议内容参考资料:不清楚SBUS,这份SBUS协议详解请收藏_振华OPPO的博客-CSDN博客
需特别注意sBus在STM32中串口参数的配置。
2.1、实现路线第一段
该段需要注意的事项为接收机的5V供电,其中5V供电的正极需和实现路线第二段中的STM32的ST-Link上5V针脚连接,5V供电的负极需和STM32的GND引脚连接(至于为什么这样连接详见本文文末“关于装置电源共地问题的注意事项”),STM32系统选择STM32F103C8T6最小系统板。此外,还需注意的事项为硬件取反电路的3.3V供电,如果3.3V供电的正极和STM32的3.3V引脚连接可能会出现硬件取反电路不工作的情况,问题可能为STM32板子上的3.3V引脚的驱动能力不强,此时可能需要外接3.3V的电源,此处对于硬件取反电路没有和STM32共地的要求。
检验实现路线第一段是否成功实现需要一个USB转串口模块,如图2、图3所示,具体操作为:将接收机上的正负极暂时分别与ST-Link的5V针脚和STM32的GND脚断开,并将其分别于USB转串口模块上的5V与GND连接,然后将搭载了 经取反电路转换后的sBus数据 的信号线与USB转串口接线端上的Rx引脚连接,打开串口调试助手并设置好串口调试助手相关通信参数,其通信参数的具体值参考sBus协议中对波特率、奇偶校验位、停止位等的要求。
图2:USB转串口模块正面
图3:USB转串口模块反面
当实现路线第一步成功时,串口调试助手显示接收到的数据如图4所示,其中除了第一位为0x0f和最后一位为标志位外,其余位的数据由使用相应的遥控器设备助手自定义,即除了第一位和最后一位外,其余位的数值可不必与图4中显示的相同。
图4:串口调试助手页面
当串口调试助手在配置好相关通信参数的前提下显示的数据没有规律即乱码时,考虑两个可能存在的问题:(1)硬件取反电路搭错了(2)硬件取反电路的供电问题。
2.2、实现路线第二段
实现路线第二段相比于第一段需要多一个STM32单片机,具体为STM32F103C8T6最小系统板,如图5所示,并且一起配合使用的是一个OLED屏,OLED屏的驱动函数参考B站“江协电子——江科大STM32教程”中的OLED库函数(此处为我母校学长疯狂打call)。
图5:STM32最小系统板
这一段的接线为:由于在实现路线第一段中将接收机上的5V电源的正负极分别与USB转串口模块的5V和GND连接了,所以首先将接收机的5V电源线的正负极与USB转串口模块的5V和GND断开,然后将接收机上5V电源正极与ST-Link上的5V引脚连接,接收机上5V电源负极与STM32最小系统板上的GND引脚连接(必须要这样连接,因为sBus协议本质上是UART协议,USART/UART接线要求中要求数据的发送端要与接收端“共地”),然后将搭载了 经取反电路转换后的sBus数据 的信号线 与STM32最小系统板上用于USART通信的Rx引脚即负责串口数据接收的引脚连接(图5中为与PA10连接)。STM32的程序为:
main函数(包含了实现路线第三步中的CAN通信部分)
#include "stm32f10x.h"
#include "usart_RS.h"
#include "OLED.h"
#include "CAN_test.h"
#include "Delay.h"int main(void)
{ usart_RS_Init();CAN_Config();CanTxMsg CAN_TxMessage;//SendString(USART1,"This is a USART receive and send test!");OLED_Init(); uint16_t* CH_main;uint8_t* buf_main;while(1){ if(Get_flag_CH_deal_finish() == 1){CH_main = Get_CH();buf_main = Get_buf();//OLED_Clear();CAN_TxMessage.ExtId = 0x1314;CAN_TxMessage.DLC = 8;CAN_TxMessage.IDE = CAN_ID_EXT;CAN_TxMessage.RTR = CAN_RTR_DATA;CAN_TxMessage.Data[0] = CH_main[0];CAN_TxMessage.Data[1] = CH_main[1];CAN_TxMessage.Data[2] = CH_main[2];CAN_TxMessage.Data[3] = CH_main[3];CAN_TxMessage.Data[4] = CH_main[4];CAN_TxMessage.Data[5] = CH_main[5];CAN_TxMessage.Data[6] = CH_main[6];CAN_TxMessage.Data[7] = CH_main[7];// OLED_ShowNum(1,1,CH_main[0],4);
// OLED_ShowNum(1,6,CH_main[1],4);
// OLED_ShowNum(1,11,CH_main[2],4);
// OLED_ShowNum(2,1,CH_main[3],4);
// OLED_ShowNum(2,6,CH_main[4],4);
// OLED_ShowNum(2,11,CH_main[5],4);
// OLED_ShowNum(3,1,CH_main[6],4);
// OLED_ShowNum(3,6,CH_main[7],4);
// OLED_ShowNum(3,11,CH_main[8],4);
// OLED_ShowNum(4,1,CH_main[9],4);
// OLED_ShowNum(4,6,CH_main[10],4);// OLED_ShowHexNum(1,1,buf_main[0],2);
// OLED_ShowHexNum(1,4,buf_main[1],2);
// OLED_ShowHexNum(1,7,buf_main[2],2);
// OLED_ShowHexNum(1,10,buf_main[3],2);
// OLED_ShowHexNum(1,13,buf_main[4],2);
// OLED_ShowHexNum(2,1,buf_main[5],2);
// OLED_ShowHexNum(2,4,buf_main[6],2);
// OLED_ShowHexNum(2,7,buf_main[7],2);
// OLED_ShowHexNum(2,10,buf_main[8],2);
// OLED_ShowHexNum(2,13,buf_main[9],2);
// OLED_ShowHexNum(3,1,buf_main[10],2);
// OLED_ShowHexNum(3,4,buf_main[11],2);
// OLED_ShowHexNum(3,7,buf_main[12],2);
// OLED_ShowHexNum(3,10,buf_main[13],2);
// OLED_ShowHexNum(3,13,buf_main[14],2);
// OLED_ShowHexNum(4,1,buf_main[15],2);
// OLED_ShowHexNum(4,4,buf_main[16],2);
// OLED_ShowHexNum(4,7,buf_main[17],2);
// OLED_ShowHexNum(4,10,buf_main[18],2);
// OLED_ShowHexNum(4,13,buf_main[19],2);CAN_Transmit(CAN1,&CAN_TxMessage);Delay_ms(10);USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);}}
}
usart_RS.h文件代码为:
#ifndef __USART_RS_H
#define __USART_RS_Hvoid usart_RS_Init(void);
void SendString(USART_TypeDef* USARTx,char* str);uint8_t Get_flag_CH_deal_finish(void);uint16_t* Get_CH(void);uint8_t* Get_buf(void);#endif
usart_RS.c文件代码为:
#include "stm32f10x.h" // Device headervoid usart_RS_Init(void)
{//1、打开外设(GPIOA、USART1)时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//2、重置外设参数USART_DeInit(USART1);//3、配置GPIO口//配置PA9为复用推挽输出模式GPIO_InitTypeDef GPIO_InitStructure={0};GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//配置PA10为浮空输入模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//4、配置USARTUSART_InitTypeDef USART_InitStructure={0};USART_InitStructure.USART_BaudRate = 100000;USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;USART_InitStructure.USART_Parity = USART_Parity_Even;USART_InitStructure.USART_StopBits = USART_StopBits_2;USART_InitStructure.USART_WordLength = USART_WordLength_9b;USART_Init(USART1,&USART_InitStructure);//5、配置NVICNVIC_InitTypeDef NVIC_InitStructure={0};NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStructure);//6、配置中断USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//7、使能串口USART_Cmd(USART1,ENABLE);
}void SendByte(USART_TypeDef* USARTx,uint8_t data)
{USART_SendData(USARTx,data);while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
}
void SendString(USART_TypeDef* USARTx,char* str)
{unsigned int k=0;while(*(str+k)!='\0'){SendByte(USARTx,*(str+k));k++;}while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
}static uint8_t flag_receive_sBus = 0;
void copyANDdeal_data_sBus(uint8_t ucTemp);
void USART1_IRQHandler(void)
{uint8_t ucTemp;if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET){ucTemp = USART_ReceiveData(USART1);if (flag_receive_sBus == 1){copyANDdeal_data_sBus(ucTemp);}else if(ucTemp == 0x0f){flag_receive_sBus = 1;}}
}static uint8_t buf[24];
static uint8_t count = 0;
static uint16_t CH[10];
static uint8_t flag_CH_deal_finish=0;
void copyANDdeal_data_sBus(uint8_t ucTemp)
{ buf[count] = ucTemp;count++;if(count == 24){CH[ 0] = ((int16_t)buf[ 0] >> 0 | ((int16_t)buf[ 1] << 8 )) & 0x07FF;CH[ 1] = ((int16_t)buf[ 1] >> 3 | ((int16_t)buf[ 2] << 5 )) & 0x07FF;CH[ 2] = ((int16_t)buf[ 2] >> 6 | ((int16_t)buf[ 3] << 2 ) | (int16_t)buf[ 4] << 10 ) & 0x07FF;CH[ 3] = ((int16_t)buf[ 4] >> 1 | ((int16_t)buf[ 5] << 7 )) & 0x07FF;CH[ 4] = ((int16_t)buf[ 5] >> 4 | ((int16_t)buf[ 6] << 4 )) & 0x07FF;CH[ 5] = ((int16_t)buf[ 6] >> 7 | ((int16_t)buf[ 7] << 1 ) | (int16_t)buf[8] << 9 ) & 0x07FF;CH[ 6] = ((int16_t)buf[ 8] >> 2 | ((int16_t)buf[ 9] << 6 )) & 0x07FF;CH[ 7] = ((int16_t)buf[ 9] >> 5 | ((int16_t)buf[10] << 3 )) & 0x07FF; CH[ 8] = ((int16_t)buf[11] << 0 | ((int16_t)buf[12] << 8 )) & 0x07FF;CH[ 9] = ((int16_t)buf[12] >> 3 | ((int16_t)buf[13] << 5 )) & 0x07FF;flag_CH_deal_finish = 1;count = 0;flag_receive_sBus = 0;USART_ITConfig(USART1,USART_IT_RXNE,DISABLE);}
}uint8_t Get_flag_CH_deal_finish(void)
{return flag_CH_deal_finish;
}uint16_t* Get_CH(void)
{flag_CH_deal_finish = 0;return CH;
}uint8_t* Get_buf(void)
{flag_CH_deal_finish = 0;return buf;
}
关于代码需要注意的事项为:(1)USART通信参数的配置如图6所示,(2)在中断服务子函数中的(void copyANDdeal_data_sBus(uint8_t ucTemp))函数中有一个关闭串口接收中断的函数和在主函数末尾有一个打开串口接收中断的函数。由于接收机是一直向外发送sBus数据,如果没有这两个函数,STM32的主函数会一直被串口接收中断 中断执行。
图6:USART通信参数的配置
检验实现路线第二段是否成功的操作为:可在主函数中写一个“OLED_Clear()”观察OLED屏的刷新频率是否固定,刷新频率不固定即为一会儿刷新快一点,一会儿刷新慢一点。至于为什么可以这样检测的原因为:由于sBus数据段长度固定为25个字节,按照程序逻辑,每接收到24个字节(在上述程序中包头0x0f没有存储,所以是24个字节)并完成解析后会执行一次刷新OLED屏的操作,当接收到的数据不正确时,出现包头即数据0x0f的时间不固定(因为数据不正确的话数据为乱码),那么刷新OLED屏的时间也不固定,时而快时而慢。
关于实现路线第二段中OLED屏的现象有三种:(1)OLED屏不显示;(2)OLED屏显示数据,在加上OLED屏刷新函数后刷新频率不固定,显示的数据没有规律即乱码;(3)OLED屏显示数据,在加上OLED屏刷新函数后刷新频率固定,显示的数据每一组都在变化但有规律。当出现第一种情况时为串口中断服务子程序中(void copyANDdeal_data_sBus(uint8_t ucTemp))函数的串口接收数据中断未关闭导致的,由于主函数的运行一直被中断服务子函数打断,导致即使接收到数据也没办法显示在OLED屏上,需要注意的是在中断服务子函数中关闭中断后需要在合适的地方打开(上述程序中为在主函数的末尾打开);当出现第二种情况时为STM32接收到的数据有误所致,在实现路线第一段成功的情况下(即由取反电路输出的数据正确的情况下),检查接收机的5V电源GND是否与最小系统板上的GND连接,接收机5V电源的正极是否与ST-Link的5V引脚连接,当不满足USART通信数据发送端与接收端“共地”的要求时,数据接收端接收到的数据可能会存在问题;当出现上述第三种情况时为正常情况,至于显示的数据呈现有规律的变化猜测是OLED的驱动函数问题,可不用管。
2.3、实现路线第三段
实现路线第三段为将由实现路线第二段中处理后的通道数据CH[ ii ]( ii = 0、1、2...9)赋值给CAN的数据段并通过CAN总线发送出去,具体程序为(下述程序包含CAN接收数据的功能):
CAN_test.h文件代码为:
#ifndef __CAN_test_H
#define __CAN_test_Hvoid CAN_Config(void);uint8_t CAN_Get_Flag_Receive(void);CanRxMsg CAN_Get_RxMessage(void);#endif
CAN_test.c文件的代码为:
#include "stm32f10x.h" // Device headervoid CAN_Config(void)
{//1、配置(PA11)、(PA12)和CAN1对应的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);//2、配置GPIO口GPIO_InitTypeDef GPIO_InitStructure={0};//配置PA11(CAN_Rx)为上拉输入模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//配置PA12(CAN_Tx)为复用推挽输出模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//3、配置CAN模式CAN_InitTypeDef CAN_InitStructure={0}; CAN_InitStructure.CAN_ABOM = ENABLE; //自动离线功能CAN_InitStructure.CAN_AWUM = ENABLE; //自动唤醒功能CAN_InitStructure.CAN_BS1 = CAN_BS1_5tq; //配置BS1段长度CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq; //配置BS2段长度CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; //配置CAN工作模式CAN_InitStructure.CAN_NART = DISABLE; //配置自动重传功能CAN_InitStructure.CAN_Prescaler = 4; //配置CAN外设的时钟分频CAN_InitStructure.CAN_RFLM = DISABLE; //锁存FIFOCAN_InitStructure.CAN_SJW = CAN_SJW_1tq; //SJW极限值CAN_InitStructure.CAN_TTCM = DISABLE; //是否使能时间触发功能CAN_InitStructure.CAN_TXFP = DISABLE; //配置报文优先级的判别方法CAN_Init(CAN1,&CAN_InitStructure); //4、配置CAN筛选器参数CAN_FilterInitTypeDef CAN_FilterInitStructure={0};CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; //是否使能本筛选器 CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FilterFIFO0; //设置经筛选器筛选后的数据存到哪个FIFO中//筛选器中要筛选ID的高16位CAN_FilterInitStructure.CAN_FilterIdHigh = ((((u32)0x2023<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF0000)>>16; //筛选器中要筛选ID的低16位CAN_FilterInitStructure.CAN_FilterIdLow = (((u32)0x2023<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF; CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0xFFFF; //筛选器中筛选屏蔽位的高16位(设置1的位表示接收报ID的该位必须与筛选器中设置的ID的对应位一致)CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0xFFFF; //筛选器中筛选屏蔽位的低16位(同上)CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; //筛选器的工作模式(列表还是屏蔽模式)CAN_FilterInitStructure.CAN_FilterNumber = 0; //筛选器组的编号CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; //设置筛选器的长度CAN_FilterInit(&CAN_FilterInitStructure);//5、配置NVICNVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);NVIC_InitTypeDef NVIC_InitStructure={0};NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_Init(&NVIC_InitStructure);//6、使能CAN1的接收中断CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);
}static uint8_t flag_receive=0;
CanRxMsg RxMessage;void USB_LP_CAN1_RX0_IRQHandler(void)
{CAN_Receive(CAN1,CAN_FIFO0,&RxMessage); //其中CAN_FIFO0与CAN筛选器是设置的关联FIFO相对应flag_receive = 1;if((RxMessage.ExtId == 0x2023) && (RxMessage.IDE == CAN_ID_EXT) && (RxMessage.DLC == 8)){flag_receive = 2;}else{flag_receive = 3;}
}uint8_t CAN_Get_Flag_Receive(void)
{return flag_receive;
}
CanRxMsg CAN_Get_RxMessage(void)
{return RxMessage;
}
3、关于共地问题的注意事项
3.1、当使用外部的5V直流电源给接收机供电时
外部5V电源的GND与STM32最小系统板的GND电平不一致,实际情况如图7、图8所示:
图7:万用表笔头与外部电源的负极和STM32最小系统板GND连接情况
图8:外部电源负极与STM32最小系统板GND间的电压结果
此时不能满足USART/UART协议中关于数据发送端与接收端“共地”的要求,当由该外部电源给接收机供电时,其sBus信号输出端经取反电路后数据波形如图9、图10所示:
图9:当不满足“共地”要求时经取反电路取反后的信号
图10:对图9进行局部放大后的信号波形
而正常的波形如图11、图12所示:
图11: 满足“共地”要求时信号波形
图12:图11进行局部放大后的波形
3.2、外部电源负极与STM32最小系统板间的电位差
关于外部电源负极与STM32最小系统板GND间的电位差如图13所示:
图13:外部电源负极与STM32最小系统板GND端的电位差
4、结语
nnd,写了五个小时终于写完了!!!
我去干饭了,下次有好玩的再写!
这篇关于基于STM32实现sBus协议数据获取、解析并转为CAN协议数据的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!