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
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.
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:
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 (
- Bit 7 (
- 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.
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.
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!
Alongside my work on figuring out the opcodes, I have made a number of other enhancements to the code:
The rt_http code now includes a bash script to restart it if it crashes. This doesn’t happen a lot, but it’s handy not to have to SSH in and fiddle to get it up and running again.
You can now control the “ignition” signal from the web interfaces — useful if a restart of
rt_httpcauses the tank to be up and running but with the ignition off. I still have no idea why Heng Long chose to have a fake ignition, and make using it compulsory.
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.
You should try to add a paper ball or somthing like that on your turret and make a target on the screen ;)
Lan, you should add a usb missle launture
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!
In one post you said that to controll motor speed, hears what i end up with http://learn.adafruit.com/downloads/pdf/adafruit-raspberry-pi-lesson-9-controlling-a-dc-motor.pdf
Hoping for update on this fourm sooooooon
(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!
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
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
Last of all, you want to create some init scripts to run
mjpg-streameron 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-streamerin "Step 4" of this page.
If you get stuck, let me know and I'll do my best to answer your questions!