The Memoirs of Jim 'ung

Reverse Engineering a Hubsan X4 Quadcopter – Part 1: Hacking the Controller

Reverse Engineering a Hubsan X4 Quadcopter – Part 1: Hacking the Controller

Reverse Engineering a Hubsan X4 Quadcopter – Part 1: Hacking the Controller

The write up for this project is split up into 4 parts – part 1: hacking the controller, part 2: decoding the protocol, part 3: the protocol definition, and part 4: hacks using the protocol. That way you can just jump to whatever interests you, or follow the whole thing first start to finish. There’s also a bonus post on achieving a similar goal by emulating the controller joysticks using an Arduino Uno.

Introduction

The latest fad in our lab is the amazing Hubsan X4 H107L micro-quadcopter – cheap, fun, decent quality, and some of the coolest technology I’ve seen packed into something so small. This was my first brush with quad-rotors, and I think I’ve got the bug.

Of course, no gadget is particularly interesting unless you can hack it, so the first thing on my mind was how the control protocol works.

Prior Art

A quick Google search showed that there has already been some great work done on investigating and re-implementing the Hubsan X4 protocol:

  • PhracturedBlue (DeviationTX Project)
    As far as I can tell, the earliest attempts at discovering the mechanics of the Hubsan protocol for the Deviation 3rd-party firmware for Walkera RC controllers.
  • Instructables/GitHub peeps
    A great guide on how to implement the protocol, with some really good improvements to PhracturedBlue/the Deviation project’s A7105 and Hubsan protocol Arduino libraries in the comments.

These resources were really helpful in getting my head around the protocol and interfacing the A7105 with an Arduino – but they were just a bit light on reverse-engineering detail and I wanted to know how this thing worked inside and out.

Step 1: Observation

The first step in reverse-engineering the protocol is to understand what the controller and X4 can actually do. This means flying and crashing it an awful lot (at the lab we just broke our last replacement rotor… so there’s that).

Flying the quad-rotor is a lot of fun – but it takes a little getting used to if you’ve never flown one before. There are a ton of great tips in the manual on how to keep the thing in the air, and even do 360-degree flips.

The Quadrotor has 4 flight control axis’ – Elevation, Yaw, Pitch, and Roll. It also has trim control on all axes in fine increments. With a push of the left stick, you can swap the control scheme to your preference. You can also calibrate the sensitivity of each axis too. The X4 also has 2 flight modes – “anti-flip” and “expert”. Anti-flip is the default mode and is relatively easy to control, whilst Expert mode allows much faster and greater range of movement. You need to be in Expert mode to perform a flip by applying a bunch of throttle then rocking either pitch or slide quickly from one side to the other.

Binding the controller with the quadrotor is a matter of powering them both on and ensuring the quadrotor is on a flat surface (presumably for the internal gyro calibration). The lights on the Quad should stop flashing and you’re ready to go. If you kill the power to the controller mid-flight, the quad will just drop out of the sky. Similarly, if you power off the controller and then power it back on, the quadrotor won’t respond.

With these observations we can make the following guesses (which may be wrong!):

  1. Trim, Calibration, and Control-mapping are done on-controller
    The settings seem to be remembered between flights, even when the battery is pulled from the Quad –it seems unlikely the vehicle has any non-volatile memory.
  2. The controller is communicating constantly.
    Dropping the power on the controller results in an immediate loss of quad flight – it doesn’t hold station or perform a graceful emergency landing.
  3. The controller and quadrotor only bind once.
    Cycling the power on the controller once it has already bound to the quad results in the quad being unresponsive until the power is cycled on that too. The binding negotiation isn’t remembered between power-cycles.
  4. Expert mode/Anti-Flip mode is probably just a change to the calibration range or sensitivity of the controls.
    Watching a 360 flip in action, it looks like you have to use the quadcopter’s inertia by shifting it’s mass one way, then using it’s agility to rock the quadcopter into a roll in the opposite direction. As far as I can tell, this ability is provided purely by the power and agility of the quad’s motors.

Step 2: Examining the controller

When reversing toy RC helicopter/quadrotors, we can almost always assume that the controller is going to do the lion’s share of the communicating. It will almost certainly be a lot easier to look at than the control board on the vehicle too.

PCB_top_small

PCB_bottom_small

Taking the controller apart, we can see that there is a single PCB with all of the control input components surface-mounted directly on it.  This includes the LCD screen, a bunch of micro-switches, 2 dual-axis thumbsticks, and the on/off switch.  You might notice that there are no external antenna wires and that little pointy antenna nub on the molding of controller is purely for decoration.

So we’re looking for the juicy bits – the main microcontroller, the RF hardware, and any debug ports/test access points that could be handy.

The Microcontroller Unit (mcu)

MCU_closeup_small

The brains of this thing appear to be an STMicroelectronics STM8S003. This is the value line of the 8-bit STM8S series of general-purpose embedded microcontrollers.

As this is a general-purpose part, we can hope that ST will have made the Datasheet available. Google shows that this is the case:

http://www.st.com/web/en/resource/technical/document/datasheet/DM00024550.pdf

A quick glance at the marketing specs on page one of the datasheet shows that it supports UART, SPI and I2C communications. This kind of systems-engineering information is important to remember when we try and piece together how this thing works.

Unlike the big through-hole DIP IC in the Syma S107G controller, the MCU here is a small TSSOP20 package. This isn’t ideal, but it could be worse – if we can’t find a debug or test port, the IC pins are exposed so we could always solder some fine magnet wire onto them and sniff them that way.

The RF Transmitter (Tx)

Tx_closeup_small_caption

The wiggly antenna traces give away the approximate location of the RF transmitter part. A Google search of the RF part shows that it is an AMIC A7105 2.4Ghz Transceiver module (transceiver means it can transmit and receive).  Not only that, but the datasheet is freely available too:

http://www.avantcom.com.tw/AVANTCOM/TC/DATA/PRODUCT/SOLVE/18_3.pdf

This datasheet isn’t quite as easy to read as the ST one, but it still tells us everything we need to know.  The summary on page 5 tells us that it supports 3-wire and 4-wire SPI communication – this is perfect. Not only is SPI one of the communication protocols that our MCU supports, it is also widely supported by logic analyzers.

Sadly, the pins on this package are inaccessible as it’s a tiny QFN package, so we’re stuck with reading the MCU pins or finding a debug port.

Any debug or test ports?

Debug_port

Now then – this row of unpopulated pads is interesting. It looks like traces from all over the board run towards it – on both sides and apparently on different PCB layers too. That is a great indicator that this is a debug/test port.

These kinds of ports are often used in manufacturing PCBs to aid in the efficient automated programming and/or testing that the assembled devices go through in the factory. Such ports are a goldmine for reverse-engineers and hackers as they can be a window into the devices soul…or in this case maybe the communications bus between the MCU and the Transceiver. They may be distributed across the board as a constellation of test pads (ready to be probed by a bed-of-nails test jig) or closely grouped like this one.

So what did we learn?

Now we’ve had a good look at the PCB and parts that make up the controller, lets summarize all the things we’ve learned about how this thing probably works:

The Microcontroller is an STMicroelectronics STM8S003-series 8-bit processor.

The RF part is an AMIC A7105 2.4GHz Transceiver module.

There is a promising-looking set of unpopulated pads on the top-left of the board that look like a debug/test port.

Anything else?

It should be pretty obvious to anyone reading that it’s probably not a coincidence that the Tx requires SPI, and the MCU supports it.  But to remove any doubt we can take a look at the board to confirm our suspicions:

MCU to Tx Bus

Here we can see a clear communications bus between the MCU and the Transceiver chip.  As we have the Datasheet, let’s take a look at what the MCU pins connected to the bus traces do – counting from the pin marker dot at the top left, we can see the connected pins are 13, 15, and 16. Page 21 of the STM8S003 datasheet shows the pinouts for the TSSOP20 variant of the chip. We can see that pin 15 is “SPI_SCK”, pin 16 is “SPI_MOSI”, and pin 13 is “TIM1_CH3”. This is all we need for 3-wire SPI, so now we know exactly which pins to sniff with the logic analyzer if need to, and a good idea about what they will be doing.

Step 3: Sniffing the bus

As we’re interested in the data at a protocol level (we already know it must be SPI), we’re going to want to probe the test pads (or failing that the MCU pins) with a logic analyzer. Ultimately we want to design a test regime that will allow us to empirically manipulate the controls in isolation and observe the output. Understanding the relationship between changes to a given control axis and the resulting changes to the protocol data is crucial to reversing the protocol.

But first we need to get access to the test port while still being able to fly the quad – this means breaking out the test port.

Step 3a: breaking out the test port

What we need is a makeshift extension for the test port to make it accessible from outside the controller’s case. Ideally it should terminate in some kind of connector that makes it easy to access each pin individually, mitigate the chance of accidental short circuits, and help keep your work tidy.

As the pointy “antenna” nub part of the case is purely decorative and the test port is nearby, I think this is a good place for us to make a hole for the breakout without worrying about other components or structural parts of the case getting in the way. We’ll start by drilling a hole in the controller casing with incrementally increasing-gauge bits (starting with a small bit and getting larger reduces the chance of the plastic cracking and make it a bit easier to get a clean cut). A 6.5mm bit looks like it is wide enough to feed the 9 wires we’ll need through (8 test pads + GND reference).

Apologies - I forgot to take a "before" photo...

Apologies – I forgot to take a “before” photo…

Next let’s cut 8 pieces of insulated wire long enough to reach from the test port pads out through the hole in the case we drilled (to common length of about an inch outside the case). These are easily stripped and soldered onto the test port pads.

breakout_wires

Very important! It’s essential we make a note of the order that breakout wires go in from top to bottom – we’ll be soldering them to the breakout connector in the same order. This is very important if you have multiple devices or communications buses broken out to the test port – you’re going to want to know which pin is which!

We also want to run a wire from the negative terminal of the battery compartment out through the hole too. This will be our ground reference for voltage and logic measurements.

ground_wire

Next we want to solder the wires (in order) to the breakout connector of choice. I really like using standard 0.1” pin header for this – it’s super cheap, easy to grab with probes, and not too hard to solder wires to. With a bit of flux, some Helping Hands, and some heat-shrink tubing, you can have a really nice and discreet breakout in no time.

pin_breakout

Step 3b: Finding the signals

We’re almost ready to start sniffing for protocol data, but first we need to do a bit of due-diligence to make sure it’s safe to connect the logic probe. As with the Syma S107G, I’m going to be using my trusty Saleae Logic 8.  The Logic 8 has a maximum input voltage of 5V, so we’re going to want to make sure none of the test points exceed that at any time during our testing.

NB. The Saleae Logic 8 doesn’t have great input protection, so I’m always a bit paranoid about this bit. Apparently the Logic 16 has much better safeguards against over-volt inputs.

To do this we can use a digital multimeter to monitor the voltage-level of each pin – in this case, we want to make sure the voltages don’t change after the controller has bound to the quad too.

Here’s the table I used to keep track of my observations:

voltage_table

This is looking good, and already I’d be willing to wager pins 2,3, and 4 are going to be interesting. Let’s wire up our logic analyzer harness:

Logic_hookup

To ensure we can accurately and fully sample the protocol we need a sample rate of at least that of the expected communications protocol. From the A7105 datasheet (in the SPI Timing Characteristics section), we can tell the maximum possible clock rate supported for the SPI input is 10 MHz. Let’s configure our capture with a sample rate of 24MHz for 500 million samples (giving us a capture window of 20.8 seconds).

The first capture will just be to see if we can even find the SPI bus on the test port – powering the controller on, wait a few seconds, and then powering it off:

Logic_Sniffing_Test

There are clearly a lot of transitions on channels 4, 5, and 6 (corresponding to pins 2, 3, and 4 – sorry the pins and channel numbering is backwards…). This is a great sign that there’s digital communications occurring. Let’s look closer at those 3 channels:

Logic_Sniffing_Test_IDing_SPI_Pins

This is looking great – and to explain why, I should probably talk about the SPI protocol briefly.  Serial Peripheral Interface (SPI) is a clocked serial protocol that allows full-duplex (simultaneous two-way) communication and multiple devices on a shared bus. This diagram shows how multiple devices on an SPI bus can be logically connected:

Image provided courtesy of Wikimedia Foundation.

The SPI master (usually the MCU) provides the oscillating clock signal for the bus. When we want to send a 1 or a 0, we pull the Master-Out-Slave-In (MOSI) or Master-In-Slave-Out (MISO) line HIGH (for a 1) or LOW (for a 0). The state of the MOSI/MISO line at the rising or falling edge (which one depends on implementation – check the datasheet) of each clock oscillation is the bit value that is transmitted. The Master  signals that it wants to communicate with a Slave device by pulling the Chip Select pin (SS above) LOW (the default inactive state is usually HIGH for Chip Select).

For more information about SPI, see http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus.

So back to the Logic Analyzer – we clearly have 3 interesting channels, each that look very different from each other:

Logic_Sniffing_Test_IDing_SPI_Pins_closeup

Channel 5 – This one immediately jumps out as a clock signal – it’s regular and is apparently grouped into 8 oscillation sets (8 oscillations, 8 bits = 1 byte?). We can also tell from the period of the oscillations that the apparent clock rate is about 235kHz.

Channel 4 – This one is much less regular than Ch5 and often transitions in the middle of Ch5’s oscillations. That sounds a lot like a MISO/MOSI data line.

Channel 6 – This one just goes LOW for the duration of the transmission – looks just like you’d expect a Chip Select line to look.

Logic_Sniffing_SPI_Analyzer_ConfigSaleae Logic’s software package has built-in support for SPI protocol decoding. Let’s configure it with our guesses about the channel types. We’ll leave the other values as default for now – we can always check the datasheet if we have problems.

Now lets have another look at our capture:

Logic_Sniffing_SPI_Analyzer_Confirmation_closeup

Excellent – it looks like our guesses were correct. The packet we’re zoomed in on appears to be a 5 byte transmission:

0x06 0x55 0x20 0x10 0x41

Using the A7105 Datasheet (page 31, page 37, and page 16), we can decode this as:

0x06 = Command: write to ID Code (hex 06) control register.
0x55201041 = 4 byte/32-bit ID code data value.

More on how this is done in Part 2, but for now we’ve confirmed that our methodology is good: we now have a way to observe and record all communication between the MCU and transceiver, and we’ve just observed the MCU configuring the Transceiver’s ID code (and much more in the rest of the capture). In Part 2 I’ll cover how to devise a test regime to generate good data and how to logically dissect the captured traffic – hope you found this informative, and if you have any questions please leave a comment below.

Go to: Part 2 – Decoding the Protocol

Jim

22 thoughts on “Reverse Engineering a Hubsan X4 Quadcopter – Part 1: Hacking the Controller

  1. Pingback: Reverse Engineering a Hubsan X4 Quadcopter — Part 2: Reversing The Protocol | The Memoirs of Jim 'ung

  2. Pingback: Reverse Engineering a Hubsan X4 Quadcopter – Part 3: The Protocol Definition | The Memoirs of Jim 'ung

  3. Germain

    Thank you for sharing Jim ‘ung !
    It reads like a thrilling detective novel !
    I was planning to do the same, but now I realize it was so out of my reach. : )
    May I ask you what is your major ? How did you get such skills ?
    Greetings from France.

  4. Pingback: İNSANSIZ HAVA ARAÇLARI ÇALIŞTAY ÇAĞRISI | Balich Information Security

  5. J0hny

    Hello, I’m trying to control my hubsan x4 with my computer, but using a different software…. Maybe your experience can help me ?
    I have problem during the binding stage, the 2 blue leds start blinking together…
    https://www.youtube.com/watch?v=EmPDMQ5w9BE

    On my computer I use this software : https://github.com/chpatrick/hubsan

    As you can see, at the beginning, the Hubsan blinks normally : he is searching for a remote to which he can bind.

    Then, I launch the script on my computer, the blinking pattern change, but the software on my computer doesn’t detect it…

    The two blue led start blinking together, does someone recognise this pattern?

    Thanks

    1. Jim Post author

      Hey,

      That’s an interesting problem – I’m not familiar with that project, but I’ve taken a quick look at the code to see if I can see the problem. My first guess was something straightforward like the throttle control value not being initialised correctly (it has to be zero otherwise the quad won’t accept control signals), but from the look of your video it doesn’t look like the script is getting to the end of the bind() function in hubsan.py. My guess is that it’s failing somewhere in the escalating handshake – there is an issue posted to the github repo where the author mentions in a reply that it may be a timing issue causing the fault.

      I’m going to publish my own code to Github as soon as I get a chance, but in the meantime try changing line 5 of test.py from “logging.basicConfig(level = logging.INFO)” to “logging.basicConfig(level = logging.DEBUG)”, then run it and post the terminal output on here for me to take a look at.

      Jim

      1. Jim Post author

        J0hny – something else occurred to me when glancing at that project’s code: it looks like the way it handles the third phase of the binding handshake doesn’t quite reflect how the real controller does it:

        while True:
        try:
        phase2_response = self.bind_stage(9)
        if phase2_response[1] == '\x09':
        break
        except BindError:
        continue

        This code appears to jump to binding status “9” straight away, but if you look at my chart in Part 2 (http://www.jimhung.co.uk/wp-content/uploads/2014/05/Binding_Choreography_example_2.jpg) you can see that the controller handshake normally escalates from 00 all the way to 09 step by step. It could be that the quad is totally tolerant of jumping to the finish, or it could be that some firmware versions require the full binding escalation. Perhaps a modification you could attempt is just adding the rest of the binding stages before “phase2_response = self.bind_stage(9)”. It might look something like this:

        while True:
        try:
        for i in range(9):
        self.bind_stage(i)
        phase2_response = self.bind_stage(9)
        if phase2_response[1] == '\x09':
        break
        except BindError:
        continue

        self.bind_stage(0)

        1. J0hny

          Hello,

          thanks for your response 🙂

          in fact the code you mentionned is never executed, because at this line : https://github.com/chpatrick/hubsan/blob/master/hubsan.py#L79-L87, self.a7105.read_reg(Reg.MODE) is always equal to 25,

          is you do 25&1, it give 1 (this is a bitwise operator)

          1 isn’t equal to 0, so it raise a binderror on line 87 🙁

          If there is a bind error, the code you mentionned is never executed…

          I have already tried playing with the time at line 78 https://github.com/chpatrick/hubsan/blob/master/hubsan.py#L78, with no success 🙁

          Any other idea ?

          1. Jim Post author

            Hmm, your debug output makes it pretty clear that you’re right about the MODE register check – the A7105 doesn’t seem to signal that it received a reply in the period that it’s listening. In my tests, the X4 responds in about 3 or 4 milliseconds, so the 15ms in the code should be adequate.

            You seem to be receiving a \x1A in the MODE register after transmitting, which means it sent successfully (you have a 0 in the TRER position, bit 0).

            NOTE: This mechanism is a weird thing because I can’t find a reference to this in the final datasheet, but the TRER bit is obviously used for signalling that a FIFO packet has been received (when it is cleared). The only clue about this I can find is in a preliminary datasheet here: http://www.lcis.com.tw/paper_store/ps_txt_html/A7105%20Datasheet%20v0.0%20(Preliminary)-2014712161659727.pdf.html where it says:

            TRER: TRX enable register.
            0: Clear TRER
            1: Enable. It will be clear after end of packet encountered in FIFO mode.

            It seems like the quad just isn’t responding to your binding beacon, and if you’ve tried extending the amount of time to listen for the response and still have no luck, then I think we can be comfortable that it’s not that we’re just missing the response.

            One thing I noticed is that the code randomly selects the channel to use – https://github.com/chpatrick/hubsan/blob/master/hubsan.py#L122 (in your debug output, it looks like it chose channel \x64). In the real implementation, the channel is based on an averaged RSSI measurement of each channel. This makes me wonder if it is actually noise or a channel mismatch issue meaning that the X4 is either failing to successfully receive the packet intact, or just not listening for it on the right channel at all. You could have a go at implementing the RSSI sampling function that selects the channel with the highest apparent RSSI value. As a quick guide, here is how I implemented it in my version of the controller for Arduino:

            // Cycle through the 12 channels and identify the best one to use.
            Serial.println("Scanning Channel RSSI values:");
            long chan_rssi[12] = {0,0,0,0,0,0,0,0,0,0,0,0};
            for (int i=0;i<12;i++){
            a7105.writeRegister(A7105_0F_PLL_I,_channels[i]); // Set PLL Register 1 - Select Channel Offset.
            a7105.sendStrobe(A7105_PLL);
            a7105.sendStrobe(A7105_RX);
            for (int j=0;j<15;j++){
            a7105.readRegister(A7105_1D_RSSI_THRESH,test_result);
            chan_rssi[i] = (chan_rssi[i] + test_result);
            }
            }
            int temp_rssi = 0;
            for (int i=0;i<12;i++){
            if (chan_rssi[i] > temp_rssi){
            temp_rssi = chan_rssi[i];
            _channel = _channels[i];
            }
            }
            Serial.println(" - Selected Channel: 0x" + String(_channel,HEX));

            Good luck!

  6. J0hny

    I tried it but no success. I gave up this library and used an arduino instead, and it worked after 30 min 🙂 I will talk to the arduino from python via serial communication

    Anyway, thanks for your help ! 🙂

  7. Pingback: Reverse Engineering a Hubsan X4 Quadcopter – Part 4: Putting It To Use – The Memoirs of Jim 'ung

  8. Pingback: Reverse Engineering a Hubsan X4 Quadcopter – Bonus Hack! – The Memoirs of Jim 'ung

  9. Gabriel Santana

    Hello, i have a question what is for the Symbol of the speaker on the back of the PCB ? What can i connect to it, and it is possible to put a high range antenna ?

    1. Jim Post author

      Hi Gabriel – The symbol on the back labelled B2 is a Loudspeaker (http://www.rapidtables.com/electric/electrical_symbols.htm). I hadn’t really noticed it before you mentioned it, so I probed it with my ‘scope just now – it looks like it is just an alternative mounting point for the buzzer on the front. I tested it by pressing one of the trim buttons to make the controller beep – as you can see below, the B2 pins showed a 1kHz oscillation at the time of the beep:

      Buzzer Scope Trace

      As for extending the range with a new antenna, that’s a complicated question! Ultimately yes, it is possible, but it’s not straightforward – the PCB antenna already on the board is actually already fairly ideal for the 2.4GHz signal (I’m guessing it’s the ‘magic’ 1/4 wave length of 31.25mm). There’s an RC Groups thread on some hardcore ways to boost the range using replacement transmitters or boosters: http://www.rcgroups.com/forums/showthread.php?t=1830856

      Hope that helps,

      Jim

  10. Valery

    Hi Jim.
    My hubsan x4 107d flew away last weekend. I hope it successfully will get back to China 🙂
    I’m looking for a way to connect the controller to my PC as a controller for Quadcopter Simulator. Do you have any ideas how to do this?
    Best regards Valery

    1. Jim Post author

      Haha I’m sure the wind will carry it home 🙂

      That’s a really interesting idea – I made something similar for the Syma S107G helicopter controller (which is IR based, so a bit easier than the Hubsan controller which uses a 2.4GHz radio). I guess there are a couple of ways you could do this:

      – Make a PC interface controller using a Teensy board that measures the controller potentiometers and buttons, and translates that for an emulated USB HID Joystick (the Teensy makes this really easy). This would convert the controller to a wired controller (you’d need to connect to the controller via USB). I think this is probably the easiest and cheapest way.

      – Build a receiver (exact same hardware as my controller from Part 4 – http://www.jimhung.co.uk/?p=1704) and program the Arduino Due to perform the Quadcopter-side of the binding sequence (including scanning the open channels for binding announce packets), then decoding control packets to extract the control values. The Arduino Due is also capable of emulating USB HID joysticks/keyboard/mouse so that’s how you would interface it with the PC. This approach is harder and probably more expensive, but requires no modifications to the controller.

      What Quadcopter Simulator do you have in mind? I just took a quick look at FPV FreeRider (https://fpv-freerider.itch.io/fpv-freerider) which seems like fun – it looks like a hacked controller would work well too.

      Jim

      1. Valery

        Hi Jim.
        I could buy game controller, but it would be too easy way.
        I’ve never used arduino, I have some experience with C++ and I like doing somethimg by hand. If I get success, I’ll report here.
        Thank’s for a lot of information which you shared.
        Valery.

  11. Pingback: Hubsan x4 h501s fpv - Pagina 18 - Forum Modellismo.net

  12. Will Walls

    Hello, I am trying to add a 5th servo motor to my Hubsan x4. Is there a way to add an external switch to the controller to independently control a 5th servo motor? There are leads on the flight controller that don’t have any apparent use. Can I transmit a signal to those leads?

Leave a Reply

Your email address will not be published. Required fields are marked *