My adventures in writing an OTA bootloader for the ATmega128RFA1

This adventure begins with the acquisition of a low priced gadget from the secondhand market. The SMART Response XE was designed to be a classroom communicator that allowed students to vote and submit answers in real time to the teacher.


Support ended a few years ago and what once cost $100-200 each are now available on eBay for $10 or less. The hardware is just asking to be hacked. Inside is:
  • 60-key keyboard
  • 384x136x2-bpp monochrome LCD
  • ATmega128RFA1 MCU (128K Flash, 4K EEPROM, 16K RAM, integrated 802.15.4 MAC)
  • 1Mb (128KB) SPI flash
  • 4xAAA battery power supply
This particular MCU is part of the AVR family of products and that makes it easy to write code using the Arduino IDE. I initially spotted this on Hackaday because a group of people in the Arduboy community decided that it could be fun to run their games on it. My interest is more in the learning side. For me, this device was a good way to learn about:
  • Serial (SPI) FLASH memory
  • AVR Bootloaders
  • 802.15.4 wireless
I started by writing a support library with functions to initialize the display, draw text/rectangles, read keys from the keyboard and access the SPI FLASH chip. Next, I came up with a few project ideas involving wireless data - battery powered VT100 terminal, multiplayer games. I had now modified 3 of these devices and decided that a really good challenge would be to write code to allow wirelessly uploading an Arduino 'sketch' to it. The wireless aspect isn't just for a mental exercise; the device is difficult to open and doesn't have any exposed I/O except for some holes under the battery cover. The holes allow access to JTAG connections. These include Vcc, GND, SPI (SCLK, MOSI, MISO) and reset. Enough to use an ISP programmer to reprogram the bootloader.


Once a standard Arduino bootloader is installed, the next challenge arrises. The Arduino IDE uploads code over serial. On this device, it takes a lot of work to connect and expose the serial ports. Another challenge is that TX0 and RX0 are used for part of the keyboard matrix, so serial0 access will disable the "function" keys around the display. However, a bootloader could potentially be written to it without opening the case or soldering wires (by using pogo pins through those little holes). Here's a photo of the primitive pogo pin board I created:


A wireless capable bootloader would allow these devices to be more easily used for Arduino development without having to make any physical changes to the case or PCB. I made the effort to modify my first device and expose both serial0 and serial1 ports and the ISP header to an external connector. I also added a power switch since this device is permanently "on". This took so much time and effort that it was a strong incentive to make the OTA bootloader idea work. Here's my most complete modification:

Pictured above - a 5x2 female header and a small slide switch. With the crude tools at my disposal (xacto knife, glue gun, soldering iron), it came out pretty good, but took way too much time.

The Devil in the Details

The Arduino IDE runs a program called avrdude to upload sketches to target devices. It speaks the STK500 protocol written by Atmel. This protocol is a back and forth type of file transfer which doesn't do very well on communication channels which have variable delays, can lose data or have data corruption. It can really drive you nuts when the serial link has problems. I spent a good part of a day debugging a problem that turned out to be cables making poor contact and a temperamental USB-serial adapter (CP2102). Even chips with built-in USB-to-serial adaptors like the Leonardo (ATmega32u4) still have these issues. Anyone who has used the Arduino IDE can attest to frequent failures when trying to upload sketches. Often the 'writing' phase works, but the 'reading' phase fails. This doesn't mean the sketch failed to write properly, just that there was a glitch when reading it back to verify it. Now, imagine these frequent failures made even worse by sending that data over a raw packet wireless link. My first tries at solving this problem were to just treat the wireless as if was serial and pass the byte data back and forth. This didn't work at all. The small amount of data loss is not handled by STK500 at all, so avrdude immediately complains about wrong responses and being out of sync. My next try was to logically gather the message bytes into packets and that failed too. I slowly started adding canned responses to my STK500 state machine so that the wireless end wouldn't need to respond to the startup boilerplate. This included the initial "hello" back and forth, querying the firmware version and the hardware signature. I got to the point where the entire initial handshaking was contained on my "hub" device and just the writing of the data was sent over wireless. Even this wouldn't work reliably. This led me to change directions and come up with an alternative. Luckily the XE devices have 128K of SPI FLASH memory which happens to be a great place to temporarily hold a sketch as it's being uploaded to another device.

This idea requires two devices with 802.15.4 radios, one connected to your PC to talk to the Arduino IDE and another to receive the data wirelessly. This means that at least one XE device needs to be butchered to expose its serial port and serve as the "hub" to talk to other devices. In the scheme of things, this makes the OTA bootloader idea quite a bit less ideal since some prying and soldering will still be required. Ah well, nothing is perfect.

Reminder - after adding my wireless code and font, the bootloader has expanded beyond 4K, so it requires changing the fuse values. Specifically, the HFUSE value changed from 0xDA to 0xD8. This sets the bootloader length to 8K and moves the start address to 0x1E000. This change is reflected in the Makefile I shared, but you also need to specify it when you burn the bootloader using avrdude.

The 802.15.4 RF part of the ATmega128RFA1 is meant to run ZigBee. The ZigBee protocol does channel hopping and involves a lot of extra code that I didn't think was needed for this project. I decided to just use raw network packets. This functionality is supported pretty well within the ATmega128RFA1 hardware without having to write much code. I also thought it would be simplest to choose a fixed channel for my wireless activity instead of channel hopping or letting the user decide. 802.15.4 supports 16 channels numbered 11-26. Unfortunately they are in the busy 2.4Ghz range and overlap WiFi frequencies (ZigBee channels in red, WiFi in blue/green/yellow)


It looks like channels 15 and 26 have the best chance of avoiding interference from WiFi, so I chose to use 26 for the bootloader communication.

The Sender

The first step to writing the "hub" XE code is to create a state machine to talk "STK500" protocol. Most of the messages sent and received are stateless, but a few require holding on to previous info. Here's a good description of the whole conversation.

The 'important' part of the conversation is when it sends data packets meant to be written to the flash memory of the destination device. Simpler AVR chips have 128 bytes per flash page, but the ATmega128RFA1 has 256. This conveniently is the same size flash page used by the SPI flash inside the XE. I wrote my STK500 receiver to write these sketch flash pages into the SPI flash and read them back during the verify stage. Once I had the ability to capture a sketch from the Arduino IDE and write it to the SPI flash, now I needed to implement the wireless part. One of the problems I observed with the STK500 protocol is that it requires a bunch of fast back and forth query/response actions. It seems to be tolerant of delays, but not of dropped data. One of the issues with wireless data is that some of it will get lost due to collisions or just interference from other devices on that frequency. The ATmega128RFA1 has some built in logic to do automatic retries, but I thought it would be more useful to me to learn to implement this logic myself. With this in mind, I created a protocol that has the data going more in one direction than the other.

My Protocol

My initial wireless protocol idea may not be ideal, but it seems to work well. I break up each 256 byte flash page into 4 'segments' and send them in individual wireless packets. A wireless packet can only hold up to 125 bytes with the 16-bit CRC logic enabled (125 byte payload + 1 byte length + 2 byte CRC). It seemed reasonable to send my data in 64-byte chunks along with the page number and segment number (0-3). The receiver keeps a flag variable indicating which of the 4 segments for the current page has arrived and when all 4 are received, it sends back an ACK to the sender. If the sender doesn't receive the ACK in a few milliseconds, it resends the 4 segments. The time to send a sketch over wireless is slightly faster than the time to send it over a serial cable, so this worked out well. Here's a video of it in action:

What's Next?

I need to make it easier to access the serial port on these devices to turn more of them into hubs for this wireless idea. For my next case mod, I'm going to install a small CP2102 USB to serial adapter like this one inside the case and epoxy it to the PCB to withstand the force of inserting and removing the USB cable. As an added benefit, it has a 3.3v regulator built-in. It also has 3 LEDs which will need to be removed since they'll draw power when running from the batteries and will interfere with the serial lines when they're used to access the keyboard matrix or SPI FLASH:

In the end, the journey was more important than the destination. For my efforts, I learned about AVR bootloaders, SPI flash chips, STK500 protocol and 802.15.4 networking. For me, that's reward enough.

You can download all the code from Github here.

You can also follow my daily tech adventures on Twitter.


Comments

Post a Comment

Popular posts from this blog

Building the Pocket CO2 Project

How much current do OLED displays use?

Fast SSD1306 OLED drawing with I2C bit banging