Tank Day 30: Cracking the Code, Third Time Luckier

Background

For a long time — since Day 12 in fact — the Raspberry Pi has been driving the tank using a set of binary codes. These codes were reverse engineered by looking at the signal that the original TK board produced when driven with the proper remote control unit.

The codes are 32 bits long, always beginning with 11111110 (0xfe), and always ending with 00. I was storing each complete code as a constant, which made the program pretty inflexible about the things it could ask the tank to do — it could only pick one of the set that was originally recorded.

RC Tanks Australia forum member ancvi_pIII figured out long ago that the last four bits before the 00 were a checksum, and since both TheTesla and Alexandre Lugand went and posted code which calculates exactly that checksum, I figured it was long overdue for me to incorporate proper CRC generation into my own code.

Today’s Work

With the 8 header bits, 4 checksum bits and 2 footer bits being calculated automatically, the 32-bit control codes were reduced to 18 bits of real information, of which only 17 are used.

Looking at the bit patterns for my codes, you see this:

Codes from day 12 with header, checksum and footer removed

Immediately, there are some odd things noticeable about this arrangement.

Bits 16, 5, 4, 3 & 2 are high a lot of the time (0x1003c) — and look a lot like the bits set high in the “neutral” code. So this looks like the “base state” when no control is being applied. But what about my “idle” code, which is used interchangeably with “neutral”? It seems to do the same thing, but looks very different. “Fire” is an odd-looking code too — perhaps that explains why the firing has been broken for a while?

I decided to investigate. Now that the checksums were calculated automatically, I was free to play around setting whatever bits I liked, taking that 0x1003c as the base to work from. Some of my results are below. As you can see, the controls for the ignition and turret-related codes are all very simple — setting a single bit high on top of the base code makes each of these functions happen. Control of the main motors (forward, backward, left and right) however, is a lot more complex.

Day 30 opcode investigation

So, you can add (binary &) the following bits to the base opcode to produce the following effects:

I did not fare so well at figuring out the other bits:

Unfortunately, there also seem to be a few “invalid” opcodes, which has made investigation difficult. Hitting one of these seems to break the RX-18 controller, requring a power cycle of the tank. There are also a number of opcodes that seem to get the tank stuck in a particular mode until the opposite is entered — e.g. the tank can be commanded to reverse, but sending the “idle” opcode doesn’t stop it, only sending a “forward” opcode will stop the reversing.

Current State

We’re part-way there. We have a set of opcodes that control the main motors roughly how we want, but we don’t really understand why. On top of that, we know which bits in an opcode control the other functions, and these ones we fully understand.

The rt_http software has been updated to reflect this. We now use a set of “base opcodes” — fully populated opcodes for Idle, Forward, Reverse, Left and Right that “just work” although we don’t know why. In addition, we have a set of “delta opcodes” — the individual bits that we understand the function of. It’s not the nicest solution in the world, but it does mean we’re part way towards a nicer control scheme.

Because the motor controls are still using the old “base opcode” idea, we still don’t have fine control over speed or the ability to say “reverse and turn left”. But the functions represented by the “delta opcodes” can now be added on top of a base opcode in any combination, so we can say “reverse and elevate turret and shoot”.

Here’s the base and delta opcodes as currently implemented:

// BASE OPCODES
const int IDLE =         0x1003c;
const int FORWARD =      0x0803c;
const int REVERSE =      0x1803c; // Must be cancelled by a "forward", idle is not enough
const int LEFT =         0x10010; // Slower than I would like
const int RIGHT =        0x10064;

// DELTA OPCODES
const int MG_LED =       0x0001;
const int IGNITION =     0x0002;
const int FIRE =         0x0080;
const int TURRET_ELEV =  0x0100;
const int TURRET_LEFT =  0x0200;
const int TURRET_RIGHT = 0x0400;
const int RECOIL =       0x0800;
const int MG_SOUND =     0x1000;

As you can see from the comments, there are still a couple of issues that should get ironed out as the opcode investigation continues. Namely, the “left” code produces quite a slow turn, and the “reverse” code can’t seem to be cancelled out by sending the “idle” code, only by briefly sending a “forward” code.

The latest code at this point in the Build Diary is stored here on Github.

Video

Here’s a video of the tank on the target range at the end of day 30, showing off remote ignition control, multiple simultaneous commands, and the newly fixed gun!

Bonus Content!

Alongside my work on figuring out the opcodes, I have made a number of other enhancements to the code:

Comments

You should try to add a paper ball or somthing like that on your turret and make a target on the screen ;)

I'll give it a shot (pun intended) at some point. I can't find one that says it supports Linux, much less the ARM processor in the Raspberry Pi, but it can't be that hard to figure out the protocol it uses!

Anonymous 29 July 2014

Ian wrote:

I'll give it a shot (pun intended) at some point. I can't find one that says it supports Linux, much less the ARM processor in the Raspberry Pi, but it can't be that hard to figure out the protocol it uses!

(pun intended) == (fun intended)

I may move to direct control of the main drive motors at some point using a similar method to the one on Adafruit — I have some SN754410 chips lying around (quad H-bridge) that will do the trick. (They're equivalent to the L293D in the Adafruit guide.)

I've been too busy playing with the quad to do much on the tank at the moment, but I'll get back into it sometime soon!

First of all I want to thank for sharing your project with us. I created an Android App instead of using javascript and used another Webcam. The speed of left turn seems to be "Fast" in comparision with right turn equals to "Normal".

Yes, it's like that for me too. Do you also find that once reversing, you have to go forward slightly before it will stop? (If you go from reverse to no demands, it keeps reversing?) I haven't found a solution to these yet — I really need to knock up a better way of testing combinations of bits 2-6 and 13-16 so we can figure out how the main motor speeds work properly, rather than just guessing codes.

Ian wrote:

Yes, it's like that for me too. Do you also find that once reversing, you have to go forward slightly before it will stop? (If you go from reverse to no demands, it keeps reversing?) I haven't found a solution to these yet — I really need to knock up a better way of testing combinations of bits 2-6 and 13-16 so we can figure out how the main motor speeds work properly, rather than just guessing codes.

I had the same problem when reversing. Another thing that I had some issues with was the need to increase the timer on the I2c function (launch_sensors()) or else it crashed all the time.

Hi Great work
do you have a script for install

regards Martin

I'm afraid not — to be honest, most of the stuff I've written about in this guide is now pretty old and the techniques to get stuff up and running have changed. For example, Raspbian comes with I2C drivers these days so you don't need Occidentalis, my WiFi access point setup was a bit dodgy, there's an official Raspberry Pi camera to take the place of the webcam I used, etc.

If you want to recreate what I've done, I'd suggest following this guide for Access Point setup and this guide for mjpg-streamer.

I think the complete set of system packages I've installed to make my stuff work is: build-essential cmake libi2c-dev i2ctools lighttpd git subversion libjpeg8-dev imagemagick libv4l-dev hostapd dnsmasq.

After all that stuff is set up, grab my code from Github. This stuff goes in /var/www/ where lighttpd will serve it, then this stuff goes wherever you want it, e.g. /home/pi/rt_http/, and build it from there by simply running make.

Last of all, you want to create some init scripts to run rt_http and mjpg-streamer on startup. I don't think I ever uploaded the init script for rt_http, but it's pretty much identical to the one I posted for mjpg-streamer in "Step 4" of this page.

If you get stuck, let me know and I'll do my best to answer your questions!

Add a Comment