O'Reilly logo

Making Android Accessories with IOIO by Simon Monk

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. LED Matrix Light Show

This chapter uses a multicolor LED matrix to make a fun charger for your Android phone (Figure 4-1). In this particular case, “fun” takes the form of a marching Space Invader animation—and if that isn’t fun, I don’t know what is!

LED matrix light show
Figure 4-1. LED matrix light show

The project has a variety of different modes: it can just display a static image, or it can display an animation, or it can make use of the phone’s microphone to provide a spectrum type display. If you start your music player on the phone before you start the IOIO Matrix app, it will respond to the sounds coming from your phone.

This is the only project in the book that will not work using a Bluetooth adaptor rather than a USB cable. This is because Bluetooth simply isn’t fast enough to send the commands to the pins to refresh the display.

The Design

In this design, the IOIO board uses 24 of its pins to control the LED Matrix. It requires 24 pins because the LED Matrix is arranged as a grid of LEDs. Each cell in the matrix actually has two LEDs in it, one red and one green. This is used to set the color of any individual cell to red, green or—if both LEDs are lit at the same time—orange.

Schematic

Figure 4-2 shows the schematic diagram for the project.

Schematic diagram for the project
Figure 4-2. Schematic diagram for the project

The anodes of the LEDs are each driven by a GPIO pin on the IOIO. Each has a series resistor to limit the current to the LED.

Because 8 LEDs share each common cathode connection to ground, there would be too much current flowing for a GPIO pin to sink, so a MOSFET transistor is used to switch each column in turn. The gate of each MOSFET is connected directly to a GPIO pin.

Wiring Diagram

The project is built on a piece of stripboard. Stripboard is a kind of prototyping board, with parallel tracks of copper running on one side of the board. Component leads are pushed through from the top and soldered to the copper track below.

On one side of the stripboard is a set of header pins designed to accept the IOIO board with its header sockets attached. The IOIO board will be fitted upside down onto the headers. The other side contains header sockets into which the LED Matrix is fitted.

A screw terminal for Vin and GND is used to simplify the process of providing power to the project.

Figure 4-3 shows the stripboard layout for the project.

The stripboard layout
Figure 4-3. The stripboard layout

Construction

You will need the following parts to construct this project.

Table 4-1. Parts bin
QuantityDescriptionSparkFun SKUFarnell codeNewark code

1

8 x 8 Dual-color LED Matrix

COM-00682

8

2N7000 MOSFETS

9845178

89K1814

16

100Ω 0.5W metal film resistor

9339760

58K3723

2

SIL Header socket strip

PRT-00115

1217038

52K3454

2

SIL header pins

PRT-00116

1097954

93K5128

1

Screw terminal block

PRT-08084

1641932

19P1412

1

Stripboard 29 strips each of 53 holes

1201473

96K6336

Step 1. Prepare the Stripboard

The first step is to cut the stripboard to the correct size. The best way to do this is to use a craft knife to heavily score a line through the holes on the line below the last strip or column you need, and then break the board over the edge of your work desk. Be careful doing this, as it can leave sharp edges.

You then need to break the track in the positions indicated by an X in Figure 4-3. I find it useful to mark rows and columns 10, 20, 30, etc., on the top of the board to find the right position for the break and then push a wire through to find the position on the track side of the board. I use a drill bit, twisted between my fingers to remove the copper.

Figure 4-4 shows the copper side of the board, with all the breaks drilled.

Breaks drilled in the stripboard
Figure 4-4. Breaks drilled in the stripboard

You may find it easier to work from Figure 4-4 than Figure 4-3. When you have made all the breaks, go back and inspect every one carefully to make sure that there is no trace of copper remaining, as this could cause a short and potentially destroy your IOIO. If in doubt, you can also use your multimeter on continuity mode, to make sure the break is clean.

The copper tracks on the bottom will anchor our components and make some of the connections. However, there are a lot more connections to be made with linking wires. The longer leads should be made using insulated solid core wire, and the shorter connections can just be bare wire.

Using Figure 4-3 as a reference, solder link wires into place. Note that this is not a quick job. You should put aside an hour to do this, because there are a lot of links to put in place.

Do not be tempted to solder the header pins in place first. Although this would make it much easier to work out where the link wires need to go, it makes it much harder to solder the links themselves into place, as they will just fall out when you turn the board upside-down to solder it.

When all the links are in place, you should have a board that looks like Figure 4-5.

The stripboard with resistors in place
Figure 4-5. The stripboard with resistors in place

Step 3. Fit the Resistors

The next step is to fit the next lowest parts, which are the resistors. Again, using Figure 4-3 as a reference, solder them into place. When all the resistors are in position, your board should look like Figure 4-6.

The stripboard with links in place
Figure 4-6. The stripboard with links in place

Step 4. Fit the Headers

You can make it easier to fit the header plugs into the IOIO and the sockets into the LED Matrix by fitting the components into the headers before putting them in the right position on the board. Double-check that the placement is correct, as it will be hard to unsolder them once they are in position.

If the header strips are not the right lengths, you will need to cut them to the right number of connections using a craft knife. When cutting the sockets, this will usually mean sacrificing one of the socket connections, so cut through the socket after the number you need, rather than try and cut between sockets.

Once the sockets are in place, your board should look like Figure 4-7.

The stripboard with headers in place
Figure 4-7. The stripboard with headers in place

Step 5. Fit the MOSFETs

The last components to be added to the board are the MOSFET transistors. Be careful to ensure that they are the right way around, and solder them into place, raised about 1/4 inch above the surface of the board.

Figure 4-8 shows the board with the MOSFETs in place.

The stripboard with MOSFETs in place
Figure 4-8. The stripboard with MOSFETs in place

Step 6. Fit the Power Terminal and IOIO

That’s pretty much all the hardware. It just remains to solder the screw terminal for the power into place, and then fit the LED Matrix and IOIO (Figure 4-9).

Solder the screw terminal block into place first and mark the upper connection with a + to reduce the chance of applying the supply voltage reversed.

The LED Matrix has little cutouts and pegs to allow bigger displays to be made by joining more than one of them together. The correct orientation for the cutouts is at the bottom and left of the board.

Figure 4-9 shows the board fully assembled and ready to go.

The stripboard fully assembled
Figure 4-9. The stripboard fully assembled

Software

They key to this project is being able to refresh the screen fast enough. This is all wrapped up in the chain of methods in MainActivity.java that start with refreshMatrix:

private void refreshMatrix() throws ConnectionLostException {
  for (int col = 0; col < 8; col++) {
    clearPreviousColumn(col);
    displayColumn(col);
    delay(3);
  }
}

This iterates for each column, clearing its previous settings, displaying the new column, and then sleeping for 3 milliseconds:

private void clearPreviousColumn(int col) throws ConnectionLostException {
  int columnToClear = col - 1;
  if (columnToClear == -1)
  {
    columnToClear = 7;
  }
  cc[columnToClear].write(false);
  for (int row = 0; row < 8; row++) {
    r[row].write(false);
    g[row].write(false);
  }
}

Clearing the previous column is a matter of finding the column before the current one, including wrapping round. We then turn off the common cathode on the LED matrix for the column in question and then turn off all the red and green anodes for all 8 rows:

private void displayColumn(int col) throws ConnectionLostException {
  cc[col].write(true);
  for (int row = 0; row < 8; row++) {
    r[row].write((display_[col][row] & 1) > 0);
    g[row].write((display_[col][row] & 2) > 0);
  }
}

Displaying the new column involves turning on the appropriate common cathode of the LED matrix, and then setting the red and green anodes according to the current column of the 2D array of colors held in the member variable display_, which looks something like this:

private int[][] testPattern1_ = {
    {1,1,1,1,1,1,1,1},
    {1,2,2,2,2,2,2,2},
    {1,2,3,3,3,3,3,3},
    {1,2,3,1,1,1,1,1},
    {1,2,3,1,2,2,2,2},
    {1,2,3,1,2,3,3,3},
    {1,2,3,1,2,3,1,1},
    {1,2,3,1,2,3,1,2}
};

The number 0 means both LEDs are off; 1, red LED; 2, green LED; and 3, both LEDs on (orange).

Everything else in this app, including the animation, is just a matter of assigning display_ to a different 2D array every half second.

The frames of the animation are defined in a separate class file, which is also responsible for providing a value (frameDelay) for the time between frames in milliseconds:

package com.ioiobook.matrix;

public class TestAnimation {

        public final static int frameDelay = 500;

        public final static int[][][] animation = {
                { //1
                        {0,0,1,1,1,1,0,0},
                        {0,1,1,1,1,1,1,0},
                        {1,1,2,1,1,2,1,1},
                        {1,1,1,1,1,1,1,1},
                        {0,0,3,0,0,3,0,0},
                        {0,0,3,0,0,3,0,0},
                        {0,0,3,0,0,3,0,0},
                        {0,0,0,0,0,0,0,0}
                },
                { //2
                        {0,0,0,0,0,0,0,0},
                        {0,0,1,1,1,1,0,0},
                        {0,1,1,1,1,1,1,0},
                        {1,1,2,1,1,2,1,1},
                        {1,1,1,1,1,1,1,1},
                        {0,0,3,0,0,3,0,0},
                        {0,3,0,0,0,0,3,0},
                        {0,0,3,0,0,3,0,0}
                },

The spectrum display makes use of a third-party open source library, wrapped up in a class (SpectrumDrawer.java). This is instantiated with a display to draw on:

public class SpectrumDrawer {

    private float gain_ = 1000000.0f;
    private int[][] displayArray_;
    private Window win_;
    private FFTTransformer spectrumAnalyser_;
    private int historyIndex_;
    private float[] average_;
    private float[][] histories_;

    // 128 values in average_ we just want 8 - Fn = n * Fs / N 
    // where Fn is freq at data point n, Fs is the sample freq 
    // and N is the buffer size
    private final int[] frequencies_ = { 2, 4, 6, 10, 15, 25, 55, 80 };
    private final int[] colors_ = { 2, 2, 3, 3, 3, 1, 1, 1 };

    public SpectrumDrawer(int[][] display) {
        displayArray_ = display;
        win_ = new Window(MainActivity.AUDIO_BUFFER_SIZE,
                Window.Function.BLACKMAN_HARRIS);
        spectrumAnalyser_ = new 
FFTTransformer(MainActivity.AUDIO_BUFFER_SIZE, win_);        average_ = new float[MainActivity.AUDIO_BUFFER_SIZE / 2];
        histories_ = new float[MainActivity.AUDIO_BUFFER_SIZE / 2][MainActivity.AUDIO_BUFFER_SIZE / 2];
    }

    public void calculateSpectrum(short[] buffer) {
        // apply FFT to the buffer to get the spectrum, 
        // but we only have 8 columns
        // so sum into 8 bands
        spectrumAnalyser_.setInput(buffer, 0, MainActivity.AUDIO_BUFFER_SIZE);
        spectrumAnalyser_.transform();
        historyIndex_ = spectrumAnalyser_.getResults(average_, histories_,
                historyIndex_);

        for (int c = 0; c < 8; c++) {
            int resultIndex = frequencies_[c];
            // Do we need to log this?
            int power = (int) (Math.log(average_[resultIndex] * gain_));
            Log.d("SRM", "" + power);
            if (power > 7)
                power = 7;
            for (int r = 0; r < 8; r++) {
                if (power > r) {
                    displayArray_[7 - r][c] = colors_[r];
                } else {
                    displayArray_[7 - r][c] = 0;
                }
            }
        }
    }
}

When the calculateSpectrum method is called, a Fast Fourier Transform (FFT) is applied to a sample of the audio from the phone’s microphone.

A FFT is used in this case to take a sample of an audio file and find the relative sizes of each of the frequencies that make up the sound. This produces an array of the power of a range of frequencies. We can then pick off frequencies from this and use them to set the colors of the matrix display.

The histories_ array is required by the third-party library to provide averaging of the FFT results.

For each column, we light a number of LEDs in the rows equal to the power. The actual color of each of the lit LEDs is determined by the colors_ array.

To feed the SpectrumDrawer with new data, a separate thread is started in the onCreate method of the MainActivity class:

AudioReader.Listener listener = new AudioReader.Listener()
{

        @Override
        public void onReadComplete(short[] buffer) {
                spectrumDrawer_.calculateSpectrum(buffer);
        }

        @Override
        public void onReadError(int error) {

        }

};
audioReader_ = new AudioReader();
audioReader_.startReader(F, AUDIO_BUFFER_SIZE, listener);

The thread is encapsulated in the AudioReader class. This class is provided in the org.hermit library.

Conclusion

This is quite a challenging project, both for the builder and the IOIO that has to keep updating the outputs to keep the display alive.

The app is intended as a starting point for your own experiments. There are many ways that it could be improved, including a file format for the animations to that they can be loaded and a optimization of the display mechanism to reduce flicker.

In the final chapter in this book, we are going to get more physical and make a little Bluetooth-controlled rover.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required