Introduction: Arduino Sign: Displays Messages on 8x8 LED Board

About: By day, I am a mild mannered developer using the Microsoft Stack (C#, ASP.NET, ASP.MVC, SQL Server), by night, I am a rouge mad-man self-teaching himself electronics. With a major in both Computer Science and…

I created this display board for one single purpose...to display a message to the people behind me while I am driving. By using a 4 button remote, I coded in 4 different items depending how nice I want to be.

The concept of this device is very simple. We click a button on the remote, the Arduino detects the button, runs code that outputs a series of lights on the LED Matrix. The very interesting fact is, even though it looks as if all of the LEDs are on at one time, the Arduino is really only lighting one at a time, and the speed of it makes it appear to be all lit!

The signboard used is one I created in my other Instructable.Com article on 8x8 LED Boards, or you can use your own creation.

This project is actually surprisingly easy to build. I have nicknamed this project “Hyrda” due to the fact that the breadboard version has so many wires coming out of it, it can be scary to look at!

Parts Needed:

  • Breadboard with wires…a lot of wires
  • Arduino Uno
  • 74138 IC (3 to 8 line decoder)
  • 74238 IC (3 to 8 line decoder)
  • 4 button IR Remote set
  • 8x8 LED Matrix (see my other article for instructions on making this)
  • Prototype board for when you are all done.

Step 1: Wiring and Testing the Remote Control

For this project, I am using a generic four button Wireless Remote Control Receiver as shown in the picture. I simply went onto Amazon.Com and searched for “Arduino four button remote” and they started lining up. Some list the part numbers as a 2262/2272.

So, let’s get this bad beast on a breadboard to make sure it works. Now the first thing is the pinning…

From the right to left:

  • Ground
  • 5v
  • D0 (High if Button A pressed)
  • D1 (High if Button B pressed)
  • D2 (High if Button C pressed)
  • D3 (High if Button D pressed)
  • VT (High if any button is pressed)


To test the remote, I simply added it to a breadboard and hooked up the power leads. I then ran a wire from D0, D1, D2, and D3 to a single resistor. The other end of the resistor was to the LED Anode while the LED cathode went to ground. Now, if you press a button on the remote, you will see the light come on. Repeat for all four buttons. This will tell us that the remote and receiver are working properly on all four channels. Now that is done, you can take it all apart again.

So now that we have a verified working remote, we shall put it onto our larger breadboard and let the Hyrda start growing its first set of heads.

The open leads from the IR board will eventually plug into the Arduino (I am using pins 2-5). Until then, they will attack you at any chance they can!

Now, for everyone out there who knows...yes, I know you can push this through an encoder and some logic gates to reduce the number of Arduino pins to 2 from 4, but we will have enough to spare at this point.

Step 2: Setting Up the Decoders

The matrix we are using has 8 rows and 8 columns that need to be controlled. Without the use of the decoders, we would need 16 Arduino pins just to control lights, 4 for the inputs, etc. What these decoders allow us to do is give the IC a 3-digit binary number, and then based on that number will set the corresponding output pin to the appropriate value. So, for example, if the chip receives a “101” as the input, then the corresponding pin for “5” will be set to activate.

The differences in the 74138 and 74238 chips is very simple. When an output pin is marked as active, the 74138 will have that pin being LOW, and all of the other pins will output HIGH. The 74238 is the exact opposite. An active pin will output HIGH and all of the others will output LOW.

So that is exactly how we are going to control which LED is to be turned on, by sending a value for the row (anode) from the 74238 and a value to the column (cathode) from the74138. Since LEDs are polar, the non-active outputs will not have any effect on the LED, so only that one LED will light up! Ingenious!

So, now let’s look at the chip setup…both are exactly the same wiring, again, just the values of the outputs are swapped…

In looking at the two chips, you can see they are nearly identical in the pin arrangement. You can look up the specifics on each chip, but I will cover the extreme basics.

The A0, A1, and A2 pins hold the input values that you wish to decode into one of the output pins, (Y0 – Y7). This is direct mapping. So on the 138 chip, if you set A0=LOW, A1=HIGH, A2=HIGH, that is the binary for 3, so the Y2 pin will become active, which is HIGH, and all of the other Yx pins will be LOW. The reason it is Y2 is because the pins are 0 based, 0-7, not 1-8, so Y2 corresponds to 3.

Conversely, if the same values are ran through the 238 chip, then Y2 will be LOW and all of the other Yx pins will output HIGH.

The other three Pins we need to deal with are the E1, E2, and E3 pins. These control how the chip outputs values. There is a large truth table that tells how these work, but for our use, we need the following scenario…When you are not wanting any lights displayed, then the chip should not have any Y pins active. ONLY when you are wanting a light to come on, then you have to tell the chip to decode the A inputs and output the value. You can look at the chip truth table and figure it out, or, I will just tell you…to get the functionality we want, we need E1 and E2 to be LOW, and E3 will be the pin that will control whether the chip should output a value. If all three E pins are low, then nothing will be active. If E3 goes HIGH, then the values from A are decode. We are going to have the Arduino control this pin.

So, here, by the look in the picture, you can see how Hyrda is gaining even more heads!

So, for the X coordinates on the matrix, we will be sending out signals on Arduino pins 6-8 and the Y coordinates go out on 9 to 11. For the control pin, I am using pin 13, which is controlled purely by software.

Step 3: Connecting to the Arduino

Now we have all of the items in place to start removing some of the Hydra heads and connect them into the Arduino! For the connections to the Arduino, here is the complete chart.

74HC238
A0 Arduino 6
A1 Arduino 7
A2 Arduino 8
E1 Ground
E2 Ground
E3 Arduino 13

74HC238
A0 Arduino 9
A1 Arduino 10
A2 Arduino 11
E1 Ground
E2 Ground
E3 Arduino 13 (again)

IR Receiver
D0 Arduino 2
D1 Arduino 3
D2 Arduino 4
D3 Arduino 5

Step 4: Connecting to the 8x8 Matrix

The final step in connecting everything together is to connect the outputs of the chips to your matrix of LEDs. I am going to use the one from my other article on 8x8 LED Matrix which has the LED anodes as rows and the cathodes as columns.

Now, connecting in the Y0-Y7 outs for the 238, you get the next round of Hydra Heads forming… The picture shows how they outputs look when you have the 238 lines plugged in and ready to go. I use color coded wires to make life easier. You will now need to connect the Y0-Y7 outs for the 138 chips as well. In the photograph, I am showing just the 238 chip with the Y0-Y7 pins outputs connected.

Since the 238 chip outputs a HIGH on the active line, you connect the Y0 pin to the corresponding pin on the Matrix board that goes to the top row of your LED board. Follow Y1 to Y7 connecting it to the rest of the rows of the matrix. Similarly, with the 138 chip, it will output a LOW on the active, so you need to connect the Y0 to the first column of the matrix, and expand Y1-Y7 out as appropriately.

In the photo, I have shown the board I made from my prior project to allow easy use from any device. Simply plug the cables in from the breadboard into the prototype board and you have just connected everything together.

Now, when you turn the Arduino on....nothing happens! You still need to write some code.

Step 5: Coding It All Up, and Call It Done!

Everyone has their different coding styles, but I’m going to give a brief snippet of how I programmed up my signboard.

Here is how I declared a lot of variables. I like variables, so I make a lot. Pretty much, one variable to hold what each pin is, and then one matching variable to set what the value of that pin is. So, I basically initialize the pins in the Setup function. For pins 2-5 I do a quick digitalWrite to activate the pulldown resistors to keep it from floating. You can also add a parameter to the PinMode command to do the same thing.

In the loop function, I just keep an eye out for some activity on the IR lines, and if I see some, I determine which button is pushed and then run some more code. I added a OutputLight routine that takes a coordinate and a time to display in ms. By playing with this code, you can easily make the lights do whatever you wish!

Edward.Norris@BlackBatSoftware.com

//The input pins from the remote

const int remoteBit0 = 2;

const int remoteBit1 = 3;

const int remoteBit2 = 4;

const int remoteBit3 = 5;

//Value of the pins

int remotePin0Value;

int remotePin1Value;

int remotePin2Value;

int remotePin3Value;

//pins for x bits:

const int xBit0 = 6;

const int xBit1 = 7;

const int xBit2 = 8;

//values for x

int xBit0Val;

int xBit1Val;

int xBit2Val;

//pins for y bits:

const int yBit0 = 9;

const int yBit1 = 10;

const int yBit2 = 11;

//value for y

int yBit0Val;

int yBit1Val;

int yBit2Val;

//The pin to indicate that the system should be providing output.

const int activateLightPin = 13;

//This is to control the E3 activate light for activating the chips

int inUseState = LOW;

void setup() {

//Set the pins to receive the input from the remote

pinMode(remoteBit0, INPUT);

pinMode(remoteBit1, INPUT);

pinMode(remoteBit2, INPUT);

pinMode(remoteBit3, INPUT);

digitalWrite(remoteBit0, LOW);

digitalWrite(remoteBit1, LOW);

digitalWrite(remoteBit2, LOW);

digitalWrite(remoteBit3, LOW);

//Output values for determining the x and y factors

pinMode(xBit0, OUTPUT);

pinMode(xBit1, OUTPUT);

pinMode(xBit2, OUTPUT);

pinMode(yBit0, OUTPUT);

pinMode(yBit1, OUTPUT);

pinMode(yBit2, OUTPUT);

pinMode(activateLightPin, OUTPUT);

inUseState == LOW; //Initial setup to get data

digitalWrite(xBit0, LOW);

digitalWrite(xBit1, LOW);

digitalWrite(xBit2, LOW);

digitalWrite(yBit0, LOW);

digitalWrite(yBit1, LOW);

digitalWrite(yBit2, LOW);

digitalWrite(activateLightPin, LOW); //turn all off

}

void loop() {

//Pure precaution

if (inUseState == LOW)

{

//Get the values of the remote

int remotePin0Value = digitalRead(remoteBit0);

int remotePin1Value = digitalRead(remoteBit1);

int remotePin2Value = digitalRead(remoteBit2);

int remotePin3Value = digitalRead(remoteBit3);

//Check if one was clicked

if (remotePin0Value == HIGH ||

remotePin1Value == HIGH ||

remotePin2Value == HIGH ||

remotePin3Value == HIGH )

{

inUseState = HIGH;

//Fire off the Remote Sequence

if (remotePin0Value == HIGH) //Button A

{

OutputLight(0,0,1000);

}

if (remotePin1Value == HIGH ) //Button B

{

OutputLight(1,1,1000);

}

}

if (remotePin2Value == HIGH) //Button C

{

OutputLight(2,2,1000);

}

if (remotePin3Value == HIGH) //Button D

{

OutputLight(3,3,1000);

}

//Turn all lights out

digitalWrite(xBit0, LOW);

digitalWrite(xBit1, LOW);

digitalWrite(xBit2, LOW);

digitalWrite(yBit0, LOW);

digitalWrite(yBit1, LOW);

digitalWrite(yBit2, LOW);

digitalWrite(activateLightPin, LOW);

inUseState = LOW;

}

}

void OutputLight(int X, int Y, unsigned long msLightDelay)

{

xBit0Val = LOW;

xBit1Val = LOW;

xBit2Val = LOW;

yBit0Val = LOW;

yBit1Val = LOW;

yBit2Val = LOW;

if (1 && (X & B001))

{

xBit0Val = HIGH;

}

if (1 && (X & B010))

{

xBit1Val = HIGH;

}

if (1 && (X & B100))

{

xBit2Val = HIGH;

}

if (1 && (Y & B001))

{

yBit0Val = HIGH;

}

if (1 && (Y & B010))

{

yBit1Val = HIGH;

}

if (1 && (Y & B100))

{

yBit2Val = HIGH;

}

digitalWrite(xBit0, xBit0Val);

digitalWrite(xBit1, xBit1Val);

digitalWrite(xBit2, xBit2Val);

digitalWrite(yBit0, yBit0Val);

digitalWrite(yBit1, yBit1Val);

digitalWrite(yBit2, yBit2Val);

digitalWrite(activateLightPin, HIGH);

if (msLightDelay > 0)

{

msLightDisplayStartTime = millis();

while (millis() < msLightDisplayStartTime + msLightDelay)

{

}

}

digitalWrite(activateLightPin, LOW);

}