LED Based Frequency Analyzer Writeup
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 previous experience with sound systems and make an LED frequency analyzer.
This project uses entirely off the shelf hardware. The Microcontroller is a Stellaris LM4F120XL on a Stellaris Launchpad, available from TI for about $15. The LED display is from Olimex, and was originally intended to be used with an MSP430 Launchpad. It runs about $20 from Mouser at the moment. The only additional hardware used was an opamp and a few passive components, which were used to clean up the line-level audio signal and boost it to a range the ADC on the mcu could handle.
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. 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).
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
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 the CMSIS documentation. 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.
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 8×8 LED display based on the value found by the above method.
This ended up being a pretty fun intro project. It’s nowhere close to a polished, finished design, but it got me to a point where I could reliably run frequency analysis on an input audio signal. The signal capture circuit was simple enough that even a software schlub like me was able to get it working without difficulty, and DSP task was simple enough that most of the difficulty was relegated to reading APIs and documentation (as opposed to having to brush off my DSP text book from college). Plus, it blinks LEDs, which is pretty much my ultimate goal when working a project.