【背景】
各位,现在无人车和机器人大发展,但是,大家也都看到了,在无人车和机器人能够自动控制之前,都是用遥控器来控制的。那么,实现一个遥控器的控制,就变得非常有必要。然而,实际情况是,业内的人,都觉得用遥控器控制是基本的,和理所当然的;业外的人,都知道遥控器控制,要做遥控器的设计,又觉得无从下手。那么,今天我就来写一写用STM32和STM32CubeMX实现遥控器控制的保姆级教程,让大家都能实现遥控器控制自由。
【遥控器控制的方式】
市面上的遥控器有很多,甚至于游戏手柄,也是遥控器的一种。那现在比较流行的遥控器,都是用SBUS的协议来控制的。我这里就来实现SBUS协议的遥控器控制。这协议应该是能适用90%的遥控器了。如果不明白什么是SBUS协议,可以自行去网上学习。
【硬件设计】

我这是用的STM32的uart3来作为遥控器的接收电路。实际上只用了接收端RXD3, TXD3空着没用。H3是个3端子插头,接遥控器的接收端。 SBUS信号是个反的,所以要用上面的电路反一下。
【STM32CubeMX的设置】



stm32cubemx的配置就这么多,要注意的是波特率是100kbps,跟一般的串口不一样。照着我上面配就行了。
【软件编写】
sbus.h
#ifndef _RC_SBUS_H_
#define _RC_SBUS_H_
#include "main.h"
// RC遥控器相关 开始
#define SBUS_NUM_CHANNELS 16
#define SBUS_PACKET_SIZE 25
#define SBUS_HEADER 0x0F
#define SBUS_END 0x00
#define SBUS_BUFFER_SIZE 25
#define SBUS_OPT_C17 0x01
#define SBUS_OPT_C18 0x02
#define SBUS_OPT_FS 0x08
#define SBUS_OPT_FL 0x04
typedef struct {
uint16_t channels[SBUS_NUM_CHANNELS];
uint8_t ch17, ch18;
uint8_t failsafe;
uint8_t frameLost;
uint8_t sbus_flag; // 1: valid, 0: invalid
} sbus_packet_t;
enum sbus_err_t
{
SBUS_OK = 0,
SBUS_FAIL = -1,
};
typedef struct {
sbus_packet_t sbus_packet;
uint8_t sbus_Buffer[SBUS_BUFFER_SIZE];
/// flag for RC transimtter is existing or not
/// 0: not exist, 1: exist but not using, 2: exist and using
uint8_t isExist;
/// counter for RC transmitter existing judgement
uint8_t exist_cnt1;
uint8_t exist_cnt2;
} sbus_handle_t;
extern sbus_handle_t sbusHandle; // SBUS handle
extern UART_HandleTypeDef huart3;
void RC_init(sbus_handle_t sbusHandle);
void RC_run(sbus_handle_t sbusHandle);
// RC遥控器相关 结束
#endif /* _RC_SBUS_H_ */
sbus.c
#include "rc_sbus.h"
#include "uart.h"
// RC遥控器相关 开始
void RC_init(sbus_handle_t sbusHandle)
{
// Initialize SBUS packet structure
for (int i = 0; i < SBUS_NUM_CHANNELS; i++)
{
sbusHandle.sbus_packet.channels[i] = 0;
}
sbusHandle.sbus_packet.ch17 = 0;
sbusHandle.sbus_packet.ch18 = 0;
sbusHandle.sbus_packet.failsafe = 0;
sbusHandle.sbus_packet.frameLost = 0;
sbusHandle.sbus_packet.sbus_flag = 0; // Initially invalid
// Initialize SBUS buffer
for (int i = 0; i < SBUS_BUFFER_SIZE; i++)
{
sbusHandle.sbus_Buffer[i] = 0;
}
sbusHandle.isExist=1;
sbusHandle.exist_cnt1=0;
sbusHandle.exist_cnt2=0;
// Start receiving data
HAL_UARTEx_ReceiveToIdle_IT(&huart3, sbusHandle.sbus_Buffer, SBUS_BUFFER_SIZE);
// sbusHandle.isExist=1;
printf("RC Initialized\r\n");
// HAL_Delay(5); // Delay for stability
}
可以看到,遥控器是用的uart3的空闲中断,25bytes。每个中断会接收25bytes数据,然后进行解码。
解码是在uart3的中断里进行的。代码如下:
uart.c
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
// Handle idle line detection for sbus
if(huart->Instance == USART3)
{
if(Size==25) // Expected packet size is 25bytes
{
if(sbusHandle.sbus_Buffer[0]==SBUS_HEADER && sbusHandle.sbus_Buffer[SBUS_BUFFER_SIZE-1]==SBUS_END)
{
sbusHandle.sbus_packet.channels[0] = (uint16_t)((sbusHandle.sbus_Buffer[1] >> 0 | sbusHandle.sbus_Buffer[2] << 8) & 0x07FF);
sbusHandle.sbus_packet.channels[1] = (uint16_t)((sbusHandle.sbus_Buffer[2] >> 3 | sbusHandle.sbus_Buffer[3] << 5) & 0x07FF);
sbusHandle.sbus_packet.channels[2] = (uint16_t)((sbusHandle.sbus_Buffer[3] >> 6 | sbusHandle.sbus_Buffer[4] << 2 | sbusHandle.sbus_Buffer[5] << 10) & 0x07FF);
sbusHandle.sbus_packet.channels[3] = (uint16_t)((sbusHandle.sbus_Buffer[5] >> 1 | sbusHandle.sbus_Buffer[6] << 7) & 0x07FF);
sbusHandle.sbus_packet.channels[4] = (uint16_t)((sbusHandle.sbus_Buffer[6] >> 4 | sbusHandle.sbus_Buffer[7] << 4) & 0x07FF);
sbusHandle.sbus_packet.channels[5] = (uint16_t)((sbusHandle.sbus_Buffer[7] >> 7 | sbusHandle.sbus_Buffer[8] << 1 | sbusHandle.sbus_Buffer[9] << 9) & 0x07FF);
sbusHandle.sbus_packet.channels[6] = (uint16_t)((sbusHandle.sbus_Buffer[9] >> 2 | sbusHandle.sbus_Buffer[10] << 6) & 0x07FF);
sbusHandle.sbus_packet.channels[7] = (uint16_t)((sbusHandle.sbus_Buffer[10] >> 5 | sbusHandle.sbus_Buffer[11] << 3) & 0x07FF);
sbusHandle.sbus_packet.channels[8] = (uint16_t)((sbusHandle.sbus_Buffer[12] << 0 | sbusHandle.sbus_Buffer[13] << 8) & 0x07FF);
sbusHandle.sbus_packet.channels[9] = (uint16_t)((sbusHandle.sbus_Buffer[13] >> 3 | sbusHandle.sbus_Buffer[14] << 5) & 0x07FF);
sbusHandle.sbus_packet.channels[10] = (uint16_t)((sbusHandle.sbus_Buffer[14] >> 6 | sbusHandle.sbus_Buffer[15] << 2 | sbusHandle.sbus_Buffer[16] << 10) & 0x07FF);
sbusHandle.sbus_packet.channels[11] = (uint16_t)((sbusHandle.sbus_Buffer[16] >> 1 | sbusHandle.sbus_Buffer[17] << 7) & 0x07FF);
sbusHandle.sbus_packet.channels[12] = (uint16_t)((sbusHandle.sbus_Buffer[17] >> 4 | sbusHandle.sbus_Buffer[18] << 4) & 0x07FF);
sbusHandle.sbus_packet.channels[13] = (uint16_t)((sbusHandle.sbus_Buffer[18] >> 7 | sbusHandle.sbus_Buffer[19] << 1 | sbusHandle.sbus_Buffer[20] << 9) & 0x07FF);
sbusHandle.sbus_packet.channels[14] = (uint16_t)((sbusHandle.sbus_Buffer[20] >> 2 | sbusHandle.sbus_Buffer[21] << 6) & 0x07FF);
sbusHandle.sbus_packet.channels[15] = (uint16_t)((sbusHandle.sbus_Buffer[21] >> 5 | sbusHandle.sbus_Buffer[22] << 3) & 0x07FF);
sbusHandle.sbus_packet.ch17 = (sbusHandle.sbus_Buffer[23] & 0x0f) & SBUS_OPT_C17;
sbusHandle.sbus_packet.ch18 = (sbusHandle.sbus_Buffer[23] & 0x0f) & SBUS_OPT_C18;
sbusHandle.sbus_packet.failsafe = (sbusHandle.sbus_Buffer[23] & 0x0f) & SBUS_OPT_FS;
sbusHandle.sbus_packet.frameLost = (sbusHandle.sbus_Buffer[23] & 0x0f) & SBUS_OPT_FL;
sbusHandle.sbus_packet.sbus_flag = 1;
}
else
{
sbusHandle.sbus_packet.sbus_flag = 0;
}
// printf("sbus has been received successfully! sbus_flag = %d\n", sbusHandle.sbus_packet.sbus_flag);
}
else
{
sbusHandle.sbus_packet.sbus_flag = 0;
printf("sbus received invalid length: %d\n", Size);
}
HAL_UARTEx_ReceiveToIdle_IT(&huart3, sbusHandle.sbus_Buffer, SBUS_BUFFER_SIZE); // Restart receiving data
}
}
看到没有,在uart中断里面解码之后,会置一个sbusHandle.sbus_packet.sbus_flag=1; 那么在应用程序里面,进行查询,看到了sbusHandle.sbus_packet.sbus_flag=1,就可以对各个channels的数据进行处理了。
这里做一个简单的例子,就是将数据打印出来。
void RC_run(sbus_handle_t sbusHandle)
{
if (sbusHandle.sbus_packet.sbus_flag == 1)
{
sbusHandle.sbus_packet.sbus_flag = 0; // Reset flag after processing
// usb_printf("RC Data Received: ");
printf("RC Data Received: ");
for (int i = 0; i < SBUS_NUM_CHANNELS; i++)
{
printf("Channel %d: %d ", i + 1, sbusHandle.sbus_packet.channels[i]);
}
printf("Ch17: %d, Ch18: %d, Failsafe: %d, Frame Lost: %d\r\n",
sbusHandle.sbus_packet.ch17, sbusHandle.sbus_packet.ch18, sbusHandle.sbus_packet.failsafe, sbusHandle.sbus_packet.frameLost);
}
}
需要说明的是,遥控器的数据来得很快,每帧25bytes,间隔是14ms。应用程序进行处理的时候,要在14ms里面处理完一帧,并接着处理下一帧。通常来说,放在一个while(1)大循环里,是来不及的。需要用另外一个并发的任务来处理。这就牵涉到RTOS了,这是另外一个话题,这里就不展开了。
在系统上,还要能够检测到遥控器开和关,进行plug&play, 这是跟每个具体的遥控器和接收机相关的,这里也不展开了。各位自己去探索吧。
【结果检验】
这套代码用在了我实际的无人车的控制中,已经经过长时间的检验,没有问题。