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.

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.

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

Bit 0 (0x0001): Machine gun LED

Bit 1 (0x0002): Ignition

Bit 7 (0x0080): Fire

Bit 8 (0x0100): Turret Elevate

Bit 9 (0x0200): Turret Left

Bit 10 (0x0400): Turret Right

Bit 11 (0x0800): Simulated Recoil

Bit 12 (0x1000): Machine gun sound effect

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

Bit 6 (0x0040) looks like the left/right selector when turning the tank. Bits 2-5 seem to be related to the speed of the turn. All high (the normal state) corresponds to no turning at all, but I can’t work out how each bit affects the speed.

Bit 16 (0x10000), the other bit that’s switched on in the normal state, acts like a “NOT drive forwards”. When set low, the tank immediately drives forward at a speed that seems to be set in some way by bits 13-15. When set high, the tank is normally stationary, but a combination of bits 13-15 appear to set a reverse speed. (However, to further complicate things, 16 set low but 14 set high also produces a reverse.)

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”.

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:

I started on a Python port of rt_http, but as yet it doesn’t work. My suspicion is that Python’s time.sleep() just isn’t accurate enough compared to C’s usleep(), which may make the Python port a non-starter. I need to get a scope on the GPIO output to prove it though.

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!

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!

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.

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.

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.

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!