Sunday, 12 May 2013

LED wall display revisited


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 8x8 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. It is included on the next page.











Saturday, 11 May 2013

Preston Hackspace


Email from Simon at Preston Hackpace :-

"I have had an email today from Kimball Johnson. He wants to pick up the
batton for the Preston Hackspace. I have given him manager permissons for
the Facebook  and Google Group

He has already held one meeting at the Continental and they will continue
on the 1st and 3rd Tuesday of each month. Next being the 21st May.

Would there be any chance you could spread the word around your many
contacts who may be interested.

If anyone would like to attend it would be nice to see you again."




Attending Blackpool LUG meeting this week:

Mike, Les, Tony, Ollie, Elizabeth and Keiran.

Les helped Keiran with Minecraft and Ollie played with his Raspberry Pi.

From meetings

Saturday, 4 May 2013

Podcasts, command line and computer museum

Attending this week:

Mike, Les, Tony, Ollie, James, Elizabeth and Keiran.

Les, Tony and  Ollie produce a Linux podcast, and had a brief discussion about how to raise the profile of the podcast and attract more followers.

Jonathan Archer at East Lancs LUG is organizing a trip to the computer museum at http://www.bletchleypark.org.uk/  on Sunday 26th May 2013.

Entry to the park on the day will be £13
Entry to the National museum of Computing is £5, with entry to the Colossus and Tunny galleries being £2.

Several of us would like to go, and a discussion took place about what time we would need to leave to arrive at a reasonable time, and how we would get there.

For the rest of the morning, Les provided a beginners tutorial to the Linux command line

Keiran, who is still at school, visits with Elizabeth, and now has his Windows laptop dual booting Windows and Ubuntu. Keiran tells us that his school grades relating to computing have improved considerably since he started attending the LUG.

Tuesday, 2 April 2013

LUG meeting 16th March 2013

A depleted group of myself, Mike, Elizabeth and Kieran were at the meeting at Rippon Road this morning. Mike helped Elizabeth fix the problem she was having with updates in her install of Mint 14, with the help of some advice from a member of the community forum.

I helped Kieran install Ubuntu 12.10 as a dual boot on his windows 7 laptop. After the install we completed the 301 updates then did a little extra by adding the medibuntu repository and getting flash and DVD playback working. Also added the restricted extras for the additional codec support and extra fonts available in these packages. One of the reasons I run Mint is because most of this has been done and is available from the vanilla install.

Just as an aside, Kieran let us know today that since he has been attending the LUG his marks in IT classes have gone from the lowest grade up to the top grade due to the new knowledge he has gained at the LUG, so he's well chuffed.

    

Saturday, 30 March 2013

23 March 2013 - Owncloud revisited

Attending this week Elizabeth, Keiran and Mike.

 Keiran, ably assisted by Elizabeth, worked on a presentation about Linux for his IT project at school.

 I took another look at owncloud, an open source dropbox.
When I first looked at Owncloud, a few versions back, it had problems syncing. The sync would fail regularly with a message about the time not being in sync. Even setting the server and client to use the same network time server did not help, so I gave up.

Version 5 is now out, and the time sync problem is gone, and Owncloud is looking good.
My only concern now is security. I do not know enough about network server security to expose my Owncloud server to the internet, so I am using Hamachi.

By Placing an Hamachi server in the same network as the Owncloud server, and using an Hamachi client on machines that I want to access Owncloud, access is carried out in a private network.
No ports need to be opened on the router, and so it feels more secure. If it is not secure, perhaps someone will let me know!