UAV021(六):系统架构优化、SBUS协议、遥控器控制电机转动

2023-05-16

目录

    • 一、系统架构优化
        • 1.1 从全局变量到API函数
        • 1.2 函数传值
    • 二、SBUS协议读取及解析
        • 2.1 协议格式
        • 2.2 协议解析
        • 2.3 程序设计
            • 2.3.1 头文件一览
            • 2.3.2 串口配置与捕获解析Sbus帧
            • 2.2.3 数据解析细节
            • 2.3.4 遥控器校准与测试
        • 2.4 测试效果
            • 2.4.1 协议采集与解析
            • 2.4.2 遥控器控制电机



系统架构优化部分主要实现设计API代替直接引用全局变量。

Sbus协议是遥控器常用协议,此文将实现读取并解析协议内容。

作为测试,使用遥控器油门控制PWM,调节电机转速。


一、系统架构优化


1.1 从全局变量到API函数

之前实现的程序里定义了几个全局变量,例如全局的时间计时 tim,姿态角结构体 atti 等。全局变量会增强文件之间的关联性,定义、声明、赋值、应用可能在不同的文件,使得变量难以管理。对此,改用API接口的形式,一个简单的例子如下:

在 timer.c 里实现一个每 0.1ms 加1的变量 tim。之前的做法是把此变量作为全局变量使用,也即在 timer.c 里定义并赋值,在 timer.h 里使用 extern 关键词说明,其他文件只要包含 timer.h 即可使用此变量。下面分别是 timer.h, timer.c 和 attitude.c 里面定义和使用 tim 的情况:

// timer.h
extern uint32_t tim;
// timer.c
uint32_t tim;
void TIM6_DAC_IRQHandler(void)
{
	if(__HAL_TIM_GET_IT_SOURCE(&TIM6_Handler, TIM_IT_UPDATE) !=RESET)
    {
      __HAL_TIM_CLEAR_IT(&TIM6_Handler, TIM_IT_UPDATE);				// 清除中断标志位
    }
    tim ++; 														// 又是0.1ms,全局时间计数加1
}
// attitude.c
#include "timer.h"
	Ts = (float)(tim - last_t) / 10.0 / 1000.0;	  	// 0.1ms加1 = 1/10/1000s加1
	last_t = tim;									// 更新时间

这种方式有一定缺点,一是只读到 attitude.c 突然给 last_t 赋值 tim 容易让人迷惑,可能需要跳转到定义后才发现原来这是一个全局变量。对于其他一些变量,我们还想追踪在哪里赋值的,这将让问题更加复杂。二是在 attitude.c 里,我们仍然可以修改 tim 的值,这会让变量变得不安全。还有要避免命名重复,不然会让人脑阔疼的。

使用API函数后,可以有效解决上面的问题。

我们再在 timer.c 里定义一个函数 GetTimeApi(),当然,在头文件里声明:

// timer.h
uint32_t GetTimeApi(void);
// timer.c

uint32 tim_;

// 定时器6中断服务函数
void TIM6_DAC_IRQHandler(void)
{
	if(__HAL_TIM_GET_IT_SOURCE(&TIM6_Handler, TIM_IT_UPDATE) !=RESET)
    {
      __HAL_TIM_CLEAR_IT(&TIM6_Handler, TIM_IT_UPDATE);				// 清除中断标志位
    }
    tim_ ++; 														// 又是0.1ms,全局时间计数加1
}

/* 获取全局时间接口 */
uint32_t GetTimeApi(void)
{
	return tim_;
}
// attitude.c
#include "timer.h"
	Ts = (float)(GetTimeApi() - last_t) / 10.0 / 1000.0;	  		// 0.1ms加1 = 1/10/1000s加1
	last_t = GetTimeApi();			// 更新时间

做两个小小的约定,提供全局变量的函数以 Api 结束,全局变量以 下划线结束。

此时,使用函数的方式代替变量,程序可读性、安全性和独立性都增强,是一个不错的选择。


1.2 函数传值

注意函数的定义问题,以姿态结构体的传递为例,比较以下两个函数:

struct ATTI_t atti_;
/* 获取姿态接口	*/
void GetAttiApi(struct ATTI_t *atti)
{
	atti->theta = atti_.theta;
	atti->phi = atti_.phi;
	atti->psi = atti_.psi;
}
struct ATTI_t atti_;
/* 获取姿态接口	*/
void GetAttiApi(struct ATTI_t atti)
{
	atti.theta = atti_.theta;
	atti.phi = atti_.phi;
	atti.psi = atti_.psi;
}

我们希望的是传入GetAttiApi() 函数的结构体变量 atti 能够获取真实姿态 atti_ 的数据。第一种定义,使用指针的方式是有效的;第二种定义无效,atti 作为形参,函数调用结束后即被释放,不能达到预期效果。

因此,一般我们都采用指针来传值。数组和指针有一样的效果,因为数组名就是指向该数组第一个数值得指针,以下两段程序是等价的:

/* 获取三轴加速度接口	*/
void GetAccelDataApi(float acc[3])
{
	acc[0] = acc_[0];
	acc[1] = acc_[1];
	acc[2] = acc_[2];
}
/* 获取三轴加速度接口	*/
void GetAccelDataApi(float *acc)
{
	acc[0] = acc_[0];
	acc[1] = acc_[1];
	acc[2] = acc_[2];
}

二、SBUS协议读取及解析


2.1 协议格式

协议帧很简洁,一帧包括25字节数据:

首部(1字节)+ 数据(22字节)+ 标志位(1字节)+ 结束符(1字节)

  1. 首部:0x0F
  2. 数据位:22 字节的数据,分别代表16个通道的数据,也即是每个通道的值用了 11 位来表示,22x8 = 11x16。这样,每个通道的取值范围为 0~2047。
  3. 标志位:1字节,高四位从高到低依次表示:CH18数字通道、CH17数字通道、帧丢失(Frame lost)、安全保护(Failsafe),低四位不用。
  4. 结束符:0x00

这里容易有一个思维定势,就是里面的22个数据是从头到尾每11位作为一个通道的。认真看协议解析容易发现刚好是相反的(吐槽ing),是从尾到头每11位放一块。请看这张经典图片(全网几乎只此一张):

并不是第一个字节与第二个字节的高三位组合在一起,而是与低三位。其实,反过来看就对劲了:
第三个字节的12被拿走了,于是有345678,不够从第二个字节拿,又拿了12345;
第二个字节被拿走了黄色的,只剩 678了,没有了继续从第一个字节拿,拿到了12345678。


2.2 协议解析

整个协议可用串口进行解析:

8位数据
2位停止位
1位校验位
波特率100kHz

这个100kHz是非标准的,一般的串口助手不支持,只能解析出来之后再使用串口打印(吐槽ing)。

Sbus协议里,使用TTL电平,高电平(3.3V)代表逻辑 ‘0’,低电平代表逻辑 ‘1’,逻辑反了无所谓,取个反不就可以吗?还真不可以。虽然网上都说要硬件取反,还是抱着侥幸心理试一试,果然不行。非要硬件取反一下,一般的接收机也不带这功能,简直是个设计bug(吐槽ing)。

硬件取反电路如下,实际上就是一个很简单的三极管电路。Sbus的信号从基极输入,从集电极输出。基极输入 ‘0’,集电极上拉输出 ‘1’;基极输入 ‘1’,三极管导通,输出被拉低为 ‘0’,实现了反向。

不过为什么软件直接取反不行呢?还是没有想清楚,目前个人理解是接收机输出的驱动不足,或者和单片机引脚电阻不匹配?只能通过通过三极管放大来驱动引脚?暂不猜测,继续往下。总之吐槽了三次,觉得这个协议没有多少人性化的地方,它的成功或许是靠着强大的商业资本吧。

知道了规则,便可以使用串口进行解析了,请看程序。


2.3 程序设计

2.3.1 头文件一览

先看头文件,可见此文件主要功能:

  1. 包括两个宏定义、两个结构体,SBUS_t 用于存储一帧数据,MC6C_t 专门针对 MC6C遥控器,仅六通道数据。

  2. 后面还有 SBUS硬件初始化,也即配置串口2的函数;Sbus协议解析任务;遥控器校准函数,也即将遥控数据映射到想要区间;测试遥控器控制电机任务。

  3. 最后是两个API函数,向外提供遥控器数据。

#ifndef SBUS_H
#define SBUS_H

#include "sys.h"

#define USART_BUF_SIZE		4		// HAL库USART接收Buffer大小
#define SBUS_DATA_SIZE	 	25 		// 25字节

/* SBUS协议帧	*/
struct SBUS_t
{
	uint8_t head;					// 1字节首部
	uint16_t ch[16];				// 16个字节数据
	uint8_t flag;					// 1字节标志位
	uint8_t end;					// 1字节结束
};

/* MC6C遥控器只有六个通道,只使用了SBUS协议帧的部分数据	*/
struct MC6C_t
{
	float ail;						// CH1, aileron, 副翼,调节滚转角
	float ele;						// CH2, elevator, 升降,调节俯仰角
	float thr;						// CH3, throttle, 油门
	float rud;						// CH4, rudder, 方向舵,调节偏航角
	uint16_t ch5;					// CH5, 最终只三档, 取值 1,2,3
	uint16_t ch6;					// CH6, 最终值两档, 取值 1,2
};

void SBUS_Init(void);						// SBUS硬件初始化
void SbusParseTask(void *arg);				// 解析遥控器接收的数据
void CaliMc6cData(struct MC6C_t *mc6c); 	// 遥控器校准,将遥控器接收数据映射到想要区间
void TestCtrlMotorTask(void *arg);			// 测试遥控器控制电机任务

void GetSbusDataApi(struct SBUS_t *sbus);	// Sbus数据调用接口
void GetMc6cDataApi(struct MC6C_t *mc6c);	// 针对MC6C遥控器,MC6C遥控器数据接口

#endif
2.3.2 串口配置与捕获解析Sbus帧

之前使用了 USART1,用于调试打印,此处使用 USART2。

串口配置流程如下:

  1. 时钟使能。使能USART2时钟及对应GPIO时钟;
  2. 串口初始化配置。选择串口、设置波特率、字长、停止位、校验位、是否需要硬件流和输入/输出模式。
  3. 引脚配置。配置对应的GPIO引脚,注意对于 RX 引脚也不要配置输入模式,要配置为复用推挽输出。
  4. 中断配置。配置中断优先级并使能中断。
  5. 中断服务函数。实现捕获协议帧,存储在一个buffer之中。
// sbus.c USART2 配置、中断,Sbus协议捕获、解析

#include "sbus.h"
#include "delay.h"
#include "define.h"						
#include "pwm.h"

struct SBUS_t sbus_;					// SBUS接收数据全局变量

uint8_t usart_buf[USART_BUF_SIZE];
uint8_t sbus_rx_head = 0;				// 发现起始字节 0x0F
uint8_t sbus_rx_sta = 0;				// sbus_ 接收状态,0:未完成,1:已完成一帧接收
uint8_t sbus_rx_index = 0;				// 接收字节计数
uint8_t sbus_rx_buf[SBUS_DATA_SIZE];	// 接收sbus_数据缓冲区

UART_HandleTypeDef UART2_Handler;		// 串口2配置句柄

/* Sbus初始化,包括时钟、串口配置、引脚和中断配置*/
void SBUS_Init(void)
{
	GPIO_InitTypeDef GPIO_Initure;
	
	// 时钟使能
	SBUS_ENCLK();
	
	// 串口初始化配置
	// 波特率100kbps,8位数据,偶校验(even),2位停止位,无流控。
	UART2_Handler.Instance 			= USART2;
	UART2_Handler.Init.BaudRate 	= 100000;
	UART2_Handler.Init.WordLength 	= UART_WORDLENGTH_8B;
	UART2_Handler.Init.StopBits 	= UART_STOPBITS_2;
	UART2_Handler.Init.Parity 		= UART_PARITY_EVEN;
	UART2_Handler.Init.HwFlowCtl 	= UART_HWCONTROL_NONE;
	UART2_Handler.Init.Mode 		= UART_MODE_TX_RX;
	
	// 引脚 配置
	GPIO_Initure.Pin 		= SBUS_PIN;			// PA2--TX, PA3--RX
	GPIO_Initure.Mode 		= GPIO_MODE_AF_PP;
	GPIO_Initure.Pull 		= GPIO_PULLUP;
	GPIO_Initure.Speed 		= GPIO_SPEED_HIGH;
	GPIO_Initure.Alternate 	= GPIO_AF7_USART2;
	HAL_GPIO_Init(GPIOA, &GPIO_Initure);
	
	// 中断配置
	HAL_NVIC_EnableIRQ(USART2_IRQn);
	HAL_NVIC_SetPriority(USART2_IRQn, 3, 2);
	
	HAL_UART_Init(&UART2_Handler);					    						//HAL_UART_Init()会使能UART2
	HAL_UART_Receive_IT(&UART2_Handler, (uint8_t *)usart_buf, USART_BUF_SIZE);	//该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量
}

/* USART2 中断服务函数															*/ 
/* 实现对S.BUS协议缓存,头部为 0x0F,结尾为 0x00, 中间22Bytes16通道数据,1Byte标志符	*/
void USART2_IRQHandler(void)											//中断函数
{
	uint8_t chr;
	if ((__HAL_UART_GET_FLAG(&UART2_Handler, UART_FLAG_RXNE) != RESET))	// 接收中断
	{
		HAL_UART_Receive(&UART2_Handler, &chr, 1, 1000);				// 接收一个字符
		if (sbus_rx_sta == 0)											// 接收未完成
		{
			if ((chr == 0x0F) || sbus_rx_head)							// 找到首字节或已经找到首字节
			{
				sbus_rx_head = 1;										// 标明已经找到首字母
				if (sbus_rx_index < SBUS_DATA_SIZE)						// 未接收到25个字符
				{
					sbus_rx_buf[sbus_rx_index] = chr;					// 不断接收
					sbus_rx_index ++;
				}
				else													// 接收到25个字符了
				{
					sbus_rx_sta = 1;									// 接收完成
					sbus_rx_head = 0;									// 清零,准备下一次接收
					sbus_rx_index = 0;
				}	
			}
		}
	}
	HAL_UART_IRQHandler(&UART2_Handler);
}

/* 对SBUS协议数据进行解析														*/
/* 实现对S.BUS协议缓存,头部为 0x0F,结尾为 0x00, 中间22Bytes16通道数据,1Byte标志符	*/
void SbusParseTask(void *arg)
{
	while (1)
	{
		if(sbus_rx_sta==1)							// 接收完一帧
		{
			NVIC_DisableIRQ(USART2_IRQn);			// 要关闭中断,防止读写混乱
			
			sbus_.head = sbus_rx_buf[0];			// 首部
			sbus_.flag = sbus_rx_buf[23];			// 标志符
			sbus_.end  = sbus_rx_buf[24];			// 结尾

			sbus_.ch[0] =((uint16_t)(sbus_rx_buf[2]<<8)  | (uint16_t)(sbus_rx_buf[1])) & 0x07ff;           
			sbus_.ch[1] =((uint16_t)(sbus_rx_buf[3]<<5)  | (uint16_t)(sbus_rx_buf[2]>>3)) & 0x07ff;
			sbus_.ch[2] =((uint32_t)(sbus_rx_buf[5]<<10) | (uint32_t)(sbus_rx_buf[4]<<2) | (sbus_rx_buf[3]>>6)) & 0x07ff;			
			sbus_.ch[3] =((uint16_t)(sbus_rx_buf[6]<<7)  | (uint16_t)(sbus_rx_buf[5]>>1)) & 0x07ff;
			sbus_.ch[4] =((uint16_t)(sbus_rx_buf[7]<<4)  | (sbus_rx_buf[6]>>4)) & 0x07ff;
			sbus_.ch[5] =((uint16_t)(sbus_rx_buf[9]<<9)  | (uint16_t)(sbus_rx_buf[8]<<1) | (sbus_rx_buf[7]>>7)) & 0x07ff;  
			sbus_.ch[6] =((uint16_t)(sbus_rx_buf[10]<<6) | (uint16_t)(sbus_rx_buf[9]>>2)) & 0x07ff;
			sbus_.ch[7] =((uint16_t)(sbus_rx_buf[11]<<3) | (uint16_t)(sbus_rx_buf[10]>>5)) & 0x07ff;
			sbus_.ch[8] =((uint16_t)(sbus_rx_buf[13]<<8) | (uint16_t)sbus_rx_buf[12]) & 0x07ff;
			sbus_.ch[9] =((uint16_t)(sbus_rx_buf[14]<<5) | (uint16_t)(sbus_rx_buf[13]>>3)) & 0x07ff;
			sbus_.ch[10]=((uint16_t)(sbus_rx_buf[16]<<10)| (uint16_t)(sbus_rx_buf[15]<<2) | (sbus_rx_buf[14]>>6)) & 0x07ff;
			sbus_.ch[11]=((uint16_t)(sbus_rx_buf[17]<<7) | (uint16_t)(sbus_rx_buf[16]>>1)) & 0x07ff;
			sbus_.ch[12]=((uint16_t)(sbus_rx_buf[18]<<4) | (uint16_t)(sbus_rx_buf[17]>>4)) & 0x07ff;
			sbus_.ch[13]=((uint16_t)(sbus_rx_buf[20]<<9) | (uint16_t)(sbus_rx_buf[19]<<1) | (sbus_rx_buf[18]>>7)) & 0x07ff;
			sbus_.ch[14]=((uint16_t)(sbus_rx_buf[21]<<6) | (uint16_t)(sbus_rx_buf[20]>>2)) & 0x07ff;
			sbus_.ch[15]=((uint16_t)(sbus_rx_buf[22]<<3) | (uint16_t)(sbus_rx_buf[21]>>5)) & 0x07ff;

			printf("======================================\r\n");
			printf("正常: head=0x0F, flag=0x00, end=0x00\r\n\r\n");
			printf("head: %d\r\n", sbus_.head);
			printf("  %d, %d, %d, %d\r\n", sbus_.ch[0], sbus_.ch[1], sbus_.ch[2], sbus_.ch[3]);
			printf("  %d, %d, %d, %d\r\n", sbus_.ch[4], sbus_.ch[5], sbus_.ch[6], sbus_.ch[7]);
			printf("  %d, %d, %d, %d\r\n", sbus_.ch[8], sbus_.ch[9], sbus_.ch[10], sbus_.ch[11]);
			printf("  %d, %d, %d, %d\r\n", sbus_.ch[12], sbus_.ch[13], sbus_.ch[14], sbus_.ch[15]);
			printf("flag: %d\r\n", sbus_.flag);
			printf("end: %d\r\n", sbus_.end);
			printf("======================================\r\n\r\n");
			
			delay_ms(100);							// 先做完延时再开启中断与下一次捕获,否则延时期间中断到来,没有达到预期效果
			
			NVIC_EnableIRQ(USART2_IRQn);			// 打开串口中断
			sbus_rx_sta = 0;						// 准备下一次接收
		}
		else
		{			
			delay_ms(100);							// 免得异常时,到此处使得低优先级任务无法执行
		}
	}
}
2.2.3 数据解析细节

注意到 SbusParseTask() 里面延时的位置。正常的思维是放在while(1) 的最后一行,也即 if else外,此处就不行了(为此冥思苦想了几个小时,排除各种可能,偶然解决问题后才想通)。

如果把延时放在最后 if else外,逻辑是这样的:解析完此帧后,使能中断,这个函数还在延时100ms的路途中,接收机的数据蜂拥而至,一直发一直发,不出bug就不正常了。

但是把这100ms放在 if else内的开启中断和接收下一帧前,不过这 100ms,打死也进不来中断的,实现了解析完一帧,休息一下,再解析下一帧的目的,这是我们预期的效果。

2.3.4 遥控器校准与测试

上面的内容以及成功获取遥控器指令,存储在 sbus_ 结构体之中。比如油门(第三通道)数据,取值可能在196 ~1289之间。这个数据不能直接使用,现在我们希望将这个数据转化在 400 ~ 800(程序里有解释)之间,用于直接调节电机占空比。因此我们需要做一个线性变化,将油门的数据变化到我们想要的区间。其他通道亦如此,暂且习惯性地叫做“校准”吧。

除此之外,我们设计遥控器控制电机的任务,也即读取遥控器油门数据,转化为PWM波控制电机转速。两个API接口函数也在此,不再赘述。

// sbus.c 遥控器校准与控制电机测试部分 
/* 
CH1 -- 俯仰角, 归中0°, 最大最小 ±30°
CH2 -- 滚转角, 归中0°, 最大最小 ±30°
CH3 -- 油门, 归中0°, 最大设置占空比 80%, 最小设置占空比 40%, 电调驱动频率为400Hz=2.5ms, 40%=1ms, 80%=2ms
CH4 -- 偏航角角速度, 归中0°/s, 最大最小 ±36°/s 
CH5 -- 档位, 上中下分别为 1, 2, 3三档
CH6 -- 档位, 上下分别为 1, 2两档								
*/

void CaliMc6cData(struct MC6C_t *mc6c)
{
	static const float mc6c_min[6] = {64,   174,  196,   129,  193, 200};	// 转动摇杆,各通道最小值,本为 uint16_t,为方便计算直接为 float
	static const float mc6c_max[6] = {1812, 1800, 1289, 1833, 1973, 1544};	// 转动摇杆,各通道最大值
	static const float mc6c_mid[6] = {1030, 1001, 489, 948, 996, 200};		// CH6 只有两通道
	
	float k;
	float b;
	
	// CH1 映射, [64, 894] --> [-30, 0]; [894, 1812] --> [0 30]
	if (mc6c->ail < mc6c_mid[0])
	{
		k = (0 - (-30)) / (mc6c_mid[0] - mc6c_min[0]);
		b = 0 - k * mc6c_mid[0];
		mc6c->ail = k * mc6c->ail + b;
	}
	else
	{
		k = (30 - 0) / (mc6c_max[0] - mc6c_mid[0]);
		b = 0 - k * mc6c_mid[0];
		mc6c->ail = k * mc6c->ail + b;
	}
	
	// CH2 映射, [174, 1001] --> [-30, 0]; [1001, 1800] --> [0 30]
	if (mc6c->ele < mc6c_mid[1])
	{
		k = (0 - (-30)) / (mc6c_mid[1] - mc6c_min[1]);
		b = 0 - k * mc6c_mid[1];
		mc6c->ele = k * mc6c->ele + b;	
	}
	else
	{
		k = (30 - 0) / (mc6c_max[1] - mc6c_mid[1]);
		b = 0 - k * mc6c_mid[1];
		mc6c->ele = k * mc6c->ele + b;
	}	
	
	// CH3 映射, [196, 1289] --> [400, 800]
	if (mc6c->thr < mc6c_min[2])
		mc6c->thr = mc6c_min[2];
	else if (mc6c->thr > mc6c_max[2])
		mc6c->thr = mc6c_max[2];
	else
	{
		k = (800 - 400) / (mc6c_max[2] - mc6c_min[2]);
		b = 400 - k * mc6c_min[2];
		mc6c->thr = k * mc6c->thr + b;
	}
	
	// CH4 映射,[129, 948] --> [-36, 0]; [948, 1833] --> [0, 36]
	if (mc6c->rud < mc6c_mid[3])
	{
		k = (0 - (-36)) / (mc6c_mid[3] - mc6c_min[3]);
		b = 0 - k * mc6c_mid[3];
		mc6c->rud = k * mc6c->rud + b;
	}
	else
	{
		k = (36 - 0) / (mc6c_max[3] - mc6c_mid[3]);
		b = 0 - k * mc6c_mid[3];
		mc6c->rud = k * mc6c->rud + b;
	}
	
	// CH5 映射,得到三档分别赋值 1,2,3
	if (mc6c->ch5 < (mc6c_min[4] + mc6c_mid[4]) / 2)
		mc6c->ch5 = 1;
	else if (mc6c->ch5 < (mc6c_mid[4] + mc6c_max[4]) / 2)
		mc6c->ch5 = 2;
	else
		mc6c->ch5 = 3;
	
	// CH6 映射,得到两档分别赋值 1,2
	if (mc6c->ch6 < (mc6c_min[5] + mc6c_max[5]) / 2)
		mc6c->ch6 = 1;
	else
		mc6c->ch6 = 2;
}


/* MC6C遥控器数据接口		*/
/* 依赖SbusParseTask()任务	*/

void GetMc6cDataApi(struct MC6C_t *mc6c)
{
	mc6c->ail = (float)sbus_.ch[0];
	mc6c->ele = (float)sbus_.ch[1];
	mc6c->thr = (float)sbus_.ch[2];
	mc6c->rud = (float)sbus_.ch[3];
	mc6c->ch5 = sbus_.ch[4];
	mc6c->ch6 = sbus_.ch[5];
	
	CaliMc6cData(mc6c);
}

/* 获取遥控器数据接口	*/
/* 目前未使用此函数		*/
void GetSbusDataApi(struct SBUS_t *sbus)
{
	sbus->head = sbus_.head;
	sbus->flag = sbus_.flag;
	sbus->end = sbus_.end ;

	sbus->ch[0] = sbus_.ch[0];       
	sbus->ch[1] = sbus_.ch[1]; 
	sbus->ch[2] = sbus_.ch[2]; 
	sbus->ch[3] = sbus_.ch[3]; 
	sbus->ch[4] = sbus_.ch[4]; 
	sbus->ch[5] = sbus_.ch[5]; 
	sbus->ch[6] = sbus_.ch[6]; 
	sbus->ch[7] = sbus_.ch[7]; 
	sbus->ch[8] = sbus_.ch[8];
	sbus->ch[9] = sbus_.ch[9]; 
	sbus->ch[10] = sbus_.ch[10];
	sbus->ch[11] = sbus_.ch[11];
	sbus->ch[12] = sbus_.ch[12];
	sbus->ch[13] = sbus_.ch[13];
	sbus->ch[14] = sbus_.ch[14];
	sbus->ch[15] = sbus_.ch[15];
}

/* 遥控器控制电机测试		*/
/* 遥控器油门将调节电机占空比	*/
void TestCtrlMotorTask(void *arg)
{
	struct MC6C_t mc6c;
	while (1)
	{
		GetMc6cDataApi(&mc6c);
		
		SetMotorDutyApi(MOTOR1, (uint16_t)mc6c.thr);		// MOTOR1, 引脚为TIM3 CH1, PB4
		SetMotorDutyApi(MOTOR2, (uint16_t)mc6c.thr);		// MOTOR2, 引脚为TIM3 CH2, PB5
		SetMotorDutyApi(MOTOR3, (uint16_t)mc6c.thr);		// MOTOR3, 引脚为TIM3 CH3, PB0
		SetMotorDutyApi(MOTOR4, (uint16_t)mc6c.thr);		// MOTOR4, 引脚为TIM3 CH4, PB1

		delay_ms(200);
	}
}

2.4 测试效果

2.4.1 协议采集与解析

运行结果如下,正常时,head为0x0F,flag为0x00,end为0x00。

运行截图

2.4.2 遥控器控制电机

现在已经开启四路电机输出PWM波,随便测一路即可,输入捕获也开启,遥控器也接上,引脚如下:

PWM1 -- PB4
PWM2 -- PB5
PWM3 -- PB0
PWM4 -- PB1
CAP -- PA0
SBUS_TX -- PA3

注意:

  1. 可以先将PB4与PA0连接起来,调节遥控器油门,看看周期是不是400Hz,高电平脉宽是不是 1ms-2ms。
  2. 不用遥控器,直接输出固定的PWM波,电机是不会转动的。想想电调是怎么校准的就明白了。

有一个问题,依旧没有解决,遥控器油门通道总是有跳变,其他通道正常,一直没找到bug,如果遇到相同问题或解决办法,讨论区见。

此时接收Sbus协议帧没有使用DMA,后期将优化。

完整工程源程序下载需积分:https://download.csdn.net/download/weixin_41869763/13054663

— 完 —

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

UAV021(六):系统架构优化、SBUS协议、遥控器控制电机转动 的相关文章

  • flask 报错 sqlalchemy.exc.ArgumentError: Mapper mapped class BaseModel->base_model could not assemble

    报错信息 sqlalchemy exc ArgumentError Mapper mapped class BaseModel gt base model could not assemble any primary key columns
  • rospy-编写简单的服务端和客户端

    最近一直在实习公司做课程 xff0c 记录一些笔记和自己写的代码 基础知识和代码解释参考wiki 创客制造和其他博客 xff0c 侵删 背景知识 服务 service ROS中的通信方式有四种 主题 topic 服务 service 参数服
  • 树莓派 3B+ 蓝牙无法使用问题

    最近想用树莓派3B 43 做个小玩意 xff0c 结果发现蓝牙功能不可用 xff0c 图标消失且不能发现设备 xff0c 几乎试了网上所有方法都不行 最后参考了这个帖子恢复了蓝牙功能 xff1a SOLVED Bluetooth icon
  • 电源管理芯片之 Regulator用法。

    有问题请加 xff1a Q群 xff1a 241359063 共同走向创业学习之旅 原创 xff1a kylin zeng http blog chinaunix net uid 23795897 html 转载请注明原创出处 xff0c
  • 如何查看ssh登录日志信息

    less var log secure grep 39 Accepted 39 正常登录日志信息 正常退出 密码错误登录 三次密码登陆失败 输入密码时 xff0c 主动退出 为了安全期间 xff1a 我们只允许xxx xxx xxx xxx
  • C语言笔记-25-网络-TCP网络编程

    C语言笔记 25 网络 TCP网络编程 文章目录 C语言笔记 25 网络 TCP网络编程前言一 TCP编程模型概括二 inet工具htonlinet pton 三 TCP编程模型代码TCP服务端TCP客户端 总结 前言 自学笔记 xff0c
  • Failed to load nodelet [/camera/realsense2_camera] of type [realsense2_camera/RealSenseNodeFactory]

    输入 xff1a roslaunch realsense2 camera rs rgbd launch 报错 xff1a 报错原因 xff1a 是找不到realsense2 camera包 xff0c 在安装D435的包时 xff0c 以为
  • 十七、基于软核和CAN搭建FPGA在线升级系统设计

    1 系统搭建 系统主要包含 xff1a MicroBlaze软核处理器 xff0c Axi Can控制器 xff0c Axi lite user用户通信接口 xff0c MIG DDR3控制器 xff0c 中断控制器等 设计使用Can接收上
  • 【OpenCV】cv::Mat位深和通道,CV_8UC1等

    位深 每个像素由多少位来存储 是一个精度问题 xff0c 一般图片是8bit xff08 位 xff09 的 xff0c 则深度是8 通道 每个像素点能存放多少个数 类似于RGB彩色图中的每个像素点有三个值 xff0c 即三通道 一个图像的
  • darknet_ros编译报错以及在RVIZ中显示乱码

    最近在arm平台上部署 darknet ros 出现了一些错误 xff0c 这里记录一下 首先在ARM平台上部署和在AMD平台上部署是不同的 xff0c 首先应该考虑算力问题 xff0c 在ARM下 xff0c 加载yolov3 weigh
  • 萌新改代码系列(一)--VINS+GPS

    VINS与GPS组合 距离上次写博客过去了快一年了 xff0c 这一年来我一直忙于与SLAM方向几乎没有关系的科研工作 xff0c 说来惭愧 xff0c 最终也没研究出个啥 最近得空 xff0c 就把我之前开源的代码整理了一下 xff0c
  • GVINS论文阅读(VINS-MONO + gnss 紧耦合)

    原文 x1f517 https arxiv org pdf 2103 07899 pdf github x1f517 https github com HKUST Aerial Robotics GVINS 港科大 沈老师团队 在vins
  • 萌新学VINS-Fusion(一)------特征跟踪

    VINS FUSION代码心得 新人小白 xff0c 第一次写博客 xff0c 主要相当于自己做一个关于学习VINS的笔记 xff0c 不喜勿喷 xff0c 转载请注明出处 其实我之前也尝试着在VINS MONO基础上改写双目的 xff0c
  • 萌新学VINS-Fusion(二)------初始化

    最近在忙别的事情 xff0c 博客迟迟没更新 xff0c 现在终于放假了 xff0c 今天把初始化部分来分析一下 源码 xff1a https github com HKUST Aerial Robotics VINS Fusion pro
  • c 语言中 %d,%lu等区别

    转载至 xff1a http blog sina com cn s blog 7d94c35c01019f96 html d 有符号10进制整数 ld 长整型 hd短整型 hu 无符号短整形 u无符号整形 lu无符号长整形 i 有符号10进
  • 萌新学VINS-Fusion(三)------双目和GPS融合

    虽然要过年了 xff0c 而且每天还要在家里小店打工 xff0c 但是网红之路不能停对吧 xff0c 这篇博客写关于VINS Fusion和GPS的融合 其实我之前改出过一个加了GPS的VIO xff0c 并且也开源了 xff0c 之前没有
  • nuxt cookie-universal-nuxt 搭配 vuex-persistedstate 做数据持久化

    前言 因为服务端不存在 Local Storage 和 Session Storage 所以 便使用了 cookie universal nuxt 这个插件 在做Nuxt项目的时候 发现Vuex 在刷新页面后 储存的数据丢失 用 vuex
  • ROS学习(一):Navigation中GNSS与IMU数据融合定位

    1 参考博客 主要参考以下博客 感谢各位博主的分享 link https blog csdn net qinqinxiansheng article details 107108475 utm medium 61 distribute pc
  • msOS学习之路(1)

    msOS学习之路 xff08 1 xff09 1 msOS的初步认识 刚刚拿到msOS开发板的时候 xff0c 看了一下开发板 xff0c 感觉非常高端 xff0c 再看一下芯片 xff0c 用的是stm32 当时我就觉得我得先学习stm3
  • msOS学习之路(2)

    基于msOS自动回火机的实现 1 简介 1 1 背景 基于msOS自动回火机的实现的实例是学习msOS比较好的入门实例 xff0c 它包括msOS界面的设计 数据库的使用 系统节拍的使用 按键的使用 系统节拍使用等 xff0c 通过这个例子

随机推荐

  • msOS学习之路(4)

    设备层简单理解 1 设备层相关定义 设备层的相关定义是在device h文件中定义的 xff0c 包括按键 模拟量输入 数字量输入 输出枚举或者类型定义等 xff0c 对于一些结构体的理解 xff0c 例如 xff1a ADC结构体 xff
  • 【STM32】STM32CubeIDE HAL库Ring-buffer例程

    板子G474RE STM32HAL库Ring buffer使用 注意 xff1a HAL库中 xff0c 中断每执行一次 xff0c 就关闭 xff0c 所以需要重新开启中断 第一次开启在main函数中 HAL UART Receive I
  • 【ROS】多机协同ROS安装使用

    目录 通信框架ROS 安装ROS 测试ROS 控制协同 协同感知 通信框架ROS ROS是一种分布式软件框架 xff0c 节点之间通过松耦合的方式进行组合 xff0c 在很多应用场景下 xff0c 节点可以运行在不同的计算平台上 xff0c
  • 【无人机】PX4,ROS,Ubuntu20仿真运行

    参考 xff1a https docs px4 io master en ros mavros installation html https docs px4 io master en dev setup dev env linux ub
  • 【无人机】PX4,源码简要分析

    看看源码目的很单纯 xff0c 就是想有没有控制方面的代码可以移植到其它地方 无人机 PX4 xff0c ubuntu18仿真运行 从官网可能下载代码不全 xff0c 这是编译完成的源码 xff0c 有点大 xff01 xff01 xff0
  • 内核层读写应用层文件,使用filp_open函数。

    转载 xff1a http soft chinabyte com os 421 11398421 shtml 有时候需要在Linux kernel 大多是在需要调试的驱动程序 中读写文件数据 在kernel中操作文件没有标准库可用 xff0
  • 怎样去理解异常SVC和PendSV

    目录 什么是SVC和PendSV 什么是SVC和PendSV SVC xff08 系统服务调用 xff09 和 PendSV xff08 可悬挂系统调用 xff09 它们多用于在操作系统之上的软件开发中 SVC 用于产生系统函数的调用请求
  • vscode开发ROS基本配置订阅PX4中imu数据

    目录 安装vscode Linux版本 安装vscode插件 配置头文件路径 订阅PX4中imu数据 参考 xff1a ROS基本操作 ROS中订阅节点消息测试 ROS下如何订阅任意话题 步步清风皆是你的博客 CSDN博客 安装vscode
  • 执行 install_geographiclib_datasets.sh 错误!

    执行 install geographiclib datasets sh 错误 xff01 原因被墙 xff01 解决 xff1f Ubuntu 18 04 在 usr share 下目录新建 GeographicLib 目录 先将 geo
  • ubuntu查看文件依赖、安装libopencv

    ubuntu查看文件依赖 安装libopencv 1 ubuntu中 xff0c 当需要查看某个文件运行时 xff0c 需要那些依赖库时 xff0c 可以在终端输入指令 ldd name name为文件名字 2 出现libopencv相关依
  • VNC远程连接不上ubuntu服务器,显示time out

    原因 xff1a 可能是出现了wired unmanaged并且启动不了屏幕共享 xff08 screen sharing xff09 xff0c 即开关打不到on上面 解决方案1 xff1a https askubuntu com que
  • OpenCV(三):一步步实现图像定位(Python版)

    一 预期目标 如下图 xff0c 要识别图中的国旗 xff0c 然后框选出来 xff0c 并且返回国旗的中心位置 xff0c 效果如下 彩色图像大小 400 264 目标中心位置 225 218 二 准备工作 1 将下面的图像另存为在本地
  • PX4教程翻译(1)序+前言

    序 最近打算实现树莓派控制 Pixhawk 无人机 xff0c 做了近一个星期 xff0c 还是没有成功 由于没有系统的中文教程 xff0c 零散地看了许多博客 正如万山圈子里 xff0c 一山放过一山拦 各种Bugs层出不穷 于是决心静心
  • raise ResourceNotFound(name, ros_paths=self._ros_paths) ResourceNotFound: gazebo_ros

    电脑为Ubuntu16 04 xff0c 安装来ROS xff08 kinetic版本 xff09 和 gazebo xff08 9版本 xff09 xff0c 在做PX4固件仿真 xff0c 想实现外部控制Pixhawk xff0c 但是
  • ROS综合应用(一)树莓派外部控制 Pixhawk(一站到底)

    序言 上一次 说到学习 ROS xff0c 主要是因为在做多无人机协同控制项目 听说可以使用 ROS 来实现树莓派控制 Pixhawk 无人机 xff0c 这样可以不用修改飞控源码 xff0c 而且可以用树莓派做图像处理 于是开始了一场浩浩
  • 卡尔曼滤波器MATLAB实现(从一维到三维)

    一 场景设置与效果图 假设一架无人机携带了一个气压计和GPS xff0c 定位精度都为1m xff0c 数据采样频率都为5Hz xff0c 100s时间螺旋上升40m xff0c 螺旋半径为20m 在无人机飞行过程中 xff0c 1m的精度
  • linux Linux 3.x的设备树(Device Tree) dts 介绍

    在http blog csdn net 21cnbao article details 8457546 进行整理修改 xff0c 感谢此博主 1 1 1 简介 Linus Torvalds在 2011 年 3 月 17 日的 ARM Lin
  • IMU和GPS数据融合估计位置与速度(MATLAB实现)

    一 问题与仿真 假设小车在一个方向上以 2cm s2 的加速度运动了 100s xff0c 使用加速度计和GPS测量小车位置 GPS定位误差为高斯分布 xff0c 方差为4m xff1b 加速度计的误差也为高斯分布 xff0c 方差为0 0
  • px4.launch apm.launch放哪儿

    安装了MAVROS之后 xff0c 经常要启动px4 launch 或 apm launch 有时候修改其中内容为默认更方便 但是文件在哪里呢 xff1f span class token function sudo span span c
  • UAV021(六):系统架构优化、SBUS协议、遥控器控制电机转动

    目录 序一 系统架构优化1 1 从全局变量到API函数1 2 函数传值 二 SBUS协议读取及解析2 1 协议格式2 2 协议解析2 3 程序设计2 3 1 头文件一览2 3 2 串口配置与捕获解析Sbus帧2 2 3 数据解析细节2 3