RSS

Software for the LED Cube

So you’ve built is and now we need some software to run it. To do this we are going to need to perform two tasks at the same time:

  • Calculate what we are going to display in the cube
  • Run the display

Multithreading is used in order to achieve this. The program has one thread which does nothing but cycle through each layer one by one shifting the data for the layer into the shift registers and then turning the layer on.

The second thread prepares the next item to be displayed and then passes this on to the cube to display.

LEDCube Class

This is a relatively simple class having a single constructor and two methods. The constructor sets the scene for us and makes sure everything is ready to use:

public LEDCube()
{
    config = new SPI.Configuration(SPI_mod: SPI.SPI_module.SPI1,
                                   ChipSelect_Port: Pins.GPIO_PIN_20,
                                   ChipSelect_ActiveState: false,
                                   ChipSelect_SetupTime: 0,
                                   ChipSelect_HoldTime: 0,
                                   Clock_IdleState: true,
                                   Clock_Edge: true,
                                   Clock_RateKHz: 100);

    spi = new SPI(config);
    buffer = new byte[64];
    for (int index = 0; index < buffer.Length; index++)
    {
        buffer[index] = 0;
    }
}

The buffer contains the data we are going to be showing in the cube.

The next two methods perform the work or updating and displaying the buffer. We need to be careful that the buffer is not updated whilst we are trying to update the display.

public void UpdateBuffer(byte[] newValues)
{
    lock (buffer)
    {
        for (int index = 0; index < buffer.Length; index++)
        {
            buffer[index] = newValues[index];
        }
    }
}

The lock statement helps us to achieve this by locking the buffer so we can copy data into it and also stop the display method from trying to read the buffer and show the data.

The display method is a little longer:

public void DisplayBuffer()
{
    while (true)
    {
        lock (buffer)
        {
            byte[] displayData = new byte[8];

            for (int row = 0; row < 8; row++)
            {
                int offset = row * 8;
                for (int index = 0; index < 8; index++)
                {
                    displayData[index] = buffer[offset + index];
                }
                enable.Write(true);
                bit0.Write((row & 1) != 0);
                bit1.Write((row & 2) != 0);
                bit2.Write((row & 4) != 0);
                spi.Write(displayData);
                enable.Write(false);
            }
        }
    }
}

This method again uses the lock statement to lock the buffer. This means we cannot update the buffer until a full cube of data has been displayed. The method takes a block of 8 bytes representing a layer and then writes this to the shift registers using SPI. Note that the spi.Write is embedded in the writes to the enable line. This ensures that all of the layers are turned off whilst we are updating the shift registers.

Wrapping all of this in the LEDCube class means that we now have a very simple class where we can spawn the DislpayBuffer method into it’s own thread. So off to out Main program. We are going to need an instance of the cube and a buffer to hold the next “frame” we wish to display.

private static LEDCube cube = new LEDCube();
private static byte[] nv = new byte[64];

The first thing we do when entering the main program is to set all of the values in the buffer (set them to zero) and to start the display thread going:

ClearCube();
Thread display = new Thread(new ThreadStart(cube.DisplayBuffer));
display.Start();

ClearCube does just that, sets the values to zero and then calls the cube Update method to copy this zeroed buffer into the cube buffer.

The next line spawns a new thread which runs the DisplayBuffer method and the following line starts it.

At this point we have two threads, the main program loop and the display thread. We can now perform calculations in the main program loop and call cube.UpdateBuffer when we have new data to display.

So how about something to display, a little rain perhaps:

private static void AddDrops(int count, int plane = -1)
{
    for (int drops = 0; drops < count; drops++)
    {
        bool findingSpace = true;
        while (findingSpace)
        {
            int x = rand.Next() % 8;
            int y = rand.Next() % 8;
            int z;

            if (plane == -1)
            {
                z = rand.Next() % 8;
            }
            else
            {
                z = plane;
            }

            int position = ( z * 8 ) + y;
            byte value = (byte) ((1 << x) & 0xff);
            if ((nv[position] & value) == 0)
            {
                nv[position] |= value;
                findingSpace = false;
            }
        }
    }
}

private static void Rain(int noDrops, int cycles)
{
    ClearCube();
    AddDrops(noDrops);
    cube.UpdateBuffer(nv);
    for (int currentCycle = 0; currentCycle < cycles; currentCycle++)
    {
        int bitCount = 0;
        for (int plane = 0; plane < 8; plane++)
        {
            byte value = 1;
            for (int bit = 0; bit < 8; bit++)
            {
                if ((nv[56 + plane] & value) > 0)
                {
                    bitCount++;
                }
                value <<= 1;
            }
        }
        for (int plane = 7; plane > 0; plane--)
        {
            for (int currentByte = 0; currentByte < 8; currentByte++)
            {
                nv[(plane * 8) + currentByte] = nv[( (plane - 1) * 8 ) + currentByte];
            }
        }
        for (int b = 0; b < 8; b++)
        {
            nv[b] = 0;
        }
        AddDrops(bitCount, 0);
        cube.UpdateBuffer(nv);
        Thread.Sleep(75);
    }
}

AddDrops simply adds a specified number of rain drops to the buffer. It also makes sure that if asked for 10 it will always add 10 by checking to see if one exists in the location it wanted to use.

Rain adds the specified number of drops to the buffer, shows the drops and then moved them all down by one plane. Before doing this it counts how many drops are going to fall out of the cube. After all the drops have moved down one plane it adds the drops which have disappeared out of the bottom of the cube back in at the top in random positions.

We can display this by adding this to the main program:

while (true)
{
	Rain(10, 60000);
}

The full source code to this project is current hosted on Codeplex.

Conclusion

Hope that you enjoy trying out this project but be aware that it takes time. This consumed a lot of my spare time for about three weeks. There are still some tasks to complete

  • Power supply
  • A case

But the main stuff is working.

I have not covered the power regulation but you should make sure that you have a regulated 5V DC power supply capable of about 2A – but more would be better. I did actually add my own regulator to this project but it got very hot when the cube was fully lit. Beware – make sure you have a stable supply capable of supplying enough juice.

Tags: , ,

Sunday, March 18th, 2012 at 5:16 pm • Electronics, NetduinoRSS 2.0 feed Both comments and pings are currently closed.

Comments are closed.