Need help / advice for MAX7219 led driver

Hi Guys, I have a Led clock that I am trying to revamp.
Many years ago, I built this clock , it ran on only 4017 counter Ic's
it had 216 leds
60x second leds, "Single row circle"
120x min. leds, "2 rows of leds"
36x hour leds and 3 leds per hour segment

I want to update this clock to a uC, and make it GPS accurate.
I have in mind to use the MAX7219 IC/matrix driver.
I'm guessing to use 3 of the chips..
Problem is, I am not sure how to map my display.
I was thinking if i could build a test display using the 8x8 led display, and if there was a sketch that would go from 1st dot to last dot.
with this info, I could then map out my display matrix..

Or does anyone have a better suggestion? different IC or how to go about my display connections..

attached is a few pictures of my present clock..

MVC-342F
MVC-340F
MVC-306F

I was amazed that it worked as well as it did.. was as accurate as line frequency ( 60Hz )

First thought, it would be a lot easier if everything was on a PCB. Regardless of what chips you use.

That is so easy to write, when you have a library function to set/clear an individual LED, which most of them do.

Check out the HT16K33 backpack, it supports 128 LEDs but some of yours might be eliminated or consolidated. Then you could proto with a backpack board, test, and design for a PCB complete with LEDs...

One display IC instead of 3.

Fun idea, well done. My first LED clock ran from mains, 50 Hz, and as the
Swedish grid counts cycles and compensate for lag that clock ran perfectly.
The circuits were TTL 7474........ That was almost 50 years ago.
Why not get an Arduino and a GPS and make it deliver 60 Hz, keeping You old build intact?

This is using a PIC processor and not an Arduino, but it's connected to a MAX7219 with an attached 8x8 LED matrix, which was an item I found on eBay. Most likely I bought it from a Chinese vendor, but I can't remember for sure. As I recall, the data sheet was easy enough to read, and the programming was pretty straightforward.

If you want 216 LEDs, then you'll need 4 MAX7219's, because each of them can drive 64 LEDs. At minimum, you'd need 27 bytes of storage, which is nothing much on any Arduino. Maybe you'd want to use a few more, so you wouldn't be mixing minutes and hours in the same bytes, or anything like that.

Hmmm. Or, just use the 32768 Hz output of a DS3231 RTC, which is on by default, so you need no processor to make it work. A binary divider will get you 1Hz from that.

wow, I didn't think i'd get this many replies so fast... :slight_smile:
It would be too hard and out of my price range to have a circuit board made for my leds. I know i'll have to rewire the display, but that is no issue.

as far as the leds, I should have explained better,. the 2 rows of led, they are in parallel as well as the 3 rows of leds.

I was thinking 3 chips total,, 1 for seconds, 1 for min and 1 for hours..

right now, my current set up..it's sensitive to interference if something gets too close.. Clock went into warp time when i got a CFL bulb too close to it.. was funny...

I thought about the HT16K33 Adafruit backpack driver chips as well, but I don't know how it drives 128 leds..from a single chip..

That could likely be improved. Better cables, changed distribution of cables, pullup resistors, decoupling caps.... Making a shield of folia around the sensitive electronics, inside the outer surface.... Grounds......

I built another digital clock based on a WLSI, clocked by mains some 45 years ago and it still runs! Just that it's so old is fun.

You made a good presentation of the situation and Your plans! Helpers didn't need to spend lots of replies getting Your question understandable.

I'm not very good with code at all. I can recognize some things and modify. I have tried to learn things on my own, here and there, But i can't grasp things so well, I'm better with hands on help.
I have built some GPS and wifi clocks before, of my own design, but i had to get someone to write the code for me..
Have repurposed Pedestrian cross walk count down displays ( reusing the digit displays ) Had to do some circuit surgery, added in a TPIC6C596 driver for each digit.. worked out quite well..
I'm some dumb, just not plum dumb...lol

it seems you have a working circuit already.
So why not just exchange the (first) counter with an microcontroller and leave all the rest of the clock untouched?

Any chance to get a schematic of your circuit?

216/64 = 3,375 ... you would need 4 MAX7219, ... or 2 HT16K33

GPS for an indoor clock? Are you very confident that this will work where you put your clock?
What's about an WemosD1/NodeMCU (some ESP8266 based board) and use Wifi + NTP?

This should be roughly it.. The leds are in a matrix, rows and columns,
kinda like the 8x8 led dot display.. But they count to 10 x 6 , then shift to next row. (if i remember right.) and the hours count to 6 x 2 then recycle.
I'd have to open the clock to tell you 100%

As far as GPS signal, I already have such clocks in operation and they work just fine with a wooden frame house. The GPS does not have to have a lock, Just the ping? it gets the time info just fine.. I have another clock at work, that GPS does not work in a metal building. So A friend helped me make it a NTP wifi clock. We used a Wroom32 I think...

I know nothing of..... WemosD1/NodeMCU (some ESP8266 based board) and use Wifi + NTP?
I know what i want and can build it,, I just have poor knowledge of working with Uc. I can down load code to them etc, but I can't write or compile things.. That's where i need the help.
Also modifying the current clock, How would it reset to correct time if there was a power failure ? Right now if the time fails, I have to manually reset it like old fashion clock...

To use the MAX7219 you just have to consider it a little differently than as a 7 segment display driver. Think of it as a 64 LED driver which most people use as 8 7 segment LEDs or as an 8X8 dot matrix because it is conveniently set up for those configurations.

You want to use it as a 60 LED driver. To do this split your 60 LEDs into groups of 8, each group is connected to a digit driver line. To select an LED in the group use the segment driver. In your application you will only select 1 LED at a time. Use the no decode mode so that you can control the segment lines directly. See pages 5-8 of the data sheet.
https://datasheets.maximintegrated.com/en/ds/MAX7219-MAX7221.pdf

For example
led 1 digit 0 segment 0
led 2 digit 0 segment 1
led 3 digit 0 segment 2
...
led 8 digit 0 segment 7
led 9 digit 1 segment 0
led 10 digit 1 segment 1
...
There are some coding tricks to easily convert the the LED number to a digit/segment pair

EDIT
Actually it may be better to number your LEDS 0-59. It would simplify the code.

Thanks.. that gives me a little more insight. 0-59 was the idea i had as well, not 1-60.. Took me a little while, but i did learn that most all digital stuff starts off with a 0 and not a 1..
And yet, I was thinking to think of my display as an 8x8 matrix. I just have to rewire my display which will not be an issue. That was 1 reason i was looking for some test code, that would go thru the leds 1 by 1 so that i'd have confirmation that i got my display wired up right.. then i could move on to the next phase..
Right now it's a lot to digest. But i'm determined.. I'll definitely show everyone here when it's a success..

The existing 7 segment or 8x8 matrix libraries have a lot of extra stuff and are difficult to do what I was suggesting. I have written a class Aclock (analog clock) to turn on the leds one at a time as they go abound the clock face. Here is the code with a simple sample of use.

/******************************************
*
*  Aclock - A class to implement a class for analog clock parts
* 
* This class considers the MAX7219 as a 64 les driver rather than as a display 
* driver.
* The leds as considered as 8 groups of 8 leds which are individually addressable.
* The digit driver lines define the groups and the segment lines define the leds   
* within the group.
*
************************************************/

// define Max7219 registers (note not all are used)
const uint8_t REG_GRP_BASE    =  0x01;
const uint8_t REG_DECODE_MODE =  0x09;
const uint8_t REG_INTENSITY   =  0x0A;
const uint8_t REG_SCAN_LIMIT  =  0x0B;
const uint8_t REG_SHUTDOWN    =  0x0C;
const uint8_t REG_TEST        =  0x0F;

class Aclock {
  public:
    
    Aclock(){};   // constructor
    // initializer
    void begin(uint8_t clkPin,uint8_t dataPin,uint8_t loadPin,uint8_t maxCount);
    void setLed(uint8_t ledNumber);
    void clearDisplay();
    void setIntensity(uint8_t intensity);
    void setShutDown(bool mode);
    
  private:
    
    // minimize chances for user to screw up 
    Aclock (const  Aclock& a);              // disallow copy constructor
    Aclock & operator=(const  Aclock& a);   // disallow assignment operator
    
    void setReg(uint8_t reg_number);
    void setData(uint8_t data);
    void setZeros();   // data is all zeros
    void setOnes();    //data is all setOnes
    void setDecodeOff();
    void setScanAll();
    
    // object data
    uint8_t clk_Pin;
    uint8_t data_Pin;
    uint8_t load_Pin;
    uint8_t lastGroup;  // last group used
    
};  // end Aclock class definition

// implementation
void Aclock::begin(uint8_t clkPin,uint8_t dataPin,uint8_t loadPin,uint8_t maxCount){
  clk_Pin = clkPin;
  data_Pin = dataPin;
  load_Pin = loadPin;
  pinMode(clk_Pin,OUTPUT);
  pinMode(data_Pin,OUTPUT);
  pinMode(load_Pin,OUTPUT);
  lastGroup = maxCount >> 3; // divide by 8 to get the last group
  setDecodeOff();      // no digit decode
  setScanAll();        // use all groups
  setIntensity(7);     // set to half intensity
  clearDisplay();
  setShutDown(false);  // start display
}

void Aclock::setLed(uint8_t ledNumber){
  // set the led in the display and turn off previous ledNumber
  // Since the group and segmant are both powers of 2 we can optimize
  //   calculating the group and segment
  uint8_t previousGroup;  // previous group used
  uint8_t group = ledNumber >> 3;  // same as divide by 8
  uint8_t segment = ledNumber & 0x07; // same as remainder after divide by 8
  if (segment == 0){
    // we are starting a new group we need to turn off previous group
    if (group == 0) {
      // first group 
      previousGroup = lastGroup;
    }else{
      previousGroup = group-1;
    }
    setReg(previousGroup+REG_GRP_BASE );
    setZeros();    // turnoff all segmants in previous group
  }
  setReg(group+REG_GRP_BASE);     // select current group
  setData(1<<segment);  // select segment for led
}

void Aclock::clearDisplay(){
  // set all groups/segment off
  uint8_t i;
  for(i = REG_GRP_BASE; i<REG_GRP_BASE+8;i++) {
    // for all groups
    setReg(i); // select group
    setZeros(); // turn off all segments
  }
}  
      
  
void Aclock::setIntensity(uint8_t intensity) {
  setReg(REG_INTENSITY);  
  setData(intensity);
}

void Aclock::setShutDown(bool mode){
    setReg(REG_SHUTDOWN);  
    setData(mode?0:1); 
}    

void Aclock::setReg(uint8_t reg){
  // the register is only the low 4 bits so we can simplify the shift operation
  uint8_t i;
  // we dont'need to change the data because the upper bits are ignored
  digitalWrite(data_Pin,LOW); 
  for (i=0;i<4;i++){
    digitalWrite(clk_Pin,LOW);
    digitalWrite(clk_Pin,HIGH); // toggle clock lines
  } 
   // send the low 4 bits of reg ledNumber
  uint8_t mask = 0x08; // first bit of actual reg number
  for (i = 0;i<4;i++) {
    // the ? operator is shorthand for if .. then ..else
    digitalWrite(data_Pin, ((reg&mask) ? HIGH:LOW));
    digitalWrite(clk_Pin,LOW);
    digitalWrite(clk_Pin,HIGH); // toggle clock lines
    mask >>= 1;  // move down to next bit
  }
}  

void Aclock::setData(uint8_t data){
   // send the 8 bits of data
  uint8_t i;
  uint8_t mask = 0x80; // first bit of data
  for (i = 0;i<8;i++) {
    // the ? operator is shorthand for if .. then ..else
    digitalWrite(data_Pin,((data&mask) ? HIGH:LOW)); // set data bit
    digitalWrite(clk_Pin,LOW);
    digitalWrite(clk_Pin,HIGH); // toggle clock lines
    mask >>= 1;  // move down to next bit
  }
  
  digitalWrite(load_Pin,LOW);
  digitalWrite(load_Pin,HIGH); // toggle load line to store data
}  

void Aclock::setZeros(){
   // special case to send all zeros
  uint8_t i;
  digitalWrite(data_Pin, LOW); // set data bits to low
  for (i = 0;i<8;i++) {
    digitalWrite(clk_Pin,LOW);
    digitalWrite(clk_Pin,HIGH); // toggle clock lines
  }
  
  digitalWrite(load_Pin,LOW);
  digitalWrite(load_Pin,HIGH); // toggle load line to store data
}  

void Aclock::setOnes(){
   // special case to send all ones
   uint8_t i;
  digitalWrite(data_Pin, HIGH); // set data bits to low
  for (i = 0;i<8;i++) {
    digitalWrite(clk_Pin,LOW);
    digitalWrite(clk_Pin,HIGH); // toggle clock lines
  }
  
  digitalWrite(load_Pin,LOW);
  digitalWrite(load_Pin,HIGH); // toggle load line to store data
}  

void Aclock::setDecodeOff(){
  setReg(REG_DECODE_MODE);  
  setZeros();    // no decode - send segments directly
}

void Aclock:: setScanAll(){
  setReg(REG_SCAN_LIMIT);  
  setOnes();    // use all digits
}  

// end implementation


/**************
 * 
 * test Aclock class
 * 
 */

const uint8_t clk_line = 3;
const uint8_t data_line = 4;
const uint8_t sec_load = 5;
const uint8_t min_load = 6;
const uint8_t hr_load = 7;

Aclock seconds,minutes,hours; // declare parts of clock

// starting time
uint8_t timeSec = 0;
uint8_t timeMin = 0;
uint8_t timeHr = 0;
unsigned long waitTime = 1000; // wait a second
unsigned long lastTime,curTime;
bool updateMin = false;
bool updateHr = false;

 void setup()                   
{
  Serial.begin(115200);
  seconds.begin(clk_line,data_line,sec_load,59);
  minutes.begin(clk_line,data_line,min_load,59);
  hours.begin(clk_line,data_line,hr_load,11);
  seconds.setLed(timeSec);
  minutes.setLed(timeMin);
  hours.setLed(timeHr);
  lastTime = millis(); // get current ms
}
void loop()                     // run over and over again
{
 curTime = millis();  
 if ((curTime-lastTime) >= waitTime) {
    lastTime = curTime;
   // update clock
   timeSec++;  //increment seconds
   if (timeSec >= 60){     // use >= "just in case"
     timeSec = 0;  // start new minute
     updateMin = true;
   }
   seconds.setLed(timeSec);
   if (updateMin) {
     // rolled over to new minute
    timeMin++;  //increment minutes
    if (timeMin >= 60){     // use >= "just in case"
        timeMin = 0;  // start new minute
        updateHr = true;
     }
     minutes.setLed(timeMin);
     updateMin = false;
   }
   if (updateHr) {
     // rolled over to new hour
    timeHr++;  //increment hours
    if (timeHr >= 12){     // use >= "just in case"
        timeHr = 0;  // start new hour
     }
    hours.setLed(timeHr);
    updateHr = false;
   }   
 }  
}

Wow, thanks,, your awsome..
I've run into a lil speed bump. I have to order some more Nano's. for some reason the ones i have are defective..But i got more on the way..

I looked over the sketch , Am i missing anything.. I plan to use the arduino Nano or UNO.. I do not see which pins to connect to the Max7219..

Here are the pins I used in the example, you can use others as convenient . The clock and data pins go to all the MAX7219 chips in parallel. They are labeled CLK and DIN in the MAX7219 data sheet. The second,minute and hour chips have separate pins going to the LOAD pins on the individual MAX7219s.

The data is shifted into all the chips in parallel, but only loaded into one chip via the LOAD pin.

In the typical display driver mode the chips are connected serially and you have to shift data in for all the chips every time. If I had connected them that way you would need to shift in 6 bytes all the time instead of 2 bytes. For minutes that would be unnecessary 59 times out of 60, for hours it would be 3599 out of 3600.

Here is a partial schematic of the LED/pin mapping. The labels are my renaming. The text in () by the labels is the MAX7219 data sheet names.

yes, this is kinds what i had written out.. so i'll be getting started on soon...

Sorry to hijack your project. I don't have any current projects, so was a bit bored. Your project looked interesting, so I jumped on it.

I have taken the sample code I posted earlier and turned it into a library with a couple of examples. I will try to upload a .zip file. If that is successful and you can download it (not sure if the forum supports that) you can install it via the Arduino IDE.

Go to Sketch -> Include Library -> Add .ZIP Library and put in the location where you downloaded the .zip file. The sample programs should show up under File -> Examples -> Aclock.

I haven't completely tested it. I only have a single 8X8 dot matrix to test with, so I can test one function at a time. It seems to be working OK. You can change the waitTime in the simpleClock example to 100 or even 10 to run the clock faster for testing.

The POST example can be used as a hardware test to verify all the Leds are connected correctly.

Aclock.zip (6.2 KB)

EDIT
I just tried it and was able to download the .zip file. In Firefox I right clicked on the .zip file then "Save Link As..."
I assume other browsers have something similar