This is a really fun project that I made as an apartment decoration in college. An LED matrix display is used to display a realtime visualization of the frequency spectrum of an incoming audio signal. Left and right channel data are displayed on the appropriate half of the display, with low frequencies being represented at the center and higher frequencies toward the outside. This project was actually inspired by Jim Spoth, as this type of display was originally our idea for a Digital Systems Design project (instead, we ended up doing a Conway’s Game of Life implementation). However, we knew very little about DSP at the time, so I ended up tackling it later on as a personal project using a microcontroller (which I find to be much easier to use than FPGAs).
This project was really fun and also a pretty major learning experience for a few reasons. First, this is the first project for which I had ever designed and etched a PCB. Another new facet about project is that it revolves around a DSPIC series chip, which has a lot of pretty serious feature improvements over the 8-bit PICs that I had previously worked with. Of particular note, this chip has a very powerful but complex ADC peripheral with DMA buffers. Also, this project did involve a simple analog frontend (in general, I’m pretty uncomfortable in the analog domain even for simple tasks).
This project was my first attempt both at creating a PCB layout and at actually etching a board. I had no previous experience with PCB design, as it was not covered in our curriculum. Fortunately, Jon (my roommate throughout most of college) is a more avid electronics hobbyist than I am, and had already gone through some of the trials and tribulations of PCB design and etching. Following in his footsteps, I used the Cadsoft Eagle design tools to perform schematic capture, design some custom component layouts, and create a single-layer PCB layout. I must admit that it was a major pain. The board ended up being much bigger than I had initially anticipated in order to compensate for my (poor) routing. In fact, because I was having so much difficulty in my first board design, I decided to cut out several rather important stages of my analog frontend. I ended up foregoing an adjustable gain and a low-pass filter on the input. The adjustable gain was not a huge disadvantage because I intended to use this project with portable devices that have an adjustable output volume anyway. The lack of a low-pass filter, however, is a bit of a no-no from a DSP standpoint, because it will cause aliasing to occur from any input signal components that are greater than half the sampling frequency (as per Nyquist-Shannon sampling theorem). Fortunately, the input signals will likely already be bandlimited from the music player due to the storage format of the audio. Any other high frequency components (for instance, from electrical noise) should be low enough in amplitude that any aliased components will be negligible.
Designing the board, of course, is only half the battle. I still needed to try my hand at etching the board. Again copying Jon’s procedure, I opted for the toner-transfer method. The board layout was printed onto an overhead transparency, and then ironed (using a conventional household iron) onto a copper-clad circuit board to transfer the toner. This sounds much easier than it actually is. It took me a couple of tries (sanding off the toner, reprinting, and retransferring) to get a transfer with an acceptable proportion of the traces in tact on the copper. I did, however, still have to manually redraw several of the traces using a Sharpie. (NOTE: I have since moved to using “Press-n-Peel” sheets for toner transfer and it works much more reliably.) Once the design is successfully transferred onto the copper, the board is immersed in a bath of ferric chloride. The resulting chemical reaction will dissolve the exposed copper surface, while the toner (and Sharpie) will mask the traces, preventing them from being dissolved. Once all of the unwanted copper has been removed, the board can be washed with soap and water in order to clean off the corrosive chemicals. After drying, the masked traces must then be sanded in order to remove the toner coating and expose the copper. Finally, the board is completed.
The hardware for this project consists of a few segments. The power supply segment is pretty straightforward. Some of the components in this project run at 5V, while others require 3.3V. I opted to use a 5V DC power adapter to provide the main supply, and then added a 3.3V voltage regulator to provide power for the 3.3V components. Because of the dual-supply issue, there was an additional interfacing consideration between the LED display control pins (which require 5V logic) and the DSPIC. This interfacing is accomplished through the use of a 74LS244 buffer IC, which has a V_IH of only 2.0V, making it well suited for turning the 3.3V digital outputs from the MCU into the 5V signals required for the LED matrix.
The analog frontend for the audio signals is fairly simple. First, each channel’s signal goes through a decoupling capacitor in order to remove any DC component that the signal has. Each channel then goes through an op-amp in a summing configuration in order to apply a fixed gain and a DC offset to the signal. The DC offset it provided via a voltage divider and another op-amp acting as a buffer to provide a voltage reference of half the 3.3V supply voltage. Together, these components bias the audio signal at the center of the ADC input range and allow it to span the entire ADC range.
The DSPIC provides some very convenient hardware functionality with regard to the ADC. First, the ADC has its own timer functionality such that the ADC can sample at regular intervals without the need for servicing any interrupts. Furthermore, the data from these samples can be used to automatically populate arrays with up to 128 samples through the magic of DMA. The DMA buffering can (and does, in this case) operate in “ping-pong” mode, in which the ADC peripheral automatically alternates between two buffers such that one buffer can be read from while the other one is being written to for uninterrupted sampling. Even better, all of this functionality can be multiplexed for multiple ADC channels (in this case, two channels for left and right stereo audio) with no software overhead. An interrupt simply fires when each buffer is full and ready-to-read, and the ping-pong functionality will seamlessly transition to the other buffer.
The LED matrix is a prefabricated unit sold by Sure Electronics. I simply mounted it into a photo frame and put a sheet of paper between the LED’s and the frame glass in order to obscure the LED’s when they are not illuminated.
The software portion of this project has two main roles: driving the LED display and performing the frequency decomposition of the audio signals. Driving the LED display actually takes a fairly substantial amount of computational resources because the LED matrix hardware does not contain memory for an entire screen buffer. Thus, it becomes the microcontroller’s job to actively feed the display the necessary pixel data. The LED matrix I/O interface requires serially shifting in the red and green pixel data to be displayed in the current row. By serially shifting, I don’t mean to suggest that there is a simple serial command protocol for operating the display. In fact, the display is controlled by 11 input pins. The hardware is actually organized into top and bottom halves, each of which may have one row illuminated at a given time. Populating and illuminating each row in quick succession creates the illusion of the entire board being illuminated. The control pins consist of red data for the top half, red data for the bottom half, green data for the top half, green data for the bottom half, a clock pin for shifting in this data, a pin for latching the shift register data to the LED outputs, four row select pins to determine which of the 16 rows is illuminated on each half of the board, and an output enable pin to control whether the currently selected row is illuminated. The rough sequence of operations to drive the LED display is as follows:
- Disable all shift register outputs. This eliminates “ghosting” and any other corruption effects that may occur by modifying the row data while the LED’s are illuminated.
- Shift in the 64 bits of pixel data for the row (red and green, top and bottom)
- Latch the data to the outputs
- Select which row the data should be displayed for
- Enable the shift register outputs to the LED’s
- Repeat the process for each of the other 15 rows
A timer interrupt on the DSPIC is used to perform this row-wise every 1ms, for a refresh rate of 62.5Hz (1kHz / 16 rows per refresh).
The signal processing aspect of the software is actually pretty simple due to the fact that Microchip provides a signal processing library for the DSPIC series chips. Among the useful functions provided by the library are routines for efficiently pre-computing FFT twiddle factors, performing the actual FFT, and computing element-wise square magnitudes of an array. The FFT is performed on sets of 512 samples (i.e., a 512 point FFT) aggregated over the course of 4 DMA buffer fills. Because the sampled signals are real-valued, the positive and negative frequency spectra are reflections of one another, and half of the spectra is useful. In this case, we take the positive frequency spectra, which provides 256 linearly-spaced frequency bins between 0Hz and half the sampling rate.
Actually, the output of the FFT computation is complex-valued, and contains both real and imaginary components (because each frequency component also has associated phase information). For an audio visualization such as this, the phase information can be disregarded; only the magnitude data is of interest. Computing the actual magnitudes of each frequency bin would be computationally expensive because it would require a square root operation:
To avoid this unnecessary overhead, we compute only the squared magnitude (i.e., prior to the square root operation) of each bin. In order to determine how many of the 32 pixels in the display should be illuminated to represent each magnitude, a lookup table is used that contains the pre-computed squared magnitude values for logarithmically-spaced (because that is the way that our ears best perceive volume) magnitudes. Thus, determining whether a particular column pixel should be illuminated for a certain frequency bin requires only comparing the squared magnitude of the frequency bin to the appropriate value in the lookup table.
As far as the coloring goes for the columns, I rather arbitrarily decided that the top 1/8 of every bar should be red, the next 1/8 should be orange, and the remainder should be green. For better or for worse, this means that even very low amplitude frequency components may have red or orange components. I think it looks cool though.
Although this project is pretty cool overall, I do have some ideas for software improvements that I could (but probably won’t) make:
- Although I collect 256 frequency bins worth of spectral data, I’m actually currently only displaying the lowest 32 bins for each channel. I should scale the frequency axis logarithmically (because that is the way that our ears best perceive pitch) in order to incorporate all of the data into the display.
- There is no synchronization between the refreshing of FFT values and refreshing of the display. This means that the display sometimes displays partial data from one FFT data update and the rest from another. I should probably implement a double buffering scheme to ensure that this does not occur.
- Some more eye candy would be fun. For example, a falling peaks display seems to be a common thing to implement on audio spectrum analyzers. Perhaps mine should have one too.