The Memoirs of Jim 'ung

Reverse Engineering a Hubsan X4 Quadcopter – Bonus Hack!

Reverse Engineering a Hubsan X4 Quadcopter – Bonus Hack!

Reverse Engineering a Hubsan X4 Quadcopter – Bonus Hack!

Once I completed my series on reverse engineering the Hubsan X4 and reimplementing the protocol using an Arduino Due and a compatible radio chip, I decided that it might be useful for me to talk about a much more hacky way that you could control the Quadcopter if you don’t want to mess around with transmitters and SPI…

Analogue Input Emulation with PWM on an Arduino Uno

So if you’ve taken a look at the PCB for the Hubsan X4 controller, you will probably have noticed that the flight thumb sticks are essentially Joystick Potentiometers (Digikey) that control Throttle, Yaw, Pitch, and Roll, and have small microswitch buttons for push-button functionality:


These potentiometers work by acting as an adjustable potential divider that varies the voltage on the input pins of the Controller’s micro-controller. The voltage on these pins is measured by the controller using internal Analogue to Digital Converters (ADCs) to determine the position of the thumb-stick in a given axis.

Reverse-engineering the nature of this mechanism is MUCH easier than in the previous work on the X4 – it really just boils down to determining what the voltage ranges are at the potentiometer wiper pin are so we can emulate them using an Arduino Uno. Using a multimeter it’s trivial to determine that the voltage output ranges from 0 to 3.3v.

Emulating the Potentiometers

The venerable Arduino Uno is much cheaper and easier to get hold of than the Due along with A7105 radio modules, so wouldn’t it be great if we could control the X4 without all that?

Luckily we can, because the Arduino Uno features 6 PWM output pins that we can use as basic Digital to Analogue Converters (DACs). Because our output will be measured by another microcontroller, we also need to run the output through a low-pass filter to smoothly output an analogue signal. If you have a slow-reacting load like a motor, you don’t always need this, but in this case we want to generate a pure analogue signal for the controller to measure.

The simplest low pass filter is a basic RC circuit that looks like this:


I opted for R=10KΩ, C=10uF (based on some googling for basic Arduino DAC outputs). This appears to give me stable output voltages that change fast enough for control inputs to the Controller.

So far so good – but the Arduino Uno is a 5v device, and we need at most 3.3v. This is easily handled (as you will see later on) by simply limiting the output value of analogWrite: 3.3v is 66% of 5V, analogWrite() takes a range of values from 0 to 255, so if we limit this to 168 (66% of 255) we can make sure that we don’t blow up the Controller chip.

Here’s the little array of low-pass filters I put together on some perfboard with some male and female header added to make connecting it up to the Arduino and Controller easier:


The male pin-header next to each capacitor/resistor pair is the output connector, the one-off male pin in the middle of the photo is a ground connection to connect the Arduino’s ground to the Controller’s. The female header at 90-degrees is for jumper wires from the Arduino Uno’s PWM output pins to connect nicely.

Adapting the Controller

In order to emulate the control signals with our Arduino, we need to reroute the input signals to the Controller IC from the joystick potentiometers to our DAC outputs. The copper traces on the PCB are very thin, but using a fibreglass abrasive pen to carefully scrape away the soldermask we can precisely solder jumper wires to them to inject our signal:


And with the jumper wires attached…

PCB mod

If you look closely at the picture above, I have also cut the traces between the potentiometer solder pad and the jumper wire with a very sharp scalpel.

The reason there is also a jumper wire attached to the potentiometer pad is because I wanted to preserve the functionality of the controller if necessary. Having the option to join the potentiometer back up to the PCB trace using a jumper cap/wire is important to me so I can use the controller without the Arduino. As such, for each of the 4 potentiometers, there are 2 jumper wires.

To keep things neat and tidy, and make the connections easily accessible whilst still being able to hold the controller, I opted to make a dual-row male header connector to join the jumper wires to. I cut out a slot in the plastic shell and filed it smooth and to the correct shape:


The top row of pins carry the signals directly to the controller’s IC, and the bottom row carries the connection to the original potentiometer. The top left pin is connected to the ground plane (so we can easily share a common ground with the Arduino Uno). The final loom of jumper wires going to the makeshift breakout port looks like this (not too pretty, but it works):


The dual-row of pin headers looks nice (externally at least) and is very functional, but it was a real pain to solder the wires to. You might notice one side’s wires are much longer – that’s because I realised that I needed as much slack as possible to manoeuvre when soldering to the connector.

When the entire thing is reassembled and connected up to the low-pass filters and Arduino, it looks like this:


The nice thing about connecting the original potentiometers along with the signal traces to the dual-row makeshift connector in pairs is that we can just use little jumper caps to restore the controller to it’s original functionality:

controller jumpers

Writing the Software

This part was extremely easy – no external libraries are required and the logic is very similar to the Arduino Due sketch example, but much simpler. Apart from the setup (which is just setting up the Serial link and setting the pin modes), we just execute the following loop:

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

We are just using the map() function to limit our control input values (0-255) to the range that will produce a safe voltage (0-3.3v) for our DACs to send to the Controller IC (see “emulating the potentiometers”, above). This means that our emulated controller is by definition less precise than the native implementation: with the native version we have a range of 256 values per axis, whereas here we are limited to 168 due to the 8-bit DAC functionality provided by analogWrite() coupled with our 0 to 3.3v limit.

For the head-end controller, you might recognise that the switch statement reflects the exact same control protocol scheme used in my earlier Arduino Due sketch – this means we can use the same Python-based example controller from my earlier posts to fly the thing! It’s important to remember to enable “expert” mode on the controller, otherwise our example control inputs are too weak to control it properly.

The code

I’ve added the example PWM emulation sketch for Arduino Uno to the libHubsan project on GitHub:

You can find it in the “\example\Hubsan_H107L_Controller_PWM_Emu” folder. Once you have flashed it to your Arduino Uno, you should be able to use the same Python example controller in the “example/Hubsan_H107L_Controller/_PythonController” folder to control it.

Here’s a quick demonstration of it in action:

So there you go – a cheaper and more straightforward alternative to reimplementing the protocol and radio controller yourself, albeit at the cost of controller precision and some manual fumbling when binding.

As ever, any questions about the build or theory, post a comment below.


2 thoughts on “Reverse Engineering a Hubsan X4 Quadcopter – Bonus Hack!

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

Leave a Reply

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