Adaptador PPM a USB en STM32F3 Discovery, o conectamos la consola del modelo de avión a la computadora como un joystick HID con STM32Cube



En este artículo te diré cómo:
  • Cree un proyecto en STM32CubeMX y configure temporizadores para capturar señales externas.
  • Decodifica una señal PPM de una consola modelo de avión.
  • Cree un dispositivo de interfaz humana en STM32 y escriba su descriptor de informe HID.
  • Vuela en un simulador en un quadrocopter de carreras. :)

Prefacio


Recientemente, las carreras de FPV en quadrocopters de la clase 250 (FPV - First Person View) están ganando cada vez más popularidad. El número 250 significa la distancia entre los ejes de los motores en diagonal, típica de pequeños helicópteros maniobrables. Dichos dispositivos están construidos sobre cuadros de carbono duraderos que resisten caídas y colisiones. Las hélices con un diámetro de 5-6 pulgadas con un gran paso (ángulo de inclinación de las palas) se colocan en motores potentes para el vuelo más dinámico. La imagen de la videocámara con rumbo analógico se transmite a una frecuencia de 5.8 GHz al monitor o las gafas de video del piloto. Dado que la transmisión digital a través de WiFi crea un largo retraso (200-300 ms), el video siempre se transmite en un canal analógico. Para grabar clips espectaculares, se colocan cámaras de acción (GoPro, Mobius, SJcam, Xiaomi Yi, etc.).


Aquí hay algunos videos interesantes sobre los helicópteros FPV:



Antes de construir mi mini quadrocopter, quería volar en un simulador y ver si me interesarían las carreras de FPV. Para el entrenamiento, el simulador FPV FreeRider es muy adecuado . Es económico, tiene una versión demo gratuita y, según pilotos experimentados, imita con mucha precisión la mecánica real del vuelo.

Puede controlar la aeronave en el simulador desde el teclado o desde el joystick. El teclado no es adecuado para el pilotaje, ya que los botones solo pueden transmitir un valor discreto (el botón se presiona / no se presiona) y es imposible transmitir valores intermedios que varían suavemente. Los joysticks de las consolas de juegos con dispositivos analógicos son mucho mejores, pero tienen un dispositivo muy pequeño, que no le permite controlar el dispositivo con la suficiente precisión. Una opción ideal para un simulador es una consola modelo de avión conectada a una computadora a través de un adaptador especial, gracias al cual el sistema operativo lo ve como un joystick.

Ya tenía un quadrocopter, ensamblado para vuelos pausados ​​y fotografía, pero demasiado grande y pesado para competir. En consecuencia, había un control remoto: Turnigy 9X (en la primera ilustración). En la parte posterior, tiene un conector para conectar un adaptador al que se emite una señal PPM. Esta señal es un pulso corto con intervalos de 1 a 2 milisegundos, cuya duración corresponde a la posición de los controles (más sobre esto en la sección sobre decodificación).



Debo decir que los adaptadores para conectar el control remoto de PPM a USB se han lanzado durante mucho tiempo y se venden con poder y principal. Se puede comprar un adaptador similar en el formato de unidad flash por $ 5 en China o un poco más caro en las tiendas rusas. También hay proyectos de adaptadores de código abierto en los controladores AVR.

Pero un agudo deseo de volar me llegó de inmediato al anochecer, cuando todas las tiendas de modelos de aviones de Moscú ya estaban cerradas. No quería esperar en la mañana, no había tiempo para envenenar y soldar la placa con ATmega, así que decidí hacer un adaptador PPM-USB en la placa de descubrimiento STM32F3, que había estado inactiva durante mucho tiempo y estaba a mano.

Qué se necesita


Para hacer un adaptador, necesitará:


Las placas de depuración de descubrimiento son bastante caras. El F3 descrito cuesta alrededor de $ 20 y sus capacidades son redundantes para un proyecto tan simple. Lo usé porque al momento de escribir esta era la única placa con hardware USB que encontré en casa. Para aquellos que aún no lo han comprado, puedo aconsejarles que presten atención a las placas en miniatura con el controlador STM32F103C8T6 de AliExpress por $ 3 y el programador ST-Link desde allí. El proceso no difiere del descrito en el artículo. A menos que sea necesario elegir otro controlador al principio, indique la presencia de un resonador de cuarzo y use un pinout ligeramente diferente.



Crear un proyecto en STM32CubeMX


STM32Cube es un paquete desarrollado por STMicroelectronics para facilitar la vida de los desarrolladores de dispositivos STM32. Consiste en la utilidad de gráficos CubeMX, controladores HAL y componentes de middleware.

CubeMX es una herramienta para crear proyectos e inicializar periféricos. Para comenzar, simplemente seleccione el controlador, marque las casillas de los módulos requeridos, seleccione los modos requeridos en el menú e ingrese los valores deseados en varios campos. CubeMX generará el proyecto y le conectará las bibliotecas necesarias. El desarrollador del dispositivo solo escribirá la lógica de la aplicación.

Conductores HAL(Capa de abstracción de hardware) es una API para trabajar con módulos y periféricos del microcontrolador. HAL le permite separar la capa superior de la aplicación que crea el desarrollador de trabajar con registros y hacer que el código del programa sea lo más portátil posible entre las familias de controladores STM32.

Middlewares , o componentes intermedios, incluyen el sistema operativo FreeRTOS, una biblioteca para trabajar con el sistema de archivos, bibliotecas USB, TCP / IP, etc.

Parece que ahora es posible "programar con el mouse" en lugar de escribir bits manualmente en los registros. Pero la simplicidad y la conveniencia no cancelan el hecho de que necesita estudiar la documentación, especialmente en los casos en que necesita exprimir la velocidad máxima, el consumo mínimo de energía o usar periféricos en modos no estándar. STM32Cube aún no cubre el 100% de todas las capacidades del microcontrolador, pero se está acercando a esto. STMicroelectronics actualiza Cube de vez en cuando, extiende funciones y corrige errores. Por lo tanto, si ya tiene Cube instalado, verifique que sea la última versión.

Ajustes iniciales



El trabajo con el proyecto comienza con la elección del controlador. Inicie STM32CubeMX, haga clic en Nuevo proyecto . En la pestaña Selector MCU , puede seleccionar el controlador deseado de los filtros. Dado que tenemos una placa de depuración terminada, en la pestaña Selector de placa , encontramos STM32F3Discovery . Después de elegir una placa, aparecerá una imagen del controlador con pines resaltados y firmados.

Hay cuatro pestañas grandes en la parte superior de la ventana:
Pinout : para configurar funciones de pin y preajustar módulos. Estamos en ello en este momento.
Configuración del reloj: configuración del reloj, PLL, divisores.
Configuración : configuración más detallada de periféricos y middleware.
Calculadora de consumo de energía : cálculo de la energía consumida por el microcontrolador.



En el menú de la izquierda en la pestaña Pinout , puede usar los periféricos deseados, y en el circuito del controlador, seleccione una función para cualquiera de las salidas del microcontrolador. Algunos elementos a la izquierda son iconos de advertencia. Esto significa que los módulos (en este caso ADC, DAC, OPAMP2, RTC) ahora se pueden usar de forma incompleta, ya que algunas de sus salidas ya están ocupadas por otras funciones.

Los pines configurados se resaltan en verde en el circuito del controlador. Dado que elegimos no un controlador simple sin flejes, sino una placa de depuración F3-Discovery lista para usar, algunas de las salidas ya están configuradas, por ejemplo, un botón azul está conectado a PA0 y LED a PE8 ... 15. Los pines a los que se conectan algunos dispositivos externos en Discovery se resaltan en naranja, pero los módulos periféricos para ellos aún no se han configurado. Como puede ver, estos son pines para USB, resonadores de cuarzo, SPI e I2C para giroscopio y brújula, DP y DM para USB. Actualmente no se utilizan conclusiones grises, cualquiera de ellas podemos aplicar para nuestros fines.

Selección de entrada

Vamos a capturar la duración de los pulsos, por lo que la entrada debe estar conectada a uno de los canales de cualquier temporizador. Además, el nivel de señal con Turnigy 9X no es 3.3V, como el voltaje de alimentación del STM32, sino 5V. Somos demasiado vagos para soldar el divisor de voltaje, por lo que debe elegir una entrada que pueda soportar 5V (estas entradas se llaman tolerantes a 5V). Los pines adecuados se pueden encontrar en la hoja de datos en STM32F303VCT6 en la sección Descripción de pines y pines . Hay muchos temporizadores en el STM32F3, se encuentran dispersos en casi todos los pines. Una opción conveniente es PC6. Puede soportar 5 voltios y se encuentra en la esquina inferior izquierda de la placa, al lado de GND. Asigne el primer canal del tercer temporizador TIM3_CH1 a este pin.



Ajuste del reloj

Para que el USB funcione, el microcontrolador debe sincronizarse a una frecuencia muy estable, por lo que casi todos los dispositivos USB tienen resonadores de cuarzo. La estabilidad de frecuencia del generador RC incorporado no es suficiente para USB. Pero en el tablero STM32F3 Discovery, los desarrolladores por alguna razón eran codiciosos y no pusieron cuarzo. Sin embargo, si estudia cuidadosamente el circuito , puede ver que la señal MCO está conectada a la entrada PF0-OSC_IN , donde debe conectarse el cuarzo . Proviene del programador ST-Link en la misma placa en la que hay cuarzo. El Manual del usuario para F3 Discovery (UM1570) en la sección Reloj OSC dice que se están enviando 8 MHz a esta línea.



Por lo tanto, el microcontrolador se sincroniza desde una fuente externa. Este modo se llama Bypass. En el menú de configuración periférica en la sección RCC , para marcar el reloj de alta velocidad, seleccione BYPASS Clock Source .



Antes de proceder a una configuración de reloj más detallada, notamos en el menú periférico que el microcontrolador actuará como un dispositivo USB.



Ahora puede ir a la siguiente pestaña grande: Configuración del reloj . Aquí veremos un gran diagrama que muestra qué señales de reloj están presentes en el microcontrolador, de dónde provienen, cómo se ramifican, multiplican y dividen. En amarillo, destaqué los parámetros que deben tenerse en cuenta.



Compruebe que la frecuencia de entradaLa frecuencia de entrada es de 8 MHz.
Configuramos el interruptor PLL Source Mux a HSE (High Speed ​​External) para que registre desde una fuente externa en lugar de una fuente interna.
PLL : Phase Lock Loop, o PLL : Phase Lock Loop, sirve para multiplicar la frecuencia externa varias veces. Establezca el multiplicador PLLMul en 9. Luego alcanzaremos la frecuencia máxima posible para el STM32F303 - 72 MHz.
System Clock Mux debe estar en la posición PLLCLK para que la frecuencia del reloj se multiplique con el PLL.
Para el módulo USB, se necesitan 48 MHz, así que coloque un divisor de 1.5 frente al USB.
Presta atención a la frecuenciaEl temporizador APB1 registra el lado izquierdo del circuito. Va a los temporizadores y es útil para nosotros en el futuro.
Si alguna frecuencia está configurada incorrectamente, excede el valor máximo posible o los interruptores están en una posición no válida, entonces CubeMX resaltará este lugar en rojo.

Ajuste del temporizador

Para medir la duración del pulso, iniciaremos el temporizador TIM3 en modo Captura de entrada. En el Manual de referencia , en la sección Temporizadores de uso general (TIM2 / TIM3 / TIM4), hay un diagrama que ilustra el funcionamiento de los temporizadores. Con los colores, destaqué las señales y los registros utilizados en el modo de captura de entrada.


La señal de reloj resaltada en verde ingresa continuamente en el registro del contador CNT e incrementa su valor en 1 en cada ciclo de reloj. En el divisor PSC Prescaler , la frecuencia del reloj puede disminuir para un conteo más lento.
Se ingresa una señal externa a TIMx_CH1 . Detector de bordereconoce los bordes de la señal de entrada: transiciones de 0 a 1 o de 1 a 0. Al registrar el borde, da dos comandos resaltados en amarillo:
- un comando para escribir el valor del contador CNT en el registro de captura / comparación 1 (CCR1) y la llamada de interrupción CC1I .
- un comando para el controlador de modo Esclavo , mediante el cual el valor CNT se restablece a 0 y la cuenta regresiva comienza nuevamente.

Aquí hay una ilustración del proceso en una línea de tiempo:



Cuando ocurre una interrupción, realizaremos acciones con el valor capturado. Si los pulsos de entrada llegan con demasiada frecuencia y las acciones que ocurren en el controlador de interrupciones son demasiado largas, entonces el valor de CCR1 puede sobrescribirse antes de leer el anterior. En este caso, debe verificar el indicador de sobrecaptura o aplicar DMA (Acceso directo a memoria) cuando los datos de CCR1 llenan automáticamente la matriz preparada en la memoria. En nuestro caso, el pulso más corto tiene una duración de 1 milisegundo, y el controlador de interrupción será simple y corto, así que no se preocupe por sobrescribir.

Regrese a la pestaña Pinout y configure el temporizador TIM3 en el menú Peripherals .



Modo esclavo: modo de reinicio- significa que en algún evento el temporizador se restablecerá a 0.
Fuente de disparo: TI1FP1 - el evento utilizado para restablecer e iniciar el temporizador es el borde de la señal capturado desde la entrada TI1.
ClockSource: reloj interno : el temporizador se sincroniza desde el generador interno del microcontrolador.
Canal 1: Modo directo de captura de entrada: captura los intervalos del primer canal en el registro CCR1.

En la próxima pestaña de Configuración grande, realizaremos configuraciones de temporizador adicionales.





Prescaler es un divisor de temporizador. Si es 0, la frecuencia se toma directamente del reloj APB del bus de reloj - 72 MHz. Si el preescalador es 1, la frecuencia se divide por 2 y se convierte en 36 MHz. Establezca el divisor en 71 para que la frecuencia se divida entre 72. Luego, la frecuencia del temporizador será de 1 MHz y los intervalos se medirán con una resolución de 1 microsegundo.
Periodo de contador : establece el valor máximo posible de 16 bits 0xFFFF. El período es importante para generar intervalos de tiempo, por ejemplo, para PWM. Pero el período no es importante para capturar señales; haremos que se sepa que es grande para cualquier pulso de entrada.
Selección de polaridad: borde descendente: los valores del temporizador se capturarán en el borde descendente de la señal de entrada.
En la pestaña Configuración de NVIC , pon un dawInterrupción global TIM3 , de modo que los eventos relacionados con el 3er temporizador generan una interrupción.

Configuración del dispositivo USB

Ya hemos notado que el controlador será un dispositivo USB. Dado que el joystick pertenece a la clase de dispositivos HID, en el menú Middlewares -> USB_DEVICE, seleccione Class For FS IP: Human Interface Device Class (HID) . Luego, CubeMX conectará las bibliotecas para el dispositivo HID al proyecto.



Vayamos a la configuración de USB_DEVICE en la pestaña Configuración en la sección Middlewares :



La identificación del proveedor y la identificación del producto son dos identificadores de 16 bits que son únicos para cada modelo de dispositivo USB. VID corresponde al fabricante del dispositivo, y cada fabricante asigna PID, guiado por sus propias consideraciones. No pude encontrar la lista oficial de VID y PID, solo encontré la base de identificadores compatible con entusiastas. Para obtener su propia identificación de proveedor, debe ir al Foro de implementadores USB en usb.org y pagar unos pocos miles de dólares. Las pequeñas empresas o los desarrolladores de código abierto que no pueden pagar su VID pueden solicitar un fabricante de chips USB y recibir oficialmente un par VID / PID para su proyecto. Tal servicio es ofrecido, por ejemplo, por FTDI o Silicon Laboratories.

Si conecta dos dispositivos con el mismo VID / PID, pero de un tipo diferente (por ejemplo, uno es un dispositivo HID y el otro es Almacenamiento masivo), el sistema operativo intentará instalar el mismo controlador para ellos, y al menos uno de ellos no trabajará. Es por eso que los pares VID / PID para diferentes modelos de dispositivos deben ser únicos.

Como no estamos fabricando un dispositivo para nosotros, no lo vamos a vender y distribuir, dejaremos VID 0x0483, correspondiente a STMicroelectronics, y crearemos nuestro propio PID. Por defecto, CubeMX ofrece PID 0x5710 para el dispositivo HID. Reemplácelo, por ejemplo, con 0x57FF.

Reemplace la cadena del producto con el adaptador STM32 PPM-USB. Este nombre aparecerá en la lista de dispositivos en el Panel de control de Windows. Todavía no cambiaremos el número de serie (S \ N).

Cuando Windows detecta que detecta un dispositivo con una combinación de VID, PID y S \ N que no ha visto antes, el sistema instala el controlador apropiado para él. Si la combinación de VID, PID y S \ N ya se ha utilizado, Windows sustituye automáticamente el controlador utilizado anteriormente. Puede ver esto, por ejemplo, cuando conecta la unidad flash al USB. La primera vez que se conecta e instala toma algo de tiempo. En conexiones posteriores, la unidad comienza a funcionar casi instantáneamente. Sin embargo, si conecta otra instancia de la unidad Flash del mismo modelo, pero con un número de serie diferente, el sistema instalará un nuevo controlador, aunque tenga el mismo VID y PID.

Explicaré por qué esto es importante. Si creó un mouse USB en su STM32 con su VID, PID y S \ N, lo conectó a la computadora y luego hizo un joystick USB sin cambiar el VID, PID y S \ N, entonces Windows percibirá el nuevo dispositivo como un mouse que ya utilizado en el sistema y no instalará el controlador de joystick. En consecuencia, el joystick no funcionará. Por lo tanto, si desea cambiar el tipo de su dispositivo, sin modificar el VID / PID, asegúrese de cambiar su número de serie.

Generación de proyectos para IDE

La última configuración que debe realizar es la configuración de generación del proyecto. Esto se hace a través del Proyecto -> Configuración ... Allí estableceremos el nombre, la carpeta de destino y el IDE deseado, bajo el cual CubeMX creará el proyecto. Elegí MDK-ARM V5 , porque uso Keil uVision 5. En la pestaña Generador de código , puede marcar la casilla Copiar solo los archivos de biblioteca necesarios para no saturar el proyecto con archivos innecesarios.

Presione el botón Proyecto -> Generar código . CubeMX creará un proyecto con código que se puede abrir en Keil uVision y compilar y flashear sin configuraciones adicionales. En el archivo main.c en la función main (void)Ya se han insertado funciones para inicializar el reloj, los puertos, el temporizador y el USB. En ellos, los módulos del microcontrolador están configurados de acuerdo con los modos que configuramos en CubeMX.



En el código, a menudo se encuentran construcciones de este tipo:

/* USER CODE BEGIN 0 */
(...)
/* USER CODE END 0 */

Se supone que el usuario incrustará su código en estas secciones. Si la opción Conservar código de usuario al volver a generar está habilitada en la configuración del proyecto CubeMX , el código encerrado entre estas líneas no se sobrescribirá durante la generación secundaria de un proyecto existente. Desafortunadamente, solo las secciones creadas por CubeMX se guardan. Secciones / * CÓDIGO DE USUARIO * / creado por el usuario se perderán. Por lo tanto, si después de escribir el código en el IDE desea volver a CubeMX y generar el proyecto nuevamente con la nueva configuración, le recomiendo hacer una copia de seguridad del proyecto.

En la configuración de firmware en uVision ( Flash -> Configurar herramientas de Flash ), le aconsejo que habilite la opción Restablecer después de flashpara que el microcontrolador se inicie inmediatamente después de parpadear. De forma predeterminada, está deshabilitado y, después de cada parpadeo, debe presionar el botón Restablecer en el tablero.



Decodificación PPM


PPM - Modulación de posición de pulso: un método de codificación de señales transmitidas, muy extendido en la electrónica de modelos de aviones. Es una secuencia de pulsos, los intervalos de tiempo entre los cuales corresponden a los valores numéricos transmitidos.



Según este protocolo, la consola envía información al módulo de transmisión de radio, que se inserta en la consola en la parte posterior. Muchos receptores que se colocan a bordo del helicóptero pueden transmitir señales de control para el controlador de vuelo a través de PPM. Además, casi cualquier consola tiene conectores para conectar una segunda consola en el modo entrenador-alumno y para conectar la consola a un simulador, que también suele usar PPM.

Escribimos una señal desde la salida del simulador de Turnigy 9X con un analizador lógico:



Cada secuencia codifica el estado actual de los controles en el control remoto. Por lo general, los primeros cuatro valores (también llamados canales) corresponden a la posición de los sticks analógicos, y los siguientes a la posición de los interruptores o potenciómetros.
La posición mínima del control corresponde al intervalo 1000 μs, el máximo - 2000 μs, la posición promedio - 1500 μs. Las ráfagas de pulsos, o cuadros, están separadas por intervalos sustancialmente más largos y siguen con un período de 20–25 ms.

Echemos un vistazo más de cerca a la señal:



Como puede ver, tres palos están en la posición neutral (1, 3, 4), y uno está en la posición extrema (2). Tres interruptores de palanca están apagados (5, 6, 7) y el último está encendido (8). El microcontrolador, que actúa como un adaptador, debe capturar dicha secuencia, agregar los valores a una matriz y enviarla a través de USB como un comando desde el joystick. Escribamos un decodificador de secuencia de pulsos.

Interrupción de captura

Después de la inicialización en main.c, antes del ciclo while principal , inicie el temporizador TIM3 en el modo de captura de captura de entrada desde el canal 1, con la generación de interrupciones de captura. Para hacer esto, use la función correspondiente de HAL:

HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);

La estructura htim3 declarada en main.c es el controlador del temporizador TIM3, que contiene todas las estructuras y variables asociadas con el temporizador: parámetros para la inicialización, punteros a todos los registros del temporizador (valor de contador, divisor, todas las configuraciones, banderas de interrupción), puntero en un controlador DMA que funciona con este temporizador, etc. El desarrollador no necesita buscar qué bits en qué registro son responsables de qué y establecerlos y restablecerlos manualmente. Es suficiente pasar el controlador a la función HAL. Las bibliotecas HAL harán el resto ellos mismos.

Los principios de la estructura HAL se describen con más detalle en el documento Descripción de los controladores HAL STM32F3xx .(UM1786). Cabe señalar que las propias bibliotecas HAL están bien documentadas. Para comprender cómo funciona el HAL para el temporizador y cómo usarlo, puede leer los comentarios en los archivos stm32f3xx_hal_tim.h y stm32f3xx_hal_tim.c .

Para cada interrupción que genera el temporizador TIM3, se llama al manejador TIM3_IRQHandler . Se encuentra en el stm32f3xx_it.c archivo , en el que el controlador de HAL_TIM_IRQHandler , estándar para todos los contadores de tiempo, se llama en él, y un puntero a la estructura htim3 se pasa a ella.

void TIM3_IRQHandler(void)
{
  /* USER CODE BEGIN TIM3_IRQn 0 */

  /* USER CODE END TIM3_IRQn 0 */
  HAL_TIM_IRQHandler(&htim3);
  /* USER CODE BEGIN TIM3_IRQn 1 */

  /* USER CODE END TIM3_IRQn 1 */
}

Si nos fijamos en el interior HAL_TIM_IRQHandler archivo stm32f3xx_hal_tim.c , vemos un gran manipulador que los cheques de las banderas de interrupción de temporizador causas de devolución de llamada de función y borra la bandera después de la ejecución. Si se produce un evento de captura, llama a la función HAL_TIM_IC_CaptureCallback . Se parece a esto:

__weak void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
/* NOTE : This function Should not be modified, when the callback is needed, the __HAL_TIM_IC_CaptureCallback could be implemented in the user file
*/
}

Esto significa que podemos anular esta función en main.c . Por lo tanto, inserte esta devolución de llamada antes de la función int main (void) :

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
};

Me gustaría ver cómo se realiza la interrupción. Agregue un encendido y apagado rápido de una de las conclusiones:

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
  HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, GPIO_PIN_SET);
  __nop();__nop();__nop();__nop();__nop();__nop();
  HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, GPIO_PIN_RESET);
};

El pin PE8 ya se ha inicializado como salida. Entre el encendido y el apagado, se insertan las instrucciones __nop () , lo que forma un retraso de 1 ciclo de reloj. Esto se hace para que mi analizador lógico chino de $ 8 que funcione a 24 MHz no pierda un pulso demasiado corto de un microcontrolador de 72 MHz. Ahora compile el proyecto Proyecto -> Compilar destino y pregunte al controlador Flash -> Descargar . Conectaremos el PPM desde el control remoto a la PC6 y veremos qué sucede en la PC6 y la PE8 con el analizador.



La devolución de llamada se llama realmente en los momentos correctos, justo después de que la señal de entrada haya pasado de 1 a 0. Por lo tanto, todo se hizo correctamente.

Captura y procesa datos capturados

Editaremos la devolución de llamada para que agregue cada valor capturado al búfer capturado_valor sin cambios. Si el temporizador captura un valor muy grande (más de 5000 μs), esto significa que se registró una pausa, el paquete se recibió en su totalidad y puede procesarse. Los valores procesados ​​se agregan a la matriz rc_data de 5 elementos. En los primeros cuatro, las posiciones de la palanca se reducen al rango [0; 1000], en el quinto, los bits individuales se establecen de acuerdo con los interruptores de palanca, que se interpretarán como presionar botones en el gamepad.

uint16_t captured_value[8] = {0};
uint16_t rc_data[5] = {0};
uint8_t pointer = 0;
uint8_t data_ready = 0;

...

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    uint8_t i;
    uint16_t temp;
    //     
    temp = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
    //    , ,  
    if ((temp > 5000) && (!data_ready))
    {
        pointer = 0;
        //        [0;1000]
        for (i = 0; i < 4; i++)
        {
            if (captured_value[i] < 1000)
                captured_value[i] = 1000;
            else if (captured_value[i] > 2000)
                captured_value[i] = 2000;
            rc_data[i] = captured_value[i]-1000;
        };
        //     
        rc_data[4] = 0;
        if (captured_value[4] > 1500)
            rc_data[4] |= (1<<4);
        if (captured_value[5] > 1500)
            rc_data[4] |= (1<<5);
        if (captured_value[6] > 1500)
            rc_data[4] |= (1<<6);
        if (captured_value[7] > 1500)
            rc_data[4] |= (1<<7);
        data_ready = 1;
    }
    else //      
    {
        captured_value[pointer] = temp;
        pointer++;
    };
    if (pointer == 8) //   
        pointer = 0;
}

Permítanme explicar por qué coloqué los bits correspondientes a los botones no en los 4 bits inferiores, sino en los bits quinto a octavo. En el simulador, se supone que conecta un gamepad desde la Xbox, donde se usan los botones LB, RB, Start y Back, y tienen números del 5 al 8.

En el bucle principal, el indicador data_ready se rotará continuamente, por lo que los datos se enviarán a la computadora.

while (1)
{
  if (data_ready)
  {
    //      
    data_ready = 0;
  }
}

Para comprobar cómo funciona esto, conecte el control remoto, compílelo y vuelva a flashearlo, y luego comience a depurar Debug -> Iniciar / Detener sesión de depuración .
Abrir la ventana para las variables de seguimiento Ver -> reloj de Windows -> Reloj 1 y añadir captured_value y rc_data allí .
Comenzamos la depuración con el comando Depurar -> Ejecutar y en tiempo real, sin siquiera agregar puntos de interrupción, veremos cómo cambian los números después de los sticks.



A continuación, debe enviar datos a la computadora en forma de un comando de joystick.

Configure el dispositivo HID y cree el descriptor de informe HID


USB HID (Dispositivo de interfaz humana) es una clase de dispositivos para la interacción humano-computadora. Estos incluyen teclados, ratones, joysticks, gamepads, paneles táctiles. La principal ventaja de los dispositivos HID es que no requieren controladores especiales en ningún sistema operativo: Windows, OS X, Android e incluso iOS (a través del adaptador USB-Lightning). Se puede encontrar una descripción detallada en el documento Device Class Definition for HID . Lo principal que necesitamos saber para crear un adaptador PPM-USB es qué son el Informe HID y el Descriptor de informe HID .

El dispositivo HID envía paquetes de bytes a la computadora en un formato predefinido. Cada paquete de este tipo es un informe HID. El dispositivo informa a la computadora sobre el formato de datos cuando se conecta, enviando un Descriptor de Informe HID, una descripción del paquete que indica cuántos bytes contiene el paquete y el propósito de cada byte y bit en el paquete. Por ejemplo, el Informe HID de un mouse simple consta de cuatro bytes: el primer byte contiene información sobre los botones presionados, el segundo y el tercer bytes contienen el movimiento relativo del cursor a lo largo de X e Y, y el cuarto byte contiene la rotación de la rueda de desplazamiento. El Descriptor de informes se almacena en la memoria del controlador del dispositivo como una matriz de bytes.



Antes de crear un descriptor, me gustaría detenerme por separado en la terminología. Dos términos son comunes en el entorno del idioma inglés: joystick y gamepad . La palabra joystick generalmente se llama manipulador que se sostiene con una mano y se inclina en diferentes direcciones, y el gamepad es un dispositivo con botones y palos que se sostiene con dos manos. Los usuarios de habla rusa generalmente llaman al joystick tanto eso como otro. En la descripción del dispositivo HID, hay una diferencia entre el joystick y el gamepad. La consola del modelo de avión es más similar en su propósito funcional a un gamepad, por lo que en el futuro a veces usaré el término "gamepad".

Generamos un proyecto, que indica que el dispositivo actuará como un dispositivo de interfaz humana. Esto significa que una biblioteca USB HID está conectada al proyecto y ya se ha generado un Descriptor de dispositivo. Se encuentra en el archivo usbd_hid.c , describe el informe del mouse y tiene este aspecto:
HID_Mouse_Report_Descriptor
__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE]  __ALIGN_END =
{
  0x05,   0x01,
  0x09,   0x02,
  0xA1,   0x01,
  0x09,   0x01,

  0xA1,   0x00,
  0x05,   0x09,
  0x19,   0x01,
  0x29,   0x03,

  0x15,   0x00,
  0x25,   0x01,
  0x95,   0x03,
  0x75,   0x01,

  0x81,   0x02,
  0x95,   0x01,
  0x75,   0x05,
  0x81,   0x01,

  0x05,   0x01,
  0x09,   0x30,
  0x09,   0x31,
  0x09,   0x38,

  0x15,   0x81,
  0x25,   0x7F,
  0x75,   0x08,
  0x95,   0x03,

  0x81,   0x06,
  0xC0,   0x09,
  0x3c,   0x05,
  0xff,   0x09,

  0x01,   0x15,
  0x00,   0x25,
  0x01,   0x75,
  0x01,   0x95,

  0x02,   0xb1,
  0x22,   0x75,
  0x06,   0x95,
  0x01,   0xb1,

  0x01,   0xc0
};

La creación manual del Descriptor de informes HID lleva mucho tiempo. Para facilitar la tarea, hay una herramienta llamada HID Descriptor Tool (DT). Este programa puede crear un descriptor para su dispositivo. En el archivo que contiene puede encontrar varios ejemplos de descriptores para diferentes dispositivos.



Aquí hay un muy buen artículo sobre cómo crear su propio descriptor HID para mouse y teclado (en inglés). Te diré en ruso cómo hacer un mango para un gamepad.

El informe HID enviado por la consola debe contener cuatro valores de 16 bits para dos ejes de sticks analógicos y 16 valores de un bit para botones. Total 10 bytes. Su asa creada en DT se verá así:

    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x05,                    // USAGE (Game Pad)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x09, 0x01,                    //   USAGE (Pointer)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x09, 0x30,                    //     USAGE (X)
    0x09, 0x31,                    //     USAGE (Y)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0xe8, 0x03,              //     LOGICAL_MAXIMUM (1000)
    0x75, 0x10,                    //     REPORT_SIZE (16)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //   END_COLLECTION
    0xa1, 0x00,                    //   COLLECTION (Physical)
    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, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //   END_COLLECTION
    0x05, 0x09,                    //   USAGE_PAGE (Button)
    0x19, 0x01,                    //   USAGE_MINIMUM (Button 1)
    0x29, 0x10,                    //   USAGE_MAXIMUM (Button 16)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x10,                    //   REPORT_COUNT (16)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0xc0                           // END_COLLECTION

No parece menos intimidante que un descriptor de mouse. Pero si comprende lo que significa cada línea, todo resulta bastante comprensible y lógico.

USAGE muestra cómo el sistema debe interpretar los datos que van más allá.
Hay muchos tipos de uso, se clasifican en grupos: páginas de uso. Por lo tanto, para seleccionar un Uso específico, primero debe consultar la USAGE_PAGE correspondiente. Sobre qué uso se puede encontrar en el documento Hid Tablas de uso . Al comienzo del descriptor, indicamos que se describirá el joystick:
USAGE_PAGE (Escritorio genérico)
USAGE (Game Pad)

COLLECTION combina varios conjuntos de datos relacionados.
La recopilación física se usa para datos relacionados con un punto geométrico específico, por ejemplo, un stick analógico. Application Collection se utiliza para combinar diferentes funciones en un solo dispositivo. Por ejemplo, un teclado con un panel táctil integrado puede tener dos colecciones de aplicaciones. Describimos solo el joystick, lo que significa que la colección será una:
COLLECTION (Aplicación)
...
END_COLLECTION

Después de eso, debe especificar que se describirán los elementos que transmiten las coordenadas. Usage Pointer se usa para describir ratones, joysticks, gamepads, digitalizadores:
USO (puntero)

Las siguientes son descripciones de palos analógicos combinados en una colección:
COLECCIÓN (Física)
USAGE (X)
USAGE (Y)
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1000)
REPORT_SIZE (16)
REPORT_COUNT (2)
INPUT (Data,Var,Abs)
END_COLLECTION

USO aquí indica que se
utilizan los valores de desviación a lo largo de los dos ejes, X e Y. LOGICAL_MINIMUM y LOGICAL_MAXIMUM especifican la medida en que el valor transmitido puede variar.
REPORT_COUNT y REPORT_SIZE establecen, respectivamente, cuántos números y qué tamaño vamos a transferir, es decir, dos números de 16 bits.
INPUT (Datos, Var, Abs) significa que los datos provienen del dispositivo a la computadora, y estos datos pueden cambiar. Los valores en nuestro caso son absolutos. Por ejemplo, los valores relativos provienen del mouse para mover el cursor. A veces los datos se describen como Const, no Var. Esto es necesario para transmitir bits no significativos. Por ejemplo, en un informe de mouse con tres botones, se transfieren 3 bits de Var para botones y 5 bits de Const para complementar el tamaño de transferencia a un byte.
Como puede ver, las descripciones de los ejes X e Y se agrupan. Tienen el mismo tamaño, los mismos límites. El mismo stick analógico podría describirse como sigue, describiendo cada eje individualmente. Tal descriptor funcionará de manera similar al anterior:
COLECCIÓN (Física)
USO (X)
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1000)
REPORT_SIZE (16)
REPORT_COUNT (1)
INPUT (Datos, Var, Abs)
USO (Y)
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1000)
REPORT_SIZE (16)
REPORT_COUNT (1)
INPUT (Datos, Var, Abs)
END_COLLECTION

Después del primer stick, se describe el segundo stick analógico. Sus ejes tienen un uso diferente para que pueda distinguirlos del primer palo: Rx y Ry:
COLECCIÓN (Física)
USO (Rx)
USO (Ry)
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1000)
REPORT_SIZE (16)
REPORT_COUNT (2)
INPUT (Datos, Var, Abs)
END_COLLECTION

Ahora necesitas describir algunos botones del gamepad. Esto puede hacerse de la siguiente manera:
USAGE_PAGE (Botón)
USAGE (Botón 1)
USAGE (Botón 2)
USAGE (Botón 3)
...
USAGE (Botón 16)

La engorrosa grabación de botones del mismo tipo se puede reducir utilizando el rango de uso:
USAGE_PAGE (Botón)
USAGE_MINIMUM (Botón 1)
USAGE_MAXIMUM (Botón 16)

Los datos transmitidos por los botones son 16 valores de un solo bit, que varían de 0 a 1:
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1)
REPORT_SIZE (1)
REPORT_COUNT (16)
INPUT (Data,Var,Abs)

El orden de las líneas en el descriptor no es estricto. Por ejemplo, Logical_Minimum y Logical_Maximum se pueden escribir antes de Usage (Button), o las líneas Report_Size y Report_Count se pueden intercambiar.
Es importante que el comando Entrada tenga todos los parámetros necesarios para la transferencia de datos (Uso, Mínimo, Máximo, Tamaño, Recuento).

Cuando se forma el descriptor, se puede verificar con el comando Parse Descriptor para ver si hay errores.
Si todo está en orden, expórtelo con la extensión h. En el archivo usbd_hid.c, reemplace el descriptor por uno nuevo y ajuste en usbd_hid.h el tamaño del descriptor HID_MOUSE_REPORT_DESC_SIZE de 74 a 61.

Los informes se envían utilizando el indicador data_ready . Para que estomain.c incluiremos el archivo de encabezado usbd_hid.h y en el bucle principal llamaremos a la función de envío de informes. La matriz rc_data es del tipo uint16, por lo que el puntero debe convertirse a un tipo de 8 bits y pasar el tamaño 10 en lugar de 5.

#include "usbd_hid.h"
...
while (1)
{
  if (data_ready)
  {
    USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)rc_data, 10);
    data_ready = 0;
  };
};

Compilamos el proyecto y lo flasheamos nuevamente.

Conexión y uso


Vuelva a conectar el cable USB del conector USB ST-LINK al conector USB del USUARIO. Windows detectará el nuevo dispositivo e instalará automáticamente el controlador. Vayamos al Panel de control -> Dispositivos e impresoras y veamos nuestro dispositivo adaptador USB-PPM STM32 con un ícono de gamepad.



En los parámetros del dispositivo, puede ver cómo la cruz se mueve alrededor del campo y las columnas se mueven después de mover los palos, y los símbolos de los botones se iluminan desde el interruptor de palanca. La calibración no es necesaria porque los valores mínimo y máximo ya se han establecido en el descriptor.



Al iniciar FPV FreeRider, veremos cómo en la pantalla principal del gamepad virtual dibujado los palos se mueven de acuerdo con nuestro control remoto. Si los ejes no están asignados correctamente por algún motivo, puede reconfigurarlos en la sección Calibrar controlador .
Los interruptores de palanca correspondientes a los botones del control remoto se utilizan para cambiar los modos de vuelo (acrobático / estabilizado), cambiar la vista de la cámara (desde el tablero / desde el suelo), iniciar un vuelo desde el principio o encender la carrera por un tiempo.



Voló!




En el video, el resultado de varios días de mi entrenamiento. Mientras vuelo con nivelación automática, y no en modo acrobático, como lo hacen todos los maestros de las carreras de FPV. En modo acro, si suelta los palos, el helicóptero no vuelve automáticamente a la posición horizontal, sino que continúa volando en el mismo ángulo que voló. Administrar en modo acro es mucho más difícil, pero puede lograr una mayor velocidad, maniobrabilidad, agitar el aire e incluso volar al revés.

A los maestros CharpuTodavía estoy muy lejos, pero sigo entrenando y puedo decir con certeza que la idea de un mini helicóptero de carreras me interesó aún más. Y pronto, definitivamente estaré involucrado en su construcción y vuelos, ya no en el simulador, sino en una dura realidad, con choques reales, hélices rotas, motores rotos y baterías quemadas. Pero este es un tema para otros artículos :)



El proyecto para Keil uVision 5 y STM32CubeMX está en GitHub .

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


All Articles