The Memoirs of Jim 'ung

Reverse Engineering a Hubsan X4 Quadcopter – Part 4: Putting It To Use

Reverse Engineering a Hubsan X4 Quadcopter – Part 4: Putting It To Use

Reverse Engineering a Hubsan X4 Quadcopter – Part 4: Putting It To Use

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

So we’ve reversed the hardware and control protocol of the X4, now what? Put it to use, I guess! There are a bunch of things that everyone who is interested in hacking cheap RC toys always think of:

  • autopilots,
  • navigational waypoints,
  • synchronised acrobatics,
  • interoperability with other controllers (like Turnigy controllers)

Now, most of these are entire projects on their own so I won’t go into detail on the specifics of actually building them, but one thing that they all have in common (apart from adding interop’ for other controllers) is the requirement for an interface between your computer and the quadcopter.

This part will focus on creating a controller replacement using an Arduino and A7105 radio module that can be controlled via a PC/Mac/RasPi that can be used as the basis for a lot of projects.

Basic Systems Engineering

To make sure we have a flexible and well-designed controller to use as a base for our other projects, it helps starting with a clear overall design strategy for the system. We can quickly run through a few thought-exercises to build a picture of an approach that might work:

Requirements:

  • High-level application capabilities need to be handled by a PC/Mac/RasPi
    • We can call this the Head-End. Keeping the high-level application logic here means we can be flexible to build computer-vision apps, keyboard/mouse/gamepad control input ready-made, use scripting tools like Python to keep things simple and integrate the capabilities of other cool projects/libraries, etc… without having to worry about implementing this in low-level code or in hardware.
  • Low-level functions like radio communication and binding should be automatic and abstracted away for us by a separate Controller.
    • We don’t want to have to implement the binding protocol or radio configuration and control at a high level for every application we write.
    • Maintaining the state of the control axis states of the quad at a low-level helps simplify the process of communicating between the head-end and the controller, and with meeting the required timing characteristics of the protocol.
      • Decoupling the radio control protocol from the high-level control messages makes life a lot easier too (to avoid timing and synchronization issues).
  • The control interface between head-end and controller should be simple and straightforward to keep things fast and efficient.

One Possible Solution:

There are many ways to satisfy these requirements, and equally many different priorities and additional needs depending on how you analyse the problem. I’d like to keep it simple, and based on my own knowledge and capabilities, I would attempt to build a solution as follows:

  • Head-End: PC/Mac/RasPi running a control program written in Python 2.7 (with the PySerial library, and PyGame for basic keyboard control input as an example).
  • Controller: An Arduino Due to communicate via Hardware SPI to the Amic A7105 Transceiver
    • The reason I chose an Arduino Due rather than the cheaper and simpler Uno is that the Due features dedicated Hardware SPI support and is natively 3.3V. If you wished to use an Arduino Uno, you would need to step the control signals down from 5V otherwise you risk damaging the A7105 module.
  • Control Interface: Serial TTY over USB is very simple and supported on most platforms. We can define a basic set of control messages (one for throttle, one for yaw, and so on) that allow us to communicate changes to the control axis’ quickly and efficiently. It is also somewhat forgiving in terms of buffering the messages, so we don’t have to worry too much about synchronisation between the controller and Head End.

Building the Damn Thing

ControllerStack

The Hardware

The Controller hardware is fairly straightforward – we essentially just need a power and SPI communications interface between an AMIC A7105 module and our Arduino Due. I opted to keep it modular to simplify the design using a proto-shield PCB and a perfboard breakout for the radio module.

A7105 Module

I picked up a bunch of “XL7105-SY” modules from DealExtreme (link) which are perfect for this kind of project and super cheap. the problem is that documentation is a real pain to come by, particularly in english. The AMIC A7105 datasheet gives us pretty much all we need to know about the chip itself, and I managed to dig up a pinout for this module using Google:

XL7105 Pinout

The required input power and logic levels are 3.3V, so be really careful not to connect it to a 5V input or you might blow it up.

The pad spacing on the XL7105 module isn’t 0.1″ (2.54mm) like regular perfboard/breadboard (I think the module is more like 2mm), so I put my fine motor control to the test and created a small breakout board for it:

73211

If you plan to make something similar to make the module easier to work with, make sure you use plenty of flux and tin the pads and jumper wires with solder first. This will make it much easier to get a clean result on such a small job. The pin-headers that I used here are not extra long or anything – because I had to solder them on the opposite side of the perfboard than normal, I just pressed the short end flat on the tabletop to push the pins through to gain a few mm to ensure they mate properly with the makeshift “socket” made out of female header.

Protoshield

To make our Controller tidy and modular, I used a Sparkfun Arduino ProtoShield V2 to make a nice shield to carry the SPI interface connections and pin header to plug the A7105 into:

35050

A few key notes on how this worked out:

  • I added a 5-pin male header test point breakout to make troubleshooting with a logic analyser or oscilloscope much easier. You can see this on the centre-right of the top image. This also made laying out the jumper wires to the radio module socket easier.
  • The green wire that carries the MOSI signal to the XL7105’s SDIO pin requires a 10kΩ resistor to bring the signal voltage down within the tolerance of the A7015. This is strange since you would assume that the Arduino’s SPI output would be perfectly compatible with the A7105 logic threshold voltage, but I couldn’t get it to successfully communicate without a resistor to temper the voltage.
  • I created a small 2×3 female socket on the underside to connect to the Arduino Due hardware SPI pins (centre-right of the bottom photo). Kudos to Sparkfun for adding the pads for this to their protoboard PCB, it made this much easier.

The Software

You can find all the Arduino code, sample Controller, and the simple Python head-end implementation on GitHub here:

https://github.com/NotionalLabs/libHubsan

If you plan to use this for your own projects, don’t forget to add libA7105 and libHubsan to your Arduino IDE libraries. It’s also written for the Arduino Due and relies on the Hardware SPI on that specific board – unless you have the SAM3X board pack installed the code won’t compile.

If you are using my Arduino Due sketch and just want to know how to structure the Serial protocol commands in your Head-End application, you can jump down to the section on The Serial Control Protocol.

The Low-Level Controller/Arduino Code

Without delving too deep into code abstraction and all that jazz, our Arduino code design can logically be split up into 3 parts:

  • A library for communicating with the A7105 radio via SPI.
    • This needs to take care of basic operations like reading and writing to registers, controlling the strobes, and so on.
  • A library for implementing the Hubsan X4-specific protocol.
    • This needs to implement things like configuring the radio so it can communicate with the quadcopter, perform the binding sequence, and provide functions to send control packets.
  • A main sketch to run on our Arduino Due to implement the other libraries and act as the Controller element.
    • It needs to be able to receive commands from the Head-End via Serial, update the internal control axis states, and transmit the control packets on time (to match the original controller).
libA7105

I wrote this basic library to abstract the basic SPI function set of the A7105 module. This includes configuring the SPI mode on the Arduino Due to the correct mode and enabling 4-wire SPI on the A7105 (turning the GIO1 pin into SPI MISO).

There’s not much to say about this library – it’s mostly just basic implementation of the communication techniques defined in the A7105 data sheet. One handy trick when writing this kind of thing is to enumerate the register names and state-machine strobes as constants in the header file. This makes it easy to tell at a glance what is happening when debugging and can on having to keep going back to the datasheet.

libHubsan

I wrote this library to implement the logic of my reversed protocol for the Hubsan and abstract the nuts and bolts of basic things like transmitting complete control packets (including calculating the checksum) and binding.

The init() function runs through a very close approximation of the configuration procedure witnessed during reversing the real controller. This includes the calibration tests that occur at the end of the initialization phase. I tried to emulate the logic of the setup as closely as possible, just in case there were odd unintended or undocumented effects on the operation that would be hard to troubleshoot if things weren’t working. I also included the proper logic for channel selection, so we should be able to avoid interference like the original controller.

The bind() function is also very closely matched to the real procedure, and no handshake steps are skipped (I noticed that some similar attempts at Hubsan libraries sometimes omit handshake steps and it seemed to intermittently cause issues). The random session ID is generated using randomSeed() and it is used to configure the IDCODE register at the appropriate time.

Basic functions to transmit (txPacket) and receive (rxPacket) are provided so they can be called by the main sketch. txPacket() expects a fully formed 16 byte array and simply reads the string of bytes into the A7105’s FIFO buffer, strobes the transmit signal, and then waits for it to complete before returning.

Finally, the getChecksum() function takes the control packet byte array and performs the checksum algorithm on it, updating the final byte with the correct checksum value.

The Controller Sketch example – Hubsan_H107L_Controller.ino

The controller operates on a similar principle to my Syma S107G control library. I have found that the paradigm of having an decoupled controller greatly simplifies the relationship between head-end and controller, and makes controlling the vehicle much more robust.

The sketch is pretty simple – the basic setup looks like this:

  • a 16-byte array representing the quadcopter control packet is initialised to establish the basic structure of what we need to repeatedly transmit after a pre-set interval. This is called “controlpacket”. It is set up to contain the proper protocol structure (as per Section 4.2 of my protocol spec).
  • an array called “controlbuffer” is created. This holds the values of the primary control axis’ and is initialised to the defaults (0 Throttle, 128 Yaw/Pitch/Roll).
  • a timing interval is established that controls when a control packet needs to be sent.
  • a Serial connection (115200 baud) is set up with the head-end.

The sketch loops until the number of milliseconds specified by the interval has elapsed, then the contents of “controlpacket” are transmitted (with checksum). On each loop, the sketch checks to see if the controller has received a control value update from the Head-end (i.e. the Serial buffer contains at least 2 bytes). If so, it determines which control axis to update and sets the value of the appropriate “controlbuffer” element. The control packet is updated with the values from the “controlbuffer” just prior to being transmitted to make sure we only update the packet once per transmission instead of every time a control message is received (which might be very often, depending on your control input).

The Serial Control Protocol

We want the Serial control protocol to be as simple and efficient as possible. The control scheme works by sending a command code that tells the controller which control axis to update, along with the byte value to set it to. This keeps each axis independently updated and cuts down on any redundant transmission over the (slow) Serial line. It is also very extensible – we could easily add a control code to say, flip the LED on/off flag in the control packet if we wanted to.

Here’s the very simple switch/case statement that demonstrates the control protocol handling:

 if (Serial.available() >= 2) {
     inctype = Serial.read(); // Get incoming control byte type.
     switch(inctype) {
         case '0':controlbuffer[0] = byte(Serial.read());break; // Elevation
         case '1':controlbuffer[1] = byte(Serial.read());break; // Yaw
         case '2':controlbuffer[2] = byte(Serial.read());break; // Pitch
         case '3':controlbuffer[3] = byte(Serial.read());break; // Roll
         case '4':toggleSmoothing();break;
         default:break;
 }

Note: It’s very important we wait for a whole control message (2 bytes) to be in the Serial buffer before we drop into the switch/case – if we happen to hit the IF conditional with only 1 byte in the pipe, we might end up way out-of-sync with the next messages and crazy values ending up in the control arrays. Trust me, I have the rotor blade scars to prove it.

The Head-End example – pyHubsan.py

pyHubsan_demo2

This was a very basic proof-of-concept head-end implementation that purely provides keyboard control input for the X4. It uses the serial library for communications with the Arduino, ConfigParser to store pre-set trim values in a config file (“pyHubsan.config”), and the pyGame library to poll the keyboard inputs.

The basic controls are:

  • ‘w’ to increase throttle incrementally, ‘s’ to decrease it.
  • ‘arrow up’ to pitch forward, ‘arrow down’ to pitch back.
  • ‘arrow left’ to roll left, ‘arrow right’ to roll right.
  • ‘a’ to yaw left, ‘d’ to yaw right.
  • IMPORTANT: Spacebar to kill the engines immediately. You will need this a lot.

Some interesting features I added are:

  • An attempt to interpolate control values (“smoothing”) – this is actually implemented in the Arduino sketch, but it’s toggled here in the Head-End. I don’t think it works especially well. Toggle it with the ‘q’ key.
  • A flip button! This executes a pre-canned forward flip – make sure you have plenty of space, and I take no responsibility for damage to people or property that might result… Press ‘e’ to flip.
  • Edit the ‘pyHubsan.config’ file to modify the pre-set trim values for your X4. This will almost certainly be required to keep your x4 stable.

Here’s a little video of it in action:

Conclusion

So there you have it, the sum total of my work on investigating the Hubsan X4 quadcopter. I want to take a moment to thank the visitors to my blog that have continued to visit over the past few years and message me about this post series. I had a tremendous amount of fun digging into it and still feel like there’s so much more to explore with these great toys.

Stay tuned for some more quadcopter hacks – I’ll be looking at turning the Syma X5C into something a little more…fun, soon.

And finally, apologies that it took me 2 years to get around to writing this last post…

Jim

6 thoughts on “Reverse Engineering a Hubsan X4 Quadcopter – Part 4: Putting It To Use

  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. Pingback: Reverse Engineering a Hubsan X4 Quadcopter – Part 1: Hacking the Controller – The Memoirs of Jim 'ung

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

  5. Silas

    Hi, Really nice info…my hubsan x4 107c+ is not showing any light when the battery is insereted ? it worked on 1-3 times..but now not working …when the usb be is inseted with or without battery the red light glows ? no major crash done so far…no spare battery. to check..? what can be the reason and best thing to do first…?

    Thanks
    silas

Leave a Reply

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