Go Down

Topic: Arduino to 384 LED display module (MCU Interface) (Read 44726 times) previous topic - next topic

westfw

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...

webobject

Excellent! Looking forward to the new stuff!

Andrew

#17
Dec 06, 2008, 02:44 pm Last Edit: Dec 06, 2008, 04:45 pm by Andrew Reason: 1
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!):

[media]http://uk.youtube.com/watch?v=Zbz-X-qCqGA[/media]
(http://uk.youtube.com/watch?v=Zbz-X-qCqGA)

Andrew

PS code to follow very shortly

Andrew

#18
Dec 06, 2008, 04:58 pm Last Edit: Dec 06, 2008, 05:02 pm by Andrew Reason: 1
And here's the code:

Quote

/*
* 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
  • [y][1] = world
  • [y][0];
           int count = neighbours(x, y);
           if (count == 3 && world
  • [y][0] == 0) {
             // A new cell is born
             world
  • [y][1] = 1;
           }
           if ((count < 2 || count > 3) && world
  • [y][0] == 1) {
             // Cell dies
             world
  • [y][1] = 0;
           }
         }
       }
     
       // Copy next generation into place


[continued in next post]

Andrew

... and the rest:

Quote



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

    int neighbours(int x, int y) {
    return world[(x + 1) % X_SIZE][y][0] +
            world
  • [(y + 1) % Y_SIZE][0] +
            world[(x + X_SIZE - 1) % X_SIZE][y][0] +
            world
  • [(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();
    }



Andrew

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

Quote

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

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

               // Erase flakes
               for (int i = 0; i < NUMFLAKES; i++) {
                 plot(flakes
            • , flakes[1]-1, 0);
                   plot(flakes
              • +1, flakes[1], 0);
                     plot(flakes
                • , flakes[1]+1, 0);
                       plot(flakes
                  • -1, flakes[1], 0);
                       }
                     
                       // Move down
                       for (int i = 0; i < NUMFLAKES; i++) {
                         flakes[1] = flakes[1] + 1;
                         if (random(10) > 7) {
                           flakes
                    • = flakes
                      • + (random(3) - 1);
                             }
                             if (flakes[1] > (Y_MAX + 1)) {
                               flakes[1] = -1;
                               flakes
                        • = random(X_MAX + 1);
                               }
                             }
                           }
                          }


                          void loop ()
                          {
                           snowflakes();
                          }


Andrew

westfw

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())
Code: [Select]

/*
* 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!)

wncranger

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.

wncranger

I read the source and your notes and was able to successfully connect the arduino to the display board. Thank you, again!

Andrew, I am curious about your comments regarding the "multiple write" capabilities of the ht1632. Could you elaborate on that? Would that mean that you can pre-load the graphics/text data before commanding the board to begin display?

What about text? Do I need to design my own font or does the chip have one on board?

westfw

The complete code is getting a bit big to post here, but you should be able to get it from http://www.geocities.com/westfw/demo16x24.zip
This version has some 5x7 text capability, with a font for A-Z and 0-9 (the chip does NOT have any internal character generator.)

I'm trying to add some text and explanations to get the whole thing into a more "article-like" publication.  Assuming I get it finished, I'll put a note here and eventually try getting it up on the playgroup as well.  (it is, however, somewhat painful to have working code and sketches and be faced with writing mere text.  Sigh.)

The multiple-write thing is a speedup.  Normally you send a "write data" command, followed by 7 bits of address, followed by 4 bits of data, and then you start a new command.  Most of the library code I wrote does that, so the display is always updated 4bits at a time, taking a good 16-odd "pin wiggles" for each pixel you change.  In the demo program, it seems like that is "fast enough" for most of the things I tried, so I didn't do a lot of additional work.  However, if you send an additional 4 bits of data after the first set, the ht1632 will increment the address and put those in the next word, and so on, for as many words of data as you provide.  That means that you COULD update the entire display in about 12 + (96*4) ~= 400 wiggles instead of 384*16 ~= 6000 wiggles; a rather impressive improvement...


JucaBlues

Could you please tell us what is the licence of the code you have posted here?
I'd like to use it on my personal pinball machine project. For that purpose, I intend to host modified versions of this code at Google Code.

JucaBlues

this is the Google Code page for my Pinball Fantasies / Party Land Remake project: http://code.google.com/p/how-to-build-a-pinball/

also there is a video here: http://www.youtube.com/watch?v=41dogkzbWbo
and some photos here: http://www.flickr.com/photos/felipesanches

JucaBlues

Oh! I just noticed the licensing statement on the header!

In my project, I intend to concatenate a series of displays so that I can have a 168x16 dot matrix display. I have some ideas on how to proceed, in order to do that. I will host this code at google code and then I can give you write access to the svn if you wish to contribute there.

JucaBlues

give me your username at google and I will add you as a contributor, westfw.

then you can follow these instructions:
http://code.google.com/p/how-to-build-a-pinball/source/checkout

Ced2911

#29
Dec 16, 2008, 07:21 pm Last Edit: Dec 16, 2008, 07:23 pm by Ced2911 Reason: 1
Hi !
Big thanks for westfw !
Just playing with arduino since a week .. and i am in love with it !

I made a little librairie

Need to do more work.. but it's working

How to use
Code: [Select]

#include <Led_matrix.h>

matrix lmatrix(10,11,12);//Data, write, cs
void setup()
{
 delay(1000);
 lmatrix.setup();
}

void loop()
{
 for( int i=0;i<24;i++)//24lignes
 {
   int r=random(0,7);
   int n;
   if(i>12)
   {
     n=(23-i);
   }
   else
   {
     n=i;
   }
   float BarSize=sin(n*(1.57/12))*r;
   lmatrix.DrawLine(i,(8+BarSize),i,(8-BarSize),55 );
 
 }
 //lmatrix.DrawString("ee",1,8);
 lmatrix.ShadowDsp();
 delay(100);
}



Edit: Link in next thread

Go Up