Serially Addressable LEDs as Ukulele Learning Tools
ukule-LED is a ukulele with LEDs embedded in its fret board. These LEDs can light up in configurations corresponding to different chords, which can aid the ukulele player in learning how to play chords and entire songs. The system’s microcontroller interfaces with the LEDs and a Python-based command line interface running on the user’s computer.
If you’ve ever picked up a guitar or a ukulele, you know how much memorization is involved in learning a single song. Sure, you can learn four chords. It works fine until you run into an exotic chord. A#6, anyone? You have to consult a chord chart. Next, you have to remember the order of the chords, and that’s just one song. Then, finally, you have one song in your repertoire. We’ve struggled with these problems in the past, and we still do. But, as students in Dr. Bruce Land’s ECE4760 microcontroller course at Cornell University, we sought the solutions. So, we built the ukule-LED.
The ukule-LED is a modified ukulele with digitally addressable RGB LEDs embedded in its fretboard. These LEDs can light up in configurations corresponding to different chords, which can aid a beginning ukulele player in learning how to play certain chords, or can act as a chord reference for a more advanced player. The ukule-LED has two modes of operations. In play mode, the user can feed the system a song file, a text file that contains the tempo, time signature, and an ordered listing of the chords in a song. The ukulele will light up the correct chords at the correct times in the song. In practice mode, the user can specify a single chord, which is lit up indefinitely.
The ukule-LED is made up of two major components. First is the ukulele itself. We use 16 RGB LEDs embedded in the fretboard before the first four frets on each string. These LEDs are wired to an Atmel ATmega1284P microcontroller, which is mounted on the ukulele itself. The ukulele can be connected to a PC, which runs a companion program with a command line interface. From this interface, the user can put the device in play mode and specify a path to a song file or put the device in practice mode and specify a chord name.
We used an ATmega1284P microcontroller for this project because it has the two 16-bit timers we needed for our project. One was used for task scheduling, and the other was used to keep time for the music. Additionally, we used Bruce Land’s custom PCB for the ATmega1284P, which includes an external crystal, an LED, and a power regulator, among other components. This made interfacing with the microcontroller easy. The stars of the show, though, are the ukulele and the RGB LEDs.
To prepare the ukulele, we used a Dremel tool to cut grooves in the ukulele’s fretboard to accommodate the LEDs. Critically, we had to keep all the fret wires intact and keep the LEDs below them in order to maintain playability of the ukulele. Otherwise, the LEDs, not the fret wires, would stop the string, pulling notes out of tune. We hot glued four sets of four LEDs to the Dremeled grooves in the fretboard, one for each of the first four frets on each of the four strings. Photo 1 shows the completed ukulele fretboard.
We Adafruit Industries NeoPixel Mini PCBs, each of which includes a WS2812B RGB LED. We used the PCBs instead of directly using WS2812B LEDs to make soldering less painful. All of the RGB LEDs are driven from a single microcontroller I/O pin. WS2812Bs are driven serially, using a highly timing-sensitive 1-wire protocol, with the data output, power, and ground pins of one connected to the data input, power, and ground pin of the next. The data input pin of the first LED is connected to an I/O pin on the microcontroller through a 330-Ω resistor to protect the first LED from any voltage spikes. As an additional protection, there is a 100-µF capacitor between the power and ground lines to prevent large inrush currents from destroying the first LED, according to Adafruit’s NeoPixel PCB documentation.
Soldering the RGB LEDs together was the most time consuming part of building the ukule-LED. Each of the 16 NeoPixel PCBs needed to be individually soldered. We used very short lengths of wire, soldered to the pads on the PCBs, such that there was about 1 to 2 mm of space between each of the four PCBs placed in each Dremeled groove in the ukulele’s fretboard. Next, we wired these four groups using longer lengths of wire. Finally, we hooked all 16 NeoPixel PCBs to the main solder board with three long lengths of wire.
Our next task was adequately powering the LEDs. According to the NeoPixel documentation, each RGB LED driven at maximum brightness consumes 60 mA of current, resulting in a total current draw of 960 mA for 24 RGB LEDs. However, we drove a maximum of four LEDs at a time, with the macro INTENS set to 20, out of a maximum value of 256. A back-of-the-envelope calculation gives approximately 18.75 mA of current, which is well below the absolute maximum DC current of 100 mA for the LM340 power regulator on the microcontroller’s custom PCB. As such, we decided that we could drive the RGB LEDs using the microcontroller’s VCC pin, instead of resorting to an external power source as the NeoPixel documentation suggested. In the end, we only needed to use a 9-V power supply to power the entire system. A schematic of the electronics involved in the ukule-LED is shown in Figure 1.
Finally, we connected all of the electrical components–the microcontroller, the LED circuitry, the power circuitry, and some wiring and pins for a serial-to-USB cable–to a small solder board which we then glued to the base of the ukulele, as shown in Photo 2.
COMMAND LINE INTERFACE
To interact with the microcontroller, our project requires a computer running a Python-based command line interface. We used docopt, an excellent command line argument parser for Python, in order to painlessly build it. There are two command formats that a user can use to start a ukule-LED session: ukule_led practice “chord” port “portname” and ukule_led play “file” port “portname”.
The first type of command tells the microcontroller to go into practice mode. The arguments for this mode are chord, which is the chord to be played (A, C#m, or Gb7, for example), and the other argument is portname, which is the name of the serial port on the computer that’s connected via the serial-to-USB cable to the microcontroller. The second command type tells the microcontroller to go into play mode. The arguments are file, which specifies the path to the song file on the user’s computer, and port, which is serves the same function as previously described.
We designed our own song file format for the ukule-LED. We felt existing standards and protocols, such as MIDI, were overkill. Listing 1 shows an example of a song file. The first line of the song file contains two numbers, the tempo of the song in beats per minute and the time signature of the song in beats per measure of the song’s time signature. The tempo range we currently support is between 60 and 240 beats per minute, which is good enough for a vast majority of music. We also assume that the user only plays one chord per beat, which is reasonable because the ukulele is usually an accompaniment to voice and not a leading instrument. It doesn’t need to be able to play complicated rhythms.
Listing 1 Example of a song file 240 4 |A B C D|E F G -|A - - B| |A - Bbm A7|Bm X A#7 -|A - X D|
The remainder of the song file contains the measures of the song. Each measure is delimited by a | and contains a number of chords equal to the specified time signature. There can be an arbitrary number of measures, broken up arbitrarily into lines. We support major, minor, and seventh chords. We designed the ukule-LED to light up major chords in green, minor chords in red, and seventh chords in blue. Examples of chords are shown in Photo 3. A chord’s base can be any note, with # denoting a sharp and b denoting a flat. Finally, a – tells the system to hold a previous chord for another beat, and an X means no chord should be displayed during that beat.
After the song file is written and the user uses the command line interface to start a ukule-LED session, the data contained in the song file is serialized and sent to the microcontroller. Similarly, in practice mode, data corresponding to a single chord is serialized and sent to the microcontroller.
When the Python CLI receives a command from the user, it uses docopt to parse the command’s arguments and determines the desired mode of operation and the associated chord data. The program serializes chord data using a string of numerical characters delimited by the / character. We developed an encoding for chords that assigns each chord name a unique chord number starting from 1. Additionally, we use 0 to denote an X in the song file.
The command is then converted into a string so that it can be serialized and sent over the serial connection. An example of a practice mode string is 1/17, and an example string for play mode is 2/200/4/14/13/02/30/-1. The first number in the string is a flag. 1 denotes practice mode and 2 denotes play mode. In practice mode, the number following the 1 denotes the chord to be played. In play mode, after the 2, the next number provided is the tempo, and the third is the number of beats per measure, which are 200 and 4 in the example respectively. The next numbers represent the chords of the song in sequential order. There can be indefinitely many of them, and the list is terminated by a –1. Using Chris Liechti’s pySerial package, the program then opens a connection to a serial port provided as a command line argument by the user, and writes the string to the port.
The microcontroller has a few major tasks (see Figure 2). The first task receives commands from the CLI via a serial port. The microcontroller stores in its memory the chord array, called lookup_table, whose indices are chord numbers and whose values denote the configuration of LEDs to be lit up. Every hundredth of a second, a second task polls the value of a pointer into this chord array and updates lights up the correct LEDs. In practice mode, this pointer remains constant. However, in play mode, a timer with frequency specified by the tempo in the song file updates the pointer according to the chords in the song file.
We used the TinyRealTime (TRT) kernel, developed by Dan Henriksson and Anton Cervin, to handle the scheduling of these two tasks. We also used Alan Burlison’s WS2812 LED driver, which is written in assembly and takes care of the timing needs of the RGB LEDs. The driver’s primary function takes in an array of length 3 arrays. Each length 3 array contains the red, green, and blue values of the light output of one RGB LED, from 0 to 256, where 0 denotes no output. The larger array, then, contains the red, green, and blue values of all of the LEDs. Lower indices into this larger array correspond to LEDs wired closer to the microcontroller (recall that these LEDs are wired serially to one microcontroller pin), and vice versa. This means a chord is represented by a length-16 array of length-3 arrays, which are fed into the LED driver. The values in the chord array are these sorts of arrays. By controlling which LEDs are turned on, and in what color, we can control which chords are displayed.
The embedded code contains a main function that initializes everything. It first sets up the LED driver, specifying the I/O port that the LEDs are physically connected to. Next, it sets up TRT according to the TRT documentation. TRT uses timer1, one of the Atmega1284P’s two 16-bit timers. main next sets up the other 16-bit timer, timer3, which drives the timed transitions between chords, setting its prescaler to 256, which fits our allowable range of tempos. After this, main enters a while(1) loop.
The first real-time task, carried out by function serialComm, is responsible for waiting for a CLI command to arrive over serial and parsing the command. It first allocates a buffer cmd to store the CLI command, then waits on a semaphore until a command arrives.
We use a global flag to specify whether the system is in practice mode, play mode, or a third idle mode, in which serialComm waits for a command. Recall that this information is contained in the command string. If the command is for practice mode, serialComm sets the variable chord_idx to the number assigned to the chord specified in the command. chord_idx is the pointer into the chord array. In practice mode, the value of chord_idx remains unchanged until another command is received.
If the command is for play mode, serialComm first parses the second element of the cmd array, the tempo of the song. Tempo values outside our range of 60 to 240 beats per minute are coerced. The timer3 (for timed transitions between chords) period is set to match the tempo.
After the timer3 period is set, the function extracts the third number in cmd, which is the number of beats per measure, and saves the value in the global variable time_sig, which is used for a count-in to prepare the ukulele player for the tempo.
Finally, serialComm calls a helper function that walks through the cmd array, converts the numbers from characters to integers, and inserts them into a global array called song.
We use the timer3 ISR to drive the song. When the ISR fires, it first checks which mode the system is in. If the system is in idle mode, the ISR sets chord_idx to 0, which makes sure none of the LEDs are on. If the system is in practice mode, then chord_idx is already set to the correct value, so the ISR does nothing.
If the system is in play mode, the ISR lights up the LEDs on the ukulele according to the data in the song file. First is the count-in phase. We set chord_idx to one of the special indices in lookup_table. If the current count is 3, for example, 3 LEDs are lit up in white. Each time the ISR fires during the count-in phase, the current count is incremented until it hits time_sig, after which the count-in is done.
After this, every time the ISR fires we run chord_idx = song[song_idx], where song_idx is the current index into song, and then increment song_idx. If the song is done the function will find that song[song_idx] == -1, which denotes the end of the song. In this case, song_idx is reset and the system is put into idle mode.
Our second real-time task simply polls the chord_idx value set by the ISR, gets the corresponding array from lookup_table, and feeds it to the LED driver.
We tested the system primarily by feeding it sample song files and ensuring that the behavior was correct. We included a variety of chords: major, minor, and seventh chords, and made sure the correct LEDs were lit up and with the correct colors. We also ensured that, when no chord was specified in the current time in the song, that no LEDs were lit. We tried different time signatures to make sure the count-in mechanism worked. We also tested practice mode by feeding the system a variety of chords. Next, we tested the software and hardware together for system stability issues.
Soldering together the RGB LEDs was difficult, and the connections were prone to breakage if the LEDs were jostled. In addition, all of the LEDs were in series, so breakage of one wire would disconnect all successive LEDs. We therefore had to test the connection every time we physically adjusted the LEDs. This was especially important before we glued the LEDs into their places within the ukulele’s fretboard.
Our goal for the ukule-LED project was to build a usable and extensible system for learning how to play the ukulele. The ukule-LED has a similar size and weight to an unmodified ukulele. The fretboard feels similar to that of an unmodified ukulele as well. This makes it easy to transition between ukule-LED and a ukulele. On the software side, the command line interface follows conventional patterns for commands and arguments, which makes it intuitive. We’ve found practice mode to be especially helpful as a chord reference.
Additionally, we can easily extend the ukule-LED. Currently, the system only supports major, minor, and 7th chords, where major chords are lit up in green, minor chords in red, and 7th chords in blue. The files chord_map.py and chord_lookup_table.h determine what chords are supported and what colors individual chords light up in. More specifically, chord_map.py maps chord names to their indices, and chord_lookup_table.h (containing lookup_table) maps these indices to LED patterns and colors. By editing these two files, a user can extend the system’s functionality. In one possible extension, a user can add support for all types of chords. In another possible extension, different LED colors can correspond to different finder positions, aiding the user’s technique. For example, a red LED would correspond to the index finger position. There are many directions in which this project can be taken, and it is straightforward to do so.
Authors’ Note: We’re grateful for the support of Dr. Bruce Land and his teaching assistant, Eileen Liu. The Ukule-LED was almost painless thanks to the open-source software written by Dan Henriksson, Anton Cervin, Jeorg Wunsch, Alan Burlison, Chris Liechti, and the developers behind docopt.
 B. Land, “Microcontroller Board,” http://people.ece.cornell.edu/land/PROJECTS/ProtoBoard476/.
 P. Burgess, “The Magic of NeoPixels: Adafruit NeoPixel Überguide,” 2014, https://learn.adafruit.com/adafruit-neopixel-uberguide.
Atmel, “8-Bit Atmel Microcontroller with 16/32/64/128K Bytes In-System Programmable Flash,” 2011, www.atmel.com/Images/8152s.pdf.
A. Burlison, “Driving the WS2811 at 800KHz with a 16MHz AVR,” 2014, http://bleaklow.com/2012/12/02/driving_the_ws2811_at_800khz_with_a_16mhz_avr.html.
A. Cervin, “Automatic Control—TinyRealTime,” 2014, www.control.lth.se/~anton/tinyrealtime/.docopt, http://docopt.org/.
B. Land, “ECE 4760: Designing with Microcontrollers,” Cornell University, http://people.ece.cornell.edu/land/courses/ece4760/.
C. Liechti. pySerial, https://github.com/pyserial/pyserial.
Wordlsemi, “WS2812: Intelligent Control LED Integrated Light Source,” www.adafruit.com/datasheets/WS2812.pdf.
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • APRIL 2016 #309 – Get a PDF of the issue