Time almost displaying correctly on LED watch matrix

Hi everyone,

I've posted here before and was appreciative of all the help I received. I've since continued work on my watch.

Currently the code accurately keeps time but the problem is:
The displayed leds don’t transition correctly between the minutes 4 to 5, 14 to 15, 24 to 25, etc. Referencing the 6 row x 6 column matrix below, when the time changes from 3:24 (matrix_coords/leds [1,0], [1,3] and [3,4] are on) to 3:25, the +4 remainder ([3,4]) turns off but [0,3] doesn’t move to [1,2]. At 3:26, [0,3] moves to [1,2], and [0,4] is turned on as expected.


The same happens when minutes change from 9 to 10, 19 to 20, etc. For example, when the time changes from 3:29 (matrix_coords/leds [1,0], [2,2], and [3,4] are on) to 3:30, [3,4] disappears but [2,2] doesn’t change to 2,3. At 3:31, matrix_coords/leds [1,0], [2,3], and [0,4] are turned on.

The code:

/// Libraries
#include "Wire.h"                /// I2C connections
#include "M41T62.h"              /// M41T62 real time clock
#include "PinChangeInterrupt.h"  /// pin change interrupt
#include "LowPower.h"            /// low power sleep
#include "OneButton.h"           /// multi-button functionality
RTC_M41T62 RTC;                  /// real time clock

#define NUM_COL 6   /// columns are anodes
#define NUM_ROW 6   /// rows are cathodes
#define COL_ON LOW  /// the following four lines of code control switching the multiplexed LEDs on and off
#define COL_OFF HIGH
#define ROW_ON HIGH
#define ROW_OFF LOW

const int colLED[NUM_COL] = { 2, 3, 4, 5, 6, 7 };      /// pins for anode (+) connections on ATmega328p, columns in LED matrix
const int rowLED[NUM_ROW] = { 0, 1, A0, A1, A2, A3 };  /// pins for cathode (-) connections on ATmega328p, rows in LED matrix

const int button1 = 9;   /// pin for button 1
const int button2 = 10;  /// pin for button 2

volatile int hourRow;
volatile int hourColumn;
volatile int minuteRow;
volatile int minuteColumn;
volatile int minuteRemainder;
volatile int weekday;

int lastButtonState = LOW;  // the previous reading from the input pin

uint8_t display[NUM_ROW][NUM_COL];  // this array holds the current image to display

unsigned long previousMillis = 0;
unsigned long previousMillisT = 0;
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers
unsigned long onTime = 20 * 1000;    /// duration time is displayed before going to deep sleep
unsigned long sleepTimer = 0;        /// keep time of how long watch has been awake

const long interval1 = 400;  /// duration of button 1 function
const long interval2 = 200;  /// duration of button 2 function

bool myFlag1 = false;  /// create flag1 for pin change interrupt
bool myFlag2 = false;  /// create flag2 for pin change interrupt

//OneButton button1(9, true);
//OneButton button2(10, true);

/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// - - - - - - - - - - - - - - - setup + loop - - - - - - - - - - - - - - - -

/// No access to serial monitor

void setup() {
  pinMode(button1, INPUT_PULLUP);            /// button1 pulled LOW when pressed
  pinMode(button2, INPUT_PULLUP);            /// button2 pulled LOW when pressed
  Wire.begin();                              /// I2C communication with the RTC
  RTC.begin();                               /// start RTC
  RTC.adjust(DateTime(__DATE__, __TIME__));  /// set RTC time to computer time

  for (int i = 0; i < NUM_COL; i++)  /// set all column pins to OUTPUT and OFF
  {
    pinMode(colLED[i], OUTPUT);        /// set column LEDs to output
    digitalWrite(colLED[i], COL_OFF);  /// turn all columns off
  }
  for (int j = 0; j < NUM_ROW; j++)  /// set all row pins to OUTPUT and OFF
  {
    pinMode(rowLED[j], OUTPUT);        /// set row LEDs to output
    digitalWrite(rowLED[j], ROW_OFF);  /// turn all rows off
  }
  attachPCINT(digitalPinToPCINT(button1), setFlag1, CHANGE);  /// interrupt when button 1's state changes
  attachPCINT(digitalPinToPCINT(button2), setFlag2, CHANGE);  /// interrupt when button 2's state changes

  /*
  /// link the button 1 functions
  button1.attachClick(singleClick1);
  button1.attachDoubleClick(doubleClick1);
  button1.attachMultiClick(multiClick1);
  button1.attachLongPressStart(pressStart1);
  button1.attachLongPressStop(pressStop1);
  button1.attachDuringLongPress(longPress1);

  /// link the button 2 functions
  button2.attachClick(singleClick2);
  button2.attachDoubleClick(doubleClick2);
  button2.attachMultiClick(multiClick2);
  button2.attachLongPressStart(pressStart2);
  button2.attachLongPressStop(pressStop2);
  button2.attachDuringLongPress(longPress2);
*/
}

void loop() {
  /// If the switch changed, due to noise or pressing:
  if (digitalRead(button1) != lastButtonState) {
    /// reset the debouncing timer
    lastDebounceTime = millis();

    if (myFlag1 == true)  /// if myFlag1 is true, display time
    {
  displayTime();
      unsigned long currentMillis = millis();           /// set the millis riiiiight now!
      if (currentMillis - previousMillis >= interval1)  /// if now minus the time when the next step is over is longer than <interval>
      {
        allOff();
        previousMillis = currentMillis;
        myFlag1 = false;  /// flag off
      }
    }
  }

  /// If the switch changed, due to noise or pressing:
  if (digitalRead(button2) != lastButtonState) {
    /// reset the debouncing timer
    lastDebounceTime = millis();

    if (myFlag2 == true)  /// if myFlag2 is true, display time
    {
      light();
      //displayTimeBright();
      unsigned long currentMillisT = millis();            /// set the millis riiiiight now!
      if (currentMillisT - previousMillisT >= interval2)  /// if now minus the time when the next step is over is longer than <duration>
      {
        allOff();
        previousMillisT = currentMillisT;
        myFlag2 = false;  /// flag off
      }
    }
  }

  if (myFlag1 == false && myFlag2 == false)  /// if myFlag1 and myFlag2 are false, go to sleep
  {
    LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);  /// Zzzzzzz
  }
}

/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// - - - - - - - - - - - - - - - button functions - - - - - - - - - - - - - - -

void setFlag1() {
  myFlag1 = true;
}

void setFlag2() {
  myFlag2 = true;
}

/*
/// button1
void singleClick1() {  /// function called when button pressed once
}
void doubleClick1() {  /// function called when button was pressed two times in a short timeframe
}
void multiClick1() {  /// function called when button pressed multiple (>2) times in a short timeframe
}
void pressStart1() {  /// function called when button held down for one second or more
}
void pressStop1() {  /// function called when button released after long hold
}

/// button2
void singleClick2() {  /// function called when button pressed once
}
void doubleClick2() {  /// function called when button was pressed two times in a short timeframe
}
void multiClick2() {  /// function called when button pressed multiple (>2) times in a short timeframe
}
void pressStart2() {  /// function called when button held down for one second or more
}
void pressStop2() {  /// function called when button released after long hold
}
*/

/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// - - - - - - - - - - - - - - - time functions - - - - - - - - - - - - - - - -

void getTime() {
  static int lastMinute = -1;
  DateTime now = RTC.now();  /// set "now" from the computer's time

  if (now.minute() == lastMinute)
    return;  /// No need to re-calculate if the time has not changed

  lastMinute = now.minute();
  int hour = now.hour() % 12;
  int minute = now.minute() - 5;
  weekday = now.dayOfWeek() - 1;

  /// set hour
  if (hour == 0)
    hour = 12;
  hourRow = (hour - 1) / 2;
  hourColumn = (hour - 1) % 2;

  /// set minute --- something not quite right here
  minuteRow = minute / 10;
  minuteColumn = 2 + ((minute % 10) > 5);
  // minuteColumn = 2 + ((minute - 1) / 5)  % 2;
  minuteRemainder = minute % 5;
}

/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// - - - - - - - - - - - - - - - display functions - - - - - - - - - - - - - -

/// turn on specific LEDs
inline void ledOn(int row, int col) {
  digitalWrite(rowLED[row], HIGH);  /// row pin to +3V
  digitalWrite(colLED[col], LOW);   /// column pin to 0V
  //allOff();
}

/// turn off specific LEDs
inline void ledOff(int row, int col) {
  digitalWrite(rowLED[row], LOW);   /// row pin to 0V
  digitalWrite(colLED[col], HIGH);  /// column pin to +3V
}

/// turn on all LEDs
void allOn() {
  for (int i = 0; i < NUM_COL; i++) {
    digitalWrite(colLED[i], COL_ON);  /// all columns to 3V
  }
  for (int j = 0; j < NUM_ROW; j++) {
    digitalWrite(rowLED[j], ROW_ON);  /// all rows to 3V
  }
}

/// turn off all LEDs
void allOff() {
  for (int i = 0; i < NUM_ROW; i++) {
    digitalWrite(rowLED[i], ROW_OFF);  /// all row pins to 0V
  }
  for (int j = 0; j < NUM_COL; j++) {
    digitalWrite(colLED[j], COL_OFF);  /// all column pins to 0V
  }
}

void displayTime() {  /// still slight flicker to the LEDs, especially when viewed up close. Decreasing the delay reduces the flicker but dims the LEDs
  getTime();
//  allOff();
  ledOn((int)hourRow, (int)hourColumn);
  delayMicroseconds(150);
  ledOff((int)hourRow, (int)hourColumn);
  ledOn((int)minuteRow, (int)minuteColumn);
  delayMicroseconds(150);
  ledOff((int)minuteRow, (int)minuteColumn);
  ledOn((int)minuteRemainder, 4);
  delayMicroseconds(100);
  ledOff((int)minuteRemainder, 4);
  ledOn((int)weekday, 5);
  delayMicroseconds(150);
  ledOff((int)weekday, 5);
}

void displayTimeBright() {  /// the LEDs are much brighter than displayTime(), but additional/wrong LEDs are turned on aswell for some reason
  getTime();
  ledOn((int)hourRow, (int)hourColumn);
  ledOn((int)minuteRow, (int)minuteColumn);
  ledOn((int)minuteRemainder, 4);
  ledOn((int)weekday, 5);
}

void light()  /// turns on four white LEDs
{
  ledOn(0, 4);
  ledOn(1, 4);
  ledOn(2, 4);
  ledOn(3, 4);
}

/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// - - - - - - - - - - - - - - - - power functions - - - - - - - - - - - - - -

const long InternalReferenceVoltage = 1062;  /// Adjust this value to your board's specific internal BG voltage

/// results are Vcc * 100, so 3.3V would be 330?
int getBandgap() {
  /// REFS0 : Selects AVcc external reference
  /// MUX3 MUX2 MUX1 : Selects 1.1V (VBG)
  ADMUX = bit(REFS0) | bit(MUX3) | bit(MUX2) | bit(MUX1);
  ADCSRA |= bit(ADSC);           /// start conversion
  while (ADCSRA & bit(ADSC)) {}  /// wait for conversion to complete
  int results = (((InternalReferenceVoltage * 1024) / ADC) + 5) / 10;
  return results;
  if (results > 0.0000001) {  /// is this the right value?
    allOff();
    digitalWrite(rowLED[5], HIGH);  /// ON - cathode pin to +5V
    digitalWrite(colLED[5], LOW);   /// ON - anode pin to 0V
    delay(100);
    digitalWrite(rowLED[5], LOW);   /// OFF - cathode pin to 0V
    digitalWrite(colLED[5], HIGH);  /// OFF - anode pin to 5V
    delay(100);
  }
}

I think it's a matrix coordinates problem but I can't quite get it. Could someone please suggest a change to my code in order to overcome this issue?

Thanks,
Dylan

minuteReminder is 0, 1, 2, 3, 4 for minutes 0, 1, 2, 3, 4 but you only have lights 1, 2, 3, and 4 in rows 0, 1, 2, and 3. I think you need to filter out 0 and shift the value down one:

  if (minuteRemainder > 0)
  {
    ledOn((int)minuteRemainder-1, 4);
    delayMicroseconds(100);
    ledOff((int)minuteRemainder-1, 4);
  }
1 Like

You need a small test sketch for the display matrix only.
Then you need more explanation how to convert the time. If you look at the code a year from now, it should still be clear what the code is doing. When I look my own code after a few years, then I sometimes wished that I had followed my own advice :rofl:

Does the RTC give the day of week as 1...7 and 1 is Sunday ?

I have a few notes:

  • Why the "volatile" ? I think you don't need them.
  • Why the /// (three slashes), it is confusing.
  • Why so many (int) casts ? It makes the code harder to read.
  • If the day of the week has no led for Sunday, how can you keep the day-of-week-leds off ? That is not possible with your sketch.
  • The same for minute 0 and 5, the remainder leds should stay off.
  • I have my doubts with the minutes, because 00 minutes is at the bottom.
  • Is it possible to make those delays for the leds longer ? In the range of milliseconds perhaps ?
  • Please take care of the comments. Say "led" instead of "leds" if only one led is used. You had the comment for row/column and anode/cathode mixed up.

When rewriting the code, I think that I have something different. But at least it works.

// Forum: https://forum.arduino.cc/t/time-almost-displaying-correctly-on-led-watch-matrix/1104816/2
// This Wokwi project: https://wokwi.com/projects/359845167514629121

#define NUM_COL 6   /// columns are anodes
#define NUM_ROW 6   /// rows are cathodes
#define COL_ON LOW  /// the following four lines of code control switching the multiplexed LEDs on and off
#define COL_OFF HIGH
#define ROW_ON HIGH
#define ROW_OFF LOW

// pins for cathode (-) connections on ATmega328p, columns in LED matrix
const int colLED[NUM_COL] = { 2, 3, 4, 5, 6, 7 };      
// pins for anode (+) connections on ATmega328p, rows in LED matrix
const int rowLED[NUM_ROW] = { 0, 1, A0, A1, A2, A3 };  

int hour         = 5;      // 0...12 or 0...23
int minute       = 35;     // 0...59
int dayoftheweek = 2;      // 1(sunday)...7

void setup() {

  for (int i = 0; i < NUM_COL; i++)  /// set all column pins to OUTPUT and OFF
  {
    pinMode(colLED[i], OUTPUT);        /// set column LEDs to output
    digitalWrite(colLED[i], COL_OFF);  /// turn all columns off
  }
  for (int j = 0; j < NUM_ROW; j++)  /// set all row pins to OUTPUT and OFF
  {
    pinMode(rowLED[j], OUTPUT);        /// set row LEDs to output
    digitalWrite(rowLED[j], ROW_OFF);  /// turn all rows off
  }
}

void loop() {
  displayTime();
}

// displayTime is the led matrix multiplexing function.
void displayTime() {
  // ----------------------------------------------------
  // hour
  // ----------------------------------------------------
  int h = hour;        // 0...11
  if (h == 0) 
    h = 12;            // 1...12

  int hourRow = (h - 1) / 2;
  int hourColumn = (h - 1) % 2;

  ledOn( hourRow, hourColumn);
  delayMicroseconds(150);
  ledOff( hourRow, hourColumn);

  // ----------------------------------------------------
  // minute
  // ----------------------------------------------------
  int m = minute;      // 0...59
  int minuteRow;
  if( m < 5)           // zero minutes is located at the bottom
    minuteRow = 5;
  else
    minuteRow = (m - 5) / 10;  // start at 5 at the top
  // A change of column for each 5 minutes: (m / 5)
  // But the 10,20,30 are the right column: + 1
  // There are two columns                : %2
  // The start column is                  : 2
  int minuteColumn = 2 + ((m / 5) + 1) % 2;

  ledOn( minuteRow, minuteColumn);
  delayMicroseconds(150);
  ledOff( minuteRow, minuteColumn);

  // minute remainder 1...4
  int r = m % 5;
  if( r > 0)          // leds off for seconds 0 and 5
  {
    r--;              // row 0 for remainder 1
    ledOn( r, 4);
    delayMicroseconds(100);
    ledOff( r, 4);
  }

  // ----------------------------------------------------
  // day of the week
  // ----------------------------------------------------
  int w = dayoftheweek - 1; // 1(sunday)...7 -> 0...6
  if( w > 0)                // no led for sunday
  {
    w--;                    // monday is row 0
    ledOn(w, 5);
    delayMicroseconds(150);
    ledOff(w, 5);
  }
}

// turn on specific LED
void ledOn(int row, int col) {
  digitalWrite(rowLED[row], HIGH);  // row pin to +3V
  digitalWrite(colLED[col], LOW);   // column pin to 0V
}

// turn off specific LED
void ledOff(int row, int col) {
  digitalWrite(rowLED[row], LOW);   // row pin to 0V
  digitalWrite(colLED[col], HIGH);  // column pin to +3V
}

Try the sketch in Wokwi:

1 Like

Thanks @johnwasser. Your suggestion didn't seem to change the behaviour of the display. I have responded to @Koepel below, whose suggestion has solved my problem. Thanks again!

Thank you so much @Koepel! Your suggested code has done the trick! My hours and weekdays were already working okay, but now the time is displaying as it should, every minute.
I have responded to your notes below:

Why the "volatile" ? I think you don't need them.

I have removed these and followed your suggestions.

Why the /// (three slashes), it is confusing.

Apologies for the confusion, I find /// helps me more quickly differentiate between commented out code and my notes.

Why so many (int) casts ? It makes the code harder to read.

This my poor coding, so I will take note of this and learn.

If the day of the week has no led for Sunday, how can you keep the day-of-week-leds off ? That is not possible with your sketch.

When it's not a weekday, I'm off the clock so I don't care! :slight_smile: I was thinking of turning on the first two leds for Sunday, and the last two leds for Saturday.

I have my doubts with the minutes, because 00 minutes is at the bottom.

If I did it all again, I would probably rearrange the led positions on the watch face.

Is it possible to make those delays for the leds longer ? In the range of milliseconds perhaps ?

For efficiency, I am using persistence of vision (PoV) to flash each led so quickly that it looks like they are all on at the same time. It's a fine balance between too short of a delay (which makes the leds too dim) and too long of a delay (which introduces a discerable flicker). I would like the leds displayed more brightly (even if it used more power) but I'm not sure how.

Again, thank you very much for your time and assistance, it is very much appreciated! It has reinvigorated me to continue working on this project and developing my code. I will endeavour to improve my coding.

If you really need it to be that fast, then you could turn the previous led off when a new led needs to be turned on.

Remove the ledOff() function.
Rename the ledOn() to updateLed() or something like that.
Add global variables:

int prevRow, prevCol;    // the previous led to turn off.

Turn the led off just before the new led is turned on.
At startup, the uninitialized variables are all zero as defined by the C++ language. Therefor the led at [0,0] is turned off at startup. That is no problem.

The update function:

void updateLed(int row, int col) {
  // Turn off the previous led
  digitalWrite(rowLED[prevRow], LOW);   // row pin to 0V
  digitalWrite(colLED[prevCol], HIGH);  // column pin to +3V

  // Turn on the new led
  digitalWrite(rowLED[row], HIGH);  // row pin to +3V
  digitalWrite(colLED[col], LOW);   // column pin to 0V

  // Remember the row and col for the new led to be turned off next time
  prevRow = row;
  prevCol = col;
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.