Sunday, September 9, 2012

The Secret Sauce

Source code is up!
https://github.com/EuphonistiHack/launchpad-freq-analyzer.git

I'm assuming that TI will follow the standard procedure for Stellaris installs for Launchpad, which is to say that when you install StellarisWare, the file structure will be such that you have a root (defaulted to C:/StellarisWare) in which driverlib will be copied.  That root will also contain a  boards directory, which in turn should contain a directory for the lm4f120.  If you do a git clone off of the above address, you'll find a similar file structure.  You should just be able to copy the led_equalizer project into the lm4f120 board directory, and copy dsplib into the stellarisWare root.

In it's current state, the only thing I've verified working is the Code Composer Studio build.  I have included project files that should at least be 95% good for all the other Stellaris supported environments: a makefile for you command line GCC enthusiasts and project files for Keil uVision, IAR Embedded Workbench, and CodeSourcery's IDE.  I'm guessing that if you tried to import and build into any of those as is, you will hit two problems.

For one, there's a config array that the uDMA engine uses that needs to be stored on a 1024 byte boundary.  I have some pragmas in place to handle that for CCS and IAR, but I haven't yet figured out the syntax for handling this in other compilers.  Shouldn't be too hard to figure out, and hopefully I'll have that done soon.

The second issue will be with the CMSIS library.  The current release contains a precompiled .lib and the headers necessary to interface with it.  This was compiled with the tms470 compiler, though, so it won't work with anything other than CCS.  If you download CMSIS for yourself, it should come with precompiled binaries for use with uVision, GCC, and CodeSourcery.  You'll want to add those to dsplib, then edit your project's library include path/list to point to the proper binary.  I am considering including these in dsplib, but I'm not really sure what ARM's policy is on redistributing their binaries, and I'd prefer not get sued out of existence :)

Enjoy!

Wednesday, September 5, 2012

The writeup

Hi, everybody!

I like playing with microcontrollers.  When I heard that TI was going to release a hobbyist centered Stellaris evaluation kit, the ek-lm4f120XL, I got excited.  When I convinced a coworker to let me have a rev0 board to work with, I got even more excited.  I decided that I would combine my love of microcontroller projects with my love of all things audio and make an LED frequency analyzer.

Many thanks to my wife, Heather Wills, for taking a decent photo of the board for me!

Hardware


The total cost of my setup is about $25 (the majority of which is from the LED display).  The Stellaris ADC requires that its input signal be a low impedance signal that is between ground and 3.3V.  This is not consistent with line level audio, so I implemented a very simple circuit on a breadboard to condition the input signal using an NTE987 opamp that I grabbed from Fry's, two 47k resistors, and a 1 uF capacitor.  The output of this circuit goes to one of the ADC input pins on the launchpad.  I found an MSP430 booster pack that is essentially an SPI controlled 8x8 LED array that I decided to use for the display.

Bill of Materials

ek-l4f120XL: $5
olimex MOD-LED8x8: $15
Opamp: $2
Resistors/Capacitors: Negligible (had them lying around in my wiring kit)

Total Cost: $22

Implementation

The signal condition circuit has to perform two functions.  First, it must bias the signal to center at ~1.65V and be limited to a 1.65V swing.  Line level audio is centered at ground and has at most around a 1.5V swing, so a simple resistor network can be used to bias this up to 1.65V.  Second, the output must be a signal with low enough impedance to power the switched capacitor array that the ADC input uses for its signal capture.  A low grade opamp can be wired up at unity gain to accomplish this.  The final result looks something like this:


For the input, I also realized that I'd need a headphone jack with exposed leads, which I picked up at Radio Shack for something like $1.50.  Were I a more patient engineer, I'm sure I could find something cheaper on digikey or mouser.  I slapped all of that together on my breadboard, which gave me the following:


The last piece of hardware work was connecting the Olimex booster pack to the Launchpad.  Unfortunately, I had to blue wire this, as the SPI RX pin on the booster pack was not aligned with the SPI TX pin on the launchpad.  Not a big deal, though, as the interface to the board is only five pins (SPI_CLK, SPI_TX, SPI_LATCH, VCC, GND).

Software


The software running on the Stellaris was responsible for the following functions:
  • ADC Sampling at a specific frequency
  • Digital Signal Processing on the captured audio data
  • SPI communication with the LED array
I'm a software developer by day, so I did all that I could to make those steps run as efficiently as possible.  The source code I'm using is up on github at https://github.com/EuphonistiHack/launchpad-freq-analyzer.git

Audio Capture

For the ADC interaction, I ended up using three separate peripherals to give me an incredibly software efficient audio capture process.  First, I set up a timer to count down at 44.6 kHz.  I set up the timer to trigger an ADC capture at the hardware level.  The ADC capture complete signal was set up to initiate a uDMA transfer, which was configured to move the data from the ADC capture FIFO to a global array of samples.  The result is very, very software efficient; One line of code starts the sampling timer, and SAMPLE_SIZE/44600 seconds later a software interrupt occurs, letting you know that the data has been captured and is ready in the sample array.  I was very proud of the efficiency of this process :-)

Digital Signal Processing

I am fortunate in that I've been playing around with audio hardware for years in the form of running soundboards and messing around with studio mixing and recording.  My last DSP class was a long, long time ago, but my interest in audio gave me a decent foundation in audio based DSP.  I am also fortunate in that ARM has a DSP library specifically designed to take advantage of the M4F's floating point instructions.  TI even has an application note detailing how to build that DSP library in Code Composer Studio.  From what I've heard, the amazingly handsome, charismatic, genius of an engineer who wrote the note did an incredible job on it, and it is wonderfully thorough and well written.  It might also be a little out of date (as ARM released a new version of CMSIS since the app note was published), but it's still good.

With those tools at my disposal, the DSP portion of my code wasn't too difficult to figure out once I wrapped my head around CMSIS.  Step one is to multiply the samples by a windowing function.  I chose a hamming window, because I vaguely remember hearing that that was good for audio.  Next, I take the FFT of the input data.  I'm pretty proud of this part as well; the M4F ended up being powerful enough to take a 2048 point fft on the input data, which gives you 1024 frequency bins, each of which represents the energy found in a 20.3 Hz wide frequency band.  So once I have the fft output, I take the complex magnitude of each sample, giving me the total power found in each frequency bin.

LED Display

To figure out how to translate 1024 frequency bins into 8 LEDs, I had to do some trial and error to figure out what looked the best.  I ended up splitting the 8 LEDs into logarithmicly spaced frequency ranges.  Each time I execute my signal processing loop, I calculate the average power found in the bins for a given frequency range (again using CMSIS for efficiency).  I divide this value by the maximum value I've seen in that frequency range.  Then, I just say if the result is greater than 1/8, turn on one LED; Greater than 2/8, turn on 2, etc.  If the result is bigger than the max, then turn on all 8 LEDs, and make that the new maximum. I also multiplied the current maximum by 0.999 to have a decay factor, which made it so my output wouldn't be decimated by a single spike in the audio.

To actually turn on the LEDs, I used a second timer for an easily configured refresh rate.  Each time the timer hits 0, I use the hardware SPI module to update the next column of the 8x8 LED display based on the value found by the above method.  The results are, if I say so myself, pretty impressive.

Future Work


I have a few ideas of where to go from here.  For one, the Olimex LED tile has an expansion header that can be used to have multiple tiles connected to the same SPI bus.  My code is theoretically written such that I can change a single macro to use a different number of frequency display elements (so I can go from 8 to 16 frequency LED columns with a simple recompile).  It'd be pretty fun to try expanding my LED tile to 16 columns wide.

A coworker of mine built what he calls his LED cube a few months ago.  Basically, it's an 8x8x8 cube of LEDs that functions very similarly to the olimex booster pack.  I have some cool ideas for how to do 3 dimensional frequency displays :)

I also found a booster pack that contains a 160x128 LCD display.  I'm pretty sure I can talk to the LCD just using Stellaris' graphics library without much work at all, and I think it'd be really visually impressive to expand my display to be contain 160 frequency bins.

Finally, it's been a long time since I've done a board layout from scratch, so I've been toying with the idea of making a custom PCB to get rid of the protoboard and blue wires that I currently have to use to make everything work.

Showing Off

For those interested in seeing how this works, I have a rather short video showing off the display!  For those who don't care much about the setup, you can fast forward to about the 2:05 mark to see the demo with music :)

I will likely add one more in the future that just shows off the finished working product with the audio overlayed directly on top of the video (as opposed to captured as it comes out of my headphones).