要实现一个遥控小车当然要有一个遥控器了,目前市面上常用的航模遥控器基本都是2.4G无线信号,从遥控器到小车子的链路是这样子的。
接收机出来的信号一般有SBUS信号、PPM信号、PWM信号,SBUS信号就是一路串行信号,连接到控制板的串口按协议解析就行了。PWM信号就不说了,多路PWM接口表示多个信号,PPM信号也是一路信号,只不过它是一串波,它把多路PWM值混合到一帧信号里了,一帧数据的高电平时间按顺序解析出来就是各路PWM的值。这里不讲后两种,解析起来麻烦还浪费控制板资源,后面所有机器人系列都用SBUS信号,也推荐大家用SBUS信号。
关于STM32解析SBUS信号我之前写过一篇文章,有源码,大家可以直接去读那一篇,传送门:STM32解析SBUS信号例程详解. 在这里我简单再介绍一下。
SBUS全称serial-bus,是一种串口通信协议,广泛应用于航模遥控器(接收机)中。只用一根信号线就能传输多达16通道的数据,比多路PWM捕获高效且省资源。
在上一章STM32实现四驱小车(一)硬件与软件准备的工程模板基础上,创建sbus.h,sbus.c两个文件用于SBUS解析,再创建uart.h, uart.c两个文件写串口驱动。
sbus.h的内容如下
#ifndef __SBUS_H
#define __SBUS_H
#include "sys.h"
#define SBUS_FRAME_SIZE 25
#define SBUS_INPUT_CHANNELS 16
//定义subs信号的最小值 最大值 中值 死区 以及希望转换成PWM值的范围(1000-2000)
#define SBUS_RANGE_MIN 300.0f
#define SBUS_RANGE_MAX 1700.0f
#define SBUS_TARGET_MIN 1000.0f
#define SBUS_TARGET_MAX 2000.0f
#define DEAD_RANGE_MIN 960 //死区
#define DEAD_RANGE_MAX 1040
#define SBUS_RANGE_MIDDLE 1000.0f
#define SBUS_CONNECT_FLAG 0x00
//低速与高速模式,这里用一个二段开关控制速度档位
#define LOW_SPEED 0
#define HIGH_SPEED 1
// 定义四个摇杆与拨动开关的功能
#define YAW 1
#define THROTTLE 2
#define PITCH 3
#define ROLL 4
#define SPEED_MODE 6
extern int command[20]; //遥控器数据
typedef struct
{
uint16_t signal[25];
uint16_t CH1;//通道1数值
uint16_t CH2;//通道2数值
uint16_t CH3;//通道3数值
uint16_t CH4;//通道4数值
uint16_t CH5;//通道5数值
uint16_t CH6;//通道6数值
uint16_t CH7;//通道7数值
uint16_t CH8;//通道8数值
uint16_t CH9;//通道9数值
uint16_t CH10;//通道10数值
uint16_t CH11;//通道11数值
uint16_t CH12;//通道12数值
uint16_t CH13;//通道13数值
uint16_t CH14;//通道14数值
uint16_t CH15;//通道15数值
uint16_t CH16;//通道16数值
uint8_t ConnectState;//遥控器与接收器连接状态 0=未连接,1=正常连接
}SBUS_CH_Struct;
extern SBUS_CH_Struct SBUS_CH;
//SBUS信号解析相关函数
u8 update_sbus(u8 *buf);
u16 sbus_to_pwm(u16 sbus_value);
float sbus_to_Range(u16 sbus_value, float p_min, float p_max);
#endif
这里稍作解释,SBUS信号读出来是300-1700的数据,我们需要把它转换成1000-2000的PWM值(不转其实也没关系,只是为了方便)。遥控器上两个摇杆对应的是1-4四路通道,范围是连续变化的,其他的拨码开关是离散值,二段开关对应通道值为300和1700,三段开关对应的通道值为300,1000,1700.根据数值就可以判断拨码开关是在什么状态,就可以进行换挡。本文只使用了一个二段开关,用于低速与高速模式。在比较复杂的系统里面所有的拨码开关可能都会用到甚至复合作用。比如PX4飞控里面有定高/手动模式,有头/无头模式,一键起飞/降落模式,定点、特技模式等,都需要用拨码开关来进行切换和换挡。在这里定义了一个全局变量command[20],用来存储遥控器各个通道的值,用PWM值表示,范围1000-2000. 知道了各个通道的值就可以去映射到不同的功能。
sbus.c的内容如下
#include "sbus.h"
SBUS_CH_Struct SBUS_CH;
int command[20]; //遥控器数据
//将sbus信号转化为通道值
u8 update_sbus(u8 *buf)
{
int i;
for (i=0;i<25;i++)
SBUS_CH.signal[i] = buf[i];
if (buf[23] == SBUS_CONNECT_FLAG)
{
SBUS_CH.ConnectState = 1;
SBUS_CH.CH1 = ((int16_t)buf[ 1] >> 0 | ((int16_t)buf[ 2] << 8 )) & 0x07FF;
SBUS_CH.CH2 = ((int16_t)buf[ 2] >> 3 | ((int16_t)buf[ 3] << 5 )) & 0x07FF;
SBUS_CH.CH3 = ((int16_t)buf[ 3] >> 6 | ((int16_t)buf[ 4] << 2 ) | (int16_t)buf[ 5] << 10 ) & 0x07FF;
SBUS_CH.CH4 = ((int16_t)buf[ 5] >> 1 | ((int16_t)buf[ 6] << 7 )) & 0x07FF;
SBUS_CH.CH5 = ((int16_t)buf[ 6] >> 4 | ((int16_t)buf[ 7] << 4 )) & 0x07FF;
SBUS_CH.CH6 = ((int16_t)buf[ 7] >> 7 | ((int16_t)buf[ 8] << 1 ) | (int16_t)buf[9] << 9 ) & 0x07FF;
SBUS_CH.CH7 = ((int16_t)buf[ 9] >> 2 | ((int16_t)buf[10] << 6 )) & 0x07FF;
SBUS_CH.CH8 = ((int16_t)buf[10] >> 5 | ((int16_t)buf[11] << 3 )) & 0x07FF;
SBUS_CH.CH9 = ((int16_t)buf[12] << 0 | ((int16_t)buf[13] << 8 )) & 0x07FF;
SBUS_CH.CH10 = ((int16_t)buf[13] >> 3 | ((int16_t)buf[14] << 5 )) & 0x07FF;
SBUS_CH.CH11 = ((int16_t)buf[14] >> 6 | ((int16_t)buf[15] << 2 ) | (int16_t)buf[16] << 10 ) & 0x07FF;
SBUS_CH.CH12 = ((int16_t)buf[16] >> 1 | ((int16_t)buf[17] << 7 )) & 0x07FF;
SBUS_CH.CH13 = ((int16_t)buf[17] >> 4 | ((int16_t)buf[18] << 4 )) & 0x07FF;
SBUS_CH.CH14 = ((int16_t)buf[18] >> 7 | ((int16_t)buf[19] << 1 ) | (int16_t)buf[20] << 9 ) & 0x07FF;
SBUS_CH.CH15 = ((int16_t)buf[20] >> 2 | ((int16_t)buf[21] << 6 )) & 0x07FF;
SBUS_CH.CH16 = ((int16_t)buf[21] >> 5 | ((int16_t)buf[22] << 3 )) & 0x07FF;
return 1;
}
else
{
SBUS_CH.ConnectState = 0;
return 0;
}
}
//将sbus信号通道值转化为PWM的数值 [1000,2000]
u16 sbus_to_pwm(u16 sbus_value)
{
float pwm;
pwm = (float)SBUS_TARGET_MIN + (float)(sbus_value - SBUS_RANGE_MIN) * SBUS_SCALE_FACTOR;
// 1000 300 1000/1400
if (pwm > 2000) pwm = 2000;
if (pwm < 1000) pwm = 1000;
return (u16)pwm;
}
//将sbus信号通道值转化为特定区间的数值 [p_min,p_max]
float sbus_to_Range(u16 sbus_value, float p_min, float p_max)
{
float p;
p = p_min + (float)(sbus_value - SBUS_RANGE_MIN) * (p_max-p_min)/(float)(SBUS_RANGE_MAX - SBUS_RANGE_MIN);
if (p > p_max) p = p_max;
if (p < p_min) p = p_min;
return p;
}
这个文件的核心是实现update_sbus()函数,在串口中断函数里面会调用这个函数,用于更新SBUS信号值,后面的sbus_to_pwm()函数在main.c中的通信任务里会调用,用于将SBUS信号转换为PWM值。
uart.h的内容如下
#ifndef _USART_H
#define _USART_H
#include "sys.h"
#include "stdio.h"
#include "pid.h"
#define USART_REC_LEN 100 //定义最大接收字节数 200
#define RXBUFFERSIZE 1 //缓存大小
//串口1
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收
extern u8 USART1_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u16 USART1_RX_STA; //接收状态标记
extern UART_HandleTypeDef UART1_Handler; //UART句柄
extern u8 aRxBuffer1[RXBUFFERSIZE];//HAL库USART接收Buffer
//串口2
#define EN_USART2_RX 1 //使能(1)/禁止(0)串口1接收
extern u8 USART2_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u16 USART2_RX_STA; //接收状态标记
extern UART_HandleTypeDef UART2_Handler; //UART句柄
extern u8 aRxBuffer2[RXBUFFERSIZE];//HAL库USART接收Buffer
//串口3
#define EN_USART3_RX 1 //使能(1)/禁止(0)串口1接收
extern u8 USART3_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u16 USART3_RX_STA; //接收状态标记
extern UART_HandleTypeDef UART3_Handler; //UART句柄
extern u8 aRxBuffer3[RXBUFFERSIZE];//HAL库USART接收Buffer
//如果想串口中断接收,请不要注释以下宏定义
void uart1_init(u32 bound);
//如果想串口中断接收,请不要注释以下宏定义
void uart2_init(u32 bound);
void uart3_init(u32 bound);
#endif
这里我们将会使用到三个串口,串口1用于解析SBUS信号,串口2用于读取姿态传感器数据,串口3在不久的将来用于地面站通信(小车系列不涉及)。
uart.c的内容如下
#include "usart.h"
#include "sys.h"
#include <iwdg.h>
#include "pid.h"
#include "HT905.h"
#include "string.h"
#include "sbus.h"
//
//如果使用os,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h" //os 使用
#endif
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
//#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while ((USART1->ISR & 0X40) == 0)
; //循环发送,直到发送完毕
USART1->TDR = (u8)ch;
return ch;
}
#endif
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART1_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART1_RX_STA = 0; //接收状态标记
u8 aRxBuffer1[RXBUFFERSIZE]; //HAL库使用的串口接收缓冲
UART_HandleTypeDef UART1_Handler; //UART句柄
//串口2中断服务程序
u8 USART2_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
u16 USART2_RX_STA=0; //接收状态标记
u8 aRxBuffer2[RXBUFFERSIZE];//HAL库使用的串口接收缓冲
UART_HandleTypeDef UART2_Handler; //UART句柄
//串口3
u8 USART3_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
u16 USART3_RX_STA; //接收状态标记
UART_HandleTypeDef UART3_Handler; //UART句柄
u8 aRxBuffer3[RXBUFFERSIZE];//HAL库USART接收Buffer
//初始化IO 串口1
//bound:波特率
void uart1_init(u32 bound)
{
//UART 初始化设置
UART1_Handler.Instance = USART1; //USART1
UART1_Handler.Init.BaudRate = bound; //波特率
UART1_Handler.Init.WordLength = UART_WORDLENGTH_9B; //字长为9位数据格式
UART1_Handler.Init.StopBits = UART_STOPBITS_1; //一个停止位
UART1_Handler.Init.Parity = UART_PARITY_EVEN; //偶校验位
UART1_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控
UART1_Handler.Init.Mode = UART_MODE_TX_RX; //收发模式
HAL_UART_Init(&UART1_Handler); //HAL_UART_Init()会使能UART1
HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer1, RXBUFFERSIZE); //该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量
}
void uart2_init(u32 bound)
{
//UART3 初始化设置
UART2_Handler.Instance=USART2; //USART1
UART2_Handler.Init.BaudRate=bound; //波特率
UART2_Handler.Init.WordLength=UART_WORDLENGTH_8B; //字长为8位数据格式
UART2_Handler.Init.StopBits=UART_STOPBITS_1; //一个停止位
UART2_Handler.Init.Parity=UART_PARITY_NONE; //无奇偶校验位
UART2_Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE; //无硬件流控
UART2_Handler.Init.Mode=UART_MODE_TX_RX; //收发模式
HAL_UART_Init(&UART2_Handler); //HAL_UART_Init()会使能UART2
HAL_UART_Receive_IT(&UART2_Handler, (u8 *)aRxBuffer2, RXBUFFERSIZE);//该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量
}
//串口3解析SBUS信号,100k波特率,2个停止位,偶校验
void uart3_init(u32 bound)
{
//UART3 初始化设置
UART3_Handler.Instance=USART3; //USART1
UART3_Handler.Init.BaudRate=bound; //波特率
UART3_Handler.Init.WordLength=UART_WORDLENGTH_8B; //字长为8位数据格式
UART3_Handler.Init.StopBits=UART_STOPBITS_1; //2个停止位
UART3_Handler.Init.Parity=UART_PARITY_NONE; //偶校验位
UART3_Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE; //无硬件流控
UART3_Handler.Init.Mode=UART_MODE_TX_RX; //收发模式
HAL_UART_Init(&UART3_Handler); //HAL_UART_Init()会使能UART1
HAL_UART_Receive_IT(&UART3_Handler, (u8 *)aRxBuffer3, RXBUFFERSIZE);//该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量
}
//UART底层初始化,时钟使能,引脚配置,中断配置
//此函数会被HAL_UART_Init()调用
//huart:串口句柄
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_Initure;
if (huart->Instance == USART1) //如果是串口1,进行串口1 MSP初始化
{
__HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOA时钟
__HAL_RCC_USART1_CLK_ENABLE(); //使能USART1时钟
GPIO_Initure.Pin = GPIO_PIN_9; //PA9
GPIO_Initure.Mode = GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull = GPIO_PULLUP; //上拉
GPIO_Initure.Speed = GPIO_SPEED_FAST; //高速
GPIO_Initure.Alternate = GPIO_AF7_USART1; //复用为USART1
HAL_GPIO_Init(GPIOA, &GPIO_Initure); //初始化PA9
GPIO_Initure.Pin = GPIO_PIN_10; //PA10
HAL_GPIO_Init(GPIOA, &GPIO_Initure); //初始化PA10
#if EN_USART1_RX
HAL_NVIC_EnableIRQ(USART1_IRQn); //使能USART1中断通道
HAL_NVIC_SetPriority(USART1_IRQn, 3, 2); //抢占优先级3,子优先级3
#endif
}
if(huart->Instance==USART2)//如果是串口3,进行串口3 MSP初始化
{
__HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOB时钟
__HAL_RCC_USART2_CLK_ENABLE(); //使能USART1时钟
GPIO_Initure.Pin=GPIO_PIN_2; //PA2 TX
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST; //高速
GPIO_Initure.Alternate=GPIO_AF7_USART2; //复用为USART3
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化PA9
GPIO_Initure.Pin=GPIO_PIN_3; //PA3 RX
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化PA10
#if EN_USART2_RX
HAL_NVIC_EnableIRQ(USART2_IRQn); //使能USART1中断通道
HAL_NVIC_SetPriority(USART2_IRQn,3,3); //抢占优先级3,子优先级3
#endif
}
if(huart->Instance==USART3)//如果是串口3,进行串口3 MSP初始化
{
__HAL_RCC_GPIOB_CLK_ENABLE(); //使能GPIOB时钟
__HAL_RCC_USART3_CLK_ENABLE(); //使能USART1时钟
GPIO_Initure.Pin=GPIO_PIN_10; //PB10 TX
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST; //高速
GPIO_Initure.Alternate=GPIO_AF7_USART3; //复用为USART3
HAL_GPIO_Init(GPIOB,&GPIO_Initure); //初始化PA9
GPIO_Initure.Pin=GPIO_PIN_11; //PB11 RX
HAL_GPIO_Init(GPIOB,&GPIO_Initure); //初始化PA10
#if EN_USART3_RX
HAL_NVIC_EnableIRQ(USART3_IRQn); //使能USART1中断通道
HAL_NVIC_SetPriority(USART3_IRQn,2,1); //抢占优先级3,子优先级3
#endif
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
int i;
while (huart->Instance == USART1) //如果是串口1
{
USART1_RX_BUF[USART1_RX_STA] = aRxBuffer1[0];
if (USART1_RX_STA == 0 && USART1_RX_BUF[USART1_RX_STA] != 0x0F) break; //帧头不对,丢掉
USART1_RX_STA++;
if (USART1_RX_STA > USART_REC_LEN) USART1_RX_STA = 0; ///接收数据错误,重新开始接收
// if (USART1_RX_BUF[0] == 0x0F && USART1_RX_BUF[24] == 0x00 && USART1_RX_STA == 25) //接受完一帧数据
if (USART1_RX_BUF[0] == 0x0F && USART1_RX_STA == 25) //接受完一帧数据
{
update_sbus(USART1_RX_BUF);
for (i = 0; i<25; i++)
{
USART1_RX_BUF[i] = 0;
}
USART1_RX_STA = 0;
#ifdef ENABLE_IWDG
IWDG_Feed(); //喂狗 //超过时间没有收到遥控器的数据会复位
#endif
}
break;
}
if (huart->Instance==USART2)//如果是串口2
{
}
if (huart->Instance==USART3)//如果是串口3
{
}
}
//串口1中断服务程序
void USART1_IRQHandler(void)
{
u32 timeout = 0;
u32 maxDelay = 0x1FFFF;
#if SYSTEM_SUPPORT_OS //使用OS
OSIntEnter();
#endif
HAL_UART_IRQHandler(&UART1_Handler); //调用HAL库中断处理公用函数
timeout = 0;
while (HAL_UART_GetState(&UART1_Handler) != HAL_UART_STATE_READY) //等待就绪
{
timeout++; 超时处理
if (timeout > maxDelay)
break;
}
timeout = 0;
while (HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer1, RXBUFFERSIZE) != HAL_OK) //一次处理完成之后,重新开启中断并设置RxXferCount为1
{
timeout++; //超时处理
if (timeout > maxDelay)
break;
}
#if SYSTEM_SUPPORT_OS //使用OS
OSIntExit();
#endif
}
//串口2中断服务程序
void USART2_IRQHandler(void)
{
u32 timeout=0;
u32 maxDelay=0x1FFFF;
#if SYSTEM_SUPPORT_OS //使用OS
OSIntEnter();
#endif
HAL_UART_IRQHandler(&UART2_Handler); //调用HAL库中断处理公用函数
timeout=0;
while (HAL_UART_GetState(&UART2_Handler)!=HAL_UART_STATE_READY)//等待就绪
{
timeout++;超时处理
if(timeout>maxDelay) break;
}
timeout=0;
while(HAL_UART_Receive_IT(&UART2_Handler,(u8 *)aRxBuffer2, RXBUFFERSIZE)!=HAL_OK)//一次处理完成之后,重新开启中断并设置RxXferCount为1
{
timeout++; //超时处理
if(timeout>maxDelay) break;
}
#if SYSTEM_SUPPORT_OS //使用OS
OSIntExit();
#endif
}
//串口3中断服务程序
void USART3_IRQHandler(void)
{
u32 timeout=0;
u32 maxDelay=0x1FFFF;
#if SYSTEM_SUPPORT_OS //使用OS
OSIntEnter();
#endif
HAL_UART_IRQHandler(&UART3_Handler); //调用HAL库中断处理公用函数
timeout=0;
while (HAL_UART_GetState(&UART3_Handler)!=HAL_UART_STATE_READY)//等待就绪
{
timeout++;超时处理
if(timeout>maxDelay) break;
}
timeout=0;
while(HAL_UART_Receive_IT(&UART3_Handler,(u8 *)aRxBuffer3, RXBUFFERSIZE)!=HAL_OK)//一次处理完成之后,重新开启中断并设置RxXferCount为1
{
timeout++; //超时处理
if(timeout>maxDelay) break;
}
#if SYSTEM_SUPPORT_OS //使用OS
OSIntExit();
#endif
}
uart.c里面就是实现串口的驱动和串口中断函数的编写。这里串口2和串口3中断函数都是空的,后续章节会补上,咱们一步步来。看串口一的中断函数,这段不多讲了,先进行数据校验,再进行数据接收和解析,核心如前所述,调用update_sbus()函数更新信号值,这个值在主函数里面会周期性调用。
while (huart->Instance == USART1) //如果是串口1
{
USART1_RX_BUF[USART1_RX_STA] = aRxBuffer1[0];
if (USART1_RX_STA == 0 && USART1_RX_BUF[USART1_RX_STA] != 0x0F) break; //帧头不对,丢掉
USART1_RX_STA++;
if (USART1_RX_STA > USART_REC_LEN) USART1_RX_STA = 0; ///接收数据错误,重新开始接收
// if (USART1_RX_BUF[0] == 0x0F && USART1_RX_BUF[24] == 0x00 && USART1_RX_STA == 25) //接受完一帧数据
if (USART1_RX_BUF[0] == 0x0F && USART1_RX_STA == 25) //接受完一帧数据
{
update_sbus(USART1_RX_BUF);
for (i = 0; i<25; i++)
{
USART1_RX_BUF[i] = 0;
}
USART1_RX_STA = 0;
#ifdef ENABLE_IWDG
IWDG_Feed(); //喂狗 //超过时间没有收到遥控器的数据会复位
#endif
}
break;
}
还要记得在main函数里面调用串口初始化函数,这里串口1用于解析SBUS,波特率是100k。
uart1_init(100000); //串口1初始化
uart2_init(115200); //串口2初始化
uart3_init(115200); //串口3初始化
在上一章实现了SBUS的驱动部分,下面我们写应用部分。还记得上一篇文章里的communicate_task函数吗,现在我们来补充它。
void communicate_task(void *p_arg)
{
OS_ERR err;
CPU_SR_ALLOC();
char remotor_ready = 0; //遥控器通讯是否准备好
char motor_ready = 0; //电机通讯是否正常
u8 sbus_count = 0;
u8 can_count = 0;
SBUS_CH.ConnectState = 0;
//等待遥控器通讯正常,否则一直等待。遥控器校正,初值初始化
while (sbus_count < 10)
{
if (SBUS_CH.ConnectState == 1)
{
sbus_count++;
SBUS_CH.ConnectState = 0;
}
LED0_Toggle;
LED1_Toggle;
delay_ms(100);
}
remotor_ready = 1; //遥控器通讯成功
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); //遥控器就绪LED1常亮
//等待CAN通讯正常,否则一直等待
while (can_count < 10)
{
if (get_moto_measure(&moto_info, &CAN1_Handler))
can_count++;
delay_ms(100);
}
motor_ready = 1;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); //电机就绪LED0常亮
//读遥控器数据
while (1)
{
if (SBUS_CH.CH6 < 400) //速度档位
command[SPEED_MODE] = LOW_SPEED;
else
command[SPEED_MODE] = HIGH_SPEED;
// 左手油门
command[THROTTLE] = sbus_to_pwm(SBUS_CH.CH3);
command[YAW] = sbus_to_pwm(SBUS_CH.CH4);
command[ROLL] = sbus_to_pwm(SBUS_CH.CH1);
command[PITCH] = sbus_to_pwm(SBUS_CH.CH2);
delay_ms(10);
}
}
这段代码很简单,开始时等待遥控器状态正常,然后等待CAN总线通信正常(也就是电机通信正常,因为用的总线电机)。在等待的时候用指示灯指示状态,当然这里可以用多个指示灯,设计酷炫的效果用于显示各种运行状态。等所有的连接都是正常的之后,进入while死循环,每隔10ms读一次遥控器的数据,并转换为PWM值和档位值。这些数据在StabilizationTask姿态控制中会用到,具体如何应用,请听下回分解。
这里做下说明,本系列文章笔者重在分享思想、算法,在讲解上会弱化一些基本知识(比如单片机各个外设的原理、单片机编程的基本知识、操作系统的具体原理等),在代码的粘贴上会忽视一些底层的驱动代码和无关紧要的部分,事实上上面的代码我都经过删减了,只留下了干货。所以可以说面向的是中高级选手,拿来主义者可以打道回府了,本系列文章不开源,不提供源码,请见谅。
文章浏览阅读3.4k次,点赞8次,收藏42次。一、什么是内部类?or 内部类的概念内部类是定义在另一个类中的类;下面类TestB是类TestA的内部类。即内部类对象引用了实例化该内部对象的外围类对象。public class TestA{ class TestB {}}二、 为什么需要内部类?or 内部类有什么作用?1、 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。2、内部类可以对同一个包中的其他类隐藏起来。3、 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。三、 内部类的分类成员内部_成员内部类和局部内部类的区别
文章浏览阅读118次。分布式系统要求拆分分布式思想的实质搭配要求分布式系统要求按照某些特定的规则将项目进行拆分。如果将一个项目的所有模板功能都写到一起,当某个模块出现问题时将直接导致整个服务器出现问题。拆分按照业务拆分为不同的服务器,有效的降低系统架构的耦合性在业务拆分的基础上可按照代码层级进行拆分(view、controller、service、pojo)分布式思想的实质分布式思想的实质是为了系统的..._分布式系统运维工具
文章浏览阅读174次。1.数据源准备2.数据处理step1:数据表处理应用函数:①VLOOKUP函数; ② CONCATENATE函数终表:step2:数据透视表统计分析(1) 透视表汇总不同渠道用户数, 金额(2)透视表汇总不同日期购买用户数,金额(3)透视表汇总不同用户购买订单数,金额step3:讲第二步结果可视化, 比如, 柱形图(1)不同渠道用户数, 金额(2)不同日期..._exce l趋势分析数据量
文章浏览阅读3.3k次。堡垒机可以为企业实现服务器、网络设备、数据库、安全设备等的集中管控和安全可靠运行,帮助IT运维人员提高工作效率。通俗来说,就是用来控制哪些人可以登录哪些资产(事先防范和事中控制),以及录像记录登录资产后做了什么事情(事后溯源)。由于堡垒机内部保存着企业所有的设备资产和权限关系,是企业内部信息安全的重要一环。但目前出现的以下问题产生了很大安全隐患:密码设置过于简单,容易被暴力破解;为方便记忆,设置统一的密码,一旦单点被破,极易引发全面危机。在单一的静态密码验证机制下,登录密码是堡垒机安全的唯一_horizon宁盾双因素配置
文章浏览阅读7.7k次,点赞4次,收藏16次。Chrome作为一款挺不错的浏览器,其有着诸多的优良特性,并且支持跨平台。其支持(Windows、Linux、Mac OS X、BSD、Android),在绝大多数情况下,其的安装都很简单,但有时会由于网络原因,无法安装,所以在这里总结下Chrome的安装。Windows下的安装:在线安装:离线安装:Linux下的安装:在线安装:离线安装:..._chrome linux debian离线安装依赖
文章浏览阅读153次。中国发达城市榜单每天都在刷新,但无非是北上广轮流坐庄。北京拥有最顶尖的文化资源,上海是“摩登”的国际化大都市,广州是活力四射的千年商都。GDP和发展潜力是衡量城市的数字指...
文章浏览阅读3.3k次。前言spark在java使用比较少,多是scala的用法,我这里介绍一下我在项目中使用的代码配置详细算法的使用请点击我主页列表查看版本jar版本说明spark3.0.1scala2.12这个版本注意和spark版本对应,只是为了引jar包springboot版本2.3.2.RELEASEmaven<!-- spark --> <dependency> <gro_使用java调用spark注册进去的程序
文章浏览阅读4.8k次。汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用,代码精简高效,大厂出品有量产保证。:139800617636213023darcy169_uds协议栈 源代码
文章浏览阅读4.6k次,点赞20次,收藏148次。AUTOSAR基础篇之OS(下)前言首先,请问大家几个小小的问题,你清楚:你知道多核OS在什么场景下使用吗?多核系统OS又是如何协同启动或者关闭的呢?AUTOSAR OS存在哪些功能安全等方面的要求呢?多核OS之间的启动关闭与单核相比又存在哪些异同呢?。。。。。。今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是本文的主题大纲:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCXrdI0k-1636287756923)(https://gite_autosar 定义了 5 种多核支持类型
文章浏览阅读2.2k次,点赞6次,收藏14次。原因:自己写的头文件没有被加入到方案的包含目录中去,无法被检索到,也就无法打开。将自己写的头文件都放入header files。然后在VS界面上,右键方案名,点击属性。将自己头文件夹的目录添加进去。_vs2013打不开自己定义的头文件
文章浏览阅读3.3w次,点赞80次,收藏342次。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。当数据量很大时,count 的数量的指定可能会不起作用,Redis 会自动调整每次的遍历数目。_redis命令
文章浏览阅读449次,点赞3次,收藏3次。URP的设计目标是在保持高性能的同时,提供更多的渲染功能和自定义选项。与普通项目相比,会多出Presets文件夹,里面包含着一些设置,包括本色,声音,法线,贴图等设置。全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,主光源和附加光源在一次Pass中可以一起着色。URP:全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,一次Pass可以计算多个光源。可编程渲染管线:渲染策略是可以供程序员定制的,可以定制的有:光照计算和光源,深度测试,摄像机光照烘焙,后期处理策略等等。_urp渲染管线