HÄRDBÖRD: Interesting Bits
Update : See follow-up post C++ Corrections.
OK, so I need to get better at blogging on caffeine. I spent about four times the amount of time writing the last blog post on HÄRDBÖRD than I did working on the stuff described in it. And let’s face it, what I described were my personal insecurities about electronics hacking becoming more popular & accessible, making my own talents less sexy to the laypeople. That, and a shady DAC with three passive components hooked up. Big whoop. So here’s the next part, with actual interesting stuff.
Transmitten §
would be a great name if my setup involved some sort of sensory glove and HÄRDBÖRD were some sorta German name, but it doesn’t and I have…
The transmitter §
I could explain the design, but I think this video is more helpful (though not very helpful):
I think it’s fairly obvious what I was trying to do here, though poor camerawork kind of obscured how moving the joystick on the Nunchuk controlled Jamison’s three-pound battlebot, Cake. Wirelessly. Anyways, a series of dysconscious design decisions led to the following desired specs:
- Wireless control of the BÖRDs
- Wii Nunchuk as the human interface
- Some sort of reliable 2.4GHz link (radio hardware with a protocol stack built in)
Everything else fell into place right after that. I grabbed two pairs of the lowest-end Xbee radio transceivers as well as the cheapest USB adapters and breakout boards I could find for them. “O Captain,” you cry, “that’ll be from Sparkfun, right?”
Yeah no, Sparkfun is for tools, man. They were so much better when they were underground. </hípster>
Surprisingly enough, Parallax has Xbees at the same price as Digi-Key/Mouser, $3 breakout adapter boards*, and a $20 USB breakout†. And you thought they’re those overpriced backwards BASIC Stamp kids who supposedly got superseded by the Arduino clusterfugue‡.
The rest of the article is about the technical hacks I used in software to reduce complexity, part count, cost, etc. Boring stuff.
Nunchuk §
First off, it’s “nunchuk.” Not “nunchuck,” which requires a convent.
Secondly, these things are an awesome addition to any project requiring an analog joystick, two buttons, and a three axis gyro, mounted in an overly ergonomic housing with Apple aesthetics and Nintendo unbreakability. Take care not to drop them though—you might break the floor. Nunchuks can be accessed through a simple I²C interface, exposed physically through six spring contacts in the connector for which simple adapters are available.
Keep in mind that the Nunchuks have gold-plated spring contacts and there is a tin finish on those adapters. This causes some sort of galvanic reaction and you get a lot of streaky black residue on both after a few months of use. Ew.
Now, I haven’t written straight C for a while now, so I wondered what sort of twisted interface code I’d come up with to grab data off of these things. Oddly enough I didn’t screw up the external interface that badly:
typedef struct {
uint8 joyX;
uint8 joyY;
uint16 accelX;
uint16 accelY;
uint16 accelZ;
uint8 buttonZ;
uint8 buttonC;
} NunchukData_t;
void nunchukInit(void);
int nunchukReadData(void);
NunchukData_t nunchukGetData(void);
void nunchukPrintData(void);
Yeah, so if you were to use my code, you’d see a C struct for the data output and four self-explanatory functions. Clean and simple.
However, my implementation in the source file (attached) is way shakier, involving GCC attributes, bitfields, and anonymous union-nested anonymous structs. But hey, that’s how I roll when I want to decode binary data. First I define this… thing:
typedef struct __attribute__((__packed__)) {
union {
struct {
uint8 joyX;
uint8 joyY;
uint8 accelX_HI;
uint8 accelY_HI;
uint8 accelZ_HI;
uint8 buttonZ : 1;
uint8 buttonC : 1;
uint8 accelX_LO : 2;
uint8 accelY_LO : 2;
uint8 accelZ_LO : 2;
};
uint8 buffer[6];
};
} NunchukRegisters_t;
What is that supposed to achieve? Well, it’s basically a 6-byte long C struct which corresponds to the bit patterns read off of the Nunchuk. Based on reading materials, I know that the data is encoded/ciphered by the byte, and all the decoded data is swizzled together to save bandwidth.
Thus, I have a union between the aforementioned struct and a size 6 byte array so I can fill up and decode the struct in a per-byte fashion, just by looping over the unioned array. The __attribute__((__packed__))
is just an futile, unnecessary hint to my compiler to make sure it doesn’t pad out my struct with extraneous bytes.
How do I use this struct? Now this is the beautiful part:
int nunchukReadData(void) {
NunchukRegisters_t nunchukRegs; // allocate space
readNI2C1(NUNCHUK_ADDR, nunchukRegs.buffer, 6); // read from nunchuk
int i;
for(i = 0; i < 6 ; i++) { // decode by byte
nunchukRegs.buffer[i] = nunchukByteDecode(nunchukRegs.buffer[i]);
}
// assemble raw data
nunchukData.joyX = nunchukRegs.joyX;
nunchukData.joyY = nunchukRegs.joyY;
nunchukData.accelX = (nunchukRegs.accelX_HI << 2) | nunchukRegs.accelX_LO;
nunchukData.accelY = (nunchukRegs.accelY_HI << 2) | nunchukRegs.accelY_LO;
nunchukData.accelZ = (nunchukRegs.accelZ_HI << 2) | nunchukRegs.accelZ_LO;
nunchukData.buttonZ = !nunchukRegs.buttonZ;
nunchukData.buttonC = !nunchukRegs.buttonC;
DELAY_US(50); // comm delay so nunchuk doesn't complain
nunchukRequestData(); // signal nunchuk to read sensors again
return 1;
}
Notice how the unioning lets me fill up the registers without janky casting, and the bitfields in the nested anonymous prevents me from having to shift or mask off bits in order to decode the Nunchuk registers into my output data struct. For comparative delta, this is how the “reference implementation” looks on the Arduino:
// from http://todbot.com/arduino/sketches/WiichuckDemo/nunchuck_funcs.h
int joy_x_axis = nunchuck_buf[0];
int joy_y_axis = nunchuck_buf[1];
int accel_x_axis = nunchuck_buf[2]; // * 2 * 2;
int accel_y_axis = nunchuck_buf[3]; // * 2 * 2;
int accel_z_axis = nunchuck_buf[4]; // * 2 * 2;
int z_button = 0;
int c_button = 0;
// byte nunchuck_buf[5] contains bits for z and c buttons
// it also contains the least significant bits for the accelerometer data
// so we have to check each bit of byte outbuf[5]
if((nunchuck_buf[5] >> 0) & 1)
z_button = 1;
if((nunchuck_buf[5] >> 1) & 1)
c_button = 1;
if((nunchuck_buf[5] >> 2) & 1)
accel_x_axis += 2;
if((nunchuck_buf[5] >> 3) & 1)
accel_x_axis += 1;
if((nunchuck_buf[5] >> 4) & 1)
accel_y_axis += 2;
if((nunchuck_buf[5] >> 5) & 1)
accel_y_axis += 1;
if((nunchuck_buf[5] >> 6) & 1)
accel_z_axis += 2;
if((nunchuck_buf[5] >> 7) & 1)
accel_z_axis += 1;
Yeah… I’d rather have nested anonymous structs than line after line of nasty bit twiddling. Let’s ignore the fact that the above example is a snippet of application code, while my decoding was library code, making it instantly superior from a software engineering standpoint. That is, the programmer who uses my code can pick up and run with an interface which is decoupled from how bits are swizzled by the Nunchuk. But, the coder above would have to reference a couple of websites to write their own decoding routines before printf
'ing the data, for example.
Non-volatile storage §
If there’s one thing I’m unhappy about with my choice of microcontroller, it’s that it doesn’t have built-in data EEPROM. EEPROM is flash memory, which is non-volatile—its contents are retained even when powered off. Most decent PICs have, in addition to RAM and program memory flash (where all your code is stored), a piece of data EEPROM acting as a memory-mapped peripheral which you can use as if it were an external EEPROM chip over SPI or I²C. The dsPIC33 family does not have this§.
But I still want my non-volatile storage, so I can store Nunchuk calibration data in the transmitter. How? We write it to the program flash. To make a long story short, this is a fairly complicated procedure which was achieved by using Microchip’s Data EEPROM Emulation library, which turns unused program flash space into software-addressable EEPROM blocks. It even spreads writes over different blocks of allocated program flash in order to reduce wear on the EEPROM cells.
Trouble was, this library had some fairly odd software engineering practices, like putting way too much internals in a public header. It also used a separate unit of assembly language routines so the library could use specific instructions for flash reads and writes, when they could have been done in inline assembly, or even better, compiler built-in functions. It’s not like your assembly is more portable when you put it in a separate file, Microchip. :\
My modifications and simplifications to the library are attached to this post.
The protocol §
RC control is essentially packeted data; each piece of data you send is a full state of what motors/LEDs should be doing what, with no dependence on past or future data. Xbees provide you with a bidirectional stream (yes, I know it’s packets underneath) rather than a packet interface like Nordic radios. For those who grok teh nets, it’s more TCP and less UDP.
So, I hacked up a protocol that took advantage of both: use escape characters and base64 encoding to represent binary packets, and human-readable ASCII which is ignored by the receiver.
First I define a data structure. Starting to see a pattern?
typedef struct {
union {
struct {
uint16 speed;
uint8 reversed;
uint8 brake;
};
uint8 buffer[4];
};
} FluffyData_t;
FluffyData_t
¶ is the information the receiver cares about: speed, direction, and whether to brake. This is put through a base64 encode♠, and surrounded with non-base64 escape characters, ^ and $, into a 10-byte ASCII string.
Now, everything outside of those escape chars can be easily parsed out on the receiving end. So, now we have easily-decoded binary data represented as ASCII so it doesn’t screw with our terminals, and we can stick “comments” into the stream so we can peek in and read debug output.
For example, this is what actually goes through my Xbees:
(Accel) x: 664, y: 450, z: 612 (Joystick) x: 148, y: 196 (Button) z: 1, c: 0
(fluffy) speed: 46260, reversed: 0, brake: 0
^tLQAAA==$
(Accel) x: 664, y: 450, z: 614 (Joystick) x: 148, y: 197 (Button) z: 1, c: 0
(fluffy) speed: 47031, reversed: 0, brake: 0
^t7cAAA==$
That’s right, most of it is human readable. Only a tiny fraction of it is for the machine. This is called taking advantage of the bandwidth.
On the receiver end, it’s trivial to listen for a ^ character, start decoding base64 into another FluffyData_t struct, and then stop decoding upon receiving a $ character. Of course I am oversimplifying data integrity issues here, which are remedied in my design with a few simple checks and CRC16 checksums.
Code listings §
Note that I use the excellent Reese/Jones/Bruce library for PIC24 & dsPIC33 as my hardware interface library, which is referenced in the nunchuk.c code for I²C routines.