LCD and refreshing a countdown timer - best practices?

I'm using a Seeedstudio 2.8" LCD display to display data from a few sensors. I've also got a timer that counts down to zero in the "00:00:00" format. I'm pretty happy with my counter logic (about 30 seconds fast after an hour with no RTC; could be tweaked with a small delay), but there's got to be an alternative to how I've got it displaying/updating now.

I'm converting my seconds, minutes and hours into a string using dtostrf() and then concatenating them with the ":" separator into one composite string that get written to the LCD each pass through the main loop. However, the timer sort of "flashes" when it's written to the screen in white and then rewritten in black immediately after (in order to clear the pixels out for the next update of the timer).

I was wondering if the best way to circumvent this is to break up the "00:00:00" composite string into three separate components (one for hour, one for minute, and one for second) and only update the components when necessary. That way, only the second component would be flashing continuously, and the minute/hour components would only flash when they're updated.

Does anyone have any experience with this or have any ideas? I tend to over-complicate things when left in a vacuum so I thought I'd reach out and get some perspective.

p.s. I'm using this to monitor/control beer brewing equipment, so a 30 second error after an hour is totally acceptable as that's the longest I'll ever really be "timing" anything in the process.

Thanks!

KCB

Hi

kbarre123: I was wondering if the best way to circumvent this is to break up the "00:00:00" composite string into three separate components (one for hour, one for minute, and one for second) and only update the components when necessary. That way, only the second component would be flashing continuously, and the minute/hour components would only flash when they're updated.

That's what i would do (and part of it is what i've been doing so far). Updating only when necessary will also get rid of flashing seconds indicator, as you'd only erase and rewrite a new value once every second instead of every iteration. So yours sounds like a good plan to me.

Happy brewing !

Thanks man. I'll get on it then! Just out of curiosity, are you only updating one digit at a time or one component at a time? For instance, moving from 00:00:00 to 00:00:01, are you updating the one's digit of the seconds component or both? I'm guessing I'd have to compare the char0 and see if it needs to be rewritten? That's going to result in quite the nested if/else statement to do the whole thing!

I went and looked at one of the libraries for that display.
The text rendering in that library looks pretty simplistic.
With that library you have to clear the box around the characters first or else it renders as overstrike.
Perhaps the library you are using is the same?
There are other ways of rendering which draw all pixels contained in the full framed box of the character glyph.
When doing that style of rendering, there won’t be a flicker because there is no period of time where all the
pixels are off, the pixels are simply re-draw as needed.
If the new pixels are the same the pixels don’t change if the new pixels are different, they change.
If you have the first style, you will want to update as few characters as possible, and only clear
the exact box for the character(s) you are changing.
If you have the other style, you only need to make sure you don’t clear the area yourself,
and you can simply overwrite the previous text with the new text.

kbarre123:
I’m guessing I’d have to compare the char[0] (the first zero in the seconds component) and see if it needs to be rewritten? That’s going to result in quite the nested if/else statement to do the whole thing!

If you have the first style rendering mentioned above,
Yes you will have to compare for changes and only write the characters that are different.
It can be simplified if you maintain a “last” and “current”
buffer and simply compare them in a loop and erase and write out the characters that don’t match.
Then move your “current” buffer to your “last” buffer.

Just me, but I’d use sprintf() or snprintf() for creating the character buffers, as the formatting so much simpler than
using dtostrf() and concatenating multiple buffers. A single sprintf() and the character buffer is created.
sprintf() may not be that much larger if at all given that dtostrf() supports floating point and by default sprintf()
does not support floating point.

Your time tracking seems to drift quite a bit.
If you have a board with a crystal I would think the drift should be less than that.
Are you tracking your time based on using the millis() timer?

----bill

Hey Bill,

With that library you have to clear the box around the characters first or else it renders as overstrike.
Perhaps the library you are using is the same?

Yes, I’m using the TFT library from Seeed (maker of the board) and there is overstrike unless I reprint the character in black to clear it out.

If I were to use another, more advance library, would that be a lot of work making sure all the proper pin declarations (etc.) from the library written for my device are correctly included in the other library? I’ve never done that before and am ignorant when it gets down to manipulating libraries (yay for abstraction!). But the more I think about it, a nice smooth transition and none of this flickering business would be totally worth my time figuring it out.

Just me, but I’d use sprintf() or snprintf() for creating the character buffers, as the formatting so much simpler than using dtostrf() and concatenating multiple buffers.

I’m no longer concatenating the hours/minutes/seconds into one string, but I’m still using dtostrf() since my sensor data is floating point. I suppose I could use sprintf() or snprintf() for the integers as I haven’t used either one before and this would be a good excuse to learn some new things.

Your time tracking seems to drift quite a bit. If you have a board with a crystal I would think the drift should be less than that. Are you tracking your time based on using the millis() timer?

I am using millis(). And also, I was wrong about my estimated error/hour. I let it run for almost an hour and was only fast by < 3 seconds. But I attribute that to not being able to start my external timer exactly when the millis() started. I’m sure it’s < 3 seconds. I intended to let it run for the full hour and get a more accurate reading, but little did I know that when you click on the Tools menu in the IDE, it resets my board and all was lost.

Thanks for the guidance.

KCB

kbarre123:
I am using millis(). And also, I was wrong about my estimated error/hour. I let it run for almost an hour and was only fast by < 3 seconds. But I attribute that to not being able to start my external timer exactly when the millis() started. I’m sure it’s < 3 seconds. I intended to let it run for the full hour and get a more accurate reading, but little did I know that when you click on the Tools menu in the IDE, it resets my board and all was lost.

You should synchronize your timer with millis(), not use millis() as the timer.
Indeed you will loose something as the millis() are already running while you are still in setup() and not yet ready to actually do what you wanted to do by building that sketch of yours.
This synchronizing will fix that.
Bill already pointed out that you should use “last” and “current” buffers, you can do that for your timer too.
Such timer can keep on running for some 49 days, after which it will overflow and reset.

Hey MAS3,

I don’t quite follow what you mean by

You should synchronize your timer with millis(), not use millis() as the timer.

As you can see below, I’m in the middle of implementing the “last” and “current” buffers for my second/minute/hour variables, but I’m not sure how I’d go about doing it for the time. Any quick tips? Thanks again for the help.

/**
 * Seeedstudio LCD 2.8" TFT touchscreen
 * Will display data from two temp sensors and has a countdown clock.
 * Intended use if for the brew_bot.v2
 */
#include <stdint.h>
//#include <TouchScreen.h> // Will be used to read touch events
#include <TFT.h>
#include <math.h>

#ifdef SEEEDUINO
#define YP A2   // must be an analog pin, use "An" notation!
#define XM A1   // must be an analog pin, use "An" notation!
#define YM 14   // can be a digital pin, this is A0
#define XP 17   // can be a digital pin, this is A3 
#endif

#ifdef MEGA
#define YP A2   // must be an analog pin, use "An" notation!
#define XM A1   // must be an analog pin, use "An" notation!
#define YM 54   // can be a digital pin, this is A0
#define XP 57   // can be a digital pin, this is A3 
#endif 

/* DEBUG: These variables are temp and represent output of the DallasTemp sensors,
 * as converted from floats to char[] for display to the screen.
 */
float tempUpper = 125.65;
float tempMash = 75.22;

// Variables for the timer
long interval = 1000;  // Threshold at which to update the Timer
unsigned long currentMillis;  // current millis
unsigned long previousMillis = 0;  // Will store last time Timer was updated
unsigned long benchMillis = 0;  // Variable used to be able to reset second to zero and still be able to keep track of time
int second = 0, minute = 0, hour = 0; // Keep track of time so we can increment min/hours based on elapsed seconds
const long MILLIS_IN_MINUTE = 60000;  // Constant to calculate printable time
char _hourCurrent[3]; // Buffer to store the current hour in. Used to compare to see if it has changed and needs to be updated. Prevents blinking every pass through main loop.
char _hourPrevious[3]; // Buffer to store the previous hour in.
char _minCurrent[3];
char _minPrevious[3];
char _secCurrent[3];
char _secPrevious[3];

void setup()
{
  Serial.begin(9600);
  Tft.init();  //init TFT library
  Tft.paintScreenBlack();  // Clear screen
  //delay(1000);

  Tft.setDisplayDirect(UP2DOWN);
  Tft.drawString("Upper: ",220,20,2,WHITE);
  Tft.drawString("Mash: ",180,20,2,WHITE);
  Tft.drawString("Timer: ",140,20,2,WHITE);
  Tft.fillRectangle(10, 20, 50, 125, GREEN);
  Tft.fillRectangle(10, 175, 50, 125, RED);
  Tft.drawString("Start",45,27,3,BLACK);
  Tft.drawString("Stop",45,193,3,BLACK);
}

void loop()
{
  /***** READ SENSORS AND DISPLAY *****/
  // Convert sensor data of Upper Pot to string for display. 
  char textUpper[8]; // buffer to store the results of dtostrf
  dtostrf(tempUpper, 1, 2, textUpper);  // Arguments are (float, width, precision, buffer)
  // Convert sensor data of Mash Tun to string for display
  char textMash[8];  // buffer to store the results of dtostrf
  dtostrf(tempMash, 1, 2, textMash);
  // Display results
  Tft.drawString(textUpper,220,180,2,WHITE);
  //Serial.print("textUpper: ");  // DEBUG
  //Serial.println(textUpper);  // DEBUG
  Tft.drawString(textMash,180,180,2,WHITE);
  //Serial.print("textMash: ");  // DEBUG
  //Serial.println(textMash);  // DEBUG
  //Serial.println("");  // DEBUG
  /***** END READ SENSORS AND DISPLAY *****/

  /***** TIMER *****/
  currentMillis = millis();
  // Check to see if 1 second has elapsed
  if(currentMillis - previousMillis > interval) 
  {
    // Save the last time you updated Timer
    previousMillis = currentMillis;
    
    /*** UPDATE TIMER COMPONENTS ***/
    if (currentMillis > benchMillis + MILLIS_IN_MINUTE) 
    {
      minute++;
      second = 0;
      benchMillis = currentMillis;
    }
    else
    {
      second = (currentMillis - benchMillis) / 1000;
    }

    if (minute > 59) 
    {
      hour++;
      minute = 0;
    }
    
    sprintf(_hourCurrent, "%02d", hour); // Arguments are: (buffer, format, variable to insert)
    Serial.print(_hourCurrent);
    Serial.print(":");
    
    char _minCurrent[3];
    sprintf(_minCurrent, "%02d", minute); 
    Serial.print(_minCurrent);
    Serial.print(":");
    
    char _secCurrent[3];
    sprintf(_secCurrent, "%02d", second);
    Serial.println(_secCurrent);
    Serial.println("");
    /*** END UPDATE TIMER COMPONENTS ***/
    
    /*** DISPLAY TEXT ***/
    Tft.drawString(_hourCurrent,140,180,2,WHITE);
    Tft.drawString(":",140,210,2,WHITE); // 30px spacing since its double digits
    
    Tft.drawString(_minCurrent,140,225,2,WHITE);
    Tft.drawString(":",140,255,2,WHITE);
    
    Tft.drawString(_secCurrent,140,270,2,WHITE);
    /*** END DISPLAY TEXT ***/
    
    /*** ERASE TEXT ***/
    Tft.drawString(_hourCurrent,140,180,2,BLACK);
    Tft.drawString(_minCurrent,140,225,2,BLACK);
    Tft.drawString(_secCurrent,140,270,2,BLACK);
    /*** ERASE TEXT ***/
    
    /*** UPDATE BUFFERS ***/
    // Update the old buffers to the new buffers so we can compare to see if 
    // any of the clock's components need to be updated. This will limit the 
    // flashing of the components to only those being updated.
    
    
  }/***** END TIMER *****/
}/***** END MAIN LOOP *****/

Hi.

In lines 33 and 34 you are declaring variables currentMillis and previousMillis as long integers, with a preset value of zero. In your section *Timer *, you are updating currentMillis. That is what i meant, you seem to be doing that already. So your first timer check will be off, because the first use of that timer will be a lot later than the start of the millis() counter. But still you are comparing it to 0 that first time. Therefore you should set previousMillis and currentMillis to the same value (not being 0) at the first use (its initialization) of that timer. If i were you, i'd do this right before you enter void loop (). If you are going to use the timer multiple times without resetting the Arduino, you could create a function which might be called resetTimer to do this, or use a flag for this purpose. If you're not ready to use functions yet, use the flag.

Hi MAS3,

So your first timer check will be off, because the first use of that timer will be a lot later than the start of the millis() counter. But still you are comparing it to 0 that first time. Therefore you should set previousMillis and currentMillis to the same value (not being 0) at the first use (its initialization) of that timer. If i were you, i'd do this right before you enter void loop ().

So, I should set currentMillis to millis() at the end of setup()? I guess I'm kind of lost on this one but would like to better understand.

If you are going to use the timer multiple times without resetting the Arduino, you could create a function which might be called resetTimer to do this

I'm fine with writing a function which would reset the timer. So, it would set previousMillis to whatever currentMillis is equal to at the time of the call to "close the gap" between the two? If I said previousMillis = currentMillis, wouldn't previousMillis just continue to be equal to whatever currentMillis is updated to and their values would never diverge?

or use a flag for this purpose. If you're not ready to use functions yet, use the flag.

Really not sure how to use flags. Are you speaking of interrupts? I've never used those before but am open to exploring them if it'd make life easier. Thanks again for the help.

KCB

kbarre123:
So, I should set currentMillis to millis() at the end of setup()? I guess I’m kind of lost on this one but would like to better understand.

Not quite.
You should make previousMillis equal to millis at that point, bacause that is your reference to which you are comparing your counter.
During setup, you are defining the variable and filling it with a value of 0.
That’s fine, but at that point millis() has been running for a while already, and this way you are synchronizing with the “system clock”.
You are updating currentMillis already right before you are comparing its value to previousMillis and the wanted offset (a second).

I’m fine with writing a function which would reset the timer. So, it would set previousMillis to whatever currentMillis is equal to at the time of the call to “close the gap” between the two? If I said previousMillis = currentMillis, wouldn’t previousMillis just continue to be equal to whatever currentMillis is updated to and their values would never diverge?

That’s not what i was suggesting.
I was suggesting to reset previousMillis to millis() (which can be the same as currentMillis, but doesn’t have to be) in case you wanted to have your clock run again.
But you do not need that, because you are resetting or updating previousMillis each second already.

Really not sure how to use flags. Are you speaking of interrupts? I’ve never used those before but am open to exploring them if it’d make life easier.

A flag in this case is a variable that tells you in what state you are in at any given moment.
You could decide to fill it with a value of 0 or 1.
Say your variable would be named timerElapsed.
In case timerElapsed is 0 the normal routine would be continued.
In case timerElapsed is 1 (or any other value but 0) it is time to skip the normal routine and reset the timer so it can run once again.
This might be easier than writing and calling functions.
Interrupts aren’t necessary for this and would be overkill.