LED wall display revisited

<!– H2.western { font-family: "Albany", sans-serif; font-size: 14pt; font-style: italic } H2.cjk { font-family: "HG Mincho Light J"; font-size: 14pt; font-style: italic } H2.ctl { font-family: "Arial Unicode MS"; font-size: 14pt; font-style: italic } PRE.western { font-family: "Courier New", monospace } PRE.cjk { font-family: "NSimSun", monospace } PRE.ctl { font-family: "Courier New", monospace }

Project Brief : Produced by David M Hewitt

While working at PC Recycler, we had a second hand LED wall board with no manual. I decided to try and get it displaying custom messages. To do this, I had to reverse engineer the circuitry to get an understanding of how to encode the messages to be sent to the display.
Then I had to design and write software for a microcontroller that interfaced with the display. The software needed to be able to display custom messages on the board. These messages will come from a computer attached to the microcontroller or they could be preprogrammed into the controllers memory.
This document will focus largely on how the software was designed and how it works. However, the final page will include some details into how the board was reverse engineered and how the electronics work.

Design

The display can be broken down into segments, each containing a grid of LEDs arranged in a square 8×8 formation. There are two rows of these segments on the display containing 24 segments each. Since each segment can be used to accurately represent any alphanumeric character, we have space for a 48 character message across two lines.
The design of the display means that only one row of LEDs can be switched on at any one time. It relies on persistence of vision to create a readable message. Therefore, the microcontroller and software have to be fast enough to display 8 slices of the message sequentially without the human eye noticing.
The slices of the message have to be pushed into the board in a serial binary format. Once the first row of data is ready in the display, the microcontroller will enable on the corresponding row of LEDs. Then the next row can be pushed into the display and enabled. The process will repeat for all 8 rows and then go back to the top row.
The microcontroller will be capable of accepting messages on its serial port that it will then display on the board. It will also have one preprogrammed message that can be displayed in the case of having no computer connected.

LED Wall display arrives and gets dismantled

Testing one segment of the display to check the pinout against the data sheet


1st test, scrolling some dots.


Arduino in the foreground, controlling the display.

Flow Chart

Included on the next page is a flow chart that demonstrates the way that the code for the project works. In this flow chart, I use three variables as follows:

  message – A string containing the message we will be writing on the display. This could be a default value contained within the controller’s memory or a value sent to the controller from a computer.
currentRow – Because the message has to be pushed onto the display in 8 horizontal slices, the code has to keep track of which one it is currently working with. This value (between 0-7) determines which row of the display is currently switched on and which slice of text should be pushed into the display.
n – We will be using a lookup table to determine the pattern we send to the display for each character. This variable keeps track of our position within the message as we look up individual characters from the font lookup table.

// Where each of the pins on the arduino board is connected to the display header
#define CLOCKPIN 2 // PORTD (BIT 2)
#define DATAPIN 3 // PORTD (BIT 3)
#define BCD_ZERO 4
#define BCD_ONE 5
#define BCD_TWO 6
#define BCD_THREE 7
#define STRBPIN 8 // PORTB (BIT 0)

// Binary encoded font, each line of this array represents a character, each byte represents the
// 8 bits that make up a row of that character

const static unsigned short font[95][8] =
{ { 0, 0, 0, 0, 0, 0, 0, 0 }, // SPACE
{ 64, 64, 64, 64, 64, 0, 64, 0 }, // ! CHARACTER
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 56, 68, 76, 84, 100, 68, 56, 0 }, // NUMBER 0
{ 16, 48, 16, 16, 16, 16, 56, 0 }, // NUMBER 1
{ 56, 68, 4, 8, 16, 32, 124, 0 }, // NUMBER 2
{ 124, 8, 16, 8, 4, 68, 56, 0 }, // NUMBER 3
{ 8, 24, 40, 72, 124, 8, 8, 0 }, // NUMBER 4
{ 124, 64, 120, 4, 4, 68, 56, 0 }, // NUMBER 5
{ 24, 32, 64, 120, 68, 68, 56, 0 }, // NUMBER 6
{ 124, 4, 8, 16, 32, 32, 32, 0 }, // NUMBER 7
{ 56, 68, 68, 56, 68, 68, 56, 0 }, // NUMBER 8
{ 56, 68, 68, 60, 4, 8, 48, 0 }, // NUMBER 9
{ 0, 48, 48, 0, 48, 48, 0, 0 }, // : CHARACTER
{ 0, 48, 48, 0, 48, 16, 32, 0 }, // ; CHARACTER
{ 8, 16, 32, 64, 32, 16, 8, 0 }, // < CHARACTER
{ 0, 0, 124, 0, 124, 0, 0, 0 }, // = CHARACTER
{ 32, 16, 8, 4, 8, 16, 32, 0 }, // > CHARACTER
{ 56, 68, 4, 8, 16, 0, 16, 0 }, // ? CHARACTER
{ 56, 68, 4, 52, 84, 84, 56, 0 }, // @ CHARACTER
{ 56, 68, 68, 124, 68, 68, 68, 0 }, // UPPERCASE A
{ 120, 68, 68, 120, 68, 68, 120, 0 }, // UPPERCASE B
{ 56, 68, 64, 64, 64, 68, 56, 0 }, // UPPERCASE C
{ 112, 72, 68, 68, 68, 72, 112, 0 }, // UPPERCASE D
{ 124, 64, 64, 120, 64, 64, 124, 0}, // UPPERCASE E
{ 124, 64, 64, 120, 64, 64, 64, 0}, // UPPERCASE F
{ 56, 68, 64, 92, 68, 68, 60, 0 }, // UPPERCASE G
{ 68, 68, 68, 124, 68, 68, 68, 0 }, // UPPERCASE H
{ 56, 16, 16, 16, 16, 16, 56, 0 }, // UPPERCASE I
{ 28, 8, 8, 8, 8, 72, 48, 0 }, // UPPERCASE J
{ 68, 72, 80, 96, 80, 72, 68, 0 }, // UPPERCASE K
{ 64, 64, 64, 64, 64, 64, 124, 0 }, // UPPERCASE L
{ 68, 108, 84, 84, 68, 68, 68, 0 }, // UPPERCASE M
{ 68, 68, 100, 84, 76, 68, 68, 0 }, // UPPERCASE N
{ 56, 68, 68, 68, 68, 68, 56, 0 }, // UPPERCASE O
{ 120, 68, 68, 120, 64, 64, 64, 0 }, // UPPERCASE P
{ 56, 68, 68, 68, 84, 72, 52, 0 }, // UPPERCASE Q
{ 120, 68, 68, 120, 80, 72, 68, 0 }, // UPPERCASE R
{ 60, 64, 64, 56, 4, 4, 120, 0 }, // UPPERCASE S
{ 124, 16, 16, 16, 16, 16, 16, 0 }, // UPPERCASE T
{ 68, 68, 68, 68, 68, 68, 56, 0 }, // UPPERCASE U
{ 68, 68, 68, 68, 68, 40, 16, 0 }, // UPPERCASE V
{ 68, 68, 68, 84, 84, 84, 40, 0 }, // UPPERCASE W
{ 68, 68, 40, 16, 40, 68, 68, 0 }, // UPPERCASE X
{ 68, 68, 68, 40, 16, 16, 16, 0 }, // UPPERCASE Y
{ 124, 4, 8, 16, 32, 64, 124, 0 }, // UPPERCASE Z
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 56, 4, 60, 68, 60, 0 }, // LOWERCASE A
{ 64, 64, 64, 88, 100, 68, 120 }, // LOWERCASE B
{ 0, 0, 56, 64, 64, 68, 56, 0 }, // LOWERCASE C
{ 4, 4, 4, 52, 76, 68, 60, 0 }, // LOWERCASE D
{ 0, 0, 56, 68, 124, 64, 56, 0 }, // LOWERCASE E
{ 24, 36, 32, 112, 32, 32, 32, 0 }, // LOWERCASE F
{ 0, 60, 68, 68, 60, 4, 56, 0 }, // LOWERCASE G
{ 64, 64, 88, 100, 68, 68, 68, 0 }, // LOWERCASE H
{ 0, 16, 0, 16, 16, 16, 16, 0 }, // LOWERCASE I
{ 8, 0, 24, 8, 8, 72, 48, 0 }, // LOWERCASE J
{ 64, 64, 72, 80, 96, 80, 72, 0 }, // LOWERCASE K
{ 48, 16, 16, 16, 16, 16, 56, 0 }, // LOWERCASE L
{ 0, 0, 104, 84, 84, 68, 68, 0 }, // LOWERCASE M
{ 0, 0, 88, 100, 68, 68, 68, 0 }, // LOWERCASE N
{ 0, 0, 56, 68, 68, 68, 56, 0 }, // LOWERCASE O
{ 0, 0, 120, 68, 120, 64, 64, 0 }, // LOWERCASE P
{ 0, 0, 52, 76, 60, 4, 4, 0 }, // LOWERCASE Q
{ 0, 0, 88, 100, 64, 64, 64, 0 }, // LOWERCASE R
{ 0, 0, 56, 64, 56, 4, 120, 0 }, // LOWERCASE S
{ 32, 32, 112, 32, 32, 36, 24, 0 }, // LOWERCASE T
{ 0, 0, 68, 68, 68, 76, 52, 0 }, // LOWERCASE U
{ 0, 0, 68, 68, 68, 40, 16, 0 }, // LOWERCASE V
{ 0, 0, 68, 68, 84, 84, 40, 0 }, // LOWERCASE W
{ 0, 0, 68, 40, 16, 40, 68, 0 }, // LOWERCASE X
{ 0, 0, 68, 68, 60, 4, 56, 0 }, // LOWERCASE Y
{ 0, 0, 124, 8, 16, 32, 124, 0 }, // LOWERCASE Z

};

// The 8 fets controlling which row of the display is turned on are controlled by a BCD code
// (Numbered 0-7). Therefore, using a value of 8 with this function turns the display off
void setActiveFET(int id)
{
digitalWrite(BCD_ZERO, HIGH && (id & B00000001));
digitalWrite(BCD_ONE, HIGH && (id & B00000010));
digitalWrite(BCD_TWO, HIGH && (id & B00000100));
digitalWrite(BCD_THREE, HIGH && (id & B00001000));
}

void pulseClock()
{
// Pulse Clock Line On and then Off
PORTD |= 1 << 2;
PORTD &= ~(1 << 2);
}


void pulseStrobe()
{
// Pulse strobe Line On and then Off
PORTB |= 1 << 0;
PORTB &= ~(1 << 0);
}

// Function to shift a single row of a single character into the registers
void shiftCharacterRow(char character, int row)
{
// Shift the data into the display using bit manipulation for speed
for(unsigned short i = 1; i < 8; i++)
{
short b = (font[character-32][row]) & (1 << (7 - i));
if(b)
{
PORTD |= 1 << 3;
} else {
PORTD &= ~(1 << 3);
}
pulseClock();
}

// The arduino library includes a similar function for
// shifting data (good for testing, but too slow for
// persistence of vision)

}

void drawStringRow(char* string, int row, int length)
{
for(unsigned short i=0; i < length; i++)
{
shiftCharacterRow(string[i], row);
}
}

char text[49];

void setup()
{
// Set pins 2 through 8 to be outputs as these interface with the display
for(unsigned short i = 2; i<= 8; i++)
{
pinMode(i, OUTPUT);
}

// Enable serial communication for receiving messages from the computer
Serial.begin(9600);

// Initialise the array containing the message
for(unsigned short i = 0; i < 49; i++)
{
text[i] = ' ';
}

// Default message for the display
const static char* message = "Blackpool Computer Club";
strcpy(text, "Blackpool Computer Club");


}





short curRow = 0;

void loop()
{
unsigned int messageLen = strlen(text);

// Buffer serial data one byte at a time
char serialBuffer[2] = " ";
if(Serial.available() > 0)
{
serialBuffer[0] = Serial.read();
// Append it to the end of the display string
strcat(text, serialBuffer);
// If we're overflowing the display, blank the string
if(messageLen > 48)
{
strcpy(text, "");
}
}

// Push the string into the display
drawStringRow(text, curRow, messageLen);

// Calculate the length of the string we're using and fill the rest of the display with 0s
for(unsigned int i = 0; i < (384 - (messageLen*7)); i++)
{
PORTD &= ~(1 << 3);
pulseClock();
}

// Enable the current row of LEDs
setActiveFET(curRow);
// Latch the current data so the display doesn't change while we're pushing the next row
pulseStrobe();

if(++curRow > 7)
{
curRow = 0;
}
}


Reverse Engineering Methods

To discover how the display worked and how the data should be sent I had to apply some electronics knowledge and trace the circuitry. It was a case of understanding all the electronics between the header where the microcontroller was to be connected and the actual LEDs themselves.
To trace the circuitry, I used an electronic multimeter with a continuity setting. This allowed me to put the two probes at two different points on the board and it would tell me if there was an electrical connection between the two. I started drawing diagrams of connections so that I could gain an understanding of where to connect the microcontroller and how to program it.
There are 14 pins in total on the connector to the board. I quickly determined that 6 of these pins were used for power and ground leaving 8 that needed to be identified.
First of all, I started by finding datasheets online for all of the major components and integrated circuits on the board. I discovered that there were a lot of MIC5841 ICs on the board. These are 8-bit shift registers with latches. So this initially suggested to me that the display would be accepting the data in a serial format along one wire.
After tracing the tracks on the board, I could identify 4 pins on the connector that directly related to the shift registers. The LEDs were capable of displaying two different colours, so two sets of shift registers were used, one for each colour. One pin on the header was for green data and one for red data. The other two pins were clock and strobe pins for the shift registers. Every time the clock is pulsed the shift registers move all the data along the board by one space and take one more bit of data from each of the two data inputs. Finally, the strobe pin is used to latch or freeze the outputs from the registers so that the display stays steady while the next set of data is being shifted in.
This left 4 pins to identify. After discovering a 4028 IC on the board, it was found by tracing connections that the remaining 4 pins were used to control which row of the display was turned on. The 4028 is a Binary Coded Decimal to Decimal converter. Therefore, a 4 bit BCD number applied to the remaining pins specifies which row of the display is active (numbered 0-7).
I created a circuit diagram to clarify the above information and to help me write the code for the microcontroller.

Link to to circuit diagram as PDF:

https://drive.google.com/file/d/0B_qAavrsv4SQQTBZVkV2ajk4T2s/edit?usp=sharing

Advertisements
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s