Arduino to 384 LED display module (MCU Interface)

Well, it's more like an LCD display than a simple set of registers connected to LEDs. You can probably get by with a write-only setup; you write either commands that control the configuration of the display, or data that turns on and off particular LEDs. To get anything to happen at all, it looks like you have to send several configuration commands first. shiftout() probably works, but it has enough variables that I wouldn't like to try to debug it without a device in front of me...

(want to have a second display shipped to me? "Will develop open source HW/SW in exchange for toys." :slight_smile: )

There is C code for talking to the display, that accompanies their App Note HA0127E, that looks like it could be converted to arduino code without TOO much difficulty (mostly replacing direct port manipulation with digitalWrite() and similar?) Not particularly a beginner project, though; and still tough to debug without hardware.

http://www.holtek.com/english/tech/appnote/uc/pdf/ha0127e.pdf (app note. Sorta sucky.)
http://www.holtek.com/english/tech/appnote/uc/asm_zip/ha0127.zip (code, including C code.)

Hi westfw,
Pm me your address please and I'll get a board shipped out to you within the next seven days.

Just wondering if anything came of this. I ordered a board, too, and am looking for others who have tried to interface to it via the arduino. Any help appreciated.

Hi wncranger,

In exchange for figuring it out, i had a board sent to westfw. He should get to it in due course and I'll share the results with you when i hears something back :slight_smile:

Huh. This went rather more quickly than I thought it would. Practically worked the first time, even.

Here's a demo program that "does something." I expect to write a more "generally useful" library this coming holiday weekend, but this is a good start...

This is the sketch and include file. Comments to follow in a separate message.

Code demo16x24.pde:

/*
 * demo16x24.c - Arduino demo program for Holtek HT1632 LED driver chip,
 *            As implemented on the Sure Electronics DE-DP016 display board
 *            (16*24 dot matrix LED module.)
 * Nov, 2008 by Bill Westfield
 */

#include "ht1632.h"

/*
 * Set these constants to the values of the pins connected to the SureElectronics Module
 */
static const byte ht1632_data = 10;  // Data pin (pin 7)
static const byte ht1632_wrclk = 11; // Write clock pin (pin 5)
static const byte ht1632_cs = 12;    // Chip Select (1, 2, 3, or 4)
                                     // The should also be a common GND.
                                     // The module with all LEDs like draws about 200mA,
                                     //  which makes it PROBABLY powerable via Arduino +5V
/*
 * ht1632_writebits
 * Write bits (up to 8) to h1632 on pins ht1632_data, ht1632_wrclk
 * Chip is assumed to already be chip-selected
 * Bits are shifted out from MSB to LSB, with the first bit sent
 * being (bits & firstbit), shifted till firsbit is zero.
 */
void ht1632_chipselect(byte chipno)
{
  DEBUGPRINT("\nHT1632(%d) ", chipno);
  digitalWrite(chipno, 0);
}

void ht1632_chipfree(byte chipno)
{
  DEBUGPRINT(" [done %d]", chipno);
  digitalWrite(chipno, 1);
}

void ht1632_writebits (byte bits, byte firstbit)
{
  DEBUGPRINT(" ");
  while (firstbit) {
    DEBUGPRINT((bits&firstbit ? "1" : "0"));
    digitalWrite(ht1632_wrclk, LOW);
    if (bits & firstbit) {
      digitalWrite(ht1632_data, HIGH);
    } 
    else {
      digitalWrite(ht1632_data, LOW);
    }
    digitalWrite(ht1632_wrclk, HIGH);
    firstbit >>= 1;
  }
}

static void ht1632_sendcmd (byte command)
{
  ht1632_chipselect(ht1632_cs);  // Select chip
  ht1632_writebits(HT1632_ID_CMD, 1<<2);  // send 3 bits of id: COMMMAND
  ht1632_writebits(command, 1<<7);  // send the actual command
  ht1632_writebits(0, 1);       /* one extra dont-care bit in commands. */
  ht1632_chipfree(ht1632_cs); //done
}

static void ht1632_senddata (byte address, byte data)
{
  ht1632_chipselect(ht1632_cs);  // Select chip
  ht1632_writebits(HT1632_ID_WR, 1<<2);  // send ID: WRITE to RAM
  ht1632_writebits(address, 1<<6); // Send address
  ht1632_writebits(data, 1<<3); // send 4 bits of data
  ht1632_chipfree(ht1632_cs); // done
}

void setup ()  // flow chart from page 17 of datasheet
{
  pinMode(ht1632_cs, OUTPUT);
  digitalWrite(ht1632_cs, HIGH);       /* unselect (active low) */
  pinMode(ht1632_wrclk, OUTPUT);
  pinMode(ht1632_data, OUTPUT);
  ht1632_sendcmd(HT1632_CMD_SYSDIS);  // Disable system
  ht1632_sendcmd(HT1632_CMD_COMS11);  // 16*32, PMOS drivers
  ht1632_sendcmd(HT1632_CMD_MSTMD);       /* Master Mode */
  ht1632_sendcmd(HT1632_CMD_SYSON);       /* System on */
  ht1632_sendcmd(HT1632_CMD_LEDON);       /* LEDs on */
  for (byte i=0; i<128; i++)
    ht1632_senddata(i, 0);  // clear the display!
  delay(1000);
}

void loop ()
{
  byte bits;
  byte addr;
  for (addr=0; addr < 96; addr++) {        // Shift in ON bits
    for (bits=8; ; bits=(bits>>1)+8) {
      ht1632_senddata(addr, bits);
      delay(30);
      if (bits == 15) break;
    }
  }
  delay(1000);
  for (addr=0; addr < 96; addr++) {      // Now shift in OFF bits
    for (bits=15; ; bits=(bits>>1)) {
      ht1632_senddata(addr, bits);
      delay(10);
      if (bits == 0) break;
    }
  }
  delay(1000);
}

Code ht1632.h:

/*
 * ht1632.h
 * defintions for Holtek ht1632 LED driver.
 */


#if !defined(DEBUGPRINT)
#define DEBUGPRINT(fmt, args...)
#endif

/*
 * commands written to the chip consist of a 3 bit "ID", followed by
 * either 9 bits of "Command code" or 7 bits of address + 4 bits of data.
 */
#define HT1632_ID_CMD 4            /* ID = 100 - Commands */
#define HT1632_ID_RD  6            /* ID = 110 - Read RAM */
#define HT1632_ID_WR  5            /* ID = 101 - Write RAM */

#define HT1632_CMD_SYSDIS 0x00      /* CMD= 0000-0000-x Turn off oscil */
#define HT1632_CMD_SYSON  0x01      /* CMD= 0000-0001-x Enable system oscil */
#define HT1632_CMD_LEDOFF 0x02      /* CMD= 0000-0010-x LED duty cycle gen off */
#define HT1632_CMD_LEDON  0x03      /* CMD= 0000-0011-x LEDs ON */
#define HT1632_CMD_BLOFF  0x08      /* CMD= 0000-1000-x Blink ON */
#define HT1632_CMD_BLON   0x09      /* CMD= 0000-1001-x Blink Off */
#define HT1632_CMD_SLVMD  0x10      /* CMD= 0001-00xx-x Slave Mode */
#define HT1632_CMD_MSTMD  0x14      /* CMD= 0001-01xx-x Master Mode */
#define HT1632_CMD_RCCLK  0x18      /* CMD= 0001-10xx-x Use on-chip clock */
#define HT1632_CMD_EXTCLK 0x1C      /* CMD= 0001-11xx-x Use external clock */
#define HT1632_CMD_COMS00 0x20      /* CMD= 0010-ABxx-x commons options */
#define HT1632_CMD_COMS01 0x24      /* CMD= 0010-ABxx-x commons options */
#define HT1632_CMD_COMS10 0x28      /* CMD= 0010-ABxx-x commons options */
#define HT1632_CMD_COMS11 0x2C      /* CMD= 0010-ABxx-x commons options */
#define HT1632_CMD_PWM    0xA0      /* CMD= 101x-PPPP-x PWM duty cycle */

Here are the comments to go with the demo program.

First, the video (if it works):

The hardware:
I used a separate power supply for the LED board. It ends up drawing about 250mA with all the LEDs on, so it's probably ok to power it from the arduino...

  • Digital Output 12 of the Arduino is connected to pin 1 of the module connector (CS). the "1" position of the CS siwtch on the module is flipped to "on".
  • Digitial Output 11 goes to pin 5 of the module (WRCLK)
  • Digital Output 10 goes to pin 7 of the module (DATA)
  • GND from the digital output are goes to pin 11 on module (GND)

The 24-g wires that fit nicely in the arduino connectors were pretty lose in the connectors that came with the module. You might need to use "real" connectors!

The chip documentation mentions that the chip may not be up to driving ALL the LEDs in a full-sized matrix without driver transistors (which are not used on the SureElectronics Board), and this is easily observable. In the demo, the LEDs are turned on one at a time, and you can see the brightness start to go down after about 1/3 of the LEDs are lit ("only 128"), and it continues to get dimmer till they're all lit. I would say that brightness is still "acceptable" will all LEDs on, and even-ness is pretty good, but it may be bothersome if you're trying to maintain even brightness between "few leds" and "many leds" displays.

Of course, the display does not reset when the arduino resets, and the initial configuraton does not clear the LED memory until the setup() code does it explicitly. This clearing is an example of "full speed" access to the display by the arduino - hit your reset button when the display is full, and watch how fast it blanks. (Note: this is not at all optimized; there are several things that would make it faster. It's just how fast the current code is without any "delay" calls.)

The addressing of individual dots is a bit weird (or "inconvenient", anyway.) The "bottom left LED" is something like bit 0 of word 3, for example. It's sort of a shame, because I think it would have been easy enough to build the board differently. Oh well; it's possible that the current scheme works out better for text displays... This is something I should fix in software in the final version of the code.

I guess that's all I have for now... It's a neat little board!

Thanks westfw! Great work!

Video below of it running with Arduino onboard power.
I just tinkered with the delays a little. I'd no idea how to implement the .h file and couldn't find a howto on the subject, tried making a standalone file that went into the /library e.t.c. Figured it out in the end. wncranger If you need help just PM me.

Glad you got it working!

In general, you can just put the .h files in the same directory as the sketch and it will find it without problems...

Here's some more demo code to play with. Now has "plot(x,y)" function, and line drawing.

The brightness "problem" is more serious than I thought since LEDs start to dim when more than one third are lit in any particular ROW (my initial statement suggest 1/3 of all the LEDs, which was silly given than only one row is actually illuminated at any one time. Sigh.) As a result, for example, a horizontal line and a vertical line have significantly different brightnesses. This shows up in the "Cross" part of the demo.

It looks like the top 8 levels of PWM dimming are all about the same brightness when used with the displays in this board.

/*
 * demo16x24.c - Arduino demo program for Holtek HT1632 LED driver chip,
 *            As implemented on the Sure Electronics DE-DP016 display board
 *            (16*24 dot matrix LED module.)
 * Nov, 2008 by Bill Westfield ("WestfW")
 */

#include "ht1632.h"
#define X_MAX 23
#define Y_MAX 15

#define ASSERT(condition) //nothing


/*
 * Set these constants to the values of the pins connected to the SureElectronics Module
 */
static const byte ht1632_data = 10;  // Data pin (pin 7)
static const byte ht1632_wrclk = 11; // Write clock pin (pin 5)
static const byte ht1632_cs = 12;    // Chip Select (1, 2, 3, or 4)
// The should also be a common GND.
// The module with all LEDs like draws about 200mA,
//  which makes it PROBABLY powerable via Arduino +5V

#define DEMOTIME 30000  // 30 seconds max on each demo is enough.
#define DISPDELAY 40    // Each "display" lasts this long
#define LONGDELAY 1000  // This delay BETWEEN demos


/*
 * ht1632_writebits
 * Write bits (up to 8) to h1632 on pins ht1632_data, ht1632_wrclk
 * Chip is assumed to already be chip-selected
 * Bits are shifted out from MSB to LSB, with the first bit sent
 * being (bits & firstbit), shifted till firsbit is zero.
 */
void ht1632_chipselect(byte chipno)
{
  DEBUGPRINT("\nHT1632(%d) ", chipno);
  digitalWrite(chipno, 0);
}

void ht1632_chipfree(byte chipno)
{
  DEBUGPRINT(" [done %d]", chipno);
  digitalWrite(chipno, 1);
}

void ht1632_writebits (byte bits, byte firstbit)
{
  DEBUGPRINT(" ");
  while (firstbit) {
    DEBUGPRINT((bits&firstbit ? "1" : "0"));
    digitalWrite(ht1632_wrclk, LOW);
    if (bits & firstbit) {
      digitalWrite(ht1632_data, HIGH);
    } 
    else {
      digitalWrite(ht1632_data, LOW);
    }
    digitalWrite(ht1632_wrclk, HIGH);
    firstbit >>= 1;
  }
}

static void ht1632_sendcmd (byte command)
{
  ht1632_chipselect(ht1632_cs);  // Select chip
  ht1632_writebits(HT1632_ID_CMD, 1<<2);  // send 3 bits of id: COMMMAND
  ht1632_writebits(command, 1<<7);  // send the actual command
  ht1632_writebits(0, 1);       /* one extra dont-care bit in commands. */
  ht1632_chipfree(ht1632_cs); //done
}

static void ht1632_senddata (byte address, byte data)
{
  ht1632_chipselect(ht1632_cs);  // Select chip
  ht1632_writebits(HT1632_ID_WR, 1<<2);  // send ID: WRITE to RAM
  ht1632_writebits(address, 1<<6); // Send address
  ht1632_writebits(data, 1<<3); // send 4 bits of data
  ht1632_chipfree(ht1632_cs); // done
}

void setup ()  // flow chart from page 17 of datasheet
{
  pinMode(ht1632_cs, OUTPUT);
  digitalWrite(ht1632_cs, HIGH);       /* unselect (active low) */
  pinMode(ht1632_wrclk, OUTPUT);
  pinMode(ht1632_data, OUTPUT);
  ht1632_sendcmd(HT1632_CMD_SYSDIS);  // Disable system
  ht1632_sendcmd(HT1632_CMD_COMS11);  // 16*32, PMOS drivers
  ht1632_sendcmd(HT1632_CMD_MSTMD);       /* Master Mode */
  ht1632_sendcmd(HT1632_CMD_SYSON);       /* System on */
  ht1632_sendcmd(HT1632_CMD_LEDON);       /* LEDs on */
  for (byte i=0; i<128; i++)
    ht1632_senddata(i, 0);  // clear the display!
  delay(LONGDELAY);
}

/*
 * we keep a copy of the display controller contents so that we can
 * know which bits are on without having to (slowly) read the device.
 * Note that we only use the low four bits of the shadow ram, since
 * we're shadowing 4-bit memory.  This makes things faster, but we
 * COULD do something with the other half of our bytes !
 */
byte ht1632_shadowram[96];  // our copy of the display's RAM

/*
 * plot a point on the display, with the upper left hand corner
 * being (0,0), and the lower right hand corner being (23, 15).
 * Note that Y increases going "downward" in contrast with most
 * mathematical coordiate systems, but in common with many displays
 * No error checking; bad things may happen if arguments are out of
 * bounds!  (The ASSERTS compile to nothing by default
 */
void plot (char x, char y, char val)
{
  char addr, bitval;

  ASSERT(x >= 0);
  ASSERT(x <= X_MAX);
  ASSERT(y >= 0);
  ASSERT(y <= y_MAX);

  /*
   * The 4 bits in a single memory word go DOWN, with the LSB
   * (last transmitted) bit being on top.  However, writebits()
   * sends the LSB first, so we have to do a sort of bit-reversal
   * somewhere.  Here, this is done by shifting the single bit in
   * the opposite direction from what you might expect.
   */
  bitval = 8>>(y&3);  // compute which bit will need set
  addr = (x<<2) + (y>>2);  // compute which memory word this is in
  if (val) {  // Modify the shadow memory
    ht1632_shadowram[addr] |= bitval;
  } 
  else {
    ht1632_shadowram[addr] &= ~bitval;
  }
  // Now copy the new memory value to the display
  ht1632_senddata(addr, ht1632_shadowram[addr]);
}

/*
 * scan()
 * scan all the leds one at a time,
 * using the plot() function, so as to demonstate the limits
 * of "plot" performance.
 */
void scan ()
{
  byte x,y, bits;
  for (y=0; y<=Y_MAX; y++) {
    for (x=0; x <= X_MAX; x++) {
      plot(x,y,1);
      delay(DISPDELAY);
      plot(x,y,0);
    }
  }
}

/*
 * random_walk()
 * have a single LED walk all over the display, randomly.
 */
void random_walk ()
{
  char x,y, dx, dy;
  dx = dy = 1;
  byte change;

  for (int i=0; i < (DEMOTIME/DISPDELAY);  i++) {
    plot(x,y,1);  // draw a point
    delay(DISPDELAY); // wait a bit.
    change = random(1,32);
    /*
     * figure out where to go next.  This code is a bit
     * random in more senses than one, but it seems to
     * have results that are more or less what I had in
     * mind for this portion of the demo.
     */
    if (change < 9) {
      // do nothing
    } 
    else if (change == 10) {
      dx = -dx;
    } 
    else if (change == 11) {
      dy = -dy;
    } 
    else if (change == 12) {
      dx = dy = 0;
    } 
    else if (change == 13) {
      dx = dy = 1;
    } 
    else if (change == 14) {
      dx = dy = -1;
    }

    plot(x,y,0); // erase the point

    x = x + dx;
    y = y+ dy;
    if (x > X_MAX) {
      dx = -1;
      x = X_MAX;
    } 
    else if (x < 0) {
      x = 0;
      dx = 1;
    }
    if (y >  Y_MAX) {
      y = Y_MAX;
      dy = -1;
    } 
    else if (y < 0) {
      y = 0;
      dy = 1;
    }
  }
}
// end of part 1

oops. It didn't fit in a single message any more. Here's part 2...

//begin part 2

/*
 * Draw a line between two points using the bresenham algorithm.
 * This particular bit of code is copied nearly verbatim from
 *   http://www.gamedev.net/reference/articles/article1275.asp
 * I don't like it much (too many local variables!), but it works,
 * and is fully explained at the site, so...
 */
void bres_line(byte x1, byte y1,
unsigned char x2, unsigned char y2,
unsigned char val )
{
  char deltax = abs(x2 - x1);        // The difference between the x's
  char deltay = abs(y2 - y1);        // The difference between the y's
  char x = x1;                       // Start x off at the first pixel
  char y = y1;                       // Start y off at the first pixel
  char xinc1, xinc2, yinc1, yinc2, den, num, numadd, numpixels, curpixel;

  if (x2 >= x1) {                // The x-values are increasing
    xinc1 = 1;
    xinc2 = 1;
  }  
  else {                          // The x-values are decreasing
    xinc1 = -1;
    xinc2 = -1;
  }

  if (y2 >= y1)                 // The y-values are increasing
  {
    yinc1 = 1;
    yinc2 = 1;
  }
  else                          // The y-values are decreasing
  {
    yinc1 = -1;
    yinc2 = -1;
  }

  if (deltax >= deltay)         // There is at least one x-value for every y-value
  {
    xinc1 = 0;                  // Don't change the x when numerator >= denominator
    yinc2 = 0;                  // Don't change the y for every iteration
    den = deltax;
    num = deltax / 2;
    numadd = deltay;
    numpixels = deltax;         // There are more x-values than y-values
  }
  else                          // There is at least one y-value for every x-value
  {
    xinc2 = 0;                  // Don't change the x for every iteration
    yinc1 = 0;                  // Don't change the y when numerator >= denominator
    den = deltay;
    num = deltay / 2;
    numadd = deltax;
    numpixels = deltay;         // There are more y-values than x-values
  }

  for (curpixel = 0; curpixel <= numpixels; curpixel++)
  {
    plot(x, y, val);             // Draw the current pixel
    num += numadd;              // Increase the numerator by the top of the fraction
    if (num >= den)             // Check if numerator >= denominator
    {
      num -= den;               // Calculate the new numerator value
      x += xinc1;               // Change the x as appropriate
      y += yinc1;               // Change the y as appropriate
    }
    x += xinc2;                 // Change the x as appropriate
    y += yinc2;                 // Change the y as appropriate
  }
}

/*
 * Draw a crpss on the display, and then use the PWM function
 * to demonstate how well (or not) the ht1632 dimming function works.
 */
void cross ()
{
  byte x, y;
  char intensity;
  for (x=0; x <= X_MAX; x++) {
    plot(x,7,1);
  }
  for (y=0; y < Y_MAX; y++) {
    plot(12,y,1);
  }
  for (intensity=14; intensity >= 0; intensity--) {
    ht1632_sendcmd(HT1632_CMD_PWM + intensity);
    delay(LONGDELAY/4);
  }
  ht1632_sendcmd(HT1632_CMD_PWM + 15); // back to max brightness
}

/*
 * bouncyline
 * Do the classic "bouncing line" demo, where the endpoints of a line
 * move independently and bounce off the edges of the display.
 * This should demonstrate (more or less) the performance limits of
 * the line drawing function.
 */
void bouncyline ()
{
  char x1,y1, x2,y2, dx1, dy1, dx2, dy2;

  x1 = random(0,X_MAX);
  x2 = random(0,X_MAX);
  y1 = random(0,Y_MAX);
  y2 = random(0,Y_MAX);
  dx1 = random(1,4);
  dx2 = random(1,4);
  dy1 = random(1,4);
  dy2 = random(1,4);
  for (int i=0; i < DEMOTIME/DISPDELAY; i++) {
    bres_line(x1,y1, x2,y2, 1);
    delay(DISPDELAY);
    bres_line(x1,y1, x2,y2, 0);

    x1 += dx1;
    if (x1 > X_MAX) {
      x1 = X_MAX;
      dx1 = -random(1,4);
    } 
    else if (x1 < 0) {
      x1 = 0;
      dx1 = random(1,4);
    }

    x2 += dx2;
    if (x2 > X_MAX) {
      x2 = X_MAX;
      dx2 = -random(1,4);
    } 
    else if (x2 < 0) {
      x2 = 0;
      dx2 = random(1,4);
    }

    y1 += dy1;
    if (y1 > Y_MAX) {
      y1 = Y_MAX;
      dy1 = -random(1,3);
    } 
    else if (y1 < 0) {
      y1 = 0;
      dy1 = random(1,3);
    }

    y2 += dy2;
    if (y2 > Y_MAX) {
      y2 = Y_MAX;
      dy2 = -random(1,3);
    } 
    else if (y2 < 0) {
      y2 = 0;
      dy2 = random(1,3);
    }
  }
}


void loop ()
{
  randomSeed(analogRead(0));

  bouncyline();
  delay(LONGDELAY);
  
  bres_line(0,0, 16,16, 1);
  delay(LONGDELAY);
  
  cross();
  delay(LONGDELAY);
  
  scan();
  delay(LONGDELAY);

  random_walk();

  delay(LONGDELAY);
}

Again, Great Work!
Also, thanks for the detailed comments in the sketches, it really helps when trying to decipher them and is an invaluable learning aid!

The last bits are some character displays (A-Z, 0-9) and "life" (can't have a graphics display without life, eh?)

Then factor the code a bit better so that only functions dependent on the chip details get ht1632_ in front of them, and "device independent graphics" get plot_ or something, and the demo routines get demo_, and a whole separate text document explaining at least the bottom level...

Excellent! Looking forward to the new stuff!

First of all, enormous thanks to webobject for tipping us off to these display boards and to westfw for doing the hard work of writing the code. And now as suggested, Conway's Life powered by an Arduino Nano (cool!):

(Arduino Nano & 24x16 LED display board - YouTube)

Andrew

PS code to follow very shortly

And here's the code:

/*

  • matrix_16x24_life
  • Andrew Hedges
  • Dec, 2008
  • Adapted from code by Bill Westfield ("WestfW")
    */

#include "ht1632.h"
#define X_MAX 23
#define Y_MAX 15
#define X_SIZE 24
#define Y_SIZE 16

#define ASSERT(condition) //nothing

/*

  • Set these constants to the values of the pins connected to the SureElectronics Module
  • NOTE - these are different from the original demo code by westfw
    */
    static const byte ht1632_data = 3; // Data pin (pin 7)
    static const byte ht1632_wrclk = 4; // Write clock pin (pin 5)
    static const byte ht1632_cs = 6; // Chip Select (1, 2, 3, or 4)
    // The should also be a common GND.
    // The module with all LEDs like draws about 200mA,
    // which makes it PROBABLY powerable via Arduino +5V

#define DEMOTIME 30000 // 30 seconds max on each demo is enough.
#define DISPDELAY 40 // Each "display" lasts this long
#define LONGDELAY 1000 // This delay BETWEEN demos

/*

  • ht1632_writebits
  • Write bits (up to 8) to h1632 on pins ht1632_data, ht1632_wrclk
  • Chip is assumed to already be chip-selected
  • Bits are shifted out from MSB to LSB, with the first bit sent
  • being (bits & firstbit), shifted till firsbit is zero.
    */
    void ht1632_chipselect(byte chipno)
    {
    DEBUGPRINT("\nHT1632(%d) ", chipno);
    digitalWrite(chipno, 0);
    }

void ht1632_chipfree(byte chipno)
{
DEBUGPRINT(" [done %d]", chipno);
digitalWrite(chipno, 1);
}

void ht1632_writebits (byte bits, byte firstbit)
{
DEBUGPRINT(" ");
while (firstbit) {
DEBUGPRINT((bits&firstbit ? "1" : "0"));
digitalWrite(ht1632_wrclk, LOW);
if (bits & firstbit) {
digitalWrite(ht1632_data, HIGH);
}
else {
digitalWrite(ht1632_data, LOW);
}
digitalWrite(ht1632_wrclk, HIGH);
firstbit >>= 1;
}
}

static void ht1632_sendcmd (byte command)
{
ht1632_chipselect(ht1632_cs); // Select chip
ht1632_writebits(HT1632_ID_CMD, 1<<2); // send 3 bits of id: COMMMAND
ht1632_writebits(command, 1<<7); // send the actual command
ht1632_writebits(0, 1); /* one extra dont-care bit in commands. */
ht1632_chipfree(ht1632_cs); //done
}

static void ht1632_senddata (byte address, byte data)
{
ht1632_chipselect(ht1632_cs); // Select chip
ht1632_writebits(HT1632_ID_WR, 1<<2); // send ID: WRITE to RAM
ht1632_writebits(address, 1<<6); // Send address
ht1632_writebits(data, 1<<3); // send 4 bits of data
ht1632_chipfree(ht1632_cs); // done
}

void setup () // flow chart from page 17 of datasheet
{
pinMode(ht1632_cs, OUTPUT);
digitalWrite(ht1632_cs, HIGH); /* unselect (active low) /
pinMode(ht1632_wrclk, OUTPUT);
pinMode(ht1632_data, OUTPUT);
ht1632_sendcmd(HT1632_CMD_SYSDIS); // Disable system
ht1632_sendcmd(HT1632_CMD_COMS11); // 16
32, PMOS drivers
ht1632_sendcmd(HT1632_CMD_MSTMD); /* Master Mode /
ht1632_sendcmd(HT1632_CMD_SYSON); /
System on /
ht1632_sendcmd(HT1632_CMD_LEDON); /
LEDs on */
for (byte i=0; i<128; i++)
ht1632_senddata(i, 0); // clear the display!
delay(LONGDELAY);
}

/*

  • we keep a copy of the display controller contents so that we can
  • know which bits are on without having to (slowly) read the device.
  • Note that we only use the low four bits of the shadow ram, since
  • we're shadowing 4-bit memory. This makes things faster, but we
  • COULD do something with the other half of our bytes !
    */
    byte ht1632_shadowram[96]; // our copy of the display's RAM

/*

  • plot a point on the display, with the upper left hand corner
  • being (0,0), and the lower right hand corner being (23, 15).
  • Note that Y increases going "downward" in contrast with most
  • mathematical coordiate systems, but in common with many displays
  • No error checking; bad things may happen if arguments are out of
  • bounds! (The ASSERTS compile to nothing by default
    */
    void plot (char x, char y, char val)
    {
    char addr, bitval;

ASSERT(x >= 0);
ASSERT(x <= X_MAX);
ASSERT(y >= 0);
ASSERT(y <= y_MAX);

/*

  • The 4 bits in a single memory word go DOWN, with the LSB
  • (last transmitted) bit being on top. However, writebits()
  • sends the LSB first, so we have to do a sort of bit-reversal
  • somewhere. Here, this is done by shifting the single bit in
  • the opposite direction from what you might expect.
    */
    bitval = 8>>(y&3); // compute which bit will need set
    addr = (x<<2) + (y>>2); // compute which memory word this is in
    if (val) { // Modify the shadow memory
    ht1632_shadowram[addr] |= bitval;
    }
    else {
    ht1632_shadowram[addr] &= ~bitval;
    }
    // Now copy the new memory value to the display
    ht1632_senddata(addr, ht1632_shadowram[addr]);
    }

byte world[X_SIZE][Y_SIZE][2];
long density = 50;

void setupLife() {
randomSeed(analogRead(5));
for (int i = 0; i < X_SIZE; i++) {
for (int j = 0; j < Y_SIZE; j++) {
if (random(100) < density) {
world*[j][0] = 1;*

  • }*
  • else {*
    _ world*[j][0] = 0;_
    _
    }_
    _ world[j][1] = 0;
    }
    }
    }
    void life() {
    while (1) {
    // Display current generation*
    * for (int i = 0; i < X_SIZE; i++) {
    for (int j = 0; j < Y_SIZE; j++) {*

    plot(i, j, world*[j][0]);
    }
    }
    delay(DISPDELAY);
    // Birth and death cycle*

    * for (int x = 0; x < X_SIZE; x++) {
    for (int y = 0; y < Y_SIZE; y++) {*

    * // Default is for cell to stay the same*
    * world[x][y][1] = world[x][y][0];
    int count = neighbours(x, y);
    if (count == 3 && world[x][y][0] == 0) {
    // A new cell is born*

    * world[x][y][1] = 1;
    }
    if ((count < 2 || count > 3) && world[x][y][0] == 1) {
    // Cell dies*

    * world[x][y][1] = 0;
    }
    }
    }*_

* // Copy next generation into place*
[/quote]
[continued in next post]

... and the rest:

// Copy next generation into place
for (int x = 0; x < X_SIZE; x++) {
for (int y = 0; y < Y_SIZE; y++) {
world[x][y][0] = world[x][y][1];
}
}
}
}

int neighbours(int x, int y) {
return world[(x + 1) % X_SIZE][y][0] +
world[x][(y + 1) % Y_SIZE][0] +
world[(x + X_SIZE - 1) % X_SIZE][y][0] +
world[x][(y + Y_SIZE - 1) % Y_SIZE][0] +
world[(x + 1) % X_SIZE][(y + 1) % Y_SIZE][0] +
world[(x + X_SIZE - 1) % X_SIZE][(y + 1) % Y_SIZE][0] +
world[(x + X_SIZE - 1) % X_SIZE][(y + Y_SIZE - 1) % Y_SIZE][0] +
world[(x + 1) % X_SIZE][(y + Y_SIZE - 1) % Y_SIZE][0];
}

void loop ()
{
setupLife();
life();
}

For a seasonal flavour (for those of us in the Northern hemisphere anyway), replace the appropriate bits of code with the following:

#define NUMFLAKES 7
byte flakes[NUMFLAKES][2];

void snowflakes() {
for (int i = 0; i < NUMFLAKES; i++) {
flakes*[0] = random(X_MAX + 1);*
flakes*[1] = random(Y_MAX + 1);
_
}_
_
while (1) {_
_
// Display flakes*_
* for (int i = 0; i < NUMFLAKES; i++) {*
plot(flakes_[0], flakes*[1]-1, 1);
plot(flakes[0]+1, flakes[1], 1);
plot(flakes[0], flakes[1]+1, 1);
plot(flakes[0]-1, flakes[1], 1);
}
delay(DISPDELAY * 2);
// Erase flakes*

* for (int i = 0; i < NUMFLAKES; i++) {
plot(flakes[0], flakes[1]-1, 0);
plot(flakes[0]+1, flakes[1], 0);
plot(flakes[0], flakes[1]+1, 0);
plot(flakes[0]-1, flakes[1], 0);
}*_

* // Move down*
* for (int i = 0; i < NUMFLAKES; i++) {*
flakes_[1] = flakes*[1] + 1;
if (random(10) > 7) {
flakes[0] = flakes[0] + (random(3) - 1);
}_

if (flakes[1] > (Y_MAX + 1)) {
_ flakes[1] = -1;_
flakes[0] = random(X_MAX + 1);
_ }
}
}
}
void loop ()
{
snowflakes();
}
[/quote]*

Andrew_

BTW, my version of life ran significantly faster if I only updated the pixels that changed... Though I'm not sure that running life faster is necessarily better (and I left the demo code in "slow" update-every-pixel mode, since I claimed that I was demonstrating the performance of doing that...)

For animation in general, you might want to investigate the "multiple write" capability of the chip (as seen in the latest code dump in ht1632_clear())

/*
 * ht1632_clear
 * clear the display, and the shadow memory, and the snapshot
 * memory.  This uses the "write multiple words" capability of
 * the chipset by writing all 96 words of memory without raising
 * the chipselect signal.
 */
void ht1632_clear()
{
  char i;

  ht1632_chipselect(ht1632_cs);  // Select chip
  ht1632_writebits(HT1632_ID_WR, 1<<2);  // send ID: WRITE to RAM
  ht1632_writebits(0, 1<<6); // Send address
  for (i = 0; i < 96/2; i++) // Clear entire display
    ht1632_writebits(0, 1<<7); // send 8 bits of data
  ht1632_chipfree(ht1632_cs); // done
  for (i=0; i < 96; i++)
    ht1632_shadowram[i] = 0;
}

(my; this worked out pretty nicely. I got a display and a pleasant diversion, and the code works and seems to be usable, and assorted others have taken off and done additional neat things with it!)

Wow. I mean, WOW. You are all geniuses. I have been beating my brains out trying to figure this thing out. The board expands out to 4 and I wanted something I could tinker with and build on because the price is outstanding. And to learn to use the Arduino to command it is priceless.

I'm still not clear on the connections between the Arduino and the board. Is there anyway a quickie schematic can be drawn up? That would be much appreciated.

I want to especially thank webobject, westfw and Andrew for sharing your code.