LED tiles

From London Hackspace Wiki

LED screen tiles donated by Mistamud. See mailing list thread here.

LED tile

What is it

  • Frame containing 6 LED tiles

LED Frame.jpg

  • Controller/PSU "Infiled"

LED-Infiled.jpg

  • 46 individual LED tiles

What are we doing with it?

The space is likely to keep the frame and controller/PSU, and maybe a few extra screens depending on final decision.

Project maintainers

  • Elisabeth Anderson
  • .....


Tiles for members

The space keeps the frame and controller, plus possibly a few extra screens (decision tbc), which would leave about 40 for members. Register you interest (before 16th Jan) and keep an eye on the mailing list for update on when the tiles are ready to collect. (update: added members who registered their interest on the mailing list as well)

  • 01. Lee Jones (2 if possible please).
  • 02. Nigle
  • 03. Toby Catlin
  • 04. Matt P
  • 05. Aden (2)
  • 06. Henry Sands
  • 07. Matt Wheeler (4 if possible)
  • 08. Egor Kraev
  • 09. Mike (Upto 4, if possible, please)
  • 10. Pingless
  • 11. Asc (2 please)
  • 12. James Cadman
  • 13. Henry Best
  • 14. Martin Goodman (2 please)
  • 15. Thomas Hill
  • 16. JJ (2 please)
  • 17. Michael Margolis (2 if possible)
  • 18. Robin Baumgarten (2 if possible)
  • 19. Giac0m0 (2 if possible)
  • 20. Bennage (2 if possible, please)
  • 21. Det (1)
  • 22. betandr
  • 23. Ciborg
  • 24.Stefan Sabo (HS07764) 2

No more names please, they've all gone

N.B. If you've not done anything with the tile after 3 months please bring it back to the space as someone else may wish to do something with it, don't just let it gather dust in a corner!

LED Specs

The 'tile' contains the matrix of SMDs and the driver ICs and multiplexing to run but you'll need 5v dc and some kind of Pi / Arduino / Nova or Linsn Scancard to control them.

Back of the tile LED-pinout.jpg

Rough Specs sent by Tom:

LEDs Per Pixel 1R1G1B

  • Red Wavelength (Dominant) 625 ~ 630
  • Green Wavelength (Dominant) 520 ~ 525
  • Blue Wavelenghth (Dominant) 470 ~ 475

PIXELS

  • 48 x 36
  • Pixel Configuration SMD 3528
  • LED Type Black Diamond
  • LEDs Per Area LEDs/m2 67,635
  • Physical Pixel Pitch mm 6.66

Physical Pixels Per Area pixels/m2 22,545 Panel:

  • LED Tile Width mm 320
  • LED Tile Height mm 120
  • Viewing Angle – Horizontal degrees 140
  • Viewing Angle - Vertical degrees 120

Frame and controller/PSU specs

- Frame containing 6 LED tiles - Controller/PSU "Infiled"

This is a link to the manufacturers page here: http://www.infiled.com/chanpin/yingyong-3/20131223/250.html

Also a PDF: http://www.infiled.com/uploads/soft/L%20RSS.pdf

Building a LED wall

On this Mistamud says: You'll need a card called a Scancard. (the brains found inside each and every LED panel): http://www.led-card.com/product_info.php?cPath=24&products_id=314

The signal that goes into the Scancard on Cat5 cable needs to come from a Sender Card: http://www.led-card.com/product_info.php?cPath=23&products_id=15. The signal that goes into a Sender card is good ol' DVI from your laptop.

There's also some info on the progress made with the previous screens here

Michael Margolis thinks that they can be controlled by the SmartMatrix library: https://github.com/pixelmatix/SmartMatrix

and Adafruit have a shield for the teensy with a connector that should(?) work with these tiles : https://www.adafruit.com/products/1902

You can find his mailing list post here: https://groups.google.com/d/msg/london-hack-space/Gs4_P4Je1gw/HFshYAzhEQAJ

Glen Akins has some extremely good technical detail here: http://bikerglen.com/projects/lighting/led-panel-1up/

Getting Started

Some key information and pointers for anyone getting started with a tile and a basic microcontroller like the Arduino (we used a Mega 2560), Raspberry Pi, BeagleBone etc.

The OE (Output Enable) pin is misleadingly named as it's really Output Inhibit or Blanking, having to be pulled low to enable the LEDs. All other inputs are active high.

The panel is organised as six row groups that are interlaced both vertically and horizontally (see illustration) to minimise flicker. Each group contains 288 pixels organised in a shift register so new data shifts along the row group in the order shown.

48 x 36 LED Tile Addressing Scheme

As someone has explained elsewhere the panel displays one row group at a time, so it's necessary to cycle them in equal proportions to get a full image. The row addressing performs two functions, to select the row group that's both energised and into which data will be clocked. Note that it appears that you can select a row and clock data into it, but that data is held but not transferred to the display until you toggle the latch input even if you change to a different row group. In this way it seems possible (though not yet tried) to build up in the shift register the next image to be displayed a small amount at a time to maintain a good refresh rate, and then transfer it to the display by latching.

The shift register is not cleared by the latching operation so you can add to a row and latch again. To clear a row it's necessary to send 288 'black' pixels to it.

A suitable ribbon cable is part CN16494 from CPC at around £3. http://cpc.farnell.com/1/1/110292-amphenol-fc16600-0-lead-2-54mm-f-f-600mm-16way.html

Simple Arduino code example:

digitalWrite(pinOE, LOW);
 
// select first row
digitalWrite(pinA, LOW);
digitalWrite(pinB, LOW);
digitalWrite(pinC, LOW);
 
For(int i=0; i<288; i++)
{
  // set high or low value for each pixel
  digitalWrite(pinR1, R);
  digitalWrite(pinG1, G);
  digitalWrite(pinB1, B);
  // pulse the clock line to move this data into the shift register
  digitalWrite(pinCLK, HIGH);
  digitalWrite(pinCLK, LOW);
}
 
// pulse the latch line to transfer the new data to the display
  digitalWrite(pinLAT, HIGH;
  digitalWrite(pinLAT, LOW);
 

The pixel writing could be made much faster using direct port operations, particularly with a port dedicated to the tile interface - one write with clock low to set the R G B values and the next holding the values with clock high - probably of the order of 50 times faster than the code above.

Test Sketch

The following sketch supports some test routines and primitive graphic output to the matrix. Pulse durations need to be optimized, either by testing or using a logic analyzer on the actual driver boards in the space.

/*
 *  LED Matrix test sketch
 *
 *  Matrix driver code and test routines for 48 x 36 LED tile
 *
 *  2016-11-02  Michael Margolis
 */

#include "fastwrite.h"

// this code needs at least 2K of RAM so a Mega or Sanguino should be used
#if defined(__AVR_ATmega1280__) || defined (__AVR_ATmega2560__)
// port C
#define pinR1 37   // red
#define pinG1 36   // green
#define pinB1 35   // blue
#define RGB_PORT PORTC
// port A
#define pinA  24   // brown
#define pinB  25   // orange
#define pinC  26   // yellow
// port G
#define pinCK 39  // violet
#define pinLT 40   // white
#define pinEN 41   // grey
#else
#error "Arduino pin mapping not defined for this board"
#endif

// define for printf, only needed for debug info
static char _buf[64];
#define printf(...)  do { sprintf(_buf, __VA_ARGS__); Serial.write(_buf); } while (0)

// LED pixel display
const byte displayRows = 36;
const byte displayColumns = 48;

// internal layout of matrix buffer
const int _bufferRows = 6;
const int _bytesPerBufferRow = displayColumns * 6;
byte matrixBuffer[_bufferRows][_bytesPerBufferRow];
const int columnOffsets[] = {192, 200, 96, 104, 0, 8}; // translate to buffer offset
byte rowOffsets[] = {5,0,3,4,1,2}; // row order

int testDelay = 20; //delay in milliseonds used for testing

void setup() {
  Serial.begin(115200);
  Serial.println("LED Matrix test");
  // set pin modes
  pinMode(pinR1, OUTPUT);
  pinMode(pinG1, OUTPUT);
  pinMode(pinB1, OUTPUT);
  pinMode(pinA, OUTPUT);
  pinMode(pinB, OUTPUT);
  pinMode(pinC, OUTPUT);
  pinMode(pinCK, OUTPUT);
  pinMode(pinLT, OUTPUT);
  pinMode(pinEN, OUTPUT);

  // set initial pin states
  fastWrite(pinEN, LOW);      // this one is active low
  fastWrite(pinLT, LOW);
  fastWrite(pinCK, LOW);
  clearDisplay();
}

void loop()
{
  if (Serial.available() ) {
    // all commands terminated by a newline character
    int cmd = Serial.read();
    if (cmd == 'p') {
      // send "p=x,y,c" to set pixel at x,y to colour c
      byte x = Serial.parseInt();
      byte y = Serial.parseInt();
      byte colour = 1; //Serial.parseInt();
      setBufPixel(x, y, colour);
    }
    else if (cmd == 'c') {
      clearDisplay();
    }
    else if (cmd == 'C') {
      byte colour = Serial.parseInt();
      clearDisplay(colour);
    }
    else if (cmd == 'T') {
      testAll(); // scan all 36 rows
    }
    else if (cmd == 't') {
      byte y = Serial.parseInt();
      testRow(y); // scan the given row
    }
    else if (cmd == 's') {
      scanBuffers(); // code from JJ
    }
    else if (cmd == 'd') {
      // set test delay in  milliseconds
      testDelay = Serial.parseInt();
      printf("delay set to %d\n", testDelay);
    }
    else if (cmd == 'b') {
        //draw a box at x,y with given width, height and colour
        // "b=0,0,6,5,4" draws a blue box at upper left, witdh 6, height 5 
        byte x = Serial.parseInt();
        byte y = Serial.parseInt();
        byte width = Serial.parseInt();
        byte height = Serial.parseInt();
        byte colour = Serial.parseInt();
        drawRectangle(x,y,width,height,colour);      
    }
  }
  refresh();
}

//================= Matrix Code ============================/
// Save the pixel colour into a buffer mapped to the phyical row layout
// This version has the x,y pixel origin at the upper left of the matrix
void setBufPixel(byte x, byte y, byte colour)
{
  int offset = columnOffsets[y / 6] + (x % 8) + (16 * (x / 8));
  byte row = y % 6; 
  row = rowOffsets[row]; // adjust the row order 
  matrixBuffer[row][offset] = colour;
  //printf("%d,%d -> row %d, offset %d colour= %d\n", x,y, row, offset, colour);  
}

void selectRow(byte row)   // set the address lines from the row group number
{  
  fastWrite(pinA, (row & 1) );
  fastWrite(pinB, ((row >> 1) & 1) );
  fastWrite(pinC, ((row >> 2) & 1) );
  // TODO - is a small delay needed here?
}

// push colours directly to the port
void writePixel(byte colour)
{
  RGB_PORT = colour;
  fastWrite(pinCK, HIGH);  // clock it
  __asm__("nop\n\t");   __asm__("nop\n\t");   __asm__("nop\n\t");   __asm__("nop\n\t");
   // TODO - adjust the above delay?
  fastWrite(pinCK, LOW);
}

// sets all pixels to the given 3 bit colour or off
void clearDisplay(byte colour)
{
  //Serial.println("Clear");
  memset(matrixBuffer, colour, sizeof(matrixBuffer));
  RGB_PORT = colour; // colours vals 1-7, 0 is off
  for (byte row = 0; row < 6; row++) {
    selectRow(row);
    for ( int i = 0; i < 288; i++ ) {
      fastWrite(pinCK, HIGH);  // clock it
      fastWrite(pinCK, LOW);
    }
    fastWrite(pinLT, HIGH);   // toggle the latch
    fastWrite(pinLT, LOW);
  }
}

// convenience method to turn off all pixels
void clearDisplay()
{
  clearDisplay(0);
}

// write the buffer to the matrix
void refresh()
{
  for (int row = 0; row < _bufferRows; row++) {
    selectRow(row);
    for ( int i = 0; i < _bytesPerBufferRow; i++ ) {
      writePixel(matrixBuffer[row][i]);
    }
    fastWrite(pinLT, HIGH);   // toggle the latch
    //delay(1);
    fastWrite(pinLT, LOW);
  }
}
//================ Drawing Functions =====================
void drawHLine(byte x, byte y, byte width, byte color){  
    for( byte i=0; i < width; i++) 
       setBufPixel(x+i, y, color);
}

void drawVLine(byte x, byte y, byte height, byte color){
  for( byte i=0; i < height; i++) 
      setBufPixel(x,y+i,color);
}

void drawRectangle(byte x, byte y, byte width, byte height, byte color){
      drawVLine(x,y,height,color);
      drawVLine(x+width,y, height,color);
      drawHLine(x,y,width,color);
      drawHLine(x,y+height,width,color);     
}
//========================== Test Code ===================/
// scans pixels from top to bottom
void testAll()
{
  for ( byte y = 0; y < displayRows; y++)  {
    testRow(y);
  }
  Serial.println("fin test");
}

// scan pixels displayed on given row y
void testRow(byte y)
{
  for (byte x = 0; x < displayColumns; x++) {
    setBufPixel(x, y, 4); // set a pixel
    refresh();
    delay(testDelay);
    clearDisplay();
  }
}

// code from JJ
void scanBuffers()
{
  for (int row = 0; row < 6; row++) { // loop for each row group
    selectRow(row);
    for (int i = 287; i >= 0; i--) { // working backwards (as data is 'pushed' into the shift register
      for (int j = 0; j < i; j++) { // first fill all ahead with black
        writePixel(0);
      }
      for (int j = 0; j < 8 && j < 288 - i; j++) { // set up to seven pixels to colours
        writePixel(j % 8);  // use colours in reverse order
      }
      for (int j = i + 7; j < 288; j++) { // then fill all behind with black
        writePixel(0);
      }
      fastWrite(pinLT, HIGH);   // toggle the latch
      fastWrite(pinLT, LOW);
      delay(testDelay);    // delay to make the pattern easier to follow
    }
  }
}

The fastwrite.h header file is as follows

/*
 * arduino_io.h
 * this file maps arduino pins to avr ports and pins
 *
 * The header file: pins_arduino.h is used if this exits
 * otherwise the following controllers are defined in this file
 * Arduino (ATmega8,168,328), Mega, Sanguino (ATmega644P)
 * 
*/ 

#include "pins_arduino.h"
#if !(defined(digitalPinToPortReg) && defined(digitalPinToBit))
#if defined(__AVR_ATmega8__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__)

  // Standard Arduino Pins
#define digitalPinToPortReg(P) \
    (((P) >= 0 && (P) <= 7) ? &PORTD : (((P) >= 8 && (P) <= 13) ? &PORTB : &PORTC))
#define digitalPinToBit(P) \
    (((P) >= 0 && (P) <= 7) ? (P) : (((P) >= 8 && (P) <= 13) ? (P) - 8 : (P) - 14))

#elif defined(__AVR_ATmega1280__) || defined (__AVR_ATmega2560__)
  // Arduino Mega Pins
#define digitalPinToPortReg(P) \
    (((P) >= 22 && (P) <= 29) ? &PORTA : \
  ((((P) >= 10 && (P) <= 13) || ((P) >= 50 && (P) <= 53)) ? &PORTB : \
    (((P) >= 30 && (P) <= 37) ? &PORTC : \
  ((((P) >= 18 && (P) <= 21) || (P) == 38) ? &PORTD : \
  ((((P) >= 0 && (P) <= 3) || (P) == 5) ? &PORTE : \
    (((P) >= 54 && (P) <= 61) ? &PORTF : \
  ((((P) >= 39 && (P) <= 41) || (P) == 4) ? &PORTG : \
  ((((P) >= 6 && (P) <= 9) || (P) == 16 || (P) == 17) ? &PORTH : \
  (((P) == 14 || (P) == 15) ? &PORTJ : \
    (((P) >= 62 && (P) <= 69) ? &PORTK : &PORTL))))))))))

#define digitalPinToBit(P) \
  (((P) >=  7 && (P) <=  9) ? (P) - 3 : \
  (((P) >= 10 && (P) <= 13) ? (P) - 6 : \
  (((P) >= 22 && (P) <= 29) ? (P) - 22 : \
  (((P) >= 30 && (P) <= 37) ? 37 - (P) : \
  (((P) >= 39 && (P) <= 41) ? 41 - (P) : \
  (((P) >= 42 && (P) <= 49) ? 49 - (P) : \
  (((P) >= 50 && (P) <= 53) ? 53 - (P) : \
  (((P) >= 54 && (P) <= 61) ? (P) - 54 : \
  (((P) >= 62 && (P) <= 69) ? (P) - 62 : \
  (((P) == 0 || (P) == 15 || (P) == 17 || (P) == 21) ? 0 : \
  (((P) == 1 || (P) == 14 || (P) == 16 || (P) == 20) ? 1 : \
  (((P) == 19) ? 2 : \
  (((P) == 5 || (P) == 6 || (P) == 18) ? 3 : \
  (((P) == 2) ? 4 : \
  (((P) == 3 || (P) == 4) ? 5 : 7)))))))))))))))

#elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__)  
// Sanguino or other ATmega644 controller
#define digitalPinToPortReg(P) \
    (((P) >= 0 && (P) <= 7)   ? &PORTB : (((P) >= 8 && (P) <= 15) ? &PORTD : \
  (((P) >= 16 && (P) <= 23) ? &PORTC : &PORTA)))
#define digitalPinToBit(P) \
     (((P) >= 0 && (P) <= 23) ? (P) : (P) - 7 )
#error "ATmega644 has not been tested"
#elif defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)  
#define digitalPinToPortReg(P) \
    (((P) >= 0 && (P) <= 7) ? &PORTA : &PORTB )
#define digitalPinToBit(P) \
    (((P) >= 0 && (P) <= 7) ? (P) : \
  (((P) == 8) ? 2 : \
  (((P) == 9) ? 1 : 0 )))    
#else 
#error "Arduino pin mapping not defined for this board"
#endif

#define fastWrite(P, V) \
  if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \
    bitWrite(*digitalPinToPortReg(P), digitalPinToBit(P), (V)); \
  } else { \
    digitalWrite((P), (V)); \
  }
  
#endif