Displaying the time on LED watch using interrupt function

Hello,

I'm trying to display the time on a custom LED watch face using an ISR interrupt function so that the LEDs are turned on/off so fast that there is image persistence. I can get the watch to display the hour well enough but as soon as I try to add in the minute or day of the week, it all falls apart and the display goes wacky.

It uses an ATmega328P-MMHR (28-pin VQFN) with an M41T62 (8 pin LCC) real time clock, powered by a CR2016 coin battery, and displays the time and day of the week on 33 multiplexed LEDs, ideally whenever a button is pressed.

Can anyone tell me where I'm going wrong and what I need to change to get things working properly?

Thanks,
Dylan.


The watch:

How time is displayed:

The schematic:

I program the ATmega via six ICSP pins attached to an Arduino Uno using 'Arduino as ISP' programmer:

Full code:

#define TIMER_INTERRUPT_DEBUG         2
#define _TIMERINTERRUPT_LOGLEVEL_     0
#define USE_TIMER_1     true

#include "Wire.h"                    /// I2C library
#include "M41T62.h"                  /// RTC library for M41T62 (on-board watch) - NB: Use "dayOfWeek"
#include <TimerInterrupt.h>
RTC_M41T62 RTC;                      /// on-board M41T62 RTC

#define TIMER_FREQ_HZ        4000.0

#define NUM_COL       6                /// Columns are anodes
#define NUM_ROW       6                /// Rows are cathodes
#define COL_ON    LOW               /// badge code has these four inverted
#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 ATmega328, columns in LED matrix
const int rowLED[NUM_ROW] = {0, 1, A0, A1, A2, A3};      /// pins for cathode (-) connections on ATmega328, rows in LED matrix

const int hourRows = 6;
const int hourCols = 2;

int rowPin = 6;
int colPin = 6;
int hourRow;
int hourColumn;
int minuteRow;
int minuteColumn;

int hourArray[hourRows][hourCols] = {{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}, {11, 12}};

/// - - - - - - - - - - - SETUP + LOOP - - - - - - - - - - - - - - - -
/// No access to serial monitor

void setup() {
  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);                 /// output
    digitalWrite(colLED[i], COL_OFF);           /// turn off
  }
  for (int j = 0; j < NUM_ROW; j++)              /// set all row pins to OUTPUT and OFF
  {
    pinMode(rowLED[j], OUTPUT);                 /// output
    digitalWrite(rowLED[j], ROW_OFF);           /// turn off
  }
  ITimer1.init();                               /// initialise timer 1
  ITimer1.attachInterrupt(TIMER_FREQ_HZ, TimerHandler);
}

void loop() {
  allOff();
  getTime();
}

void TimerHandler() {
  static bool toggle = false;
  displayTime();
  toggle = !toggle;
}

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

void allOff()                       /// turns off all LEDs
{
  /// all cathode pins to 0V
  for (int i = 0; i < rowPin; i++)         /// rowPin
  {
    digitalWrite(rowLED[i], LOW);
  }
  /// all anode pins to +5V
  for (int j = 0; j < 1; j++)         /// colPin
  {
    digitalWrite(colLED[j], HIGH);
  }
}

void ledOn(int row, int col)                /// turns on specfic LEDs
{
  //allOff();
  digitalWrite(rowLED[row], HIGH);        /// cathode pin to +5V
  digitalWrite(colLED[col], LOW);         /// anode pin to 0V
}

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

void getTime()
{
  DateTime now = RTC.now();
  int hour = now.hour();
  int minute = now.minute();
  int minuteRemainder = (now.minute() % 10) - 6;
  int weekday = now.dayOfWeek() - 1;

  /// set hour
  if (hour > 12) {
    hour -= 12;
  };

  for (int i = 0; i < hourRows; i++) {
    for (int j = 0; j < hourCols; j++) {
      if (hour == hourArray[i][j]) {
        (hourRow = i);
        (hourColumn = j);
      }
    }
  }

  /// Set minute
  if (minute % 10 == 5) {
    /// The minute ends in 5
    minuteColumn = 2;
    minuteRow = (minute - 6) / 10;
  } else if (minute % 10 == 0) {
    /// The minute ends in 0
    minuteColumn = 3;
    if (minute == 0) {
      minuteRow = 5;
    } else {
      minuteRow = (minute / 10) - 1;
    }
  }
}

void displayTime()
{
  ledOn(hourRow, hourColumn);
  //ledOn(minuteRow, minuteColumn);
  //turnOnRemainderLeds(minuteRemainder);
  //ledOn(wkDay, 5);
}

Do you have a question?

2 Likes

You seem to be clearing the display every time through the loop() function, but setting the LEDs on a timed interrupt. Those processes are not synchronized. I don't see how that can work at all.

1 Like

Plan out how much time you need the bit to show for persistence of vision and then do that.

The commented-out hint that LedOn() would do allOff() makes displayTime() look like it wouldn't be on long enough for POV.

I see several potential issues:

  1. You are turning off all LEDs off in the loop() function. An interrupt could happen at any time, literally microseconds before or after you clear an LED.
  2. ALL variables shared between the ISR and non-interrupt code (e.g. loop()) should be declared with the volatile keyword.
  3. This is an 8-bit controller. Shared multibyte variables (like int) should not be manipulated in non-interrupt code unless interrupts are disabled.

Why do you think you must use an interrupt to update the display?

1 Like

Thanks Todd,
I think I've addressed the issues you saw in my code (pasted again below) but I am still not getting the desired result.

Why do you think you must use an interrupt to update the display?

I've been counselled to split my code into two parts, i.e., use the main loop for working out what to display, and then using a timer interrupt which gets called frequently and is responsible for turning the LEDs on & off. I'm using timer IRQ so that the precise intervals at which it is called keeps the LED brightness consistent. When I've tried to display the time without the interrupts, the brightness is all over the place and depends on how many LEDs are on at once.

#define TIMER_INTERRUPT_DEBUG         2
#define _TIMERINTERRUPT_LOGLEVEL_     0
#define USE_TIMER_1     true

#include "Wire.h"                    /// I2C library
#include "M41T62.h"                  /// RTC library for M41T62 - NB: Use "dayOfWeek"
#include <TimerInterrupt.h>
RTC_M41T62 RTC;

#define TIMER_FREQ_HZ        900.0

#define NUM_COL       6                /// Columns are anodes
#define NUM_ROW       6                /// Rows are cathodes
#define COL_ON    LOW               /// badge code has these four inverted
#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 ATmega328, columns in LED matrix
const int rowLED[NUM_ROW] = {0, 1, A0, A1, A2, A3};      /// pins for cathode (-) connections on ATmega328, rows in LED matrix

const int hourRows = 6;
const int hourCols = 2;

int rowPin = 6;
int colPin = 6;
volatile int hourRow;
volatile int hourColumn;
volatile int minuteRow;
volatile int minuteColumn;
volatile int minRemain;
volatile int wkDay;

int hourArray[hourRows][hourCols] = {{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}, {11, 12}};

/// - - - - - - - - - - - SETUP + LOOP - - - - - - - - - - - - - - - -
/// No access to serial monitor

void setup() {
  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);                 /// output
    digitalWrite(colLED[i], COL_OFF);           /// turn off
  }
  for (int j = 0; j < NUM_ROW; j++)              /// set all row pins to OUTPUT and OFF
  {
    pinMode(rowLED[j], OUTPUT);                 /// output
    digitalWrite(rowLED[j], ROW_OFF);           /// turn off
  }
  ITimer1.init();                               /// initialise timer 1
  ITimer1.attachInterrupt(TIMER_FREQ_HZ, TimerHandler);
}

void loop() {
  getTime();
}

void TimerHandler() {
  displayTime();
}

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

void allOff()                       /// turns off all LEDs
{
  /// all cathode pins to 0V
  for (int i = 0; i < rowPin; i++)         /// rowPin
  {
    digitalWrite(rowLED[i], LOW);
  }
  /// all anode pins to +5V
  for (int j = 0; j < 1; j++)         /// colPin
  {
    digitalWrite(colLED[j], HIGH);
  }
}

void ledOn(volatile int row, volatile int col)                /// turns on specfic LEDs
{
  //allOff();
  digitalWrite(rowLED[row], HIGH);        /// cathode pin to +5V
  digitalWrite(colLED[col], LOW);         /// anode pin to 0V
  delay(10);
  digitalWrite(rowLED[row], LOW);        /// cathode pin to +5V
  digitalWrite(colLED[col], HIGH);         /// anode pin to 0V
}

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

void getTime()
{
  DateTime now = RTC.now();
  volatile int hour = now.hour();
  volatile int minute = now.minute();
  volatile int minuteRemainder = (now.minute() % 10) - 6;
  volatile int weekday = now.dayOfWeek() - 1;

  /// set hour
  if (hour > 12) {
    hour -= 12;
  };

  for (int i = 0; i < hourRows; i++) {
    for (int j = 0; j < hourCols; j++) {
      if (hour == hourArray[i][j]) {
        (hourRow = i);
        (hourColumn = j);
      }
    }
  }

  /// Set minute
  if (minute % 10 == 5) {
    /// The minute ends in 5
    minuteColumn = 2;
    minuteRow = (minute - 6) / 10;
  } else if (minute % 10 == 0) {
    /// The minute ends in 0
    minuteColumn = 3;
    if (minute == 0) {
      minuteRow = 5;
    } else {
      minuteRow = (minute / 10) - 1;
    }
  }

  /// Set minute remainder
  /// Take 6 instead of 5 to get a zero index row to set
  if (minuteRemainder >= 0) {
    minRemain = minuteRemainder;
  }

  /// Set day of week
  if (weekday < 5) {
    wkDay = weekday;
  }
}

void displayTime()
{
  ledOn(hourRow, hourColumn);
  ledOn(minuteRow, minuteColumn);
  ledOn(minRemain, 4);
  ledOn(wkDay, 5);
}

You didn't say what result you are getting. Please always provide complete debugging information.

How much time/dutycycle do the LEDs need to be on to have the desired brightness?

Every 1.111 milliseconds you call TimerHandler() which calls displayTime() which calls ledOn() four times and ledOn() contains a 10 millisecond delay. So your ISR takes about 40 times as long a your interrupt interval. That is not going to work.

1 Like

Thanks everyone for your time and I will endeavour to provide clearer debugging info.

I've taken out the delay so now the micro should wake up every 1.11msec, turn on the LEDs I want, then turn all LEDs off.
When allOff() is placed at the end of displayTime(), e.g.,

void displayTime() {
  ledOn((int)hourRow, (int)hourColumn);

  //ledOn((int)minuteRow, (int)minuteColumn);
  //ledOn((int)minRemain, 4);
  //ledOn((int)wkDay, 5);
  
  allOff();
}

then the LEDs flicker at frequencies less than around 700Hz, so I've set my timer frequency to 1kHz for nice solid PoV.

#define TIMER_FREQ_HZ        1000.0

Here is the 3rd hour displaying okay:

I thought it would be better to put allOff() at the beginning (clear the display first) but if I do that the LEDs don't switch off at all, i.e., no flicker even at 100Hz. Any reason why this would be the case?

How much time/dutycycle do the LEDs need to be on to have the desired brightness?

The LEDs aren't super bright but if I run ledOn() multiple times, the brightness increases.

I've also made ledOn() an inline function taking non volatile arguments, only turning the LEDs on (no delay, no turning LEDs off):

inline void ledOn(int row, int col) {      /// turns on specific LEDs
  digitalWrite(rowLED[row], HIGH);        /// cathode pin to +5V
  digitalWrite(colLED[col], LOW);         /// anode pin to 0V
}

When I uncomment "ledOn((int)minuteRow, (int)minuteColumn);", I get the following display for about 80 seconds:


Then the display changes to this (the time of upload is 3:50pm):

It should only be displaying the red 3 and the blue 50, so at this stage I haven't progressed to uncommenting the other lines in displayTime().

That won't work because the rows and columns are shared. You have to turn off all the other rows and columns before turning another LED on.

I would step back from the timer-driven display and just use a delayMicroseconds() to set how long the LED is on. There are 4 LEDs to light so you should be able to light each LED for about 8 milliseconds and still get a 30 Hz refresh rate. Here is a sketch that might work:

#include "Wire.h"                    /// I2C library
#include "M41T62.h"                  /// RTC library for M41T62 (on-board watch) - NB: Use "dayOfWeek"

RTC_M41T62 RTC;                      /// on-board M41T62 RTC
/// on-board M41T62 RTC

#define NUM_COLS       6                /// Columns are anodes
#define NUM_ROWS       6                /// Rows are cathodes
const int colLED[NUM_COLS] = {2, 3, 4, 5, 6, 7};          /// pins for anode (+) connections on ATmega328, columns in LED matrix
const int rowLED[NUM_ROWS] = {0, 1, A0, A1, A2, A3};      /// pins for cathode (-) connections on ATmega328, rows in LED matrix
#define COL_ON    LOW               /// badge code has these four inverted
#define COL_OFF   HIGH
#define ROW_ON    HIGH
#define ROW_OFF   LOW


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

// Set the ON time for each LED.  
// Increase the number to increase brightness, power draw, and flicker.
// Adjust to your liking.
const unsigned long Brightness = 3000;

/// - - - - - - - - - - - SETUP + LOOP - - - - - - - - - - - - - - - -
/// No access to serial monitor

void setup()
{
  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_COLS; i++)              /// set all column pins to OUTPUT and OFF
  {
    pinMode(colLED[i], OUTPUT);                 /// output
    digitalWrite(colLED[i], COL_OFF);           /// turn off
  }
  for (int j = 0; j < NUM_ROWS; j++)              /// set all row pins to OUTPUT and OFF
  {
    pinMode(rowLED[j], OUTPUT);                 /// output
    digitalWrite(rowLED[j], ROW_OFF);           /// turn off
  }
}

void loop()
{
  getTime();
  displayTime();
}

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

void getTime()
{
  static int lastMinute = -1;
  DateTime now = RTC.now();

  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();
  weekday = now.dayOfWeek() - 1;

  /// set hour
  if (hour == 0)
    hour = 12;

  //  1   2
  //  3   4
  //  5   6
  //  7   8
  //  9  10
  // 11  12

  hourRow = (hour - 1) / 2;
  hourColumn = (hour - 1) % 2;

  /// Set minute
  minuteRemainder = minute % 5;
  minuteRow = minute / 10;
  minuteColumn = 2 + ((minute % 10) > 5);
}

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

void ledOn(int row, int col)                /// turns on specfic LEDs
{
  digitalWrite(rowLED[row], ROW_ON);
  digitalWrite(colLED[col], COL_ON);
  delayMicroseconds(Brightness);
  digitalWrite(rowLED[row], ROW_OFF);
  digitalWrite(colLED[col], COL_OFF);
}

void turnOnRemainderLeds(int remainder)
{
  if (remainder == 0)
  {
    delayMicroseconds(Brightness);
    return;
  }
  ledOn(remainder - 1, 4);
}

void displayTime()
{
  ledOn(hourRow, hourColumn);
  ledOn(minuteRow, minuteColumn);
  turnOnRemainderLeds(minuteRemainder);
  ledOn(weekday, 5);
}
2 Likes

Thank you very much for the help, John and everyone else, I am still working on it and the code you shared @johnwasser, controls the LEDs well for brightness and flicker, however the time is five minutes fast. I tried a variation to your code but the displayed time is also five minutes fast:

  minuteRow = minute / 10;
  minuteColumn = 2 + ((minute % 10) > 5);
  //minuteColumn = 2 + ((minute - 1) / 5) % 2;

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