Witmotion Servo Control Reverse Engineering

Using the Witmotion 16-channel Servo board’s native USB port the way it should be used

I wanted to control some servos from a computer. Often this from a microcontroller, but since my application is going to be entirely computer-controlled, I prefer to use a board built specifically for the purpose, rather than having to do any custom wiring. There are several such boards that have a microcontroller, servo pins, and power distribution, intended for beign controlled directly from a computer, such as the Pololu Micro Maestro 6-channel servo board which I previously used.

The Witmotion 16-channel servo board

This time, I went with a different board, a Witmotion 16-channel servo board. A board that initially attracted me because it used an STM32 with both USB, and UART headers.

Wit-motion 16 channel servo board

However, to my dismay, unlike the Micro Maestro, the Witmotion board didn’t enumerate as a serial port, and didn’t come with any SDKs either. Wit’s website provides some sample code for other microcontrollers to talk to this board over UART, but no code for running on a computer.

I dislike VCOM in general — it’s always a challenge when you have multiple devices connected to the computer and have to work out which COM port is which. But not providing an SDK is also bad. Why provide a USB port but not the means to use it with anything other than a small utility for manually moving servos with sliders?

The manual even goes as far as to suggests that you should plug in a TTL serial module into the UART socket of the board rather than controlling it from the built-in USB port if you want to control it from a computer.

Despite the board having a USB socket, the manual suggests attaching a USB serial port device

However, it is clear that the device does actually support USB control over its native USB port. The small Windows utility provided by the manufacturer does just that; and it can be seen in Device Manager that the board presents itself as a USB HID device.

The provided Windows utility for manually moving servos

USB Descriptors

So I did what any reasonable person would do, and reverse-engineer the USB protocol so that I could write my own drivers for it. I don’t do this lightly, but I was basing this on prior experience with Cortex-M3 microcontrollers with built-in USB that enumerate as HID — their protocols tend to be straightforward.

To do begin, and to confirm my theories, I needed to take a look at the USB descriptors, which describe what kind of data is expected to be sent and received.

Endpoint descriptors for the witmotion servo controller

This screen tells me there very important pieces of information:

  1. the Wit-motion controller has VID 0x1920 and PID 0x0100, I will need this info when writing the library
  2. The Wit-motion controller uses a 64 byte IN interrupt transfer at endpoint 2
  3. The Wit-motion controller uses a 64-byte OUT interrupt transfer at endpoint 1

This is good news. With only two interrupt endpoints (one for IN, and one for OUT) this should be relatively simple. However they are both 64 bytes long. Since the device has a UART port and a protocol to use on there, my initial guess is that they simply use these 64 bytes to encapsulate this other protocol, rather than doing it in proper USB style.

Having one protocol encapsulate another, and not using any advanced features of the first protocol is relatively common. For example uBlox GPS modules do a similar thing for their I2C bus — instead of using proper I2C addresses and data, it simply puts the same UART serial stream onto I2C.

USB Protocol analysis

So we need to snoop further to confirm the protocol. To do that, I use a USB analyzer to look at the actual raw data going through these endpoints, while I fire up the windows utility provided by Wit.

USB Analyzer snooping of the USB data

After repeating this experiment a few times, I’ve broken down the exchanges into a few different situations:

Starting up:

When the Windows utility starts, it connects to the USB and sends three messages:

  • OUT: 03 02 02 03
  • OUT: 03 02 03 03
  • OUT: 03 02 04 03

It’s unclear what or why it does this, but it reliably does this every startup. There’s no data returned from the device, so in the worst case I can have my driver blindly output these as well if they are needed.


The software performs what appears to be either a heartbeat or a status request every second:

  • OUT: 05 03 ff 00 12
  • IN: 05 01 ff f0 12 <followed by some more data>

Every outgoing heartbeat or status request is accompanied with a reply that contains the same 5 bytes plus some more complex data. The incoming data is probably some status data from the device itself. Fortunately, as it’s likely purely informational, and since servos are open-loop control anyway, we shouldn’t need to decode any of this (it would be much more challenging if we had to)

Unknown data payload of the status message

Moving a slider:

When moving a slider around, it can be seen that the app sends a single outgoing message:

  • OUT: 05 03 ff 02 01 f1 06

From the third byte onwards, this matches with the message displayed in the bottom left corner of the Windows app, as well as the UART data format provided in the documentation

Windows utility shows what command is being sent in the status bar
Manual outlines the command format for UART, turns out this is also used in USB

So this reveals the way the protocol works is simply a 0x05 0x03 header indicating that what follows is a UART protocol, so I assume the microcontroller simply decodes the remainder of the message as it would do for data coming in by UART. If this is true then we can easily guess how the other commands listed in the manual should look.

Trying it out

Since I’m going to be using Python, and since this device uses HID API, it is easier to build on top of an HID API library, such as cython-hidapi.

A quick test in the command line shows a successful connection, and poking the heartbeat/status message causes the blue LED to flash on the device as they do when the Windows client is connected, so it would seem the startup commands were not necessarily needed.

Testing connection to Witmotion servo controller in Python

It would seem that the device doesn’t even need the heartbeat either, and is quite happy just being sent position control commands straight from power-up. This means we don’t even have to send a heartbeat (though doing so provides us with the flashing blue LED, useful for feedback that the host is connected).

Testing sending just the servo move commands

Writing the driver

Writing the driver is now a simple case of sticking these in a class, with some input validation, and connect/disconnects.

Writing the python driver

The code is available at https://github.com/meseta/py-witmotion-servo




Meseta builds some robots and blogs about them

Recommended from Medium

How to test Laravel Dusk emails.

Test Stories for JetBrains IDEs

Best Free and Paid Courses to Learn Python Online in 2020

Kafka-Flink-ES-Kibana Data pipeline

Binary numbers and computer memory: To be, or not to be? That really is the question…

Disposable Virtual Machines in 3 commands with Multipass

The Politics of Choosing a Tech Stack

ComParE Challenge KSF-C 2022

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Yuan Gao (Meseta)

Yuan Gao (Meseta)

🤖 Build robots, code in python. Former Electrical Engineer 👨‍💻 Programmer, Chief Technology Officer 🏆 Forbes 30 Under 30 in Enterprise Technology

More from Medium

On-Balance Volume Indicator Algorithm in Python

[Solved]The CXX compiler identification is unknown CMake Error at CMakeLists.txt:3

Subject: Recursion… Recursion… Recur…

Applications of Python in Embedded