D4G AI Racing Part 2: Power is Nothing without Control. Examining the Taranis PXX Protocol
Let's go AI speeder racing! |
This article is the second in the D4G AI racing series and deals with the control aspect of flying a drone from a computer. The following examines the Frsky Taranis PXX protocol, with a view to building on this in the next article to achieve full computer control using a Taranis XJT module and Frsky RX4R D16 receiver installed in a micro drone. Before we get that far, though, we need to go through the basics of how the transmitter talks to the receiver and hack the PXX protocol. This article is primarily about digital communications at the bit level and packet level, that is to say, we assume the radio frequency electronics is taken care of by the module in the transmitter.
System diagram for computer control of a drone |
The general idea is shown above, where the laptop drives an Arduino via serial USB. This converts commands from the computer into a stream of bits which it sends to the FrSky XJT module via digital pin 2 and a suitable current limiting resistor. This is a good facsimile for understanding how a computer radio like the Taranis works. The radio itself is nothing more than a computer with some sticks and switches. The positions of all these channels get turned into a stream of digital bits, which is sent to the module and transmitted over the radio link. This is lucky for one very good reason. While the OpenTX software is open source, the PXX protocol used in the radio transmission isn't. This means that my entire source of knowledge about how the PXX protocol works comes from reverse engineering the OpenTX software. After all, that's all I'm doing, but replacing the Taranis with my own laptop so I can do some more interesting stuff later on.
The source code for OpenTX can be found here: https://github.com.opentx/opentx
But, before attempting to control anything with an XJT module, I need to see the data that the radio is sending just to make sure I understand it properly. Fortunately, this is very easy to do. Simply switch the arrow in the diagram above, change the FrSky module for the Taranis itself and change the Arduino digital output to a digital input.
System diagram for receiving the PXX data from the Taranis |
The diagram above shows how to use the laptop as a cheap version of a "protocol analyser". There's just one problem with using an Arduino. While it's clocked at 4MHz, the bit rate of the data is 62,500Hz (one bit every 16us). It's very close to the maximum serial data rate that can be handled with an Arduino, so it's going to need some clever programming.
Now that the scope of the project has been described, it's necessary to outline some of the basics of how computer radios send the channel information to the receiver in the aircraft.
There are two levels of understanding required here in order to control a drone: bit level and packet level. Let's start with the basics. You want to send a message over a radio connection. It just happens that this radio message is the position of all the sticks and switches on your radio. You have 16 channels (D16), so let's imagine that they're connected to 16 servos at the other end, which move as you move the sticks (and switches). The servo moves from one end of its travel to the other extreme and back again as you waggle the stick. We can encode this as a number between 0 and 4095, where 2048 would give us the half way position. The reason for choosing this number is that the protocol used by the radio is based on 12 bit accuracy and 4095 can be encoded as a binary number using 12 bits. This isn't quite true, but we'll cover the finer points later on. For now, just take it as given that the stick position can be encoded into any one of 4096 discrete positions between zero throttle and full throttle and everything in between. This means that 16 channels times 12 bits need to be sent to the receiver every data frame for it to be able to decode all the channels. Now, in fact, PXX doesn't do this, but splits it into two frames, sending 8 channels and then the remaining 8 channels on the following frame, plus some other bits of information too.
The pinout for the connections on the back of the Taranis is below. This is valid for any Taranis (or other radio) that accepts an XJT module. You would expect this to be listed in the Taranis manual, but, no, I can't find an official source of the information anywhere, so I've included it here for reference.
The back of my Taranis QX7S showing the XJT module pinout. Click to zoom. |
The pins are as follows, top to bottom:
- Signal (PXX) (#1)
- No Connection
- VCC, power connection +7.5v
- GND, ground 0v
- Antenna or S/Port connection (#2)
NOTE #1: The output here depends on the Taranis operating mode and could be PPM, PXX or ... (see below)
NOTE #2: I'm not able to verify the antenna or S/PORT connection, but S/PORT is listed in the Taranis documentation.
Next, the radio needs to be configured for a D16 receiver so that we get PXX output. I create a separate model definition for my experiments, which I call "arduino". Then I can keep all my modifications separate from any real aircraft definitions. The external RF output of the radio (i.e. the module bay on the back) needs to be enabled, which is on the "SETUP" page under the "External RF" section. Turn "Internal RF" OFF, then set "External RF Mode" to XJT and D16 on the following option to enable PXX output with 16 channels. Due to the whole debacle with EU flashed radios, there are no D8 radios that work with the EU "listen before talk" firmware that is required to comply with the EU directive. The RF mode takes the options of PPM, XJT, DSM2, CRSF and R9M, all of which employ a different protocol. PPM, or "pulse position modulation" always used to be the most popular one until the number of channels got too big and control latency became an issue. I might cover this in another post as it's a much easier protocol to understand and is used by the "iRangeX" multi-protocol module that I covered in a post a while back.
That's the radio configured, so the next part is the electronics. This requires some care, as YOU CAN DAMAGE YOUR TRANSMITTER if you get this wrong. The order of connecting and disconnecting things could cause problems, so I always do it in this order. Connect the arduino to the computer so it powers up. Turn the transmitter on and verify that it's in the right mode. Then connect the GND, followed by the PXX. To turn off, simply reverse the steps. Remove PXX, remove GND, turn transmitter off, disconnect arduino. The logic behind this is not to put power onto the pin of a device before it is switched on.
Connecting my QX7S to the computer via an arduino. The screen shows the PXX messages being decoded by the arduino. BB8 is there because he's very good at decoding machine languages |
In the picture above the wiring follows the schematic from earlier. The red wire from the transmitter is the PXX connection from its top pin, to the breadboard and through a 100K resistor, then to the digital input pin 2 of the arduino. The green wire is GND from the transmitter to the GND on the arduino via the breadboard.
The screenshot shows the packets being decoded. I'm going to explain how the decoding software works first and then come back to what the packets actually mean again at the end.
Putting an oscilloscope on the PXX connection shows the wire level protocol being used to transfer the bits of information.
Trace of a PXX signal from a Taranis QX7S |
Timing diagram for the PXX bitstream |
A zero is coded as an 8us low, followed by an 8us high pulse. A one is coded as a 16us low, followed by an 8us pulse. A microsecond (us) equates to 1x10e-6 seconds. This is what is called the "wire protocol". When all the bits are put together to form a message, then that is the "packet level". Looking at the OpenTX source code, they have an interesting way of transmitting the stream of serial bits. They set up a bit buffer to transmit one bit every 8us, then push a stream of 1s and 0s onto the stream to send the data. To send a 0, it's [0,1], which is 0 for 8us, then 1 for 8us. To send a 1, it's [0,0,1], which is 0 for 8us, 0 for 8us and 1 for 8us. If you look at the timing diagram above then it should be obvious how this works. Techniques like this are fairly common in digital communications, where streams of data need to be decoded very quickly.
Finally, the inter-packet timing isn't shown on the diagram, but messages last for round about 3ms (variable due to variable numbers of bits), while the stream will pause between packets for 6ms. This pause period can be used to reset the receiver for the next incoming packet.
At the wire level, there's one more trick that needs to be understood before the message can be decoded. "Bit Stuffing" refers to the practice of inserting an extra zero each time there are five ones in a row. This is because the "7E" code, which is the unique code representing the start of a packet, is binary %0111 1110. Any decoder can then use 6 ones in a row as a reset condition, which gives you an easy way to detect the start of packets and never get out of sequence.
OK, that's quite a lot of theory covered regarding digital communications over a two wire protocol. Let's assume the bits can be read correctly into messages and see what's on the data stream.
Datastream capture. '***' shows a packet with a valid checksum. Packet 3 is invalid. |
Looking at a section of the data from the earlier screenshot, we can see the channel data being received. "7E" is the start of packet. This is followed by "04", which is my receiver number. This is set when the receiver is bound and can be changed from the main setup screen. After that, we get "00 00", which are two flags that I haven't been able to fully decode yet.
Bytes 4 and onwards are the channel information, with three bytes per channel. The Taranis channel ordering is TAER (throttle, aileron, elevator, rudder), so they go in that order as CH0, CH1 etc. Now, in the diagram above, my throttle is at zero, while all the other sticks are in their centre positions.
The following graphic should make things clear, but this time, the throttle is at 100%:
Diagram explaining how to get the channel values from the PXX packet. 8 channels are shown colour coded. |
Channel 0 is shown in red and contains the value 0x700 (i.e. 700 hex, which is 1792 in decimal). This is the full deflection for the channel, showing that my throttle stick is at 100% throttle. Channel 1 (green, aileron), channel 2 (blue, elevator) and channel 3 (cyan, rudder) all have values around 0x400 (1024 decimal), which is the centre position. I've then repeated the colours for channels 4, 5, 6 and 7, but these are all separate channels and could be used individually. It's just that, on my Taranis mixer screen, I only have the regular channels 0-3 defined. That's why 4-7 all show exactly 0x400, which is the centre position.
Great, so that's all 8 channels of the D16 protocol decoded. Oh, hang on a minute, where are the other 8 channels? Well, if you're really observant, then you will have noticed that channels 0-7 range from 1 to 2046. That's 0x7fe hex, or 0111 1111 1110 in binary, so there's an extra bit in the left most position that's not being used. The computing term for this is the "most significant bit", or MSB. This is used to flag the fact that a number represents the bottom 8 channels (=0) or top 8 channels (=1). The PXX protocol is supposed to transmit the high and low channels in different messages, one after the other, but I can't see any of the upper channels in any of my data at the moment. This could either be because my receiving program is dropping packets (very likely), or because I've only got channels 0-3 programmed on my Taranis.
So, to recap, here are the ranges for the high and low channels:
CH0-7: 1..2046 (centre is 1024, 0x400)
CH8-15: 2049..4094 (centre is 3072, 0xC00)
We're very nearly there, so, to roundup, I only need to make a brief mention of the flags and the failsafe.
All the following information has been extracted from looking at the OpenTx source code, which can be found at the following address:
https://github.com/opentx/opentx/blob/2.3/radio/src/pulses/pxx1.cpp
This explains that "Flag 1" on the diagram above holds the "module type", "bind", "rangecheck" and "failsafe" modes. I haven't experimented with these options, yet, so I'll just make a brief mention about the failsafe. The failsafe on a Taranis (and some other radios) could be "Hold", "Preset Value" or "No Pulses". When the "Hold" option is selected, in the event of a loss of signal at the receiver, it simply holds the channels at the last position prior to the signal loss. With "Preset Value", the pilot sets a value for each of the channels to take on a loss of signal, for example, throttle zero, all controls to centre. This means that the failsafe flag in "Flag 1" works in conjunction with the channel positions that follow. If the flag is set, then the channels being sent are stored by the receiver and used as the failsafe positions. Then there's the final failsafe mode, which is "NO PULSES". This might seem strange, but it seems sensible for a quadcopter setup that the receiver could stop sending any information to the flight controller on loss of signal. This would tell the flight controller that the signal is lost (it's probably monitoring RSSI too), so the flight controller could then take its own loss of signal action, which could be much more intelligent than the receiver. For instance, it could decide to hover at a preset height and then return to home via GPS if the signal was not re-acquired within a preset interval.
To summarise, here are the descriptions of the three flags, obtained from the OpenTX source code:
Flag1: Module type, bind, rangecheck, failsafe
Flag2: Always zero
Trailing Flag: External Antenna on Horus, telemetry and S/Port?
Then the channels carry information as follows:
Failsafe: CH0-7: HOLD=2047, NOPULSES=0, ELSE 1..2046
Failsafe CH8-15: HOLD=4095, NOPULSES=2048, ELSE 2049..4094
That concludes the first part of this article, which is the description of the PXX protocol. The information discovered up to this point should now allow me to write the software to control a receiver and servos via a laptop. That's where part two of this article is going to pick up with the Arduino software required for the remote control of a drone.
I have read many blogs and found good and informative, but this blog is simply thrilling me with its designs, color combination and the most important thing real information. I found only genuine and useful information on this blog.
ReplyDeleteDrohnen Versicherung
In this blog is very much information about drons I found only genuine and useful information on this blog.
ReplyDelete