Difference between revisions of "LED tiles"
Tobycatlin (talk | contribs) |
(remove a members' name as tile as been collected) |
||
(9 intermediate revisions by 6 users not shown) | |||
Line 38: | Line 38: | ||
* 10. Pingless | * 10. Pingless | ||
* <strike>11. Asc (2 please)</strike> | * <strike>11. Asc (2 please)</strike> | ||
− | * 12. James Cadman | + | * <strike>12. James Cadman</strike> |
− | * 13. Henry Best | + | * <strike>13. Henry Best</strike> |
* <strike>14. Martin Goodman</strike> (2 please) | * <strike>14. Martin Goodman</strike> (2 please) | ||
* <strike>15. Thomas Hill</strike> | * <strike>15. Thomas Hill</strike> | ||
Line 45: | Line 45: | ||
* <strike>17. Michael Margolis</strike> (2 if possible) | * <strike>17. Michael Margolis</strike> (2 if possible) | ||
* 18. Robin Baumgarten (2 if possible) | * 18. Robin Baumgarten (2 if possible) | ||
− | * 19. Giac0m0 (2 if possible) | + | * <strike>19. Giac0m0 (2 if possible) </strike> |
− | * <strike>20. Bennage (2 if possible, please) | + | * <strike>20. Bennage (2 if possible, please)</strike> |
* 21. [[User:Detonate|Det]] (1) | * 21. [[User:Detonate|Det]] (1) | ||
* 22. [[User:Betandr|betandr]] | * 22. [[User:Betandr|betandr]] | ||
− | * <strike>23. Ciborg | + | * <strike>23. Ciborg</strike> |
* <strike>24.Stefan Sabo (HS07764) 2 </strike> | * <strike>24.Stefan Sabo (HS07764) 2 </strike> | ||
Line 72: | Line 72: | ||
PIXELS | PIXELS | ||
− | * 36 | + | * 48 x 36 |
* Pixel Configuration SMD 3528 | * Pixel Configuration SMD 3528 | ||
* LED Type Black Diamond | * LED Type Black Diamond | ||
Line 111: | Line 111: | ||
You can find his mailing list post here: https://groups.google.com/d/msg/london-hack-space/Gs4_P4Je1gw/HFshYAzhEQAJ | 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. | ||
+ | |||
+ | [[File:Hackspace LED Tile addressing.png|thumbnail|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: | ||
+ | |||
+ | <pre> | ||
+ | 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); | ||
+ | |||
+ | </pre> | ||
+ | |||
+ | 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. | ||
+ | |||
+ | <pre> | ||
+ | /* | ||
+ | * 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 | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
+ | The fastwrite.h header file is as follows | ||
+ | <pre> | ||
+ | /* | ||
+ | * 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 | ||
+ | </pre> |
Latest revision as of 18:57, 7 March 2016
LED screen tiles donated by Mistamud. See mailing list thread here.
What is it
- Frame containing 6 LED tiles
- Controller/PSU "Infiled"
- 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. Nigle03. Toby Catlin04. 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 Cadman13. Henry Best14. Martin Goodman(2 please)15. Thomas Hill16. 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. Ciborg24.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.
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.
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