在模拟器上进行FPV赛车(我们通过无线电遥控器制作USB游戏杆)

FPV飞行员有时间在赛车和持续故障中休息一下,拿起烙铁,并为他的爱好制作一些有用的东西,这是北纬的冬季。

由于外面很冷,我们将在模拟器上训练飞行员技能。为此,您需要通过特殊适配器将无线电设备连接至计算机,该适配器将遥控器上的PPM信号转换为计算机可以理解的USB操纵杆信号。当然,这种适配器并不罕见,在中国商店中只花一分钱。但是,订单的交付需要等待很长时间,它是否可以按我们预期的那样工作?例如,我有一个:

由于某些我尚未理解的原因,他断然拒绝在FPV Freerider模拟器中进行适当的校准,尽管它在Phoenix RC和Aerofly RC 7中表现出色。有免费的演示模式。

解决-我们自己制造适配器!

一点设备:

大多数或多或少严重的RC设备都有一个连接器,可在其中以PPM(脉冲位置调制)格式输出控制信号。PPM信号是一系列短脉冲,它们之间的间隔决定了无线电设备每个信道的控制值。
PPM的精髓完美地传达了图像:


要对PPM进行解码,您需要准确测量连续脉冲之间的时间间隔(在哪个边沿之间:前边还是后边无关紧要,因为脉冲本身的持续时间总是相同的)。

实现方式:

AlexeyStn一篇文章中 汲取灵感,该文章关于基于STM32F3Discovery创建PPM到USB适配器,但是只有具有USB硬件支持的Arduino Pro Micro(Leonardo),让我们开始一条简单的适配器路径。

您可以在github上找到一些类似的项目,有些甚至在控制器中甚至不需要硬件USB。但是,大多数文件都需要认真完成以使某些文件正常工作。合适的项目是rc-leonardo-joy,它在填入草图后几乎立即开始工作,但立即显示出一些缺陷:所有操纵杆读数都不十分稳定-控制面板中的光标标记始终在设定点附近摆动。我不能说这严重影响了模拟器的操作,但是我们想训练优质的设备!

好了,我们进入代码,看到:PPM脉冲宽度的计算是通过处理外部中断并测量对micros()函数的调用之间的间隔来完成的

void rxInt(void) {
  uint16_t now,diff;
  static uint16_t last = 0;
  static uint8_t chan = 0;

  now = micros();
  sei();
  diff = now - last;
  last = now;
  if(diff>3000) chan = 0;
  else {
    if(900<diff && diff<2200 && chan<RC_CHANS ) {
      rcValue[chan] = adjust(diff,chan);
    }
    chan++;
  }
}

在Arduino文档中 阅读有关micros()函数的信息:

返回自Arduino开发板开始运行当前程序以来的微秒数。大约70分钟后,该数字将溢出(返回零)。在16 MHz的Arduino板上(例如Duemilanove和Nano),此函数的分辨率为4微秒(即返回的值始终是4的倍数)。在8 MHz的Arduino板上(例如LilyPad),此功能的分辨率为8微秒。

也就是说,该函数不仅不是特别精确,并且始终返回4 s的倍数的值,而且在70分钟后也会溢出,从而为我们提供了所测量间隔的某种弯曲值。不好 最好使用计时器及其中断来捕获外部信号。

我们进一步观察:操纵杆位置上的大多数数据被人为限制为一个字节(0-255):
// Convert a value in the range 1000-2000 to 0-255
byte stickValue(int rcVal) {
  return map( constrain(rcVal - 1000, 0, 1000), 0, 1000, 0, 255);
}

嗯,我想更精确一些。但是为此,您将不得不重写HID描述符并更正所有相关的数据类型。

快说不做!
我们分叉存储库,重写代码以使用计时器来计数PPM间隔:
void initTimer(void) { 
	// Input Capture setup
	// ICNC1: =0 Disable Input Capture Noise Canceler to prevent delay in reading
	// ICES1: =1 for trigger on rising edge
	// CS11: =1 set prescaler to 1/8 system clock (F_CPU)
	TCCR1A = 0;
	TCCR1B = (0<<ICNC1) | (1<<ICES1) | (1<<CS11);
	TCCR1C = 0;

	// Interrupt setup
	// ICIE1: Input capture 
	TIFR1 = (1<<ICF1); // clear pending
	TIMSK1 = (1<<ICIE1); // and enable
}
...

ISR(TIMER1_CAPT_vect) {
	union twoBytes {
		uint16_t word;
		uint8_t  byte[2];
	} timeValue;

	uint16_t now, diff;
	static uint16_t last = 0;
	static uint8_t chan = 0;
	
	timeValue.byte[0] = ICR1L;    // grab captured timer value (low byte)
	timeValue.byte[1] = ICR1H;    // grab captured timer value (high byte)

	now = timeValue.word;
	diff = now - last;
	last = now;

	//all numbers are microseconds multiplied by TIMER_COUNT_DIVIDER (as prescaler is set to 1/8 of 16 MHz)
	if(diff > (NEWFRAME_PULSE_WIDTH * TIMER_COUNT_DIVIDER)) {
		chan = 0;  // new data frame detected, start again
	}
	else {
		if(diff > (MIN_PULSE_WIDTH * TIMER_COUNT_DIVIDER - THRESHOLD) 
			&& diff < (MAX_PULSE_WIDTH * TIMER_COUNT_DIVIDER + THRESHOLD) 
			&& chan < RC_CHANNELS_COUNT) 
		{
			rcValue[chan] = adjust(diff, chan); //store detected value
		}
		chan++; //no value detected within expected range, move to next channel
	}
}

在HID描述符中将摇杆偏差间隔增加到0-1000:
	// Joystick
	0x05, 0x01,			// USAGE_PAGE (Generic Desktop)
	0x09, 0x04,			// USAGE (Joystick)
	0xa1, 0x01,			// COLLECTION (Application)
	0x85, JOYSTICK_REPORT_ID,	//   REPORT_ID (3)
	...
	0xA1, 0x00,		    //   COLLECTION (Physical)
	0x09, 0x30,		    //     USAGE (x)
	0x09, 0x31,		    //     USAGE (y)
	0x09, 0x33,		    //     USAGE (rx)
	0x09, 0x34,		    //     USAGE (ry)
	0x15, 0x00,		    //	   LOGICAL_MINIMUM (0)
	0x26, 0xE8, 0x03,	    //     LOGICAL_MAXIMUM (1000)
	0x75, 0x10,	  	    //	   REPORT_SIZE (16)
	0x95, 0x04,		    //     REPORT_COUNT (4)
	0x81, 0x02,		    //     INPUT (Data,Var,Abs)
	0xc0,			    //   END_COLLECTION

	0xc0				// END_COLLECTION

在此过程中,无论发送杆偏差值的位置如何,都将uint8_t更改uint16_t
我们删除了多余的代码,添加了一打#define,我们得到了一个很好的草图,经过了改进,可以用作PPM-USB适配器。

结果在github中可用:github.com/voroshkov/Leonardo-USB-RC-Adapter

草图设置:

如果您有其他硬件,则删除futaba的定义很有意义:
#define FUTABA

如果您的设备产生其他计时,并在必要时调整参数中的微秒值:
#define STICK_HALFWAY 500
#define STICK_CENTER 1500
#define THRESHOLD 100


编译:

要编译和上传草图,需要在备份后替换Arduino环境本身中的USB库。
为此,请沿着以下路径进入Arduino的肠道... \ Arduino \ hardware \ arduino \ cores \ arduino \,备份usbapi.hhid.cpp,然后用存储库中ArduinoLibs文件夹中的相同文件覆盖它们接下来,打开草图,连接Arduino Leonardo并填充它。

连接方式:

一切都很丑陋:一方面,我们插入USB,另一方面,我们将两条线(在数字引脚4和地面上)焊接在一起,并将其分别粘贴到变送器的PPM和地面上。原来是这样的:

在Windows 7中,该设备被识别为名称为Arduino Leonardo的复合材料(键盘,鼠标,操纵杆)。

特别值得一提的是设备中的连接器。在某个地方是普通音频插孔,在某个地方(例如在我的Futaba 7C中)比较棘手:


对于组装各种“公”连接器,我长期以来一直成功使用热胶。为此,我们将纸或聚乙烯放在“母亲”上,用针刺穿该基材,使它们粘到另一侧的连接器中,然后逐渐在针之间倒入胶水,同时用湿手指使它成形。当然,电线需要预先焊接,以免在焊接时熔化固化的粘合剂。结果并不总是美观,但非常实用:

(这里,在连接器中,明确定位需要4个引脚,只有两个工作人员。)

就是这样。我们下载模拟器,连接设备并训练飞行员技能,同时在黑暗的冬天晚上在壁炉前喝热茶。

聚苯乙烯

如果没有Arduino Leonardo,但STM32F103C8T6上有这样的最低开发板怎么办?

不用担心,一路走来。对于您以及我自己的开发,我移植了已经提到 Alexey Stankevich 项目
可以在此处找到要上传到该控制器的源代码和已编译的二进制文件:github.com/voroshkov/STM32-RC-USB-Adapter

我将在评论中愉快地回答所有问题。

祝您飞行愉快!

Source: https://habr.com/ru/post/zh-CN388811/


All Articles