maker

icStation 4x4x4 LED Cube Shield for Arduino – Reprise

I’ve been playing a little more with my icStation 4x4x4 LED Cube Shield (I described the details of the build and my code previously).  First, a little hardware mod.

Eventually I’d like to use this with some kind of network connectivity and most of the add-ons I have utilise the UART on pins 0 and 1, so I wanted to free these up from being used on the shield.  Thankfully this was fairly straight forward – I simply removed the connecting pins from the shield for pins 0 and 1 and re-patched them across to pins 9 and 10.  (I avoided pin 8 as this too is used with one of the radios I might use, the Ciseco SRF modules, in the future)

This means that the pin definitions in my code now look something like this:

int HC595_clockPin=9;   // SH_CP of 74HC595 
int HC595_latchPin=10;  // ST_CP of 74HC595 
int HC595_dataPin=3;    // DS of 74HC595 
int HC595_enablePin=2;  // Not OE of 74HC595
int LED_Pin16= 4;
int LED_Pin17= 5;
int LED_Pin18= 6;
int LED_Pin19= 7;

You can see this simple mod in the pictures below.

icstation4x4x4-nouarthack

The other change I wanted to make was to make the cube more easily programmable, rather than just loading in pre-set patterns, and to make the refreshing of the cube more independent of the main code.

To do the former, I added a few functions to set and clear the appropriate bits in an array that I can simply load into the cube when convenient.

unsigned int display[4];

void setpixel (int x, int y, int z) {
  // z defines the plane
  // within the plane, x + y*4 defines the bit
  display[z] = display[z] | (1<<(x+y*4)); 
}

void clrpixel (int x, int y, int z) {
  display[z] = display[z] & (~(1<<(x+y*4))); 
}

void clrdisplay () {
  display[0] = 0;
  display[1] = 0;
  display[2] = 0;
  display[3] = 0;
}

To improve the scanning of the cube, I decided to use the hardware timer interrupt available on timer 1 in the arduino.  After initially thinking about setting the registers directly, I decided the TimerOne library is actually so simple to use, I may as well just use that – so I did.

To initialise the timer:

// Use Timer1 on the Ardunio to trigger the update scan in the background 
Timer1.initialize(1000);
Timer1.attachInterrupt (ics444_display_scan);

And the scan routine looks like this:

int ics444_scan_count=0;
void ics444_display_scan () {
  if (ics444_scan_count == 0) {
    digitalWrite(LED_Plane[0], HIGH);
    digitalWrite(LED_Plane[1], HIGH);
    digitalWrite(LED_Plane[2], HIGH);
    digitalWrite(LED_Plane[3], HIGH);
    write_74HC595 (HC595_display[0]);
    digitalWrite(LED_Plane[0], LOW);
    ics444_scan_count++;
  }
  else if (ics444_scan_count == 1) {
    digitalWrite(LED_Plane[0], HIGH);
    digitalWrite(LED_Plane[1], HIGH);
    digitalWrite(LED_Plane[2], HIGH);
    digitalWrite(LED_Plane[3], HIGH);
    write_74HC595 (HC595_display[1]);
    digitalWrite(LED_Plane[1], LOW);
    ics444_scan_count++;
  }
  else if (ics444_scan_count == 2) {
    digitalWrite(LED_Plane[0], HIGH);
    digitalWrite(LED_Plane[1], HIGH);
    digitalWrite(LED_Plane[2], HIGH);
    digitalWrite(LED_Plane[3], HIGH);
    write_74HC595 (HC595_display[2]);
    digitalWrite(LED_Plane[2], LOW);
    ics444_scan_count++;
  }
  else {  
    digitalWrite(LED_Plane[0], HIGH);
    digitalWrite(LED_Plane[1], HIGH);
    digitalWrite(LED_Plane[2], HIGH);
    digitalWrite(LED_Plane[3], HIGH);
    write_74HC595 (HC595_display[3]);
    digitalWrite(LED_Plane[3], LOW);
    ics444_scan_count = 0;
  }
}

Note that for each tick of the timer I display one plane then move onto the next one for the next tick. Initially I had it setting each plane in sequence, but then found I needed a short delay between each plane to actually allow the LEDs to illuminate without bleeding into each other, hence splitting it up. I also found that if I don’t pre-set all four planes to HIGH (i.e. turning them off) before selecting the pattern I wanted, I’d also get some bleeding of values across planes, hence the sequence: a) turn all planes off; write the 16 bits to the plane as data; turn on the plane to be illuminated; wait before doing the next plane.

This seems to create a nice scanning persistence of vision effect without having to worry about when to trigger the scanning from the main code.  The last bits of glue are something to update a buffer containing the pattern to display (stored in LED_Plane[0-3] in the above code) and pretty much the same code as before for the actual writing to the registers of the 74HC595.

One gotcha – I set all my icstation4x4x4 code into a separate cpp and header file, so when I tried to #include <TimeOne.h> in my .cpp file, I kept getting:

icstation4x4x4.cpp:1:22: fatal error: TimerOne.h: No such file or directory
 #include <TimerOne.h>

Which was starting to drive me crazy as I could see the library was installed correctly, etc. After some Googling, eventually searching for “Arduino use of libraries from other libraries” or something like that, I found out that the compiler won’t include paths to libraries unless they are included in the main sketch file.  Consequently to use <TimerOne.h> in my .cpp I also need to #include <TimerOne.h> in my main sketch file too.  A quirk of the “C++ but not quite C++” nature of the Arduino IDE I guess.

Full code for my icstation4x4x4 included below.  Note: This is not written to be particularly good code (I don’t use portable definitions for 16-bit values, and there is minimal error checking, etc – this is just my hacking about, not meant for real use anywhere – I might tidy I up one day – maybe).  If you want to do it all properly, I suggest you take a look at the rather fine library described here: https://arduinoplusplus.wordpress.com/2015/08/13/device-independent-control-for-led-cubes/

Kevin.

Main Sketch:

#include <TimerOne.h>
#include "icstation4x4x4.h"

unsigned int display[4];

void setpixel (int x, int y, int z) {
  // z defines the plane
  // within the plane, x + y*4 defines the bit
  display[z] = display[z] | (1<<(x+y*4)); 
}

void clrpixel (int x, int y, int z) {
  display[z] = display[z] & (~(1<<(x+y*4))); 
}

void clrdisplay () {
  display[0] = 0;
  display[1] = 0;
  display[2] = 0;
  display[3] = 0;
}

void setup() {
  // put your setup code here, to run once:
  ics444_setup();
}

int x=0;
int y=0;
int z=0;
void loop() {
  // put your main code here, to run repeatedly:
  clrdisplay();
  int i;
  for (i=0; i<32; i++)
  {
    setpixel (random(4), random(4), random(4));
  }
  ics444_display(&display[0]);
  delay (2000);
}

icstation4x4x4.cpp:

#include <TimerOne.h>
#include "icstation4x4x4.h"

// Note: Original shield was wired to use pins 0-7, plus GND and +5V.
//       This has been re-wired to use pins 9 and 10 instead of 0 and 1
//       in order to free up 0/1 (RX/TX) for use as a serial port and 8 for use
//       with the Ciseco SRF radios (0/1 is the serial connection to the
//       radio and 8 is the radio 'enable' pin).
//
int HC595_clockPin=9;   // SH_CP of 74HC595 
int HC595_latchPin=10;  // ST_CP of 74HC595 
int HC595_dataPin=3;    // DS of 74HC595 
int HC595_enablePin=2;  // Not OE of 74HC595
int LED_Pin16= 4;
int LED_Pin17= 5;
int LED_Pin18= 6;
int LED_Pin19= 7;
int LED_Plane[] = {LED_Pin16, LED_Pin17, LED_Pin18, LED_Pin19};
unsigned int HC595_display[4];

// Each line (8 bytes) is an entire cube, with two consecutive bytes per plane of LEDS,
// and 16 LEDS per plane. LEDs are encoded in the following order:
//    Lowest plane byte 1, lowest plane byte 2, second lowest plane 1, then 2,
//    second from top plane 1, then 2, highest plane 1, highest plane 2.
//
//    Each plane is encoded looking at the Arduino oriented with the USB/power
//    designated by 'south' by started 'north west' as follows:
//        D0    D1    D2    D3
//        D4    D5    D6    D7
//        D8    D9    D10   D11
//        D12   D13   D14   D15
//
//        D16   D17   D18   D19
//          (USB)      (Power)
//    With D16 being the lowest plane, through to D19 being the highest plane
//    Of course, if you wire the planes up differently, that is up to you!
//
//    Each two bytes of the pattern are therefore:
//        B00000000, B00000000 -> D0-7, D8-15
//    with D0 = msb of the first value, D7 being the lsb of the first value,
//    and  D8 = msb of the second value, D15 being the lsb of the second value.
//
//    So the entire pattern is:
//    B10010000,B00001001,B00000000,B00000000,B00000000,B00000000,B10010000,B00001001,
//     |      |  |     ||                                          |      |  |     ||
//     |      |  |     |\ D15 bottom plane                         |      |  |     |\ D15 top plane
//     |      |  |     \ D14 bottom plane                          |      |  |     \ D14 top plane
//     |      |  \ D8 bottom plane                                 |      |  \ D8 top plane
//     |      \ D7 bottom plane                                    |      \ D7 top plane
//     \ D0 bottom plane                                           \ D0 top plane
//
// Comment following in or out to switch patterns in or out
//#define SWAP   1
//#define SNAKE  1
//#define BURST  1
//#define SPIRAL 1
//#define ALT    1
unsigned char pattern[] = {
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
#ifdef SWAP
  B10010000,B00001001,B00000000,B00000000,B00000000,B00000000,B10010000,B00001001,
  B00000000,B00000000,B10010000,B00001001,B10010000,B00001001,B00000000,B00000000,
  B00000000,B00000000,B01100000,B00000110,B01100000,B00000110,B00000000,B00000000,
  B01100000,B00000110,B00000000,B00000000,B00000000,B00000000,B01100000,B00000110,
  B00001001,B10010000,B00000000,B00000000,B00000000,B00000000,B00001001,B10010000,
  B00000000,B00000000,B00001001,B10010000,B00001001,B10010000,B00000000,B00000000,
  B00000000,B00000000,B00000110,B01100000,B00000110,B01100000,B00000000,B00000000,
  B00000110,B01100000,B00000000,B00000000,B00000000,B00000000,B00000110,B01100000,
#endif
#ifdef SNAKE
  B11001100,B00000000,B11001100,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B11001100,B00000000,B11001100,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B11001100,B00000000,B11001100,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00001100,B11000000,B00001100,B11000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B11001100,B00000000,B11001100,
  B00000000,B00000000,B00000000,B00000000,B00000000,B01100110,B00000000,B01100110,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00110011,B00000000,B00110011,
  B00000000,B00000000,B00000000,B00110011,B00000000,B00110011,B00000000,B00000000,
  B00000000,B00110011,B00000000,B00110011,B00000000,B00000000,B00000000,B00000000,
  B00000011,B00110000,B00000011,B00110000,B00000000,B00000000,B00000000,B00000000,
  B00110011,B00000000,B00110011,B00000000,B00000000,B00000000,B00000000,B00000000,
  B01100110,B00000000,B01100110,B00000000,B00000000,B00000000,B00000000,B00000000,
#endif
#ifdef BURST
  B00000000,B00000000,B00000110,B01100000,B00000110,B01100000,B00000000,B00000000,
  B00000110,B01100000,B01101001,B10010110,B01101001,B10010110,B00000110,B01100000,
  B01101001,B10010110,B10010000,B00001001,B10010000,B00001001,B01101001,B10010110,
  B10010000,B00001001,B00000000,B00000000,B00000000,B00000000,B10010000,B00001001,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
#endif
#ifdef SPIRAL
  B11001100,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B01100110,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00110011,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000011,B00110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00110011,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B01100110,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B11001100,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00001100,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B11001100,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B11001100,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B01100110,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00110011,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000011,B00110000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00110011,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B01100110,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B11001100,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00001100,B11000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B11001100,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B11001100,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B01100110,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00110011,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00000011,B00110000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00110011,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B01100110,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B11001100,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00001100,B11000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B11001100,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B11001100,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B01100110,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110011,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000011,B00110000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110011,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B01100110,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B11001100,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00001100,B11000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B11001100,B00000000,
#endif
#ifdef ALT
  B11111001,B10011111,B10010000,B00001001,B10010000,B00001001,B11111001,B10011111,
  B00000110,B01100000,B01101001,B10010110,B01101001,B10010110,B00000110,B01100000,
  B00000000,B00000000,B00000110,B01100000,B00000110,B01100000,B00000000,B00000000,
  B00000110,B01100000,B01101001,B10010110,B01101001,B10010110,B00000110,B01100000,
#endif
};
int patternNumber=0;
int numPatterns=sizeof(pattern)/8;
int tickCount=0;
int tickCountMax=50;      // How many times to loop before changing the pattern
unsigned int currentPattern[4];

/*
  Protocol for sending the data to the hc595 is as follows:
   (see: http://www.arduino.cc/en/Tutorial/ShiftOut)
   
   "when the clock pin goes from low to high, the shift register
    reads the state of the data pin ... when the latch pin goes
    from low to high the sent data gets moved from the shift
    registers ... to the output pins"
   
   As we have two HC595s chained together, we use a 16 bit input value
*/
void write_74HC595 (unsigned int hc595value) { 
   digitalWrite(HC595_latchPin, LOW);   // ensures LEDs don't light whilst changing values
//   digitalWrite(HC595_enablePin, HIGH); // OE is negative logic
   
   // Shift each 8 bit value in sequence - the two chained HC595s automatically grab
   // the right bits - the first 8 to the first chip, second 8 to the second chip
   shiftOut(HC595_dataPin, HC595_clockPin, LSBFIRST, hc595value);  
   shiftOut(HC595_dataPin, HC595_clockPin, LSBFIRST, (hc595value >> 8));
   
   digitalWrite(HC595_latchPin, HIGH);  // data transferred from shift register to outputs when latch goes LOW->HIGH
//   digitalWrite(HC595_enablePin, LOW);  // re-enable (negative logic again)
}

/*
  Inputs: Array of 4 16-bit integers - one for each plane
*/
void ics444_display (unsigned int *pPattern) {
    HC595_display[0]=pPattern[0];
    HC595_display[1]=pPattern[1];
    HC595_display[2]=pPattern[2];
    HC595_display[3]=pPattern[3];
}

int ics444_scan_count=0;
void ics444_display_scan () {
  if (ics444_scan_count == 0) {
    digitalWrite(LED_Plane[0], HIGH);
    digitalWrite(LED_Plane[1], HIGH);
    digitalWrite(LED_Plane[2], HIGH);
    digitalWrite(LED_Plane[3], HIGH);
    write_74HC595 (HC595_display[0]);
    digitalWrite(LED_Plane[0], LOW);
    ics444_scan_count++;
  }
  else if (ics444_scan_count == 1) {
    digitalWrite(LED_Plane[0], HIGH);
    digitalWrite(LED_Plane[1], HIGH);
    digitalWrite(LED_Plane[2], HIGH);
    digitalWrite(LED_Plane[3], HIGH);
    write_74HC595 (HC595_display[1]);
    digitalWrite(LED_Plane[1], LOW);
    ics444_scan_count++;
  }
  else if (ics444_scan_count == 2) {
    digitalWrite(LED_Plane[0], HIGH);
    digitalWrite(LED_Plane[1], HIGH);
    digitalWrite(LED_Plane[2], HIGH);
    digitalWrite(LED_Plane[3], HIGH);
    write_74HC595 (HC595_display[2]);
    digitalWrite(LED_Plane[2], LOW);
    ics444_scan_count++;
  }
  else {  
    digitalWrite(LED_Plane[0], HIGH);
    digitalWrite(LED_Plane[1], HIGH);
    digitalWrite(LED_Plane[2], HIGH);
    digitalWrite(LED_Plane[3], HIGH);
    write_74HC595 (HC595_display[3]);
    digitalWrite(LED_Plane[3], LOW);
    ics444_scan_count = 0;
  }
}

/*
  Inputs: Array of 8 8-bit integers - two for each plane
*/
void ics444_displayBytes (byte *pPattern)
{
    unsigned int pattern[4];
    pattern[0] = pPattern[0]*256 + pPattern[1];
    pattern[1] = pPattern[2]*256 + pPattern[3];
    pattern[2] = pPattern[4]*256 + pPattern[5];
    pattern[3] = pPattern[6]*256 + pPattern[7];
    ics444_display (&pattern[0]);    
}

void ics444_setup() {
  // put your setup code here, to run once:
  pinMode( HC595_latchPin,  OUTPUT );
  pinMode( HC595_clockPin,  OUTPUT );
  pinMode( HC595_dataPin,   OUTPUT );
  pinMode( HC595_enablePin, OUTPUT );
  
  pinMode( LED_Pin16, OUTPUT );
  pinMode( LED_Pin17, OUTPUT );
  pinMode( LED_Pin18, OUTPUT );
  pinMode( LED_Pin19, OUTPUT );
  
  digitalWrite(LED_Pin16,HIGH);
  digitalWrite(LED_Pin17,HIGH);
  digitalWrite(LED_Pin18,HIGH);
  digitalWrite(LED_Pin19,HIGH);
//  digitalWrite(HC595_enablePin, LOW);  // Enable Not OE (negative logic)

  HC595_display[0]=0;
  HC595_display[1]=0;
  HC595_display[2]=0;
  HC595_display[3]=0;

  // Use Timer1 on the Ardunio to trigger the update scan in the background 
  Timer1.initialize(1000);
  Timer1.attachInterrupt (ics444_display_scan);

  patternNumber=0;
  tickCount = tickCountMax;
}

// To be called from the loop()
void ics444_demo ()
{
  int i;
  // only update it every tick otherwise just display as is
  tickCount--;
  if (tickCount <= 0)
  {
    tickCount = tickCountMax;
    for (i=0; i<4; i++)
    {
      currentPattern[i] = pattern[i*2 + patternNumber*8] * 256 + pattern[i*2 + 1 + patternNumber*8];
    }
    patternNumber++;
    if (patternNumber >= numPatterns)
    {
      patternNumber = 0;
    }
  }
  ics444_display(&currentPattern[0]);
}

icstation4x4x4.h:

#ifndef ICS444_H
#define ICS444_H
#include <arduino.h>

// To be called from the setup() routine
void ics444_setup(void);

// Perform one scan of the cube to display a complete pattern
//    Inputs: Four 16 bit integers, one for each plane of the cube
//    Order: Bottom plane to top plane, D0 to D15
void ics444_display (unsigned int *pPattern);

// As above, but takes the input as eight 8 bit values instead
// that is two per plane (ordered bottom to top once again)
void ics444_displayBytes (byte *pPattern);

// Displays the built-in patterns
// To be called from the loop() routine
// NB: Need to make sure the patterns are not commented out
//        in icstation4x4x4.cpp otherwise, none will be included
void ics444_demo (void);
#endif
maker

Cheap Max7219 Chainable LED Matrix

I can’t resist a cheap LED matrix, so when I stumbled across these 8×8 LED matrix displays with a Max7219 driver LED in this chainable form-factor for, get this, less than £1.50 each from electrodragon.com … well, I had to give them a go.  It is a relatively simple circuit board to build, so there are very minimal instructions, but there are still a couple of gotchas that might catch out a beginner, so I’ve put together these notes whilst putting them together.  By the way, I ordered 9 so I could eventually form a 24×24 LED square (3×3 of the matrices).

2015-08-07 13.06.582015-08-07 13.07.22

I started with the headers, then the discrete components, finally the chip.  The only thing to note is the polarity of the electrolytic capacitor (of course – look for the + on the circuit board) and the orientation of the chip itself.  Also note that ‘pin 1’ of the LED matrix sockets are indicated by a square pad in the top right of the circuit board (as seen from the top, with the writing the right way up).  It is worth fiddling with the electrolytic prior to soldering to try to ensure it doesn’t poke out over the top edge of the circuit board – although if it does, if physically mounting boards next to each other, it will quite happily overlap into the next board.

The design suggests that all the header pins face upwards and that the jumpers are used on the top of the board to chain them together.  however, I didn’t really want to have to take off the LED matrix every time I wanted to change how it was linked, so I opted to solder the connecting header pins to the underside of the board as shown.  It also gets around the issue they describe on the product webpage about the LED matrix not really fitting snugly on the board.  Mine fits nice and tight.

2015-08-07 17.44.28-sm

So all that remains is to add the LED matrix.  As I said, pin 1 should be indicated on the matrix itself and is indicated on the circuit board by the square pad near the electrolytic capacitor.

In terms of making the thing work, it is relatively simple to connect up:

  • CLK – D2
  • LD – D3
  • DIN – D4
  • VCC – VCC
  • GND – GND

Of course when chaining with jumpers DOUT goes to the next LED DIN.  The other pins pair up.

There is a lot of arduino code for these types of driver chips – start here – http://playground.arduino.cc/Main/LEDMatrix.

I used the code from here to test my setup – http://playground.arduino.cc/LEDMatrix/Max7219 – as written this assumes the same pinouts as I describe above (i.e. CLK, LD, DIN on digital pins 2, 3 and 4).

You just need to set the number of chained displays at the top:

int maxInUse = 9;

(in my case) and get to work playing.  The routines in the library provide a simple interface to setting rows on a single or all of the chained displays.  maxSingle is meant for when there is just one display.  maxAll displays the same value on all displays in the chain.  maxOne will target a specific display (starting with display number 1 up to your last – 9 in my case).

2015-08-08 16.02.16-sm

As you can perhaps see, this is using an Ardunio nano.  With 9 boards cascaded, getting the PC to recognise the nano was plugged in was sometimes a problem – it often gave me a ‘there is a problem with your USB device’ error on Windows 7.  It was fine with lesser numbers of matrices, so I guess there is a power issue with the nano struggling with the USB setup and initialising all 9 LED matrices at the same time.  Temporarily disconnecting VCC from the LEDs when plugging in the USB seems to solve the issue for me.

As I eventually want to be setting an entire row of LEDs in a larger grid, the maxOne function is a little wasteful as it has to shunt null values to all of the LED displays you are not targeting – so calling it 9 times means writing 81 bytes out across the DIN pin just to actually set 9 bytes.  Consequently it is possible to optimise it a little if you want to write an entire row to all displays in the same transaction.

Of course, if you refer back to the LedMatrix page, there are many other libraries that will do most of this for you, including Marco’s very complete library for scrolling text displays – http://parola.codeplex.com/ – but I quite like being able to see what the low-level code is actually doing to make things work.

I’ve therefore added a maxRow function as follows:

// Note: Sloppy code warning!
// There is no checking here that *col is an array of
// the correct length - i.e. maxInUse
//
// It should be defined and used as follows:
//    byte row[maxInUse];
//    // fill each byte of row with your data - row[0], row[1], row[2], etc.
//    // using one byte for each matrix in sequence
//    maxRow (1, &row[0]);
//
void maxRow (byte reg, byte *col) {
  int c=0;
  digitalWrite(load,LOW);
  for (c=maxInUse; c>=1; c--) {
    putByte(reg);
    putByte(col[c-1]);
  }
  digitalWrite(load,LOW);
  digitalWrite(load,HIGH);
}

But I haven’t mentioned the absolutely best feature of these little boards yet.  And that is that they are almost exactly the same dimension as a 4-stud Lego brick.  This means it was trivial to make a simple enclosure to hold my 3×3 grid and the nano.

2015-08-10 21.12.11 2015-08-10 21.12.04

I now have a really cool game-of-life running on my 24×24 LED grid.  At this price, I have another 8 on order so I can take it to a 4×4 grid (with one spare).

Kevin

maker

icStation 4x4x4 LED Cube Shield for Arduino – Software

As described in my previous post, I now have a working (I’ve run the demo code!) icStation 4x4x4 LED cube shield connected up to an Arduino Uno.  Unfortunately as I started to dig around in the sample library, I just couldn’t quite work out how it was meant to address each plane of the cube.  Eventually I decided it wasn’t possible, due to a number of bugs in the code.

The demo code worked as the code just showed the same pattern on each of the four planes.  This is largely because the initialisation routines set the four Ardunio data pins to low, and then never did anything else with them!  There is some mention of pins 16 and 17, but no mention of pins 18 and 19, and actually in the main display routines, there is nothing done to actually set any of these pins once initialisation is complete.  So at this point I decided to read a bit more about the 74HC595 and just go it alone.  Attached is the result.  Feel free to use as you see fit.

Basic initialisation

The two shift registers seem fairly simple to setup.  The Arduino needs some control pins setting up as outputs alongside the four digital outs used for the LEDs.  The initialisation is quite straight forward as follows:

#include <Arduino.h>

int HC595_clockPin=0;   // SH_CP of 74HC595 
int HC595_latchPin=1;   // ST_CP of 74HC595 
int HC595_dataPin=3;    // DS of 74HC595 
int HC595_enablePin=2;  // Not OE of 74HC595
int LED_Pin16= 4;
int LED_Pin17= 5;
int LED_Pin18= 6;
int LED_Pin19= 7;

void setup() {
  // put your setup code here, to run once:
  pinMode( HC595_latchPin,  OUTPUT );
  pinMode( HC595_clockPin,  OUTPUT );
  pinMode( HC595_dataPin,   OUTPUT );
  pinMode( HC595_enablePin, OUTPUT );
  
  pinMode( LED_Pin16, OUTPUT );
  pinMode( LED_Pin17, OUTPUT );
  pinMode( LED_Pin18, OUTPUT );
  pinMode( LED_Pin19, OUTPUT );
  
  digitalWrite(LED_Pin16,HIGH);
  digitalWrite(LED_Pin17,HIGH);
  digitalWrite(LED_Pin18,HIGH);
  digitalWrite(LED_Pin19,HIGH);
//  digitalWrite(HC595_enablePin, LOW);  // Enable Not OE (negative logic)
}

I never did quite work out if I needed to do anything with the enable pin, but as it is active low, I assumed that by not doing anything, everything would be enabled by default anyway.

In terms of actually writing a value to the 74HC595, again that is fairly straight forward.

/*
  Protocol for sending the data to the hc595 is as follows:
   (see: http://www.arduino.cc/en/Tutorial/ShiftOut)
   
   "when the clock pin goes from low to high, the shift register
    reads the state of the data pin ... when the latch pin goes
    from low to high the sent data gets moved from the shift
    registers ... to the output pins"
   
   As we have two HC595s chained together, we use a 16 bit input value
*/
void write_74HC595 (unsigned int hc595value) { 
   digitalWrite(HC595_latchPin, LOW);   // ensures LEDs don't light whilst changing values
   
   // Shift each 8 bit value in sequence - the two chained HC595s automatically grab
   // the right bits - the first 8 to the first chip, second 8 to the second chip
   shiftOut(HC595_dataPin, HC595_clockPin, LSBFIRST, hc595value);  
   shiftOut(HC595_dataPin, HC595_clockPin, LSBFIRST, (hc595value >> 8));
   
   digitalWrite(HC595_latchPin, HIGH);  // data transferred from shift register to outputs when latch goes LOW->HIGH
}

So 16 bits of the hc595value variable are sent in two 8-bit chunks over the serial port to the shift registers and with the appropriate signalling via the latch pin, that is basically that.  This sets the outputs of the 74HC595 to high, but in order to make the LEDs come on, the Arduino data pins corresponding to the horizontal planes must be set low.  If a different pattern is required for each plane, then some kind of simple ‘scanning’ is required as illustrated below.

int LED_Plane[] = {LED_Pin16, LED_Pin17, LED_Pin18, LED_Pin19};

/*
  Inputs: Array of 4 integers - one for each plane
*/
void display (unsigned int *pPattern)
{
  int i;
  for (i=0; i<4; i++)
  {
    int j;
    for (j=0; j<1000; j++)
    {
      // Slow this down so that there is time for the LEDS to light
      // Experimentation shows that 200+ gives brighter LEDs
      // NB: Do it this way so an empty loop isn't optimised out
      if (j==0)
      {
        digitalWrite(LED_Plane[0], HIGH);
        digitalWrite(LED_Plane[1], HIGH);
        digitalWrite(LED_Plane[2], HIGH);
        digitalWrite(LED_Plane[3], HIGH);
        write_74HC595 (pPattern[i]);
        digitalWrite(LED_Plane[i], LOW);
      }
    }
  }
}

I guess the only thing to really note here is that without the loop involving j then the ‘scanning’ was happening too fast to allow the LEDs to have any significant brightness at all.  I initially had a simple for (j=0; j<1000; j++) {}; statement at the end, but this seemed to be optimised out – at least it didn’t seem to have the delay effect I wanted, so instead I made the code only act on one pass through the loop.

So, with these basics, it is now possible to get a 64 bit pattern, in the form of four 16 bit values, each representing one plane of 16 LEDs, onto the cube.

I have the full demo code file below – it isn’t massively pretty, and its not cpp (sorry – I’ve always been more of a C person I’m afraid, and my C is a little rusty), but it works for me.  Your proverbial mileage, as they say, may vary.

Kevin

#include <Arduino.h>

int HC595_clockPin=0;   // SH_CP of 74HC595 
int HC595_latchPin=1;   // ST_CP of 74HC595 
int HC595_dataPin=3;    // DS of 74HC595 
int HC595_enablePin=2;  // Not OE of 74HC595
int LED_Pin16= 4;
int LED_Pin17= 5;
int LED_Pin18= 6;
int LED_Pin19= 7;
int LED_Plane[] = {LED_Pin16, LED_Pin17, LED_Pin18, LED_Pin19};

// Each line (8 bytes) is an entire cube, with two consecutive bytes per plane of LEDS,
// and 16 LEDS per plane. LEDs are encoded in the following order:
//    Lowest plane byte 1, lowest plane byte 2, second lowerst plane 1, then 2,
//    second from top plane 1, then 2, highest plane 1, highest plane 2.
//
//    Each plane is encoded looking at the Arduino oriented with the USB/power
//    designated by 'south' by started 'north west' as follows:
//        D0    D1    D2    D3
//        D4    D5    D6    D7
//        D8    D9    D10   D11
//        D12   D13   D14   D15
//
//        D16   D17   D18   D19
//          (USB)      (Power)
//    With D16 being the lowest plane, through to D19 being the highest plane
//    Of course, if you wire the planes up differently, that is up to you!
//
//    Each two bytes of the pattern are therefore:
//        B00000000, B00000000 -> D0-7, D8-15
//    with D0 = msb of the first value, D7 being the lsb of the first value,
//    and  D8 = msb of the second value, D15 being the lsb of the second value.
//
//    So the entire pattern is:
//    B10010000,B00001001,B00000000,B00000000,B00000000,B00000000,B10010000,B00001001,
//     |      |  |     ||                                          |      |  |     ||
//     |      |  |     |\ D15 bottom plane                         |      |  |     |\ D15 top plane
//     |      |  |     \ D14 bottom plane                          |      |  |     \ D14 top plane
//     |      |  \ D8 bottom plane                                 |      |  \ D8 top plane
//     |      \ D7 bottom plane                                    |      \ D7 top plane
//     \ D0 bottom plane                                           \ D0 top plane
//
// Comment following in or out to switch patterns in or out
#define SWAP   1
#define SNAKE  1
#define BURST  1
#define SPIRAL 1
#define ALT    1
unsigned char pattern[] = {
#ifdef SWAP
  B10010000,B00001001,B00000000,B00000000,B00000000,B00000000,B10010000,B00001001,
  B00000000,B00000000,B10010000,B00001001,B10010000,B00001001,B00000000,B00000000,
  B00000000,B00000000,B01100000,B00000110,B01100000,B00000110,B00000000,B00000000,
  B01100000,B00000110,B00000000,B00000000,B00000000,B00000000,B01100000,B00000110,
  B00001001,B10010000,B00000000,B00000000,B00000000,B00000000,B00001001,B10010000,
  B00000000,B00000000,B00001001,B10010000,B00001001,B10010000,B00000000,B00000000,
  B00000000,B00000000,B00000110,B01100000,B00000110,B01100000,B00000000,B00000000,
  B00000110,B01100000,B00000000,B00000000,B00000000,B00000000,B00000110,B01100000,
#endif
#ifdef SNAKE
  B11001100,B00000000,B11001100,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B11001100,B00000000,B11001100,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B11001100,B00000000,B11001100,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00001100,B11000000,B00001100,B11000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B11001100,B00000000,B11001100,
  B00000000,B00000000,B00000000,B00000000,B00000000,B01100110,B00000000,B01100110,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00110011,B00000000,B00110011,
  B00000000,B00000000,B00000000,B00110011,B00000000,B00110011,B00000000,B00000000,
  B00000000,B00110011,B00000000,B00110011,B00000000,B00000000,B00000000,B00000000,
  B00000011,B00110000,B00000011,B00110000,B00000000,B00000000,B00000000,B00000000,
  B00110011,B00000000,B00110011,B00000000,B00000000,B00000000,B00000000,B00000000,
  B01100110,B00000000,B01100110,B00000000,B00000000,B00000000,B00000000,B00000000,
#endif
#ifdef BURST
  B00000000,B00000000,B00000110,B01100000,B00000110,B01100000,B00000000,B00000000,
  B00000110,B01100000,B01101001,B10010110,B01101001,B10010110,B00000110,B01100000,
  B01101001,B10010110,B10010000,B00001001,B10010000,B00001001,B01101001,B10010110,
  B10010000,B00001001,B00000000,B00000000,B00000000,B00000000,B10010000,B00001001,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
#endif
#ifdef SPIRAL
  B11001100,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B01100110,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00110011,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000011,B00110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00110011,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B01100110,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B11001100,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00001100,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B11001100,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B11001100,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B01100110,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00110011,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000011,B00110000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00110011,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B01100110,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B11001100,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00001100,B11000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B11001100,B00000000,B00000000,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B11001100,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B01100110,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00110011,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00000011,B00110000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00110011,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B01100110,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B11001100,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00001100,B11000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B11001100,B00000000,B00000000,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B11001100,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B01100110,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110011,B00000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000011,B00110000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110011,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B01100110,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B11001100,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00001100,B11000000,
  B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B11001100,B00000000,
#endif
#ifdef ALT
  B11111001,B10011111,B10010000,B00001001,B10010000,B00001001,B11111001,B10011111,
  B00000110,B01100000,B01101001,B10010110,B01101001,B10010110,B00000110,B01100000,
  B00000000,B00000000,B00000110,B01100000,B00000110,B01100000,B00000000,B00000000,
  B00000110,B01100000,B01101001,B10010110,B01101001,B10010110,B00000110,B01100000,
#endif
};
int patternNumber=0;
int numPatterns=sizeof(pattern)/8;
int tickCount=0;
int tickCountMax=50;      // How many times to loop before changing the pattern
unsigned int currentPattern[4];

void setup() {
  // put your setup code here, to run once:
  pinMode( HC595_latchPin,  OUTPUT );
  pinMode( HC595_clockPin,  OUTPUT );
  pinMode( HC595_dataPin,   OUTPUT );
  pinMode( HC595_enablePin, OUTPUT );
  
  pinMode( LED_Pin16, OUTPUT );
  pinMode( LED_Pin17, OUTPUT );
  pinMode( LED_Pin18, OUTPUT );
  pinMode( LED_Pin19, OUTPUT );
  
  digitalWrite(LED_Pin16,HIGH);
  digitalWrite(LED_Pin17,HIGH);
  digitalWrite(LED_Pin18,HIGH);
  digitalWrite(LED_Pin19,HIGH);
//  digitalWrite(HC595_enablePin, LOW);  // Enable Not OE (negative logic)

  patternNumber=0;
  tickCount = tickCountMax;
}

/*
  Protocol for sending the data to the hc595 is as follows:
   (see: http://www.arduino.cc/en/Tutorial/ShiftOut)
   
   "when the clock pin goes from low to high, the shift register
    reads the state of the data pin ... when the latch pin goes
    from low to high the sent data gets moved from the shift
    registers ... to the output pins"
   
   As we have two HC595s chained together, we use a 16 bit input value
*/
void write_74HC595 (unsigned int hc595value) { 
   digitalWrite(HC595_latchPin, LOW);   // ensures LEDs don't light whilst changing values
//   digitalWrite(HC595_enablePin, HIGH); // OE is negative logic
   
   // Shift each 8 bit value in sequence - the two chained HC595s automatically grab
   // the right bits - the first 8 to the first chip, second 8 to the second chip
   shiftOut(HC595_dataPin, HC595_clockPin, LSBFIRST, hc595value);  
   shiftOut(HC595_dataPin, HC595_clockPin, LSBFIRST, (hc595value >> 8));
   
   digitalWrite(HC595_latchPin, HIGH);  // data transferred from shift register to outputs when latch goes LOW->HIGH
//   digitalWrite(HC595_enablePin, LOW);  // re-enable (negative logic again)
}

/*
  Inputs: Array of 4 integers - one for each plane
*/
void display (unsigned int *pPattern)
{
  int i;
  for (i=0; i<4; i++)
  {
    int j;
    for (j=0; j<1000; j++)
    {
      // Slow this down so that there is time for the LEDS to light
      // Experimentation shows that 200+ gives brighter LEDs
      // NB: Do it this way so an empty loop isn't optimised out
      if (j==0)
      {
        digitalWrite(LED_Plane[0], HIGH);
        digitalWrite(LED_Plane[1], HIGH);
        digitalWrite(LED_Plane[2], HIGH);
        digitalWrite(LED_Plane[3], HIGH);
        write_74HC595 (pPattern[i]);
        digitalWrite(LED_Plane[i], LOW);
      }
    }
  }
}

void displayPattern ()
{
  int i;
  // only update it every tick otherwise just display as is
  tickCount--;
  if (tickCount <= 0)
  {
    tickCount = tickCountMax;
    for (i=0; i<4; i++)
    {
      currentPattern[i] = pattern[i*2 + patternNumber*8] * 256 + pattern[i*2 + 1 + patternNumber*8];
    }
    patternNumber++;
    if (patternNumber >= numPatterns)
    {
      patternNumber = 0;
    }
  }
  display(&currentPattern[0]);
}

void loop() {
  // put your main code here, to run repeatedly:
  displayPattern();
}
maker

icStation 4x4x4 LED Cube Shield for Arduino – Hardware Build

I like LEDs – always have enjoyed flashing a few lights, so after playing around with various LED matrices I thought it was time to go 3D.  My electronics aren’t quite up to fully going it alone, so I after some googling around, I found the icStation 4x4x4 LED cube shield for the Arduino, which seemed just the thing for me – cheap (less than $6!  Yes, really!), not too difficult looking to solder, and all nicely shield-shaped to make it easy to programme.

Unfortunately, whilst now it is all up and running, and the shield is great, I was really struggling with the provided build instructions and sample code.  By way of example, the instructions document is called the ‘welding guide’ rather than soldering – so you get the idea, it has been translated over to English, which means there is not a little head-scratching before you are actually able to proceed.  With that in mind, I thought I’d make a few notes here in case anyone else has one of these and too is trying to work out quite what the thinking behind it was!

The shield is based around the 75HC595 shift register – so if you want to know the general principles, there are a number of tutorials around – I suggest reading this generic Arduino ShiftOut Tutorial to get the hang of the basics.  The basic circuit is relatively straight forward once you’ve spotted how it works.  In a nutshell, the main principles are as follows:

  • The 74HC595 provides a means to connect 8 LEDs to the Arduino serial port.
  • There are two 74HC595s chained together, which means you can control 16 LEDs at a time.
  • The Arduino serial port is used to connect to the two 74HC595 chips, so pumping a 16-bit value down the serial port, with appropriate signalling on a couple of control lines, will set the outputs of the 74HC595.
  • The outputs of the HC74595 are connected to each ‘column’ of LEDs (via their anodes).
  • Four Arduino digital out pins are used to connect to each ‘plane’ of LEDs (via their cathodes).
  • Loading the shift registers chooses which columns to illuminate and bringing some of the four Arduino digital pins low will select which planes of LEDs will actually light up.
  • If you want different patterns on each plane of LEDs, then you have to ‘scan’ them by setting the data values, and manipulating the Arduino digital out pins accordingly.

This is all apparent if you follow the tutorials, read up a bit about the 74HC595 and follow the circuit provided in the kit.  But I’m afraid to say, I didn’t get much from the example code and library provided, which seemed to me to have significant bugs in the handling of the selecting of the planes of LEDs!  Consequently, I wrote my own, which I’ll provide in another post.

Building the Cube

But first, some hints on actually building the thing.  The guide for constructing the main shield is fairly straight forward, so I’ll not repeat that here.  I thought it was quite a neat idea to use header sockets with the plastic removed as sockets for the LED legs, but breaking the plastic housing away did often mean one of the bare sockets might ping out of my wire cutters across the room! Good job there were a few spares.  Remember to put the two ICs in their sockets though before you attempt to connect up LEDs – I didn’t and nearly mangled my cube trying to insert them afterwards!

Somewhere I read that it is a good idea to test your LEDs with a 3v button cell.  I know there are 64 to do, but it is very quick to test them by slipping the button cell between the two legs, observing the correct polarity of course, and checking they work, so I did this – all good.

It did take me a good few minutes however to realise that the build instructions suggest making a frame to hold the LEDs whilst they are being soldered together.  In fact, the design suggests that you construct a vertical slice of four columns of LEDs at a time, in order to get them nicely aligned.  Extra header pin connectors are provided for this purpose, but rather than make a frame by soldering these to some stripboard, which seems to be the suggestion, I just stuck them into some breadboard as shown below.

01-breadboard and first LEDs

Note that I’ve used two breadboards together, after having first remove some of the power rails.  I took the advice of the ‘welding guide’ and placed the header pins 2cm apart and am placing the LEDs side-on, so that, again as suggested in the guide, with the cathodes (shorter legs) bent at 90% as shown.  I soldered them a single horizontal row at a time, by soldering the bent legs together.  Then, once two rows were made, connected the vertical legs together, thus building up each vertical plane of four columns as shown in the following pictures.

02-LED rows 03-all four rows

The end result is a single slice through the cube consisting of 16 LEDs in a 4×4 plane as follows.  You can start to see how the ‘matrix’ effect will work, as there should be four vertical connections and four horizontal connections, with an LED straddling each cross-over point. Naturally the verticals and horizontals themselves don’t touch otherwise it would short out across the LED.

04-LED vertical plane

Now at this point, I decided to test them all again, by connecting my 3v button cell between each vertical and horizontal end point.  Good job I did, as after constructing my second plane, I found one of the LEDs wasn’t working, so I unsoldered it and replaced it – thankfully the kit came with spares.  Eventually I had all four vertical planes, each with 16 LEDs in a 4×4 grid, finished as follows.

05 - all four planes

So, each of the ‘down’ legs will get plugged into one of the 16 header sockets already soldered to the shield connecting them to the data pins of the two 74HC595s.  But the ‘across’ legs need to be connected together somehow to form a single conductive plane, so that each horizontal plane can then be connected to one of the four Ardunio digital out pins to complete the circuit.  In order to form the horizontal planes, I bent the surplus bent LED legs over as follows:

06 - bending spare legs

Then I could start assembling each vertical plane by inserting them into the shield, one at a time:

07 - assembling columns08 - four columns 09 - soldered planes

Note that I left the last set ‘unbent’, but the others I then soldered together to form the horizontal planes.  This proved a bit tricky mind, as I ended up with three legs all coming together pretty much in the same place – so as I was attempting to solder the new horizontal in place, I was as often as not, also unsoldering the existing LED connection I had already made.  There may well be a better way to do this – maybe using jumper wires across the other end of each plane instead of the spare LED legs … but anyway, I managed it eventually.

At this point, it was worth checking the connections of each plane with that 3v button cell again (although I didn’t check all 64 combinations again – just a token few in each horizontal plane).  Finally, I used some jumper wires to connect each horizontal plane to one of the header sockets connected to the Arduino data pins.  On the diagrams these were labelled D16 to D19.  I connected the top plane to D16, then moved down for D17, D18 and connected the bottom plane to D19.  There was no guidance as to which way round it should be, but as we’ll see later, due to bugs in the library it made no difference anyway!  Ultimately it is down to you coding it the same way around as your wiring.

10 - jumper wires - side11 - jumper wires - front

And that was construction pretty much complete.  At this point you should be able to plug it into your Arduino Uno, download the sample code from icStation (really though – a rar file?) and load up the demo and you should see lights flashing and be able to spot if anything isn’t quite right.

And then things got a bit complicated – but more on that in my next post.

Kevin

maker

Using Maplins N00GA or AM03127 LED Display as an LED Matrix

One of the reasons for playing around with my N00GA (the AM03127 based LED sign you can get from Maplins) was to see if I could get to use it as a large LED matrix rather than as a scrolling text sign.  You can, but it is a bit involved.

As described in my previous post, the sign can be controlled over RS232, but to use it as a raw matrix, you need to do the following:

  • Send a message to the sign to define a series of graphic blocks
  • Send a message to the sign to display the graphic blocks

The technical manual does a pretty good job of telling you how to define the graphic blocks, but it is not a complete description and a bit of experimentation is in order.   Whilst there is some code around that appears to include functions to define and show graphic blocks, again there isn’t much by way of explanation of how it works, so here goes.

First, from the manual …

N00GA-DefineGraphicBlock1 N00GA-DefineGraphicBlock2

So what this means is that each block of 4 LEDs is encoded into a single byte, with 2 bits per LED.  The values for each LED are as follows:

00 - Off
01 - Green
10 - Red
11 - Both (i.e. orange/yellow)

Each graphic block is defined in an 8×8 chunk, and there are 4 of these 8×8 chunks in each graphics block – as per the diagram in the manual.  So that there are so that there are 32×8 “pixels” in each graphic block.
Each block itself is defined using a <Gpn> command and then incorporated into a message to be displayed so it is trivial to define one block and repeat it many times in the display (once you realise these are two different commands).

So, for the first block of 8×8 LEDs:

Row 1 Byte 1 = D0-D3 Byte 2 = D4-D7
Row 2 Byte 3 = D8-D11 Byte 4 = D12-15
Row 3 Byte 5 = D16-D19 Byte 6 = D20-23
Row 4 Byte 7 = D24-D27 Byte 8 = D28-D31
Row 5 Byte 9 = D32-D35 Byte 10 = D36-D39
Row 6 Byte 11 = D40-D43 Byte 12 = D44-D47
Row 7 Byte 13 = D48-D51 Byte 14 = D52-D55
Row 8 Byte 15 = D56-D59 Byte 16 = D60-D63

In my case, there is no Row 8 for the N00GA – it is a 80×7 display, so these two bytes are ignored (but still required).

  • Byte 17 -> Byte 32 is the next 8×8 rows (with Bytes 31+32 unused)
  • Byte 33 -> Byte 48 is the next 8×8 rows (with Bytes 47+48 unused)
  • Byte 49 -> Byte 64 is the last 8×8 rows (with Bytes 63+64 unused)

Now this is the bit that no-one seems to mention in anything I’ve found discussing these modules so far.

Whilst most of the protocol for driving these devices via RS232 uses ASCII encoded messages, this bit does not! These are encoded sequentially as raw binary values (not encoded into ASCII as dec or hex) so they will go over the serial line using the numerical range 0..255.

Yes, if echoed to a terminal, you’d see everything from NULL (0, 0x00) through the control characters right up to DEL (127, 0x7F) and then beyond using the extended 0x80-0xFF range.

So to use graphics, you really have to avoid any of the useful line-terminal related options – like echo or cat and just send it out ‘as is’. For me, this mean grabbing the Perl Device::SerialPort module.

These values are written sequentially following the <GA1>, <GA2> or <GA3> command, which defines graphic blocks 1, 2 or 3 for the first page (A).

So the full command to define graphics block 1 on page A is

   <GA1>nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn

where “<” “G” “A” “1” and “>” are all the ASCII characters for <GA1> as you’d expect, but each of the 64 “n”s is a single byte “raw” value from 0x00 through to 0xFF. To encode a full 80×7 matrix, it will need GA1, GA2 and some of GA3 (GA1 = columns 1 to 32, GA2 = columns 33 to 64, GA3 = columns 65 to 80, with 81 to 96 unused).

When it comes to actually insert the character into the display, then use the defined message format to create a static display of the three now defined graphics characters as follows:

   <ID01><L1><PA><FA><MA><WA><FK><GA1><GA2><GA3>48<E>

The commands used here are:

  • L1 – Select line 1
  • PA – Select page A
  • FA – Immediate display (no scrolling, etc)
  • MA – Normal display method (no blinking or “songs” playing)
  • WA – Waiting time (0.5 seconds – not sure if this has an effect for immediate displays?)
  • FK – Hold the display static (i.e. no “lagging” command to remove the display)
  • GA – As discussed above to insert the graphic blocks.
  • 48 – checksum for the command

So all that remains is to write a function to take an 80×7 structure as an input and turn it into the three 64-byte sequences required to define GA1, GA2 and GA3 and then send this command to display them.

The following perl subroutine will take a single 8 bit value, representing one of the rows in one of the 8×8 chunks of a graphics block, and return the two raw bytes ready to be inserted into the <GAn> command. I expect there is a much more elegant way to do this, but this will do for now!

sub led2gfxblock
{
    my ($row) = @_;
    
    # Expand - each pixel is 2 bits:
    #  10 - red
    #  01 - green
    #  11 - yellow
    #  00 - black (off)
    # defining the colour here:
    my $col = 2;
    
    # D0-3 returned first, then D4-7
    my $val1=0;
    my $val2=0;
    for (my $b=0; $b<4; $b++)
    {
        if ($row & (1 << (4+$b)))
        {
            $val1 += ($col << ($b*2));
        }
        if ($row & (1 << $b))
        {
            $val2 += ($col << ($b*2));
        }
    }
    
    return chr($val1).chr($val2);
}

Then something will need to chop the 80×7 input into these 8 bit rows calling this subroutine as required for each value (and not forgetting to insert a dummy “row 8” value each time).

Unfortunately, so far, every time I send a new command across to define a new graphics block, the display appears to flicker … not quite sure if that can be prevented. But it seems that once it has the message to display the three graphics blocks, that message does not need sending again if the graphic block changes.

Kevin.

maker

RPI UART and Maplins N00GA or AM03127 LED Display

I have a tri-colour LED matrix sign from Maplins (a bit like this one) that has an 80×7 two-colour LED display (red and green, and when both are on, a kind of orange).  This has a serial port connection so I wanted to hook this up to my Raspberry Pi.  The original sign is supposed to come with a USB to DB9 Serial and then DB9 Serial to RJ11 set of leads to allow you to connect the sign to a PC, but mine doesn’t have these.

Now there are some excellent resources out there for programming these signs, see:

But what I wanted to do was drive this from the on-board GPIO UART from the Raspberry Pi, and I couldn’t find an obvious description of what was required to make this happen.

After much reading, searching and a bit of experimentation, eventually I managed to assemble all the bits and work through the joys of the many standard RS232 pin-outs for DB9 connectors and the non-standard RS232 pin-outs for RJ11 connectors and come up with the following.

First, to get to any kind of standard RS232 port from the GPIO UART a level shifter is required to go from the 3.3/5v RPi I/O to the +-12v required for RS232 serial ports.  Thankfully these are trivial to find based on the MAX3232 chip, so I got one that includes a built-in DB9 port and 4 pin connections for VCC, RXD, TXD, GND.

max3232

For some reason, these all seem to come with a female DB9 port, so I guess they are designed to link to a PC as if the jumper wires and MAX3232 breakout board make the equivalent of a null modem cable between the Pi and the PC.

So, then I needed to decipher the pin-outs for the RJ socket on the display.  The manual for the LED sign shows the socket view as  follows:

N00GA-SerialPort

I had an old RJ11 to RJ11 telephone cable, so I ripped the plug off one end and set about working out which wires go where.  The combination that worked for me was linking TXD on the display to TXD on the DB9 socket, RXD to RXD and GND to GND.  I think that sort of follows the diagram in the manual, but the cable colours of my telephone lead didn’t match that at all.

So, the resulting combination of pins to connectors to wires to sockets is as follows:

RPI GPIO MAX3232 DB9(F) Phone Lead RJ11 Plug AM03127 RJ11 Skt
4 VCC 1 VCC
6 GND 4 GND 5 GND Black 1 Black 4 GND (Red)
8 TXD 3 TXD 3 TXD Green 3 Green 2 TXD (Black)
10 RXD 2 RXD 2 RXD Red 2 Red 3 RXD (Green)

So once you realise that everything connects to the same labelled pin, you should be fine.

Unfortunately it is not possible to test this with a simple terminal program such as minicom, as the time between key presses is too much for the protocol to be followed (I guess if you had it line buffered it might work, but I didn’t look into this), but it is possible to “cat” some commands to the display to see if it is all working ok.

Also, you need to have disabled the RPi console, which by default will be attached to the serial port.  There are good instructions for that here.

Example – to set the ID and dim the display, create two text files with the following in them:

id.cmd:
   <ID><01><E>

dim.cmd:
   <ID01><DB>04<E>

The first sets the ID of the display to 01 (the display ID can be anything from 01 to FF, 00 means broadcast to all connected displays).  The second command sets the display to its dimmest setting.  The 04 is a checksum value and will depend on the command given.

To use, simply cat these files to the serial port:

   cat id.cmd > /dev/ttyAMA0
   cat dim.cmd > /dev/ttyAMA0

Of course, you will never see the returned acknowledgement or results, but whatever text was on the display should now be dimmed.  Ideally, use the remote control to reset the display first (FUNCT -> Setup -> Clear All -> Enter)

If this doesn’t work, you’ll need to get debugging and checking cables, etc.  One tool that is great for driving the sign programmatically is the sample python code mentioned above.  I had to install the python-serial library before using it, but then it worked very well.

   sudo apt-get install python-serial
   python am03127.py -p /dev/AMA0 -v --message Hello

The only slight quirk, is that the sample code doesn’t appear to call the sync_set_sign_id() function, so if you are not sure what ID your sign is (the default should be 01) you might need to set it using the cat method above or hack a call to the function into the code.  Having an incorrect ID may generate a “timeout” error when using the script in verbose mode (i.e. with -v as shown above).

If you want to experiment with your own commands, then you’ll have to implement something to calculate the checksums.  The following simple Perl script will take a command message for the display and output the text you’ll need for the complete command, including the checksum value.

#!/usr/bin/perl

my $str = $ARGV[0];
my @ch = split (//, $str);
my $chk =0 ;
foreach my $c (@ch)
{
    $chk ^= ord ($c);
}

printf "<ID01>$str%02X<E>\n", $chk;

So, for example, a Hello World message will be:

  <ID01><L1><PA><FE><MA><WB><FE>hello world55<E>

I now plan to get to work on the technical manual and decipher some of the more sophisticated commands, especially those that let me get graphics sent to the display.  All this was possible thanks to those who have already worked out how to drive this thing.  All I needed was the details of how to connect it physically to my  RPi GPIO.  Which I now have.

Standard disclaimer – this worked for me, YMMV, I won’t be held responsible for you pumping 12v or worse into your RPi GPIO if something goes wrong! 🙂

Kevin