[Scroll to the bottom if you just want to know how it works]
Being an avid drone enthusiast it’s been difficult not to notice and get involved in the explosion of FPV racing recently. At these events, drones are timed around the track using infra-red transponders such as the one on the right. This transponder is part of the iLap racing system – a particularly well designed system albeit somewhat expensive. Hence, we’ve been developing our own race transponder/timing system (using a different protocol) but wanted it to be interoperable with iLap and recognise both types of transponders on the gates. Reverse engineering is explicitly permitted in the EU and USA for this purpose.
The transponders have a unique number identifying them stuck to a label and it’s logical to think that this is what is transmitted through the led. To decode the led protocol, I purchased an Hantek6000BE oscilloscope for £40 and thanks to my friend Niall Sheffield who let me mutilate his beautifully shrink wrapped transponder, I attached it to the led output.
Identifying the modulation
Usually, infra-red protocols are about 38khz with a variety of modulation schemes. My oscilloscope captured the below signal, which has a frequency around 460khz.
Zooming out to view the entire transmitted signal, one can rule out a bi-phasic modulation (such as manchester) because the off periods are simply too long. If one assumes a simple on-off modulation then there are many more than the 24bits that make up the transponder code, presumably a start byte and some sort of checksum. The modulation scheme is simply switching the 460khz carrier onto a UART signal at 38400 (8N1).
When one decodes this to bytes, one is left with six bytes as follows:
09 23 bc fe e9 b7
The astute reader will notice that this bears little resemblance to the transponder code on the label (6430116 or in hex 621da4). I collected a handful more transponders and started looking for patterns. One of the codes I had was a repeating number (e.g.7543543) and I noticed a similar repetition in the six byte sequence. After a bit of head scratching I noticed that the bit values were inverted, and so if one inverts the above bytes you get:
f6 dc 43 01 16 48
And you can see the transponder code, encoded using binary coded decimal (BCD). I’m not sure why it’s encoded in BCD, it’s an awful waste of the key-space.
The next question is: what are the other bytes? To work this out, I collected quite a few transponders and noticed that the first nibble (f) is always the same, so this is clearly a start nibble signifying the start of the data. Bytes 2 (dc) and 6 (48) changed every time – one or both are probably checksums.
Working out the checksum algorithm is unfortunately an exercise in determination and trial and error. Firstly, one doesn’t know the algorithm, and secondly nor does one know which bytes and in which order are fed into the checksum algorithm.
I used the tool reveng which tried a whole lot of different algorithms without success. I also experimented with XORing, summing and the python crc16 package (CRC16-Xmodem) without success.
Reversing the firmwares
Rather than spend my life working on it, I thought I would just pull the firmwares from the iLap timers and the clones made by SPFRacing given I am familiar with reversing programs (on x86) I might find the checksum algorithm on the transponders (they may check it or calculate it).
The iLap transponder had its code protected so I abandoned that. The SPF transponders did not and I promptly plugged it into my STLINK programmer and pulled the firmware. Then started the process of decoding an STM32 firmware – of course, when you start, you’re just presented with a blob of bytes – finding where the code starts is the first step.
The reference manual tells me that STMs have a Interrupt Vector table at the beginning of flash – a series of pointers to functions to handle events (such as power-on). Converting each of those to function pointers and identifying the reset vector, I was able to tell IDA where to start disassembling.
The next step is figuring out where in the code to look. I’d also noticed earlier that the transponder code was in the firmware at the end (above) as it’s very common to store data at the end of flash in STM devices. IDA helpfully showed me all the functions that reference this memory address. The first being on the right which looked like a flash integrity check and I promptly found the two identical functions in the GPLed cleanflight flight controller source code.
The other location appeared to be the main function which checks the flash data is valid (see right), initialises the GPIO, timers and DMA for outputting the code and then enters a final loop waiting for commands on the serial port. I don’t believe it verifies the checksum in the transponder code which isn’t that surprising.
Plan B paid off. Just as I began the disassembly, I also posted a series of transponder codes to stackoverflow with an explanation of the packets and asking for help identifying the checksum algorithm in the hope that if the firmware doesn’t check the code then crowd-sourcing might find the solution. Michael Karas used this online calculator and found out it was in fact CRC16-Xmodem, the same algorithm I’d tried earlier except that the bytes were being reversed before being plugged into the algorithm. The CRC bytes were then split and then put into the two different spots in the packet (which is unusual). It’s irritating to know I was so close in the beginning but goes to emphasise how much of this is trial-and-error and a bit of luck.
So, how do the codes work:
- They’re transmitted out the led as UART with a baud of 38400. The carrier frequency is 460.750khz (I think). There’s a 7.372Mhz crystal on the board, and this frequency that one would get with a prescaler of 16 which is close enough to what my cheap oscilloscope says.
- The packet format is sd cc dd dd dd cc where s=start nibble (always f), d are the BCD encoded transponder code and cccc are the checksum bytes.
- The checksum algorithm is CRC16-CCIT (XModem) of the bytes 1, 3, 4 ,5 fed in reverse. The high byte goes into position two and the low byte position six.
- The whole packet has its bits inverted before transmission.
- A single code is transmitted in 1.5ms, and repeated every 5ms.
- I’ve produced a script to generate ilap transponder packets including the six-byte data packet.
The question is how to build a circuit to capture such high frequency IR and then demodulate it on the receiver? It’ll probably involve an infra-red diode, an op-amp and PWM capture on an STM32. Ordinary 38khz infra-red receivers will not work. Watch this space for more information on the gate circuitry/code.
Thanks goes to Niall Sheffield and Chris Gundy for letting me put their transponders at risk. Matthew Austen for getting me several more transponder codes. And a huge thanks to Michael Karas from stackoverflow for identifying the checksum algorithm!