FPV racing en un simulador (hacemos un joystick USB desde un control remoto por radio)

El invierno en las latitudes del norte es el momento en que el piloto de FPV tiene tiempo para tomarse un descanso de las carreras y las constantes averías, recoger un soldador y crear algo útil para su hobby.

Como hace frío volar afuera, entrenaremos habilidades de pilotaje en simuladores. Para hacer esto, debe conectar su equipo de radio a la computadora a través de un adaptador especial, que convierte la señal PPM del control remoto a las señales del joystick USB que la computadora entiende. Tales adaptadores, por supuesto, no son infrecuentes y cuestan un centavo en las tiendas chinas. Sin embargo, la entrega del pedido tiene que esperar mucho tiempo, y ¿funcionará como esperábamos? Por ejemplo, tengo este:

Por alguna razón que aún no he comprendido, él se niega rotundamente a calibrar adecuadamente en el simulador FPV Freerider , aunque funciona muy bien en Phoenix RC y Aerofly RC 7. FPV Freerider transmite bastante bien la física del vuelo en un helicóptero de carreras, y además tiene un modo de demostración gratuito.

Resuelto: ¡nosotros mismos hacemos el adaptador!

Un poco de equipo:

La mayoría de los equipos RC más o menos serios tienen un conector donde emiten señales de control en formato PPM (modulación de posición de pulso). Una señal PPM es una secuencia de pulsos cortos, cuyo intervalo determina el valor de control de cada uno de los canales del equipo de radio.
La esencia de PPM transmite perfectamente la imagen:


Para decodificar PPM, debe medir con precisión los intervalos de tiempo entre pulsos consecutivos (no importa entre qué bordes: delantero o trasero, porque la duración de los pulsos es siempre la misma).

Implementación

Inspirándose en un artículo de AlexeyStn sobre la creación de un adaptador PPM a USB basado en STM32F3Discovery, pero que solo tiene Arduino Pro Micro (Leonardo) con soporte de hardware USB, comencemos un camino simple hacia nuestro adaptador.

Puede encontrar varios proyectos similares en el github, y algunos ni siquiera requieren hardware USB en el controlador. Sin embargo, la mayoría de ellos deben terminar seriamente con un archivo para que algo funcione. El proyecto adecuado fue rc-leonardo-joy, que comenzó a funcionar casi inmediatamente después de completar el boceto, pero de inmediato mostró algunos defectos: todas las lecturas del joystick no eran muy estables; el marcador del cursor en el panel de control bailaba todo el tiempo alrededor del punto de ajuste. No puedo decir que esto haya afectado significativamente el manejo en el simulador, ¡pero queremos entrenar con un buen equipo!

Bueno, subimos al código y vemos: el cálculo del ancho de pulso PPM se realiza procesando una interrupción externa y midiendo los intervalos entre llamadas a la función 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++;
  }
}

Lea sobre la función micros () en la documentación de Arduino:

Devuelve el número de microsegundos desde que la placa Arduino comenzó a ejecutar el programa actual. Este número se desbordará (volverá a cero), después de aproximadamente 70 minutos. En placas Arduino de 16 MHz (por ejemplo, Duemilanove y Nano), esta función tiene una resolución de cuatro microsegundos (es decir, el valor devuelto es siempre un múltiplo de cuatro). En placas Arduino de 8 MHz (por ejemplo, LilyPad), esta función tiene una resolución de ocho microsegundos.

Es decir, la función no solo no es muy precisa y siempre devuelve valores que son múltiplos de 4 μs, sino que también se desborda después de 70 minutos, lo que nos da un valor curvo del intervalo medido. No bueno Sería mejor usar un temporizador y sus interrupciones para capturar señales externas.

Miramos más allá: la mayoría de los datos sobre la posición del joystick se limitan artificialmente a un byte (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);
}

Hmm, me gustaría ser más preciso. Pero para esto, tendrá que reescribir el descriptor HID y corregir todos los tipos de datos relacionados.

Apenas dicho que hecho!
Bifurcamos el repositorio, reescribimos el código para usar un temporizador para contar los intervalos 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
	}
}

Aumente los intervalos de desviación del stick a 0-1000 en el descriptor HID:
	// 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

En el camino, cambie uint8_t a uint16_t donde se transmitan los valores de desviación del stick.
Eliminamos el código adicional, agregamos una docena de #define y obtenemos un buen boceto, afilado para funcionar como un adaptador PPM-USB.

El resultado está disponible en github: github.com/voroshkov/Leonardo-USB-RC-Adapter

Configuración de croquis:

Tiene sentido eliminar la definición de futaba si tiene otro hardware:
#define FUTABA

y, si es necesario, ajuste los valores de microsegundos en los parámetros si su equipo produce otros tiempos:
#define STICK_HALFWAY 500
#define STICK_CENTER 1500
#define THRESHOLD 100


Compilación:

Para compilar y cargar el boceto, debe reemplazar las bibliotecas USB en el entorno Arduino, después de hacer copias de seguridad.
Para hacer esto, vaya a las entrañas de Arduino a lo largo del camino ... \ Arduino \ hardware \ arduino \ cores \ arduino \ , haga una copia de seguridad de usbapi.h y hid.cpp , y luego sobrescríbalos con los mismos archivos de la carpeta ArduinoLibs del repositorio. A continuación, abra el boceto, conecte el Arduino Leonardo y llénelo.

Conexión:

Todo es feo y simple: por un lado, insertamos USB, por otro lado, soldamos dos cables (en el Pin digital 4 y tierra) y lo conectamos al PPM y tierra del transmisor, respectivamente. Resulta de alguna manera así:

En Windows 7, el dispositivo se reconoce como un compuesto (teclado, mouse, joystick) con el nombre Arduino Leonardo.

Mención especial merece el conector en el equipo. En algún lugar es un conector de audio normal, y en algún lugar (como en mi Futaba 7C) es algo más complicado:


Para el ensamblaje de varios conectores "machos", hace tiempo que utilizo pegamento caliente. Para hacer esto, colocamos papel o polietileno en la "madre", perforamos este sustrato con clavijas para que se peguen en el conector del otro lado, y luego vierte gradualmente pegamento entre las clavijas, dándole forma simultáneamente con los dedos mojados. Los cables, por supuesto, deben soldarse de antemano para no derretir el adhesivo curado al soldar. Resulta que no siempre es estético, pero sí muy funcional:

(Aquí, en el conector, se necesitan 4 pines para un posicionamiento inequívoco, solo hay dos trabajadores).

Eso es todo. Descargamos el simulador, conectamos el equipo y entrenamos habilidades de pilotaje mientras bebemos té caliente frente a la chimenea en las oscuras noches de invierno.

PS

¿Qué pasa si no hay Arduino Leonardo, pero existe una placa de desarrollo mínimo en el STM32F103C8T6?

No te preocupes, hasta el final. Para usted, así como para mi propio desarrollo, porté el proyecto ya mencionado de Alexey Stankevich.
Las fuentes y los binarios compilados para cargar a este controlador se pueden encontrar aquí: github.com/voroshkov/STM32-RC-USB-Adapter .

Contestaré todas las preguntas con gusto en los comentarios.

Que tengas un buen vuelo!

Source: https://habr.com/ru/post/es388811/


All Articles