0
votes

everyone, I am using, P10 Dot Matrix Display with Arduino Uno. I am using P10 Library from this link. P10_LED and I need to display the one-hour countdown on the display module. The given library uses TimerOne library. So for countdown i am using MsTimer2 library which usese timer2 of arduino.

When I individually run both of the libraries, my scrolling on the display is perfect and my timer library also generates a pure 1sec interrupt. Now what I did is the added both the library in my project and I am doing the countdown. But now suddenly my MsTimer2 doesn't generate pure 1sec.

Here is the code.

#include <MsTimer2.h>
#include <TimerOne.h>
#include"SPI.h"
#include <ledP10.h>

LedP10 myled;
uint8_t minute = 0, second = 0, hour = 1;
volatile bool xIsCountDone = false;
volatile bool xIsInterruptOcuured = false;
char time_buff[100];

void setup() 
{
    Serial.begin(9600);
    myled.init(3,4,8,9 ,3);
    sprintf((char*)time_buff, "    %d%d:%d%d:%d%d", (hour/10), (hour%10),(minute/10), (minute%10),(second/10), (second%10));
    Serial.println((char*)time_buff);
    myled.showmsg_single_static((char*)time_buff, 0);
    xIsInterruptOcuured = false;
    //myled.showmsg_single_scroll("this is single led test",2,8,0);
    MsTimer2::set(1000, count);
    MsTimer2::start();
}

void loop() {
  if (xIsInterruptOcuured == true)
  {
    sprintf((char*)time_buff, "    %d%d:%d%d:%d%d", (hour/10), (hour%10),(minute/10), (minute%10),(second/10), (second%10));
    Serial.println((char*)time_buff);
    myled.showmsg_single_static((char*)time_buff, 0);
    xIsInterruptOcuured = false;
  }
}

void count(){
  second--;
  if (second <= 0 || second > 59)
  {
    second = 59;
    minute--;
    if (minute <= 0 || minute > 59)
    {
      minute = 59;
      hour--;
      if (hour <= 0 || hour > 12)
      {
        xIsCountDone =true;
      }
    }
  }
  Serial.println(millis());
  xIsInterruptOcuured = true;
}

In the interrupt routine, I am printing millis() to see at after how many ms the interrupt occurs. The results are something like this.

15:33:02.684 -> 1199
15:33:04.371 -> 2396
15:33:06.059 -> 3592
15:33:07.746 -> 4783
15:33:09.434 -> 5986
15:33:11.121 -> 7181
15:33:12.855 -> 8379
15:33:14.543 -> 9578
15:33:16.230 -> 10768
15:33:17.918 -> 11974
15:33:19.605 -> 13168
15:33:21.292 -> 14365
15:33:22.980 -> 15562
15:33:24.667 -> 16751
15:33:26.402 -> 17955

When I use only MsTimer2 library the results are something like this.

15:37:21.241 -> 998
15:37:22.226 -> 1998
15:37:23.257 -> 2998
15:37:24.241 -> 3998
15:37:25.226 -> 4998
15:37:26.257 -> 5998
15:37:27.241 -> 6998
15:37:28.225 -> 7998
15:37:29.257 -> 8998
15:37:30.241 -> 9998
15:37:31.225 -> 10998
15:37:32.256 -> 11998
15:37:33.241 -> 12998
15:37:34.225 -> 13998
15:37:35.256 -> 14998

My guess, it's happening because of the TimerOne library but I couldn't find the solution. In ledP10.cpp there is a callback method for timer1 and it contains loops and may line of code. But is timer1 interrupts priority is higher than timer2? But according to the ATmega328p datasheet, the vector no. for Timer2 is less than Timer1. Doesn't that mean Timer2 has a higher priority? My ultimate goal is to do the one-hour countdown. Any help with this problem or any additional information i am missing which will be useful or any other solution other than using timer2 interrupt will be appreciated.

Regards.

EDIT

Here is the code I used with millis() and gave me around 12min difference.

uint8_t new_buff[100];
unsigned long startMillis;  //some global variables available anywhere in the program
unsigned long currentMillis;
const unsigned long period = 1000;  //the value is a number of milliseconds
uint8_t minute = 0, second = 0, hour = 1;
char time_buff[100];
void setup() 
{
    myled.init(3,4,8,9,3);
    Serial.begin(9600);
    sprintf((char*)time_buff, "    %d%d:%d%d:%d%d", (hour/10), (hour%10),(minute/10), (minute%10),(second/10), (second%10));
    //Serial.println((char*)time_buff);
    myled.showmsg_single_static((char*)time_buff, 0);
    startMillis = millis();
}



void loop() {
  currentMillis = millis();  //get the current "time" (actually the number of milliseconds since the program started)
  if (currentMillis - startMillis >= period)  //test whether the period has elapsed
  {
    Serial.println(millis());
    second--;
    startMillis = currentMillis;  //IMPORTANT to save the start time of the current LED state.
    if (second <=0 || second > 59)  {
      second = 59;
      minute--;
      if (minute <=0 || minute > 59)  {
        minute = 59;
        hour--;
        if (hour <= 0 || hour > 12) {
          hour = 0;
        }
      }
    }
    sprintf((char*)time_buff, "    %d%d:%d%d:%d%d", (hour/10), (hour%10),(minute/10), (minute%10),(second/10), (second%10));
    myled.showmsg_single_static((char*)time_buff, 0);
    startMillis = currentMillis;
  }
}
2
I don't have time to dig into your code atm. my lazy programmer's advice: get a realtime clock so you don't have to bother with timer interrupts :)Piglet
Even simpler solution would be to work with the millis() function, since the real time is not necessary for an one hour countdown.Fitzi
@Fitzi using millis() also doesn't give accurate countdown. There is a huge difference around 12 min. I measured using the stopwatch.Devjeet Mandal
@Piglet Yeah i have a spare ds3231 but i wanted to try it using timers. But if nothing happens that will be my only optionDevjeet Mandal
Can you show an example how you used millis? Because being 12 minutes off in an one hour countdown is very likely the result from some other issue, not millis being inaccurate.Fitzi

2 Answers

1
votes

This answer targets your example using millis(). You can avoid accumulating errors over time, by not setting the next update relative to the current time. Rather just increment it by one second each time. This way, it does not matter if your main loop gets blocked by an interrupt for some milliseconds.

Also note that you don't have to save hours, minutes and seconds separately, you can just calculate them:

unsigned long nextMillis = 1000;
unsigned long targetTime = 1 * 60 * 60 * 1000; // 1 hour in milliseconds

void setup(){
    myled.init(3,4,8,9,3);
    Serial.begin(9600);
    updateMyLed(0);
}


void updateMyLed( unsigned long elapsedTime ){

    char buffer[100];       
    unsigned long timeLeftInSeconds = (targetTime - elapsedTime) / 1000;

    uint8_t hour = timeLeftInSeconds / 3600;
    timeLeftInSeconds -= hour * 3600;
    uint8_t minute = timeLeftInSeconds / 60;
    uint8_t second = timeLeftInSeconds - (minute * 60);

    sprintf((char*)buffer, "    %d%d:%d%d:%d%d", (hour/10), (hour%10), (minute/10), (minute%10), (second/10), (second%10));
    myled.showmsg_single_static((char*)buffer, 0);
}

void loop() {

    if( millis() >= nextMillis ){
        updateMyLed(nextMillis);
        nextMillis += 1000;
    }

}
0
votes

In the Arduino world, some libraries are disabling the interrupts.

This happens with all WS2812 LEDS and also yours. Without disabling interrupts, there would be a timing problem with the external devices.

So, you should never use interrupts or library with interrupts, if you use another one who will disable the interrupts.

Do you want use the P10 library? You can, but do not use interrupts in your code. Do not add other libraries like IR_remote, since it will not work correctly.

Back to your problem, just update your timer in the loop. And do not wait until the a second is over to update your time by 1 second! This will always be more than 1second.

You can for example convert the millis to seconds seconds = millis() / 1000;. Your loop could be so:

void loop() {
  currentSeconds = millis() / 1000;  //get the current "time" (actually the number of milliseconds since the program started)
  if (currentSeconds != savedSeconds)  //test whether the period has elapsed
  {
    savedSeconds = currentSeconds;
    Serial.println(millis());
    second--;
    if (second <=0 || second > 59)  {
      second = 59;
      minute--;
      if (minute <=0 || minute > 59)  {
        minute = 59;
        hour--;
        if (hour <= 0 || hour > 12) {
          hour = 0;
        }
      }
    }
    sprintf((char*)time_buff, "    %d%d:%d%d:%d%d", (hour/10), (hour%10),(minute/10), (minute%10),(second/10), (second%10));
    myled.showmsg_single_static((char*)time_buff, 0);
  }
}