Pico LED Matrix Shield – State Machine

Pico LED Matrix Shield - State Machine
This post will walk you through the state-machine demo program for the shield that you can use to create your own patterns. If you’re new to shift registers then we highly recommend that you first read this post which explains how shift-registers and the circuit work. If you’re using the shield for the first time or only need to control single/multiple LEDs using basic function calls, then please read this post which explains how to do just that.

Using A State Machine To Control Multiple LEDs:

If you’ve read the previous posts on using the shield then you will know that there are multiple ways by which you can control the LEDs. Since the LEDs are multiplexed, you need to call the functions to refresh them every 20ms. This example is no different as it builds up upon the previous examples and provides you with a more structured way to create patterns.

Creating Patterns:

You can think of a pattern as a sequence of events. Since we are interested in creating patterns with LEDs, we would need to turn ON/OFF the LEDs in the correct sequence as per our desired sequence. One way to do this would be to repeatedly call the multipleLED function like before and that would work just fine, but it would result in your program being a bit messy and complicated if it’s not structured correctly. A simpler option would be to use the switch-case statement to structure your code and we will be exploring that in this post.

A pattern can be broken down into a sequence of LED combinations. Each LED combination determines which LEDs are turned ON/OFF at that particular instance. If we are using the switch case statements, then each case would be one such combination and we could then cycle through all the cases and loop back to the first once we reach the end. By cycling through all these states at the correct time, we can create patterns.

It’s much simpler if you look at it in code.

An example switch-case statement would look like this:

  switch(sequenceState)
  {
    case 1:
      multipleLED(COL1, ROW1);
      delay(3);
      multipleLED(COL2, ROW2);
      delay(3);
      multipleLED(COL3, ROW1);
      delay(3);
      break;

    case 2:
      multipleLED(COL1, ROW2);
      delay(3);
      multipleLED(COL2, ROW3);
      delay(3);
      multipleLED(COL3, ROW2);
      delay(3);
      break;

    case 3:
      multipleLED(COL1, ROW3);
      delay(3);
      multipleLED(COL2, ROW4);
      delay(3);
      multipleLED(COL3, ROW3);
      delay(3);
      break;

    case 4:
      multipleLED(COL1, ROW4);
      delay(3);
      multipleLED(COL2, ROW5);
      delay(3);
      multipleLED(COL3, ROW4);
      delay(3);
      break;

    case 5:
      multipleLED(COL1, ROW5);
      delay(3);
      multipleLED(COL2, ROW6);
      delay(3);
      multipleLED(COL3, ROW5);
      delay(3);
      break;
    
    case 6:
      multipleLED(COL1, ROW6);
      delay(3);
      multipleLED(COL2, ROW7);
      delay(3);
      multipleLED(COL3, ROW6);
      delay(3);
      break;
      
    case 7:
      multipleLED(COL1, ROW7);
      delay(3);
      multipleLED(COL2, ROW8);
      delay(3);
      multipleLED(COL3, ROW7);
      delay(3);
      break;  
                  
    default:
      multipleLED(COL1, ROW0);
      delay(3);
      multipleLED(COL2, ROW0);
      delay(3);
      multipleLED(COL3, ROW0);
      delay(3);
      break;     
  }

We have 7 different cases and 1 default case. The default case is really a fall-back case and it’s good practice to include one. Each of these cases calls the multipleLED function to turn ON a particular set of LEDs. The default case switches OFF all the LEDs.

There are two main things to keep in mind when creating patterns this way:

  • Refreshing the sequence: Just like before, we need to refresh the LEDs frequently (max 20ms) so that our eyes don’t catch the flicker. This means that we need to call the updateSequence function at least every 20ms.
  • Progressing the sequence: We also need to progress the sequence to create a pattern. In our case, this would mean that we need to increment the sequenceState variable so that we can cycle through all the cases and then cycle back to the first.

There are many ways to do this, and a simple option would be to use the millis() function. The millis() function returns the time (in milliseconds) since the Arduino was booted. We can use this value to keep track of time like so:

  timeNow = millis();
  if((timeNow - timePrevSequence) > 10)
  {
    updateSequence();
    timePrevSequence = timeNow;      
  }
  
  if((timeNow - timePrevState) > 500)
  {
    sequenceState++;
    if(sequenceState > 7)
      sequenceState = 1;
    timePrevState = timeNow;
  }

In the example above, we call the updateSequence() function every 10ms and we advance the sequence every 500ms by incrementing the sequenceState variable. Keep in mind that the sequenceState variable needs to wrap back to 1 after 7 as we have only defined 7 cases. If we fail to do this then the state-machine will execute the default case after executing the 7th case.

The timeNow variable holds the current time in milliseconds. The timePrevSequence holds the milliseconds value corresponding to the last time the sequence was updated. If more than 10ms have passed since the last update then we call the updateSequence function and pass the current time value to the timePrevSequence variable. We then wait for the next 10ms to pass.

Similarly, the timePrevState hold the milliseconds value corresponding to the last time the state was updated. If more than 500ms have passed since the state machine was progressed, then we increment the sequenceState variable and pass the current time value to the timePrevState variable. We then wait for the next 500ms to pass.

Here’s a demo of what the program actually looks like:

Pico LED Matrix - State Machine Demo
Pico LED Matrix – State Machine Demo

You can download the demo sketch below and tweak the values to see how it affects the pattern.

Download the state-machine LED demo sketch here.

Moving Forward:

We highly recommend that you use a more structured approach similar to this. It’s much easier to make changes to the different states to modify patterns. You can execute additional code using a program like this, but make sure your code executes quickly enough to get back in time for the refresh.

Another option is to use timer callbacks to refresh the LEDs. This way, you don’t have to worry much about the refreshing and it gives you a bit more flexibility. You need to install additional libraries in order to use the timer object and this is why we’ve not used it here.

Here’s a link to the Arduino timer object post:

https://playground.arduino.cc/Code/ArduinoTimerObject