Building a Graphic Equalizer using a Netduino and SPI LED strips

It’s been a while since I last took a look at my Netduino blinky project and I finished Diablo 3 so why not get back to it! I decided to put together a circuit to read an analog signal from a 3.5mm line and visualize it. You can see my results in the following video:

So, pretty cool, right! It’s not perfect but there is potential here for making something cool for DJing.

I’ll tell you how I made it.

Hardware

I went with multi-color LED strips that support SPI. You can find them on  by searching for something like “1m Addressable RGB LED Strip” on Amazon (e.g. you have prime) or can get them through Adafruit. The ones that I got from Amazon kinda went bad so you might be better off with the Adafruit ones. They all look the same to me and I have Prime shipping which pretty much was why I went with Amazon.

The serial peripheral interface bus (SPI) is really awesome for LED management. If you have done more traditional multi-led setups, you have to use PWM to manage the color of each LED and wire every LED to it’s own PWM module / bus. This is a pain and you end up with super complicated circuits. As I’m not an electrical engineer or embedded systems designer, this typically results in a giant solder ball instead of a proper circuit. I also can’t debug hardware very well. With SPI, you get a DIGITAL interface and can individually just blast RGB bytes onto an address and *BAM!*, beautiful colors.

Guess what’s even cooler about SPI, most modern embedded controllers (Arduino, Netduino) have a hardware accelerated SPI port *BOOM*. So, you can interface with these LED strips and have great hardware performance.

The generic approach that I took to interfacing the controller… Ok, I’m not a hardware engineer, I’m embellishing the fact that I connected poorly soldered jumper pins to the holes on the Netduino… Was to connect the SPI clock (ck) port to the SPI clock pin (13 on the netduino) and the SPI data line (sd) to the data pin (11 on the netduino). Ground is connected to ground and I used the 5v pin for powering directly off of the netduino board. Visually, like this:

steup-pins

As you can see, it’s not exactly rocket science, which is awesome for me because I’m happier in code with a keyboard than on a circuit with a soldering iron. After setting things up like this, I wrote a little code for testing the LEDs. As mentioned later, this circuit is suboptimal (a nice way of saying it’s a bad circuit and I have no idea what I’m doing). The LEDs should be powered through a power supply as opposed to off the Arduino and should be using resistors in series to stabilize the circuit. I, however, like to live dangerously so I threw caution to the wind on this one.

Let’s break stuff

In the great tradition of reinventing the wheel, I started a GitHub project for Netduino that does all sorts of LED management for SPI-based strips. After getting some cool results (sorry for the bleeding eye effect from the animation, I’ll try to make it possible to scroll off the page):

 

 

 

20130715_204055

 

 

 

 

… I broke stuff.

I was doing something bad in the photo (well and still am) where I just was driving the LED lights directly through the Netduino using the 5V and GND pins. Because it was insufficient power for the circuit, the LEDs got more dim as you looked further down the strip, the circuit got less stable, and power was unavailable for the LED strips at the end of the line. In the previous demo, I restricted the number of addressed LEDs for stability.

In order to “boost” the circuit, I added a new power supply directly to the circuit that powered the LEDs. An aside, I have way too many power supplies that have the same size plugs and my workspace at home is, well, cramped. So… I proceeded to blow out a number of LED strips by running the wrong voltage into a 5M chain of strips (whoopsiedaisy!) and then mothballed the project for the time being.

I salvaged a few of the modules for when I felt like rebooting the project.

Blinky revisited

After a while, I returned to the project with two working LED strips in hand. I then got my blinky demo working in order to make sure that gremlins didn’t gnaw any wires while I was away. After getting things working, I updated the project with an additional twist: I connected a stereo line to the analog inputs and then used the signal to vary the visuals that were drawn to the strips.

The analog left/right pins were connected to the Analog ports 3 and 0 (easier spacing) and then the stereo line was grounded to the additional ground pin on the board. The following photo showcases my amazing board-connecting skill (click for full size and further embarasasment for me):

IMG_20140107_155311

With the analog pins connected, I could read values off the lines and update the graphs as appropriate. The following code  shows the full equalizer function which I then run from Blinky. The code isn’t quite self-documenting so feel free to ask any questions in comments (what’s up with the magic of 32 sprinkled everywhere), but the inline comments should give you the gist of what’s going on.

        void Equalizer()
        {
            // Set ports for the stereo lines
            Microsoft.SPOT.Hardware.AnalogInput pinA0 = new Microsoft.SPOT.Hardware.AnalogInput(Cpu.AnalogChannel.ANALOG_0);
            Microsoft.SPOT.Hardware.AnalogInput pinA2 = new Microsoft.SPOT.Hardware.AnalogInput(Cpu.AnalogChannel.ANALOG_2);

            // Loop forever and update the enabled LEDs based on the signal strength
            while (true)
            {
                var colors = new byte[3 * numLEDs];
                int lastLedsToColorL = 0;
                int lastLedsToColorR = 0;
                int highValue = 0;
                int highRefresh = 3;

                byte offset = 255;
                while (!changeMode)
                {
                    double valueL = pinA0.Read();
                    valueL *= 100;
                    valueL = (valueL >= 1) ? 1 : valueL;

                    double valueR = pinA2.Read();
                    valueR *= 100;
                    valueR = (valueR >= 1) ? 1 : valueR;

                    // Implements smoothing. By averaging between the current / last LED state,
                    // you can make the transitions less jumpy
                    int ledsToColorL = (int)( (((numLEDs/2) * (valueL))  + lastLedsToColorL) / 2);
                    lastLedsToColorL = ledsToColorL;

                    int ledsToColorR = (int)((((numLEDs / 2) * (valueR)) + lastLedsToColorR) / 2);
                    lastLedsToColorR = ledsToColorR;

                    if (offset > 255)
                    {
                        offset = 0;
                    }

                    // all pixels off
                    for (int i = 0; i < colors.Length; ++i) colors[i] = (byte)(0);

                    int numLEDsP = numLEDs/2;

                    // a progressive yellow/red blend
                    for (byte i = 0; i < ledsToColorL; ++i)
                    {
                        byte[] color = new byte[3];
                        if (i < (numLEDsP / 3))
                        {
                            color = utils.RGB(offset, 0, 0);
                        }
                        else if (i < ((numLEDsP * 2) / 3))
                        {
                            color = utils.RGB(0, offset, 0);
                        }
                        else
                        {
                            color = utils.RGB(0, 0, offset);
                        }
                        for (byte j = 0; j < 3; j++)
                        {
                            colors[(3 * i) + j] = color[j];
                        }
                    }

                    // a progressive yellow/red blend
                    for (byte i = 32; i < ledsToColorR + 32; ++i)
                    {
                        byte[] color = new byte[3];
                        if (i < (numLEDsP / 3) + 32)
                        {
                            color = utils.RGB(offset, 0, 0);
                        }
                        else if (i < ((numLEDsP * 2) / 3) + 32)
                        {
                            color = utils.RGB(0, offset, 0);
                        }
                        else
                        {
                            color = utils.RGB(0, 0, offset);
                        }
                        for (byte j = 0; j < 3; j++)
                        {
                            colors[(3 * i) + j] = color[j];
                        }
                    }
                    spi.Write(colors);
                    Thread.Sleep(32); // Good #'s 32, 128
                }
            }
        }

While values change from the analog line, the LEDs light up and down – pretty nifty eh!

The demo

In the demo I showed at the top of the article, I connected the VU meter to the headphone line of my DDJ-SX and set the headphone line to match master. The results aren’t perfect but they’re good enough that you can tell the beats are loosely matched to the VU visuals if you squint your eyes and bob your head.

Next steps, bugs, feature ideas

I have a proof of concept at this point that I want to make better with smoother VU jumps and more customized colors (floating peak values would be pretty awesome too).

First, the VU jumps… my initial hypothesis is that there is a ton of line noise causing the analog signal to be randomized more than I would like while reading off of the analog pins on the Netduino.

Another theory is that there is aliasing happening where the frequency of reads from the analog lines somehow is causing weird analog signals – this is in fact much more testable and workable than the noise (again, I fail at grounding electronics and soldering) so I might be able to create a larger sliding window for values read from the analog lines. By doing this, I can better smooth the analog value changes although at a cost of update frequency.

Next, the peak values and colors. In my project I use an adaption of the Adafruit colorwheel code that generates super-awesome-mega-fantastic color blends. Switching out the red-yellow-green values with something more generative would be easy and is probably what I’ll do next.

After another look at the project code I’ll release it as a project if I feel better about how things look after a little hacking. If you’re interested in the sources and the existing github project doesn’t cut it for you, I’d be happy to share the code, I just would like to refine it a bit before releasing a project/solution.

See Also