VTX FPV video transmitter control with Arduino

How to control a FPV VTX with Arduino

Some time ago it got into my head control a 1.2 Ghz video transmitter with an Arduino.

Specifically, what I wanted to achieve was to make a antenna analyzer, also based on Arduino (which is made and works very well, I have to describe in the Blog how I did it and publish the code) that it was able to do a frequency sweep, for example from 1.1 Ghz to 1.4 Ghz, with steps every few Mhz, measuring the SWR in each of these steps to be able to capture all the data in Excel and make a antenna resonance graph in that frequency range.

I was looking a lot on the internet and I found very little information, so I decided to start almost from scratch and do all the analysis of the protocol that the VTX microprocessor uses to communicate with its IC PLL (an MB15E07L, manufactured by Fujitsu, which is the integrated circuit that creates the emission frequency), to know how to control it, and to write the Arduino code.

A little bit of reverse engineering doesn't hurt anyone.

Fortunately, the user Changosurf had already described the PLL protocol, although it did not work for me as it was and I had to do more research (the data from the 18-bit latch register did not work with my VTX, nor with any of the ones I tried ).

I also found some code to use as a base, but sorry, since I did this project several years ago, I can't find the original author to give it credit.

I did the whole process using a 800mw VTX Partom, one of the most used in model airplanes, but the good thing is that almost all video transmitters that have this physical aspect, such as the VTX FOX (and many others), use this same PLL and protocol. In fact, I have tried other transmitters of different powers and other physical aspects and all have worked correctly.

How useful is control with Arduino?

The profits are many, and only the imagination is the limit.

In addition to the example mentioned above that allows us to vary the emission frequency automatically in order to analyze antennas, we could use it to:

  • Change video channel of our plane or drone remotely by radio control. We have interference on the frequency we are using and our model aircraft is far away? Well, we change the frequency remotely (we can even save our device or avoid safe loss).
  • Use non-standard frequencies: Transmitters usually have between one and eight channels that we can select, this means that everyone is on one of those channels, if we can choose a frequency that does not coincide with that of any of those channels we can use that frequency for ourselves, exclusively, without interference.

Architecture of an FPV video transmitter

The architecture of a VTX of those used in FPV is quite simple.

It basically consists of four basic blocks:

  • Control circuit: the user selects a broadcast channel using switches or buttons and a microcontroller checks what frequency that channel corresponds to and encodes it so that the PLL can understand it.
  • Frequency generator circuit or PLL (Phase-locked Loop): This integrated circuit receives data from the microcontroller that indicates the frequency to be generated.
  • Radio Frequency (RF) Circuit: This block receives the frequency generated by the PLL (which does not have to be the actual emission frequency) and using that frequency as a base it will generate the final frequency, the subcarriers, and modulate it with the audio and the video.
  • Power amplifier: All the above is done with extremely low powers, it is this final circuit that takes that signal and multiplies it by thousands or millions of times and sends that high-power signal to the antenna for broadcast.

What we are going to do here is replace the «Control Circuit» with our own to be able to control the PLL as we want.

PLL protocol analysis

PLL VTX FOX Partom

The first thing I did was, before making any modifications to the transmitter hardware, analyze data on the PLL communication lines, with my help RIGOL DS1054Z oscilloscope 

Next, I changed the channel, manually, and with the oscilloscope (temporarily connected to the VTX with a few cables) to capture the data that the PIC microcontroller sent to the VTX.

Below, you can see the screenshots I made when switching to the different channels:

Finally I helped myself to capture channel 11 to check if it was in 18-bit or 19-bit mode:

In this last image you can see in detail the analysis of the protocol, since I documented it on the capture with numbered bits to make it easier to visualize:

VTX Control Protocol

Arduino sketch code

 Here you have the complete code to control the VTX with Arduino.

I recommend, if you are going to use it, that you take it from the eMariete page on Github, where you can find the latest version and other interesting information.

To flash the Arduino you can use the normal Arduino IDE. Keep in mind that this code was developed with a 2017 version of the Arduino IDE, so with the latest versions there could be incompatibilities.

The code is not a marvel of programming, it is something "quick and dirty" to check that it worked and I uploaded it to Github "as it is«. I did the tests on a 3.3V 8Mhz Arduino Pro Mini and it worked without problems.

/ * * 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 [] = {// Original PLL data 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 [] = {// Original PLL data 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));
  }
}

Modifying the VTX and connecting to the Arduino

The modification consists of replace the transmitter PIC microcontroller with our Arduino, programmed with the code from the previous point.

To do this we will have to carefully desolder the PIC (integrated 14-pin) from the board and solder three cables (for DATA, CLOCK and LATCH ENABLE lines) that will go to the Arduino pins 7, 8 and 9. You will also have to connect the ground between them (use a common negative cable, for example).

I think that in the following photos you can see quite well. 

Connection Partom VTX to Arduino

SWR tester antenna analyzer with Arduino

Based on this development, I made the first interesting project: a stationary meter and antenna analyzer. This project allows to characterize the antenna by means of a frequency sweep, making graphs of the resonance frequency of the antenna, to find the frequency in which it works best or to adjust it so that it gives its maximum performance in the channel that interests us.

I have yet to document well and publish this project. If you are interested in knowing it subscribe to the newsletter of eMariete so you don't miss it and find out as soon as possible.