Christmas Ornament 2015 Writeup

Each year, Silicon Labs (my current employer) puts together a Christmas ornament to give to all our employees. I was asked to help out this year, and had the idea to use finger joints to make a mini-present out of circuit boards. When we were prototyping it, I couldn’t help but notice the bare copper board looked a lot like the brick blocks from the NES Mario Brothers game. So I started playing around with making another ornament in my free time.

There were a few major features I wanted the ornament to have: capacitive sensing for touch interaction, audio playback, a set of lamp LEDs inside the ornament, and an acrylic mock coin with an embedded LED above the ornament.

Capacitive Sensing

I was very, very happy with how easy the capacitive sensing portion of the project was.  A different group at Silicon Labs makes a micro controller that’s designed to handle cap sense applications, and they release a library that made firmware integration really easy.  It’s a precompiled binary, which I’m not overly fond of, but the resulting API required only four function calls to make everything work, which was an overall pleasant experience:

//Configures all peripherals controlled by capsense
CSLIB_initHardware();
//Initialize the capsense firmware
CSLIB_initLibrary();
...
//Gather new capsense data
CSLIB_update();
//Check for a press
if (CSLIB_isSensorDebounceActive(0)) {
/* do stuff */
}

Audio Playback

The easiest way to play audio from the MCU was by utilizing the PWM peripheral.  The PWM channel was set up to run in 8 bit mode, using the system clock (24.5 MHz) as its source.  A timer was then set up to activate an interrupt at 22.1 KHz (the sampling frequency of the audio clips from the original NES Super Mario Brothers).  On each timer interrupt, the next PWM value was loaded from memory into the register that controls the PWM active width.  The digital PWM signal was then routed to a line buffer, which boosts the signal strength to ~25 mA.  I routed the audio output from the MCU to each of the four inputs on the line buffer I was using, and placed solder pads by the line buffer that can be used to enable each of the three extra channels (in case louder volume is desired).  For my ornament, I only ended up using the one channel, but theoretically all four channels can be used to significantly amplify the audio signal if the ornament will be placed in a loud environment.

Once the PWM signal has been generated and amplified, it is passed through an LC circuit that acts as a bandpass filter to get the digital PWM signal to more accurately represent an analog signal for the speaker.

Audio Storage

The FM970 MCU I used has 32k of flash on chip, which equates to a bit more than one second of audio (when using 1 byte per sample and a 22.1 KHz sampling frequency).  This is obviously not nearly enough storage, so I decided to use an external I2C EEPROM to add more storage.  This presented me with three additional challenges.

EEPROM writing

I wasn’t able to find any simple lab tools to just take an array of data and write it over I2C (at least, not on a DIY budget).  To fix this, I added UART pins to the MCU, and came up with a simple protocol that can be used to send data from a host PC to the MCU over UART, which the UART then translates to I2C writes to the EEPROM:

# Protocol:
# [0] denotes LSB. Host will assume idle on start, and send 0xFE when it is
# ready to send a message. Device will respond with 0xED when it is ready to be
# fed. Host will then send header, then data, then footer (0xF00D). Device will respond
# with 0xBEEF when it thinks the message is done, or 0xBAAD followed by one byte
# of error code if it got the message but saw something squirrely.
#
# Idle->Active: Send 0xFE, wait for 0xED from device
# Header: chunk size[0]
# chunk size[1]
# chunkLocation[0]
# chunkLocation[1]
# chunkLocation[2]
# chunkLocation[3]
# Data: data[0]
# ...
# data[chunk size - 1]
# Footer: 0xF0, 0x0D

 

EEPROM File System

I wanted to be able to generically determine where the files were, so I came up with a simple file system to allow the MCU to determine at runtime where all the files on the EEPROM lived and how to interact with them.  I allocated the first page of the EEPROM as a file table, using the following protocol to pick things up from there:

# FS Protocol:
# header is always one EEPROM page, EEPROM_ALIGNMENT bytes
# each block has a 32 bit start address and a 32 bit size.
# byte 00: number of entries in header block (files in system)
# byte 01: reserved
# byte 02: reserved
# byte 03: reserved
# byte 04: block 0 start address (byte 0, LSB)
# byte 05: block 0 start address (byte 1)
# byte 06: block 0 start address (byte 2)
# byte 07: block 0 start address (byte 3, MSB)
# byte 08: block 0 size in bytes (byte 0, LSB)
# byte 09: block 0 size in bytes (byte 1)
# byte 10: block 0 size in bytes (byte 2)
# byte 11: block 0 size in bytes (byte 3, MSB)
# byte 12: block 1 start address (byte 0, LSB)
# byte 13: block 1 start address (byte 1)
# byte 14: block 1 start address (byte 2)
# byte 15: block 1 start address (byte 3, MSB)
# byte 16: block 1 size in bytes (byte 0, LSB)
# byte 17: block 1 size in bytes (byte 1)
# byte 18: block 1 size in bytes (byte 2)
# byte 19: block 1 size in bytes (byte 3, MSB)
# byte EEPROM_ALIGNMENT: block 0 byte 0
# etc etc etc etc

 

Once I had a file system protocol in place, I wrote a few python scripts: one to take a 16 bit .wav file and convert it to 8 bit unsigned values (with a 22.1 KHz sampling frequency) and store those values as a .hex file, a script to take a number of hex files and use them to generate a single hex file containing a formatted file system, and a script to take a file system and transfer it over UART to the MCU.  This gave me a quick, simple workflow to take audio files and transfer them in a playable format to the MCU.

Real time EEPROM reading

The final challenge to playing audio was making some customizations to an I2C driver.  I was using a sample driver from a Silicon Labs app note that assumed all transactions would take place at once.  You give it an address, a buffer, and a size, and it would fill the buffer with data as fast as possible.  I had to make a slight modification to allow the driver to instead start a read transaction, read a single byte, and hold the bus idle (as opposed to ending the transaction) until another API call informed the driver that the data had been digested and the next byte could be read.  Nothing horrible, but not trivial.

LED Routing

With cap sense working and audio playing, the only remaining feature was getting the LEDs to turn off and on at the right times.  My idea for the enclosure was to have portions of the sides of the cube made with bare FR4 (no copper, no soldermask), so that if I turned on an LED inside the cube, it would give a lampshade effect, allowing light to only pass through the portions of the cube that didn’t have copper.

In order to get electrical signals from the MCU at the bottom of the cube to the LEDs at the top of the cube, I added four exposed pads to each panel of the cube.  I then used a small bit of stiff wire to connect these pads to the top and bottom of the cube to one of the sides.  This gave both electrical conductivity and structural integrity to the cube.  Four LEDs provide the internal “lamp” light, with an additional LED used to illuminate the coin on top of the cube.  To make sure the LEDs don’t draw too much current from the GPIOs, a transistor is used to switch them on and off.

Assembly

Each board was milled with finger joints along the edges, which allowed the sides of the cube to be press fit together.  The top, front, and bottom of the cube are held in place with the solder joints mentioned above.  Two of the remaining sides were held in place with superglue.  The last side was meant to be press fit, so that it could be removed to access the debug headers and batteries.  The press fit did not hold over repeated uses, though, so an alternate method had to be used to keep the last panel in place.  I ended up placing small pieces of solder wick on the exposed pads in order to create a simple hinge to hold the panel in place.

Once the board was assembled and the components soldered, the last step was to create a coin to suspend above the cube.  This was done using a laser cutter to etch and cut a piece of 1/4 inch clear acrylic and a drill to create a mounting hole for the LED.

Once the LED was soldered in place, a Christmas ornament hook was used to suspend the box from a branch of the tree.  Overall, the effect was exactly what I was going for, and I was very pleased with the final product.