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.


Tuesday, 20 June 2017

Uh oh... I broke it!

No, not the LCD display!!
My LM35 sensor ☹
Too much slotting into and out of the breadboard over the last few weeks, has obviously weakened the legs, and the middle (signal) one just broke off right at the base of the device.


Lesson learned - buy myself some simple Vero strip board and strips of header pins, so I can make up my own break-out boards for devices that I salvage from other circuit boards.  Not sure if I will be able to save this one or not.

I have seen them listed on ebay for $1.30 if I want to wait another 2 months for them to make their way here from China, but around 6-10 times that price for 'Australian' ebay sellers!  I found one Australian supplier independant of ebay, that had them priced a little more realistically at $2.08, but wanted to charge $24 delivery... for something that weighs about 5 grams? Really?  But on closer inspection, in turned out that this .com.au company was actually based in Kowloon, Hong Kong - ho hum...  Digi-Key in the US are much the same - $2.60 for the part, $24 to deliver it...  Living in Australia has its advantages, but boy oh boy - it seems we are a long way away from the rest of the world when it comes to ordering goods online...

Jaycar doesn't sell them, but does sell what is presumably an upgraded version - the LM335Z, for $4.50.  This appears to be tied to Kelvin rather than Celsius/Centigrade, putting out 10mV/°K, with it's theoretical output diminishing to 0V at 0°K.  However, the device is rated for -40°C to 100°C, so I assume the 'working range' of output voltage is 2.33V to 3.73V - this would mean having to use the 5V "analogReference(DEFAULT)" for analogRead operations, giving an analog step level of 4.88mV equivalent to temperature increments of nearly 1/2°C - mind you, when the accuracy of the device itself is quoted as only within 2-4°C anyway... I might as well just stick my finger in the air and guess!

Monday, 19 June 2017

Finally... I have a display module

Yes, it has finally arrived today.  Just 9 weeks after I ordered it - and guess what?  According to the shipping reference on the label, it IS the original, not the replacement!  So you never know - in another 4-6 weeks, I could end up with a second display after all!

Of course, the first thing I was tempted to do after connecting it up, was to run my thermometer sketch, but I'm glad I didn't, as I would have been very upset when it didn't work.  Thankfully, I remembered to run the I2C scanner sketch first, and yes - my I2C backpack has the 'AT' variant of the control chip I mentioned in the previous post, so the unit address is 0x3F, not 0x27.  In the picture, you can also see the three sets of alternate address pads.



So, then I loaded up my sketch, changed the address in the LCD declaration to 0x3F, and fired it up.  The backlight came on, but nothing else, so I twiddled the potentiometer on the backpack (the blue box thing in the image above, which controls the contrast of the screen), and up came my min/max display, just as I imagined it. Obviously, it had garbage figures, because in my impatience, I hadn't bothered to connect it up to the breadboard with the LM35, so the analogReads on pin A0 were getting random floating values - but it was enough just to prove the display works.

Now I have connected it up properly, and found a few bugs in my code, so I spent this evening debugging my theory, and adding a new feature.

The first thing I noticed was that the 5 minute average figure was always the same as the current reading.  I added a few Serial.print lines to show all the values used in my calculations, and found that the counter was always 1.  This led me to the conclusion that the problem must be in the reset area, which is only supposed to run every 5 minutes, but appeared to be running every loop.

The code looked fine except there was something niggling me about the IF statement having a calculation in it...  The line read
     if (fiveMinCount >= ((5 * 60 * 1000) / loopPeriod))
Now, I've come across this somewhere before, but can't remember why or where...  I changed the (5 * 60 * 1000) to just read 300000 instead, and the average calculations started working properly - so it appears that IF statements don't like their mathematical comparisons to be too complicated.

The other little change I made was inspired by a similar kind of project I saw in a youtube tutorial by Martin Lorton, who made a very similar looking current/max/min type display for an Arduino based voltage meter.  He added a function that showed an asterisk next to the max/min values when they changed, and also flashed an LED.  He suggested you could also use a piezo transducer and the tone command, to make a little beep.  I didn't go that far, but did add the asterisk, and flash the little onboard LED.  Here is the code that does that...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
  // if "Temp now" is a new high...
  if (celsius > maxTemp)
  {
    //store current value as new high
    maxTemp=celsius;

    // and change value on display
      lcd.setCursor(12,1);
       if (maxTemp < 10) 
        lcd.print("0");      // Add leading 0 if < 10C
      lcd.print(maxTemp,1);
      lcd.setCursor(19,1);
      lcd.print("*");
      digitalWrite(led,HIGH);
  }
  else
  {
      lcd.setCursor(19,1);
      lcd.print(" ");
  }
 
Obviously, this code is repeated for the new LOW value as well.
We also need to declare an int variable called led and assign the number 13 to it, then declare the pinmode as output at the beginning of the sketch, and also turn the led OFF at the end of each loop.

So finally, after a couple of months of planning, theorising, pontificating, researching, and changing my mind countless times, here is the actual thermometer working as planned.  Sorry I didn't get a picture with the asterisk and LED indicating a new Max or Min figure.

My next plan is to mount all this stuff onto a board, so I have a proper little test bed.  Unfortunately, the display does not work from 3.3v, and the I2C interface pin breakout set on my Arduino only offers 3.3v, so I'll have to think how to connect the display to the Arduino semi-permanently once mounted on the board.  But that's a problem for another day.  I'm off to bed know, and will leave my thermometer running to see just how cold it gets in my lounge overnight...

...and I know what I said, but I can feel a fridge/freezer experiment coming on 😀