Control transmisor de video VTX FPV con Arduino

Cómo controlar un VTX de FPV con Arduino

Hace algún tiempo se me metió en la cabeza controlar un video transmisor de 1.2 Ghz con un Arduino.

Concretamente, lo que quería conseguir era hacer un analizador de antenas, también basado en Arduino (que está hecho y funciona muy bien, tengo que pendiente describir en el Blog como lo hice y publicar el código) que fuera capaz de hacer un barrido de frecuencias, por ejemplo desde 1,1 Ghz hasta 1,4 Ghz, con escalones cada pocos Mhz, midiendo la SWR en cada uno de esos escalones para poder capturar todos los datos en Excel y hacer una gráfica de la resonancia de la antena en ese rango de frecuencias.

Estuve buscan mucho por internet y encontré muy poca información, de forma que decidí partir, casi, de cero y hacer todo el análisis del protocolo que utiliza el microprocesador del VTX para comunicarse con su IC PLL (un MB15E07L, fabricado por Fujitsu, que es el circuito integrado que crea la frecuencia de emisión), para saber cómo controlarlo, y escribir el código de Arduino.

Un poquito de ingeniería inversa no hace daño a nadie.

Afortunadamente el usuario Changosurf ya había descrito el protocolo del PLL, aunque a mí no me funcionó tal y como estaba y tuve que hacer más investigación (los datos del registro latch de 18 bits no funcionaban con mi VTX, ni con ninguno de los que probé).

Encontré también un código que utilice como base, pero, lo siento, como hace varios años que hice este proyecto, no encuentro el autor original para darle crédito.

Todo el proceso lo hice utilizando un VTX Partom de 800mw, de los más utilizados en aeromodelismo, pero lo bueno es que casi todos los emisores de video que tienen ese aspecto físico, como los VTX FOX (y otros muchos), utilizan este mismo PLL y protocolo. De hecho, he probado con otros transmisores de potencias distintas y otros aspectos físicos y todos han funcionado correctamente.

¿Qué utilidad tiene el control con Arduino?

Las utilidades son muchísimas, y solamente la imaginación es el límite.

Además del ejemplo comentado anteriormente que nos permite variar la frecuencia de emisión de forma automática para poder analizar antenas podríamos utilizarlo para:

  • Cambiar el canal de video de nuestro avión o dron remotamente por radiocontrol. ¿Tenemos interferencias en la frecuencia que estamos usando y nuestro aeromodelo está lejos? Pues cambiamos de frecuencia remotamente (podemos hasta salvar nuestro aparato o evitar una perdida segura).
  • Utilizar frecuencias no estándar: Los transmisores suelen tener entre uno y ocho canales que podemos seleccionar, esto quiere decir que todo el mundo está en uno de esos canales, si podemos elegir una frecuencia que no coincide con la de ninguno de esos canales podremos utilizar esa frecuencia para nosotros solos, en exclusiva, sin interferencias.

Arquitectura de un transmisor de video FPV

La arquitectura de un VTX de los utilizados en FPV es bastante sencilla.

Básicamente consta de cuatro bloques básicos:

  • Circuito de control: el usuario selecciona un canal de emisión mediante unos interruptores o botones y un microcontrolador comprueba a qué frecuencia corresponde ese canal y la codifica de forma que el PLL pueda entenderlo.
  • Circuito generador de frecuencia o PLL (Phase-locked Loop): Este circuito integrado recibe unos datos desde el microcontrolador que le indica la frecuencia que debe generar.
  • Circuito de Radio Frecuencia (RF): Este bloque recibe la frecuencia generada por el PLL (que no tiene por qué ser la frecuencia real de emisión) y utilizando esa frecuencia como base generará la frecuencia final, las subportadoras, y la modulará con el audio y el video.
  • Amplificador de potencia: Todo lo anterior se hace con unas potencias extremadamente bajas, es este circuito final el que coge esa señal y la multiplica por miles o millones de veces y manda esa señal, de alta potencia, a la antena para su emisión.

Lo que vamos a hacer aquí es sustituir el «Circuito de control» por el nuestro propio para poder controlar el PLL como queramos.

Análisis del protocolo del PLL

PLL VTX FOX Partom

Lo primero que hice fue, antes de realizar ninguna modificación en el hardware del transmisor, analizar los datos en las líneas de comunicación del PLL, con ayuda de mi osciloscopio RIGOL DS1054Z 

A continuación, fui cambiando de canal, manualmente, y con el osciloscopio (conectado temporalmente al VTX con unos cablecillos) capturar los datos que el microcontrolador PIC enviaba al VTX.

A continuación, puedes ver las capturas que hice al cambiar a los diferentes canales:

Finalmente me ayudé de la captura del canal 11 para comprobar si estaba en modo de 18 bits o 19 bits:

En esta última imagen se puede ver con detalle el análisis del protocolo, ya que lo documenté sobre la captura con los bits numerados para que fuera más fácil de visualizar:

VTX Control Protocol

Código sketch de Arduino

 Aquí tienes el código completo para controlar el VTX con Arduino.

Te recomiendo, si lo vas a usar, que lo cojas de la página de eMariete en Github, donde podrás encontrar la última versión y otras informaciones interesantes.

Para flashear el Arduino puedes utilizar el Arduino IDE normal. Ten en cuenta que este código se desarrolló con una versión de Arduino IDE de 2017 por lo que con las últimas versiones podría haber incompatibilidades.

El código no es una maravilla de la programación, es algo «rápido y sucio» para comprobar que funcionaba y lo subí a Github «tal cual«. Las pruebas las realicé en un Arduino Pro Mini de 3.3 V a 8 Mhz y funcionaba sin problemas.

/*
 * This sketch controls a Partom or simmilar VTX transmit frequency
 * Copyright (c) 2017 Mariete
 */
/*-----( Declare Constants )-----*/
static const int FREQ[] = {1010, 1040, 1060, 1080, 1100, 1120, 1140, 1160, 1180, 1200, 1240, 1258, 1280, 1320, 1360};
static unsigned long COUNTER18BIT_OLD[] = {
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001, /* 12 */
  0b010000010010110001,
  0b010000010010110001, /* 14 */
  0b010000010010110001};
static unsigned long COUNTER18BIT[] = { // Data original PLL for 1280Mhz
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001, /* 12 */
  0b010000001100100001,
  0b010000001100100001, /* 14 */
  0b010000001100100001};
static unsigned long COUNTER19BIT[] = {
  0b0110001010100001000,
  0b0110010110001000000,
  0b0110011110000010000,
  0b0110100101101100000,
  0b0110101101100110000,
  0b0110110101100000000,
  0b0110111101001010000,
  0b0111000101000100000,
  0b0111001100101110000,
  0b0111010100101000000,
  0b0111100100001100000,
  0b0111101011001101000, /* 12 */
  0b0111110100000001010,
  0b1000000011100100000, /* 14 */
  0b1000010011001000000};
#define VTXDataPin 7
#define VTXClockPin 8
#define VTXLatchEnablePin 9
#define VTXChannelChangePin 3
/*-----( Declare Variables )-----*/
byte VTXChannelOld = 0;
byte VTXChannel = 13;
void setup()
{
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(VTXDataPin, OUTPUT);
  pinMode(VTXClockPin, OUTPUT);
  pinMode(VTXLatchEnablePin, OUTPUT);
  pinMode(VTXChannelChangePin, INPUT_PULLUP);
  digitalWrite(VTXChannelChangePin, HIGH);
  digitalWrite(VTXDataPin, LOW);
  digitalWrite(VTXClockPin, LOW);
  digitalWrite(VTXLatchEnablePin, LOW);
}
void loop()
{
  if (!digitalRead(VTXChannelChangePin))
  {
    while (!digitalRead(VTXChannelChangePin)) {
      //
    }
    VTXChannel++;
    Serial.println("Button pressed...");
  }
  if (VTXChannel != VTXChannelOld)
  {
    Serial.println("Changing channel...");
    ChangeChannel();
  }
//  delay(9500);
//  digitalWrite(LED_BUILTIN, HIGH);
//  delay(500);
//  digitalWrite(LED_BUILTIN, LOW);
//  VTXChannel = 13;
//  ChangeChannel(VTXChannel);
//  delay(9500);
//  digitalWrite(LED_BUILTIN, HIGH);
//  delay(500);
//  digitalWrite(LED_BUILTIN, LOW);
//  VTXChannel = 11;
//  ChangeChannel(VTXChannel);
}
void bitBang(unsigned long pattern, byte numBits)  // This function is what bitbangs the data
{
//  digitalWriteFast(VTXDataPin, LOW);
//  digitalWriteFast(VTXClockPin, LOW);
//  delay(1);
  for(int i=numBits-1; i>-1; i--)
  {
    digitalWriteFast(VTXDataPin, bitRead(pattern, i));
    delayMicroseconds(300);
    digitalWriteFast(VTXClockPin, HIGH);
    delayMicroseconds(350);
    digitalWriteFast(VTXClockPin, LOW);
    Serial.print(bitRead(pattern, i));
    delayMicroseconds(50);
  }
  digitalWriteFast(VTXLatchEnablePin, HIGH);
  delayMicroseconds(500);
  digitalWriteFast(VTXLatchEnablePin, LOW);
  Serial.println("");
  digitalWrite(VTXDataPin, LOW);
}
void ChangeChannel()
{
  Serial.print("VTXChannel: ");
  Serial.println(VTXChannel);
  Serial.print("VTXChannelOld: ");
  Serial.println(VTXChannelOld);
  if (VTXChannel < 1) {
    VTXChannel = 15;
  }
  if (VTXChannel > 15) {
    VTXChannel = 1;
  }
  switch (VTXChannel) {
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
    case 7:
    case 8:
    case 9:
    case 10:
    case 11:
    case 12:
    case 13:
    case 14:
    case 15:
      Serial.print("Change to channel: ");
      Serial.println(VTXChannel);
      bitBang(COUNTER18BIT[VTXChannel-1], 18);
      bitBang(COUNTER19BIT[VTXChannel-1], 19);
      VTXChannelOld = VTXChannel;
      break;
  }
}
void digitalWriteFast(uint8_t pin, uint8_t x) {
  if (pin / 8) { // pin >= 8
    PORTB ^= (-x ^ PORTB) & (1 << (pin % 8));
  }
  else {
    PORTD ^= (-x ^ PORTD) & (1 << (pin % 8));
  }
}
=======
/*
 * This sketch controls a Partom or simmilar VTX transmit frequency
 * Copyright (c) 2017 Mario Elkati (Mariete)
 * [email protected]
 */
/*-----( Declare Constants )-----*/
static const int FREQ[] = {1010, 1040, 1060, 1080, 1100, 1120, 1140, 1160, 1180, 1200, 1240, 1258, 1280, 1320, 1360};
static unsigned long COUNTER18BIT_OLD[] = {
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001,
  0b010000010010110001, /* 12 */
  0b010000010010110001,
  0b010000010010110001, /* 14 */
  0b010000010010110001};
static unsigned long COUNTER18BIT[] = { // Data original PLL for 1280Mhz
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001,
  0b010000001100100001, /* 12 */
  0b010000001100100001,
  0b010000001100100001, /* 14 */
  0b010000001100100001};
static unsigned long COUNTER19BIT[] = {
  0b0110001010100001000,
  0b0110010110001000000,
  0b0110011110000010000,
  0b0110100101101100000,
  0b0110101101100110000,
  0b0110110101100000000,
  0b0110111101001010000,
  0b0111000101000100000,
  0b0111001100101110000,
  0b0111010100101000000,
  0b0111100100001100000,
  0b0111101011001101000, /* 12 */
  0b0111110100000001010,
  0b1000000011100100000, /* 14 */
  0b1000010011001000000};
#define VTXDataPin 7
#define VTXClockPin 8
#define VTXLatchEnablePin 9
#define VTXChannelChangePin 3
/*-----( Declare Variables )-----*/
byte VTXChannelOld = 0;
byte VTXChannel = 13;
void setup()
{
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(VTXDataPin, OUTPUT);
  pinMode(VTXClockPin, OUTPUT);
  pinMode(VTXLatchEnablePin, OUTPUT);
  pinMode(VTXChannelChangePin, INPUT_PULLUP);
  digitalWrite(VTXChannelChangePin, HIGH);
  digitalWrite(VTXDataPin, LOW);
  digitalWrite(VTXClockPin, LOW);
  digitalWrite(VTXLatchEnablePin, LOW);
}
void loop()
{
  if (!digitalRead(VTXChannelChangePin))
  {
    while (!digitalRead(VTXChannelChangePin)) {
      //
    }
    VTXChannel++;
    Serial.println("Button pressed...");
  }
  if (VTXChannel != VTXChannelOld)
  {
    Serial.println("Changing channel...");
    ChangeChannel();
  }
//  delay(9500);
//  digitalWrite(LED_BUILTIN, HIGH);
//  delay(500);
//  digitalWrite(LED_BUILTIN, LOW);
//  VTXChannel = 13;
//  ChangeChannel(VTXChannel);
//  delay(9500);
//  digitalWrite(LED_BUILTIN, HIGH);
//  delay(500);
//  digitalWrite(LED_BUILTIN, LOW);
//  VTXChannel = 11;
//  ChangeChannel(VTXChannel);
}
void bitBang(unsigned long pattern, byte numBits)  // This function is what bitbangs the data
{
//  digitalWriteFast(VTXDataPin, LOW);
//  digitalWriteFast(VTXClockPin, LOW);
//  delay(1);
  for(int i=numBits-1; i>-1; i--)
  {
    digitalWriteFast(VTXDataPin, bitRead(pattern, i));
    delayMicroseconds(300);
    digitalWriteFast(VTXClockPin, HIGH);
    delayMicroseconds(350);
    digitalWriteFast(VTXClockPin, LOW);
    Serial.print(bitRead(pattern, i));
    delayMicroseconds(50);
  }
  digitalWriteFast(VTXLatchEnablePin, HIGH);
  delayMicroseconds(500);
  digitalWriteFast(VTXLatchEnablePin, LOW);
  Serial.println("");
  digitalWrite(VTXDataPin, LOW);
}
void ChangeChannel()
{
  Serial.print("VTXChannel: ");
  Serial.println(VTXChannel);
  Serial.print("VTXChannelOld: ");
  Serial.println(VTXChannelOld);
  if (VTXChannel < 1) {
    VTXChannel = 15;
  }
  if (VTXChannel > 15) {
    VTXChannel = 1;
  }
  switch (VTXChannel) {
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
    case 7:
    case 8:
    case 9:
    case 10:
    case 11:
    case 12:
    case 13:
    case 14:
    case 15:
      Serial.print("Change to channel: ");
      Serial.println(VTXChannel);
      bitBang(COUNTER18BIT[VTXChannel-1], 18);
      bitBang(COUNTER19BIT[VTXChannel-1], 19);
      VTXChannelOld = VTXChannel;
      break;
  }
}
void digitalWriteFast(uint8_t pin, uint8_t x) {
  if (pin / 8) { // pin >= 8
    PORTB ^= (-x ^ PORTB) & (1 << (pin % 8));
  }
  else {
    PORTD ^= (-x ^ PORTD) & (1 << (pin % 8));
  }
}

Modificación del VTX y conexión al Arduino

La modificación consiste en sustituir el microcontrolador PIC del transmisor por nuestro Arduino, programado con el código del punto anterior.

Para ello tendremos que desoldar, con cuidado el PIC (integrado de 14 patas) de la placa y soldar tres cables (para las líneas DATA, CLOCK y LATCH ENABLE) que irán a los a los pines Arduino 7, 8 y 9. Tendrás que conectar también la tierra entre ellos (usar un cable de negativo común, por ejemplo).

Pienso que en las siguientes fotografías se puede ver bastante bien. 

Connection Partom VTX to Arduino

Analizador de antenas comprobador de SWR con Arduino

Basándome en este desarrollo, realicé el primer proyecto interesante: un medidor de estacionarias y analizador de antena. Este proyecto permite caracterizar la antena mediante un barrido de frecuencias, haciendo gráficas de la frecuencia de resonancia de la antena, para encontrar la frecuencia en la que mejor funciona o ajustarla para que de su máximo rendimiento en el canal que nos interesa.

Tengo pendiente documentar bien y publicar este proyecto. Si te interesa conocerlo suscríbete a la newsletter de eMariete para no perdértelo y enterarte cuanto antes.