Stopwatch with custom characters - accuracy problem

Hi

I have edited some code for a stopwatch so that it uses custom characters to display on an LCD. It seems to work fine but I noticed that the timing is slow by about 1 minute every 5 (6 minutes in real time is 5 on the stopwatch). Can anyone tell me why this is? Any other general advice on the code would be appreciated.

Thanks

unsigned long sec;
int mins;
int hundred;
#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
long interval = 9;
long previousMillis = 0;
int x, y;
// the 8 arrays that form each segment of the custom numbers
byte LT[8] =
{
  B00011,
  B00011,
  B00011,
  B00011,
  B00011,
  B00011,
  B00011,
  B00011
};
byte UB[8] =
{
  B11111,
  B11111,
  B11011,
  B11011,
  B11011,
  B11011,
  B11111,
  B11111
};
byte RT[8] =
{
  B11111,
  B11111,
  B11000,
  B11000,
  B11000,
  B11000,
  B11111,
  B11111
};
byte LL[8] =
{
  B11111,
  B11111,
  B00011,
  B00011,
  B00011,
  B00011,
  B11111,
  B11111
};
byte LB[8] =
{
  B11111,
  B11111,
  B00011,
  B00011,
  B00011,
  B00011,
  B00011,
  B00011
};
byte LR[8] =
{
  B11111,
  B11111,
  B11000,
  B11000,
  B11000,
  B11000,
  B11000,
  B11000
};
byte MB[8] =
{
  B11111,
  B11111,
  B11011,
  B11011,
  B11011,
  B11011,
  B11011,
  B11011
};
byte block[8] =
{
  B11011,
  B11011,
  B11011,
  B11011,
  B11011,
  B11011,
  B11111,
  B11111
};

// loop counter
int count = 0;
  
void setup()
{
  // assignes each segment a write number
  lcd.createChar(0,LT);
  lcd.createChar(1,UB);
  lcd.createChar(2,RT);
  lcd.createChar(3,LL);
  lcd.createChar(4,LB);
  lcd.createChar(5,LR);
  lcd.createChar(6,MB);
  lcd.createChar(7,block);
  
  lcd.begin(20,2);
 int sec=0;
 int mins=0;
 int hundred=0;
        
}

void custom0()
{ // uses segments to build the number 0
  lcd.setCursor(x,y); // set cursor to column 0, line 0 (first row)
  lcd.write(6);       // call each segment to create top half of the number                       
  y++;                // cursor to bottom line of display
  lcd.setCursor(x,y); // set cursor to colum 0, line 1 (second row)
  lcd.write(7);       // call each segment to create bottom half of the number
  y--;                // cursor back to top line               
}

void custom1()
{
  lcd.setCursor(x,y);
  lcd.write(0);
  y++;
  lcd.setCursor(x,y);
  lcd.write(0);
  y--;
}

void custom2()
{
  lcd.setCursor(x,y);
  lcd.write(4);
  y++;
  lcd.setCursor(x, y);
  lcd.write(2);
  y--;
  
}

void custom3()
{
  lcd.setCursor(x,y);
  lcd.write(4);
  y++;                                       
  lcd.setCursor(x,y);
  lcd.write(3);
  y--;            
               
}

void custom4()
{
  lcd.setCursor(x,y);
  lcd.write(7);
  y++;  
  lcd.setCursor(x,y);
  lcd.write(0);
  y--;
}

void custom5()
{
  lcd.setCursor(x,y);
  lcd.write(5);
  y++;            
  lcd.setCursor(x,y);
  lcd.write(3);
  y--;              
               
}

void custom6()
{
  lcd.setCursor(x,y);
  lcd.write(5);
  y++;                               
  lcd.setCursor(x,y);
  lcd.write(1);
  y--;                
                
}

void custom7()
{
  lcd.setCursor(x,y);
  lcd.write(4);
  y++;                         
  lcd.setCursor(x,y);
  lcd.write(0);
  y--;
}

void custom8()
{
  lcd.setCursor(x,y);
  lcd.write(1);       
  y++;                            
  lcd.setCursor(x,y);
  lcd.write(1);
  y--;              
               
}

void custom9()
{
  lcd.setCursor(x,y);
  lcd.write(1);
  y++;                           
  lcd.setCursor(x,y);
  lcd.write(0);
  y--;
}

void loop()                                  
{
    if (millis() - previousMillis > interval)     
    {
    previousMillis = millis();                        

  hundred=hundred+1;
  if (hundred==100)
  {
    hundred=0;
  }
  if (hundred==0)
    {
    sec=sec+1;
    }
   if (sec==60)
  {
    sec=0;
   if (sec==0)
    {
    mins=mins+1;
    }
  }
 lcd.setCursor(0,0);
 lcd.print("Stopwatch ");
 
 x=10, y=0;
 
 lcd.setCursor(x,y);
 
 if (mins < 10)             // use custom characters instead of normal characters
 {                          // for tens digit of minutes
    custom0();              
 }
 else
 if (mins < 20)
 {
    custom1();
 }
 else
 if (mins < 30)
 {
    custom2();
 }
 else
 if (mins < 40)
 {
    custom3();
 }
 else
 if (mins < 50)
 {
    custom4();
 }
 else
 if (mins < 60)
 {
    custom5();
 }

x++;
lcd.setCursor(x,y);    

if((mins == 0)||(mins == 10)||(mins == 20)||(mins == 30)||(mins == 40)||(mins == 50))
{
 custom0();                   // use custom characters instead of normal characters
}                             // for minutes
else
if((mins == 1)||(mins == 11)||(mins == 21)||(mins == 31)||(mins == 41)||(mins == 51))
{
 custom1();
} 
else
if((mins == 2)||(mins == 12)||(mins == 22)||(mins == 32)||(mins == 42)||(mins == 52))
{
 custom2();
} 
else
if((mins == 3)||(mins == 13)||(mins == 23)||(mins == 33)||(mins == 43)||(mins == 53))
{
 custom3();
} 
else
if((mins == 4)||(mins == 14)||(mins == 24)||(mins == 34)||(mins == 44)||(mins == 54)) 
{
 custom4();
} 
else
if((mins == 5)||(mins == 15)||(mins == 25)||(mins == 35)||(mins == 45)||(mins == 55))
{
 custom5();
} 
else
if((mins == 6)||(mins == 16)||(mins == 26)||(mins == 36)||(mins == 46)||(mins == 56))
{
 custom6();
} 
else
if((mins == 7)||(mins == 17)||(mins == 27)||(mins == 37)||(mins == 47)||(mins == 57))
{
 custom7();
} 
else
if((mins == 8)||(mins == 18)||(mins == 28)||(mins == 38)||(mins == 48)||(mins == 58))
{
 custom8();
} 
else
if((mins == 9)||(mins == 19)||(mins == 29)||(mins == 39)||(mins == 49)||(mins == 59))
{
 custom9();
} 

x++; 
lcd.setCursor(x,y);
lcd.print(".");
y++;
lcd.setCursor(x,y);
lcd.print(".");              
y--;
lcd.setCursor(x,y);
x++; 
lcd.setCursor(x,y);

if (sec < 10)
 {
    custom0();        // use custom characters instead of normal characters
 }                    // for tens digit of seconds
 else
 if (sec < 20)
 {
    custom1();
 }
 else
 if (sec < 30)
 {
    custom2();
 }
 else
 if (sec < 40)
 {
    custom3();
 }
 else
 if (sec < 50)
 {
    custom4();
 }
 else
 if (sec < 60)
 {
    custom5();
 }
 
x++;
lcd.setCursor(x,y);

if((sec == 0)||(sec == 10)||(sec == 20)||(sec == 30)||(sec == 40)||(sec == 50))
{
 custom0();           // use custom characters instead of normal characters
}                     // for seconds
else
if((sec == 1)||(sec == 11)||(sec == 21)||(sec == 31)||(sec == 41)||(sec == 51))
{
 custom1();
} 
else
if((sec == 2)||(sec == 12)||(sec == 22)||(sec == 32)||(sec == 42)||(sec == 52))
{
 custom2();
}
else
if((sec == 3)||(sec == 13)||(sec == 23)||(sec == 33)||(sec == 43)||(sec == 53))
{
 custom3();
} 
else
if((sec == 4)||(sec == 14)||(sec == 24)||(sec == 34)||(sec == 44)||(sec == 54))
{
 custom4();
} 
else
if((sec == 5)||(sec == 15)||(sec == 25)||(sec == 35)||(sec == 45)||(sec == 55))
{
 custom5();
} 
else
if((sec == 6)||(sec == 16)||(sec == 26)||(sec == 36)||(sec == 46)||(sec == 56))
{
 custom6();
} 
else
if((sec == 7)||(sec == 17)||(sec == 27)||(sec == 37)||(sec == 47)||(sec == 57))
{
 custom7();
} 
else
if((sec == 8)||(sec == 18)||(sec == 28)||(sec == 38)||(sec == 48)||(sec == 58))
{
 custom8();
} 
else
if((sec == 9)||(sec == 19)||(sec == 29)||(sec == 39)||(sec == 49)||(sec == 59))
{
 custom9();
} 


 lcd.print(".");
 if (hundred < 10)
 {
    lcd.print('0');
 }
 lcd.print(hundred);
 }
 }

SWLARGE.txt (7.19 KB)

long interval = 9;

That seems pretty arbitrary to me.

You might be losing milliseconds by trying to parcel them out 9 at a time. (9???)

Just yesterday I wrote timer code to display time since reset on a 4-digit 7-segment display. It currently shows 29:11 (29 hours 11 minutes).

Try this:

// calculate hours, minutes, seconds, and hundredths since the last reset.
unsigned long hundredths = millis() / 10;
unsigned long seconds = hundredths / 100;
unsigned long minutes = seconds / 60;
unsigned int hours = minutes / 60;

hundredths %= 100;
seconds %= 60;
minutes %= 60;

if you want to be able to re-start the timer without resetting the Arduino, use:

unsigned long startTime = 0;
...
unsigned long hundredths = (millis()-startTime) / 10;

Then use startTime = millis(); when you want the timer to start again at 0.

    sec=sec+1;

You have something against the C ++ operator?

@Jasooon

Nothing to do with your timing but whenever you see massive numbers of if/else statements each with X number of conditions it’s an indication that there’s a better way.

Here’s another way to call function X depending on the least significant character of a counter

void (*fp[])(void) = {
    custom0, custom1, custom2, custom3, custom4, custom5, 
    custom6, custom7, custom8, custom9
};

void custom0() {
  Serial.print ("zero ");
}
void custom1() {
  Serial.print ("one ");
}
void custom2() {
  Serial.print ("two ");
}
void custom3() {
  Serial.print ("three ");
}
void custom4() {
  Serial.print ("four ");
}
void custom5() {
  Serial.print ("five ");
}
void custom6() {
  Serial.print ("six ");
}
void custom7() {
  Serial.print ("seven ");
}
void custom8() {
  Serial.print ("eight ");
}
void custom9() {
  Serial.print ("nine ");
}

void setup() {                
  Serial.begin (115200);
  Serial.print ("\nFunction jump table example\n");
}

void loop() {
  char temp_str [3];
  int func_index;
  
  for (int i = 0; i <= 99; i++) {
    
    if (i % 10 == 0 ) {  // print the decade
      Serial.println ("");
      Serial.println (i / 10); 
    }
    
    sprintf(temp_str, "%02d", i);     // force i to be two digits                                 
    func_index = atoi (&temp_str[1]); // turn LSD back into a #
    fp[func_index]();                 // call appropriate function
 
    delay (300);

  } 
  
  Serial.println ("");
}

There’s probably an even better way and if so I’m sure someone here will post it, but this is just to help you think about alternative ways of doing things.

You could do something similar on your “mins” and “sec”.


Rob

Use of functions and switch statements would greatly simplify the code:

void twoDigits(unsigned int value)
    {
    oneDigit(value/10);
    oneDigit(value);
    }

void oneDigit(unsigned int value)
    {
    value %= 10;  // Use only the low-order decimal digit
    switch (value)
        {
    case 0: custom0(); break;
    case 1: custom1(); break;
    case 2: custom2(); break;
    case 3: custom3(); break;
    case 4: custom4(); break;
    case 5: custom5(); break;
    case 6: custom6(); break;
    case 7: custom7(); break;
    case 8: custom8(); break;
    case 9: custom9(); break;
        };
    }

Doh, the % operator, who would have thought? :slight_smile:

Now you can call the correct function with a single line.

fpi % 10;

void (*fp[])(void) = {
    custom0, custom1, custom2, custom3, custom4, custom5, 
    custom6, custom7, custom8, custom9
};

void custom0() {
  Serial.print ("zero ");
}
void custom1() {
  Serial.print ("one ");
}
void custom2() {
  Serial.print ("two ");
}
void custom3() {
  Serial.print ("three ");
}
void custom4() {
  Serial.print ("four ");
}
void custom5() {
  Serial.print ("five ");
}
void custom6() {
  Serial.print ("six ");
}
void custom7() {
  Serial.print ("seven ");
}
void custom8() {
  Serial.print ("eight ");
}
void custom9() {
  Serial.print ("nine ");
}

void setup() {                
  Serial.begin (115200);
  Serial.print ("\nFunction jump table example\n");
}

void loop() {
 
  for (int i = 0; i <= 99; i++) {
    
    if (i % 10 == 0 ) {  // print the decade
      Serial.println ("");
      Serial.println (i / 10); 
    }

    fp[i % 10]();                 // call appropriate function
 
    delay (300);

  } 
  
  Serial.println ("");
}

Rob

Thanks for your help, I got it to work properly using johnwasser's timing method. It makes much more sense than the way I had it! I tried to use a case structure before but it it didn't work, I probably just made a stupid mistake, I'm pretty new to this stuff. I'll try again and see what other things I can improve also.

Thanks again