Wednesday, 21 June 2017

Clock - Mk I

Without my temperature sensor to play with, I thought I'd experiment with some different ways to make a (simple) clock.  This is my most basic Mk I version, but I hope to show at least a couple of improvements.  I will be judging my results by how accurate the time remains over a period of time.

So, in this version, I am pretty much doing everything manually - starting off with getting a seed time entered into the sketch from the Serial Monitor.  I found a nicer way to do this using the Serial.parseInt() command, which can pull all the integer numbers from an input string into variables.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  // Uses Serial.parseInt() to seed the clock.

  // The required string is hh:mm:ss which is 8 chars long, but we need to allow for a 'newline' char on the end
  // so declare a string at least 9 chars long
  char seedTime[9] = "";
  char buffer[21] = "";       // for sprintf output

  // declare variables.
  int hh=0, mm=0, ss=0;

  // include LCD library
  #include <LiquidCrystal_I2C.h>  
  LiquidCrystal_I2C lcd(0x3F,20,4);

void setup() {
  // Put prompt on display
  lcd.begin();
  lcd.setCursor(0,1);
  lcd.print("Start Serial Monitor");
  lcd.setCursor(0,2);
  lcd.print("Enter seed time");
  lcd.setCursor(0,3);
  lcd.print(" (hh:mm:ss)");
     
  // Start Serial comms with monitor
  Serial.begin(9600);
  Serial.println("Please enter the seed time in the format hh:mm:ss");

  // Method 1 - parseInt - finds int values in CSV type string. Can use ":" as separator.

  // wait for input to be typed
  while (Serial.available() == 0) {}    // just loops until Serial input is available
  
  // parse the input string
  hh = Serial.parseInt();
  mm = Serial.parseInt();
  ss = Serial.parseInt();

  // Only expecting three values - no need to check for newline
  if (Serial.read() == "\n") {
     // end of input
  }

  // Clear the lcd
  lcd.clear();
}

First of all, I set up a couple of character arrays - one to accept the seed time from the monitor, and the other to take the formatted output string, so I only need to write to the LCD once per loop.

Next, I set up variables for the hours, minutes, and seconds, and set them all to 0, and then set up the LCD object at address 0x3F with 4 lines of 20 chars.

I used the setup() function to handle the prompts for, and receipt of the seed value.
Initially, I write a message to the LCD, reminding the user to startup the Serial Monitor.
Then I send a message to the monitor, asking for the seed time in hh:mm:ss format.
Line 32 checks for input on the Serial comms line, and the while command will just keep looping around doing nothing while there is no input.  This effectively is making the sketch wait right here, until the seed time is entered.
Once data has been entered on the monitor, and the newline (Enter) hit to indicate the end of input, then the sketch runs the first Serial.parseInt() command, and assigns the result to hh.  The way parseInt works is to start reading the input string from the first character.  It ignores everything it sees, until it gets to a numeric character, then it reads all the numerics until it hits a non-numeric character, and then it stops.  The numbers it has read are treated as an integer (actually, a long integer), and in this case, placed into hh.
I followed this with another Serial.parseInt() command, which picks up in the string where the previous one left off, and places the second numeric part into mm.  Then the same again with the third command, that picks up the seconds and puts them into ss.
Lines 40-42 are not needed in this case, but would be used if there was a variable amount of data, and we needed to know specifically when we had exhausted all the input.
Finally, I clear the reminder prompts from the LCD.

The loop() function is where I do the maths to increment seconds, minutes and hours, and write the output to both monitor, and LCD.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
void loop() {
  int x = sprintf(buffer, "The time is %02d:%02d:%02d", hh, mm, ss);
  Serial.println(buffer);
  lcd.setCursor(0,2);
  lcd.print(buffer);

  // Wait a second, then increment the time
  delay(1000);
  ss++;
  if(ss==60) {
    ss=0;
    mm++;
  }
  if(mm==60) {
    mm=0;
    hh++;
  }
  if(hh==24) {
    hh=0;
  }
}

Firstly, I use a sprintf command to build a formatted string of output into the array buffer.  sprintf is a c++ command, which you will not find in the Arduino reference, but you can find some info about it here, in the cplusplus reference.  It is a way of building a text string with place holders marking locations for data of different types.  The %02d notations indicate that a 2 digit numeric with leading zeroes should be printed at this position.  After the text string, come the variables with the data that should be placed where the placeholders were.  This builds a 20 character string that says "The time is 07:48:23 (or whatever the time happens to be...).  I write the text array buffer to the Serial Monitor, and to the LCD as well.

Then, I wait 1 second - exactly.  This was a mistake...  everything I have done so far with the printing, and all the maths I am about to do, take time obviously, and each time around the loop, that time is being added to the second, making each loop just a few milliseconds over a second.  I'll come back to this point at the end.

The last section of code is a series of linked increments and if statements.
I start by adding 1 to the seconds.
If that makes the seconds 60, then I reset them to 0, and add 1 to the minutes.
If that makes the minutes 60, then I reset them to 0, and add 1 to the hours.
Finally, if that makes the hours 24, then I simply reset the hours to 0.

Then we go around the loop again - using the new time values to sprintf a new "The time is..." line to the monitor and LCD, wait another second, and so on.

So, does it work?  Yes.
Is it accurate?  No.
Is it even remotely accurate?  No!!
I left the clock running for 6 hours, and in that time, it lost 10 minutes.  So the result of those few extra milliseconds in each loop, really started to add up pretty quickly.  At that rate, in a day and a half, it would lose an entire hour.

In my next attempt, I will try to fine tune the delay in the loop, to see if I can get a better accuracy that way.


No comments:

Post a Comment