Some Chronos Documentation
Moving on from my previous post (in which I muttered sullenly about brain-dead packaging of software for Linux), I began hacking on my Chronos proper tonight. Read on for some juicy tidbits.
Initial build
The first order of business was to set up a toolchain targeting MSP430. Since I’m running Arch on my primary development system, it was a simple matter to build gcc-msp430 from the AUR.
With that, I was ready to try compiling things. I assumed (correctly) that the provided firmware would not build on GCC without modification, but a little googling pointed me to OpenChronos, which effectively takes the stock firmware, makes it build with any of several compilers (TI’s compiler included with CCS, IAR’s, and GCC). Come to think of it, LLVM has an experimental MSP430 backend that might be interesting to try out.
One git checkout and an invocation of make later, and I was staring at a screenful of errors. “How auspicious,” I thought. The first part of the fix was easy– I simply needed msp430-libc for some of the more specialized functions that don’t map well into straight C- things like interrupt handling (which is in msp430-libc’s signal.h for some reason) or machine-specific delays.
The remaining compilation errors after grabbing libc were rather more troublesome, however. There were two main classes of problem.
- Uses of types at some specific bit-width (such as
uint16_t
). These were easily resolved by strategic inclusion of stdint.h, but I’m not very happy with how I had to do it. Spraying header inclusions all over the source code is a poor way to fix things. - Large delay constants. There were two cases in the radio control code which
adjusted the microcontroller’s voltage regulator, which then requires a
rather long delay before the system can be considered stable again. The solution
in code is simply to delay for as many as ~800000 clock cycles. Normally that
wouldn’t be a problem, but some of the delay constants were larger than the
input type to the
__delay_cycles
function could hold. My hacky solution was to split those into two calls of half the length, which seemed to work out OK.
After a while to figure out the compilation problems, I was able to build a firmware image. After the struggles I had with unpacking TI’s sample code and demo applications, it was fortunately painless to actually run them. I just ensured I had Tcl/Tk installed and ran the Chronos Control Center application. Putting the Chronos itself into WBSL (Wireless BootStrap Loader) mode and clicking a few times was easy, and I quickly got my new firmware image flashed onto the CC430.
Preparing for mods
Now that I had a known-working toolchain, it was time to get to work actually implementing some of the toy features I wanted to add. Since the single most interesting feature of the hardware is the radio (although the low-power capabilities of the MSP430 are quite shiny as well), I set out to see how I could communicate with the watch from my PC.
One of the USB dongles that comes packaged with the Chronos is a USB wireless access point, basically just a CC1111 (6801 core with USB and RF transciever). I understand that earlier revisions of the demo applications didn’t include source code for the software running on the CC1111, but the current release includes it. Some people had taken a bit of trouble to reverse-engineer the communications, but that alone isn’t very useful documentation. With that in mind, I set out to document for myself how to communicate with the RF access point and go through that to talk to the Chronos.
Setting up communications is easy, fortunately. The CC1111 is programmed to enumerate as a USB CDC, so one must only open the virtual serial port it creates with a 115200 bps baud rate with 8 data bits and 1 stop bit. (If that’s not terse enough for you: 115200 baud, 8n1.)
With virtual serial communications up, the upper-level protocol is rather easy- it consists of packets of at least 3 bytes each, where the first one is always 0xFF. Byte 2 provides a command ID, and byte 3 specifies the total packet size, including the overhead (so the minimum valid size is 3). Anything more in the message is interpreted based on the command ID.
Command IDs
There are a number of command IDs defined, but only a few that are of particular interest. In the hopes that somebody else will find it useful, I include my raw notes on the command bytes below.
As a little bit of context, the system can run on either of two different radio protocols. TI’s SimpliciTI is a protocol designed mainly for communication between low-power nodes in a network, while BlueRobin is a radio protocol developed by IAR Systems, notable with the Chronos because it allows communication with a heart rate monitor developed by BMi GmbH.
Command bytes:
BM_GET_PRODUCT_ID
Dumps 32-bit product ID into the usb buffer
BM_GET_STATUS
returns system_status (some file-scope var?)
== bluerobin
BM_RESET
Turns off bluerobin
BM_START_BLUEROBIN
Start bluerobin (set a flag, actually), stop simpliciti if that's going
BM_SET_BLUEROBIN_ID
BM_GET_BLUEROBIN_ID
BM_SET_HEARTRATE
BM_SET_SPEED
== simpliciti RX
BM_START_SIMPLICITI
Start simpliciti, stop bluerobin if that's going
BM_GET_SIMPLICITIDATA
Dump the 4 bytes from the simpliciti_data buffer to USB
also mark simpliciti data as read
If no pending data, usb_buffer[PACKET_BYTE_FIRST_DATA] = 0xFF
BM_SYNC_START
nop
BM_SYNC_SEND_COMMAND
copy packet from USB to simpliciti buffer and flag for tx ready
BM_SYNC_GET_BUFFER_STATUS
1-byte payload packet out, = var simpliciti_sync_buffer_status
BM_SYNC_READ_BUFFER
copy simpliciti_data buffer out to USB
BM_STOP_SIMPLICITI
Flag to turn off simpliciti
== WBSL
BM_START_WBSL
flag to start WBSL, turn off bluerobin/simpliciti if active
BM_STOP_WBSL
stop wbsl, turn off LED
BM_GET_WBSL_STATUS
copy back var wbsl_status
BM_GET_PACKET_STATUS_WBSL
copy back var wbsl_packet_flag or WBSL_ERROR if wbsl is off
BM_GET_MAX_PAYLOAD_WBSL
copy back max number of bytes allowed in wbsl payload
BM_SEND_DATA_WBSL
set wbsl_packet_flag to WBSL_PROCESSING_PACKET
deocode packet and spew it to the 430
== self-test
BM_INIT_TEST
BM_NEXT_TEST
BM_GET_TEST_RESULT
BM_WRITE_BYTE
write a byte to the access point's Flash memory
first data byte is the value to write
second and third are address, little-endian (2 is lsb, 3 => msb)
must be in test mode (precede this with BM_INIT_TEST)
(Command and system status constants are defined in BM_API.h
, FWIW.)
Knowing all the commands, it’s pretty easy to pull out the useful ones. BM_START_SIMPLICITI makes the access point switch into SimpliciTI mode, and sending BM_SYNC_START allows direct communication through the radio link with BM_SYNC_{SEND,READ}_BUFFER functions.
More.. later
This is as far as I’m going to go with this adventure for today, but there’s more to come in the coming days (hopefully, assuming my motivation holds out). This is just preliminary documentation– I’m hoping to create a more formal set of documents providing a whirlwind overview of how to get hacking on the Chronos, but I feel this is an excellent start.