I've re-arranged some code, removed the second-by-second logging, and added a bit of minute-by-minute logging instead, and tweaked the adjustment delays again (968 and 48). I left the clock running for 24 hours, and this time found it had lost 2.5 seconds. The logging showed that the figures at the end of each minute were reasonably constant, but it seems that I can count on averages drifting over time, and variations of around 5ms. I'm thinking I might need to add in an hourly adjustment now, and tweak the other delays again to make sure it gains rather than loses (if it gains time - i.e. the loops are shorter than 1 second, I can add in extra compensating delays after predetermined periods - like every 10 minutes or every hour).
I've been doing a little bit of research, and I think I might be pushing the limits of the available technology anyway. From what I've read, it looks as though even the timing crystals used on the genuine Arduino boards are at best what you might call mediocre quality, with a reported accuracy of just +/- 0.5% , and the ones on Chinese clones are most probably not even that good. I've read quite a few accounts of people 'upgrading' their boards with better quality crystals, to get a more accurate millisecond timing capability (presumably after going through the same kind of exercises that I am trying).
It seems that temperature is also a factor in the potential accuracy of these crystals, though I haven't seen any figures on the degree of variance (pardon the pun) or just how different the temperatures need to be, in order to have any affect (hmm - if only I had a temperature sensor - say, like an LM35 - I could use it to plot the ambient temperature against the timing drift... Oh darn, that's right, mine's broken - LOL). However, I have read that you can get timing crystals that are enclosed in a heated thermostatically controlled enclosure (called a Crystal Oven), in order to eliminate any temperature induced variations - now that really is taking timing seriously! I won't be soldering one of those beasts onto my $5 Arduino clone!!
An introduction to basic elctronics, and designing circuits and programs to be used with an Arduino microcontroller board such as the Arduino Uno. Learn with me as I take things apart, investigate, research, and gradually build my own knowledge.
Thursday, 29 June 2017
Monday, 26 June 2017
Clock - Mk II
The next iteration of my clock aimed at getting it to be a little more accurate. Remember - I am simply using the millis() and delay() functions to see how accurately I can keep time - for a real clock application, I would be using a Real-Time Clock module (RTC) and/or using the time library. This is simply a pedantic exercise for the sake of it.
So - in the previous version, I put together some code to set a seed time, and then to manually increment it on a periodic basis. I started that period off at 1 second - adding 1 to my seconds counter, resetting it at 60 and adding 1 to the minutes counter, and similarly resetting the minutes at 60 and incrementing the hour. Hours are reset to 0 when they reach 24.
Obviously, there is a whole bunch of code going on, doing all the incrementing and comparisons, and that takes up time which is being added to the 1 second delay, meaning each iteration through my loop is taking slightly more than 1 second, and hence my clock would lose time. I was quite shocked to see that it lost about 10 minutes in just 6 hours though - obviously there is room for improvement...
The first step in Mk II, was to add a Serial.print to the end of the loop, to show the millis() value. This revealed that each loop appeared to be taking 1031 or 1032ms, so I reduced the delay at the end of the loop to 968 to start with.
Now the loops were taking about 999 ms, meaning the clock would run fast and gain time. I increased the delay to 969, but now found the loop was running at 1001 ms! Go figure... I decided to put the delay back to 968, and then to check the millis() value each minute - perhaps I could apply a second delay once a minute to act as a compensating adjustment.
It appeared that I was gaining about 44ms per minute, so in the part of my code that resets the seconds and increments the minutes, I added another delay operation - this time for 44ms. This initially seemed to do the trick - I was now gaining just about 1ms per minute which worked out to be 1 second every 16 hours and 40 minutes. Pretty good for my needs - maybe in the next version, I could also put in an hourly adjustment to control the drift even more.
Here's the modified code, with new and amended lines highlighted yellow (actual code - not comments).
I left the sketch running overnight and checked again this morning, but... things are not going as I had hoped! After just 7 hours, the clock was about 3 seconds adrift - a lot better than the Mk I's 10 minutes, but considerably worse than the 1/2 second I was expecting. I checked back in the log on the serial monitor, and found that after running for about 8 or 9 minutes with the drift being a constant 1ms per minute, the drift suddenly started jumping to 4ms, then 6ms, then 9ms or more..! No wonder it's 3 seconds adrift already.
I am not sure exactly what to make of this. As far as I was aware - running the same set of operations in the loop repeatedly, should take exactly the same time for each loop. Obviously, if the processing has to take a different route through the logic - such as the extra processing when the seconds or minutes or hours (or all three) get reset, then the timing would be different, but if the same set of operations is run each time (as it is 59 times each minute - or the same cumulative code for an entire minute, as it is 59 times per hour), then there should be no variation in the timing.
With hindsight, I can see some very slight differences - between some loops, such as when the value is < 10, there is additional code to print an extra '0', but even that follows a distinct and predictable pattern each minute. It would not account for the variances I suddenly started getting after a few minutes, that just got bigger and bigger with no apparent pattern (other than just keeping on getting bigger).
I don't know if there are external factors that could affect this, such as voltage to the Arduino (I was running it from the USB port on my ageing laptop, and part of the time I was streaming a movie - but most of the night, it was sitting on its own in the dark, doing nothing but providing power to the Arduino). Perhaps the amount of entries in the serial monitor buffer, gradually increasing through the night is a factor (though I would have thought Arduino.exe would have been handling that independently on the PC)?
So, what next? I think I need to run a few more tests... and maybe post a question on the Arduino forum? Watch this space (or if anyone knows why the exact same code iterated a number of times can take such varying elapsed times, please let me know).
So - in the previous version, I put together some code to set a seed time, and then to manually increment it on a periodic basis. I started that period off at 1 second - adding 1 to my seconds counter, resetting it at 60 and adding 1 to the minutes counter, and similarly resetting the minutes at 60 and incrementing the hour. Hours are reset to 0 when they reach 24.
Obviously, there is a whole bunch of code going on, doing all the incrementing and comparisons, and that takes up time which is being added to the 1 second delay, meaning each iteration through my loop is taking slightly more than 1 second, and hence my clock would lose time. I was quite shocked to see that it lost about 10 minutes in just 6 hours though - obviously there is room for improvement...
The first step in Mk II, was to add a Serial.print to the end of the loop, to show the millis() value. This revealed that each loop appeared to be taking 1031 or 1032ms, so I reduced the delay at the end of the loop to 968 to start with.
Now the loops were taking about 999 ms, meaning the clock would run fast and gain time. I increased the delay to 969, but now found the loop was running at 1001 ms! Go figure... I decided to put the delay back to 968, and then to check the millis() value each minute - perhaps I could apply a second delay once a minute to act as a compensating adjustment.
It appeared that I was gaining about 44ms per minute, so in the part of my code that resets the seconds and increments the minutes, I added another delay operation - this time for 44ms. This initially seemed to do the trick - I was now gaining just about 1ms per minute which worked out to be 1 second every 16 hours and 40 minutes. Pretty good for my needs - maybe in the next version, I could also put in an hourly adjustment to control the drift even more.
Here's the modified code, with new and amended lines highlighted yellow (actual code - not comments).
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | /* * Mk. II - 25/06/2017 * Checked the value of millis() at the end of each loop from Mk. I * to fine tune the delay. */ // 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(); } void loop() { int x = sprintf(buffer, "The time is %02d:%02d:%02d", hh, mm, ss); Serial.println(buffer); Serial.println(millis()); lcd.setCursor(0,2); lcd.print(buffer); // Using delay(1000) resulted in loop times of 1031 to 1032. // Adjusted delay to 1000-32=968. // Loop is now slightly quicker than 1 second so 'time' gains about 45ms in 1 minute. // Added an extra delay in the minute change as a correction. delay(968); ss++; if(ss==60) { ss=0; mm++; delay(44); // compensates for total loop time being very slightly less than 1 second. } if(mm==60) { mm=0; hh++; } if(hh==24) { hh=0; } } |
I left the sketch running overnight and checked again this morning, but... things are not going as I had hoped! After just 7 hours, the clock was about 3 seconds adrift - a lot better than the Mk I's 10 minutes, but considerably worse than the 1/2 second I was expecting. I checked back in the log on the serial monitor, and found that after running for about 8 or 9 minutes with the drift being a constant 1ms per minute, the drift suddenly started jumping to 4ms, then 6ms, then 9ms or more..! No wonder it's 3 seconds adrift already.
I am not sure exactly what to make of this. As far as I was aware - running the same set of operations in the loop repeatedly, should take exactly the same time for each loop. Obviously, if the processing has to take a different route through the logic - such as the extra processing when the seconds or minutes or hours (or all three) get reset, then the timing would be different, but if the same set of operations is run each time (as it is 59 times each minute - or the same cumulative code for an entire minute, as it is 59 times per hour), then there should be no variation in the timing.
With hindsight, I can see some very slight differences - between some loops, such as when the value is < 10, there is additional code to print an extra '0', but even that follows a distinct and predictable pattern each minute. It would not account for the variances I suddenly started getting after a few minutes, that just got bigger and bigger with no apparent pattern (other than just keeping on getting bigger).
I don't know if there are external factors that could affect this, such as voltage to the Arduino (I was running it from the USB port on my ageing laptop, and part of the time I was streaming a movie - but most of the night, it was sitting on its own in the dark, doing nothing but providing power to the Arduino). Perhaps the amount of entries in the serial monitor buffer, gradually increasing through the night is a factor (though I would have thought Arduino.exe would have been handling that independently on the PC)?
So, what next? I think I need to run a few more tests... and maybe post a question on the Arduino forum? Watch this space (or if anyone knows why the exact same code iterated a number of times can take such varying elapsed times, please let me know).
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.
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.
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.
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.
Labels:
clock,
electronics,
millis,
parseInt(),
sketch,
sprintf
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!
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...
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 😀
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(" "); } |
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 😀
Monday, 12 June 2017
I2C and Multiple Devices.
It's been a while since my last post, and since I did anything much with the Arduino too. Unfortunately, I had to go on a 'short' business trip that went wrong and ended up doubling in length. While I was away, I know that two more of my component purchases have turned up from China, so I am hoping that one of them is my long lost LCD display. I'll find out when I get into work in the morning.Talking of the LCD display, I started wondering - what if I end up with both the original AND the replacement arriving, would it be possible to use them both at the same time ? So I started investigating...
The answer is "YES". The IIC (I2C) interface mechanism allows for this by sending instructions out on the I2C interface bus along with the address that is related to the specific device the instruction is meant for. So you can have many different devices attached to the I2C bus - they don't even need to all be the same type - and as long as each has its own 'address', then each will see its own instructions.
Think of a mailman walking down the road, dropping off letters to each house... Dr Jones at number 10 only gets his own post, and not the post for Mr Smith at number 19 (well, as long as the postman is doing his job properly). This analogy is near enough, but not quite accurate - and because I am a pedant, this is how the mailman analogy has to be changed... He walks down the street (I2C interface bus), and goes to house (device) number 1. Mr Andrews from number 1 comes out and checks through the mailbag to see if there is any mail (data instruction) with his address on it. If there is, he takes it, and the mailman moves on to number 2. Mrs Brown from number 2 comes out and checks through all the mail to see if there is any with her address, then the mailman goes to see Mr Connor at number 3, Miss Davies at number 4, Mrs Edwards at number 5, and so on all down the street. Each person has the chance to look and see if there is mail for them, regardless of whether there is or not.
I used the word 'bus' just now - and since I didn't mean a bus to take you to work (though the concept is kind of appropriate), maybe I should explain? In electrical terms, a bus is a single long common electrical contact that several devices or components can be connected to. Your breadboard probably has +ve and -ve strips that run all the way down the edge - these are examples of an electrical bus. A device interface bus is similar in concept (and relies on the same electrical principal). The I2C interface bus consists of just 4 connections (+ve and -ve obviously, a clock line that carries timing signals, called SCL, and then the actual data line, called SDA, that actually carries the address and instructions). These 4 connections go from the Arduino to device one, then to the next device, and the next device, and so on. So, not only could I have 2 LCDs, it seems I could have 8!! In fact, I could have up to 127 different devices, as long as I was able to give each one a unique address - but there are some limiting constraints - read on.
Here's a picture I found during my investigations, of 8 LCD displays all connected to a single microcontroller (not an Arduino family unit though) and all showing something different, and an article from 'Embedded Lab' about it (that explains it all much better than me). You can see the 4 wires that form the bus, visiting each device in turn, but delivering only the instructions relevant to that particular device.
It looks as though every device that has an I2C interface, has a controller that defines a base address BUT... note that each type of device could potentially use a different controller that has been programmed with a different base address, such as the I2C Real Time Clock (RTC) module DS1307, which has a default base address of 0x68..
When you purchase them, by default every device of that type, with that controller, will have the SAME address. When you look at Arduino code samples for an I2C LCD display, for example, you will probably see a line of code that looks something like...
LiquidCrystal_I2C lcd(0x27,16,2);
...where LiquidCrystal_I2C is the name of the external library of commands; lcd is the object reference you will use in your sketch; 0x27 is the address of the LCD device; and 16,2 is the number of characters and lines.
By the way, the 0x prefix indicates the number is in hexadecimal (base 16), so the value 255 in hexadecimal would be 0xFF. I am not really sure why the convention is to use hexadecimal values for the interface addresses - but it is - so get used to it...
Since the 16x2 LCD is such a common device, and they mostly tend to use variants of the PCF8574 controller, then you're on a fairly safe bet that 0x27 will be the correct base address. However, some versions of the PCF8574 - (the AT for example, I think), use different addresses such as 0x3F, or they may even use completely different controllers, which also may not use that address. A quick look at the various Arduino and other electronics forums, will reveal that many many people seem to have problems with addressing, when they get LCD 16x2 units that do NOT have that base address - maybe it is due to cheap clone devices from China, that have to be slightly different so as to avoid copyright infringements? Worry not dear reader, one of the sample sketches that comes with the LiquidCrystal_I2C library, is a scanner that will locate any I2C devices attached to your Arduino, and show you the address they are using.
So it looks relatively easy to use a single LCD device, even if it's base address isn't 0x27... but what about if we want to use 2 (or 8) LCD devices? How do we change that 0x27 base address, to something that is unique per device? Well, you are going to have to get your soldering iron heated up, for a start... If you look carefully at the I2C interface board, you should see 3 pairs of solder pads, or jumper pins, that are labelled A0, A1, A2, or something similar.
If you know anything about binary, you will probably recognise that a 3 bit binary number can hold the values 0 to 7 in decimal - which is 8 different numbers. This is what gives us our potential for 8 different addresses. The number given by these three connections (all open by default - e.g. value = 000) is added to the base address of the controller, and this in turn gives us 8 possible unique addresses that we could use. For example, add a blob of solder across the A0 contacts, and your binary number is now 001 (1), which would give this device an address of 0x28 instead of 0x27. The image above is from an Adafruit article about changing I2C addresses, that you may want to look at.
So - if I do end up with both LCD units arriving, I will certainly be giving this a go... Look out for a sketch with...
LiquidCrystal_I2C lcd1(0x27,20,4);
LiquidCrystal_I2C lcd2(0x28,20,4);
😁
Joke for the day
There are 10 types of people in the world...
Those that understand binary, and those that don't
😁
Joke for the day
There are 10 types of people in the world...
Those that understand binary, and those that don't
Saturday, 3 June 2017
Time... still waiting
Developing the theme a bit from the last post, I decided that rather than just showing the elapsed time from when the sketch started, maybe I should try and show the real time instead.
That raised an issue - how to actually initialise the time at the start of the sketch. Two methods sprang to mind - firstly to just simply type the time in to the Serial Monitor to set it going (yes - as well as being very handy to write output data on, the monitor also has an input function), and the second was to have a couple of buttons connected along with the display, and use them to shift numbers up and down until the time is right. However - that presupposes I actually have the display to use - which I don't... yet. Yes, it's been over 6 weeks now - thankfully, the seller has agreed to send a replacement. Once the display eventually arrives, I may revisit this topic and give you more detail on that solution (once I have given it some more thought...)
OK - so back to the Serial Monitor input method. I read numerous forum posts and explanations, but I must confess - while the concept seems simple enough, I still don't really understand the details of reading data from the monitor into a sketch. It all looks quite confusing when trying to understand it sufficiently to be able to write my own code from scratch. Luckily, I don't need to re-invent this particular wheel...
I was able to find an example of almost exactly what I want to do. It is from this 'Instructable' article, detailing how to use the Serial Monitor to set the time and date for an Arduino project using a Real Time Clock (RTC) module called a DS1307. The article is uncredited, so I can't thank the author or credit them here either - but my thanks go out to him or her.
Firstly, what I was aiming to do was to use the Time.h library, which has lots of time and date functionality built into it, and allows simple access to every element of the time and date for display purposes, as well as date and time calculations, etc. The first Time method I want to use is setTime, which has the following format
setTime(hour,minute,second,year,month,monthday);
For my purposes, the date portion is irrelevant, and can be set to any date at all. I am only interested in hours, minutes and seconds. This reduced the amount of Serial Monitor input processing (and also the time taken to actually initialise my clock) considerably.
Here is the code that I hacked from the Instructable article, that sends prompts to the Serial Monitor, and accepts input back again.
As you can see, I have commented out all the stuff related to the input of the date, and just left the hours and minutes, and defaulted seconds to 0. The key bits of code here are lines 19, 22, and 24. Each of these lines assigns a value to the relevant variable. Note that the variables are all byte data types... The byte data type is exactly what you might expect - a single byte of data - made up of 8 bits, each of which can hold either a zero or a one. That gives a byte a total of 256 possible different combinations of ones and zeros, so a byte can store an integer (whole number) with a value of 0 to 255. An ideal choice for hours, minutes and seconds with a maximum value of 24, 60, and 60 respectively.
Now the other part of lines 19 and 22, is a call to a function called readByte(). THIS is where the Arduino is listening out on the serial line, waiting for characters to arrive from the Serial Monitor. And THIS is where my understanding faltered, took a few steps backwards, looked again from a safer distance, and then turned and walked away with its head hung low in shame. I haven't a clue how this code actually functions at the moment - all I know is that it works. I am sure that I will keep revisiting it and chipping away until I do understand - but for now, I am treating it like the code that is stored in the various libraries (or my car engine). I don't need to know exactly how it works, just as long as it does...
The last part of the puzzle is to use the data input from the monitor, to set the time. So - we need to make sure we have the Time.h library included at the beginning of the sketch... The Master Library I found on github actually generates two include statements when you build it into a sketch. I don't know why, but for know my sketches are small enough that I don't need to worry about excessive memory usage, so will leave them both there...
#include <Time.h>
#include <TimeLib.h>
...and then of course is the setTime command
setTime(hour,minute,second,2017,6,3);
You can see I've used the time parameters, but hard-coded today's date as it is simply not required for my quickie LM35 test sketch.
By the way, actually using the input ability of the Serial Monitor utilises the single line panel at the bottom of the monitor. Note there is the option to describe how you will signal the end of each data item - in this instance, I used the 'newline ending' option suggested in the Instructable article.
Now that the functionality in the Time library has a starting point and can 'keep' time, we can access the time at any point, and it will be stored in a special 'customised' data type, called time_t. This data type contains all the elements of the date and time in a way that can be accessed by a number of other functions in the library. For example...
time_t t = now();
stores the the time NOW in a variable of the time_t data type, called t.
Serial.print(hour(t));
will print the hour component of the current time.
So that's the basics of using the Serial Monitor to initialise values for use in a sketch - it doesn't have to be time and date values, it could be setting a seed value to use for random number generation, or... well anything - the sky's the limit!
That raised an issue - how to actually initialise the time at the start of the sketch. Two methods sprang to mind - firstly to just simply type the time in to the Serial Monitor to set it going (yes - as well as being very handy to write output data on, the monitor also has an input function), and the second was to have a couple of buttons connected along with the display, and use them to shift numbers up and down until the time is right. However - that presupposes I actually have the display to use - which I don't... yet. Yes, it's been over 6 weeks now - thankfully, the seller has agreed to send a replacement. Once the display eventually arrives, I may revisit this topic and give you more detail on that solution (once I have given it some more thought...)
OK - so back to the Serial Monitor input method. I read numerous forum posts and explanations, but I must confess - while the concept seems simple enough, I still don't really understand the details of reading data from the monitor into a sketch. It all looks quite confusing when trying to understand it sufficiently to be able to write my own code from scratch. Luckily, I don't need to re-invent this particular wheel...
I was able to find an example of almost exactly what I want to do. It is from this 'Instructable' article, detailing how to use the Serial Monitor to set the time and date for an Arduino project using a Real Time Clock (RTC) module called a DS1307. The article is uncredited, so I can't thank the author or credit them here either - but my thanks go out to him or her.
Firstly, what I was aiming to do was to use the Time.h library, which has lots of time and date functionality built into it, and allows simple access to every element of the time and date for display purposes, as well as date and time calculations, etc. The first Time method I want to use is setTime, which has the following format
setTime(hour,minute,second,year,month,monthday);
For my purposes, the date portion is irrelevant, and can be set to any date at all. I am only interested in hours, minutes and seconds. This reduced the amount of Serial Monitor input processing (and also the time taken to actually initialise my clock) considerably.
Here is the code that I hacked from the Instructable article, that sends prompts to the Serial Monitor, and accepts input back again.
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 | // initialise Serial Comms Serial.begin(9600); // Get initial Time and date from Serial Monitor (set to NewLine line endings) //Serial.print("Please enter the current year, 00-99. - "); //byte year = readByte(); //Serial.println(year); //Serial.print("Please enter the current month, 1-12. - "); //byte month = readByte(); //Serial.println(month); //Serial.print("Please enter the current day of the month, 1-31. - "); //byte monthday = readByte(); //Serial.println(monthday); //Serial.println("Please enter the current day of the week, 1-7."); //Serial.print("1 Sun | 2 Mon | 3 Tues | 4 Weds | 5 Thu | 6 Fri | 7 Sat - "); //byte weekday = readByte(); //Serial.println(weekday); Serial.print("Please enter the current hour in 24hr format, 0-23. - "); byte hour = readByte(); Serial.println(hour); Serial.print("Please enter the current minute, 0-59. - "); byte minute = readByte(); Serial.println(minute); byte second = 0; Serial.println("The data has been entered."); |
As you can see, I have commented out all the stuff related to the input of the date, and just left the hours and minutes, and defaulted seconds to 0. The key bits of code here are lines 19, 22, and 24. Each of these lines assigns a value to the relevant variable. Note that the variables are all byte data types... The byte data type is exactly what you might expect - a single byte of data - made up of 8 bits, each of which can hold either a zero or a one. That gives a byte a total of 256 possible different combinations of ones and zeros, so a byte can store an integer (whole number) with a value of 0 to 255. An ideal choice for hours, minutes and seconds with a maximum value of 24, 60, and 60 respectively.
Now the other part of lines 19 and 22, is a call to a function called readByte(). THIS is where the Arduino is listening out on the serial line, waiting for characters to arrive from the Serial Monitor. And THIS is where my understanding faltered, took a few steps backwards, looked again from a safer distance, and then turned and walked away with its head hung low in shame. I haven't a clue how this code actually functions at the moment - all I know is that it works. I am sure that I will keep revisiting it and chipping away until I do understand - but for now, I am treating it like the code that is stored in the various libraries (or my car engine). I don't need to know exactly how it works, just as long as it does...
1 2 3 4 5 6 7 8 9 10 11 12 13 | byte readByte() { while (!Serial.available()) delay(10); byte reading = 0; byte incomingByte = Serial.read(); while (incomingByte != '\n') { if (incomingByte >= '0' && incomingByte <= '9') reading = reading * 10 + (incomingByte - '0'); else; incomingByte = Serial.read(); } Serial.flush(); return reading; } |
The last part of the puzzle is to use the data input from the monitor, to set the time. So - we need to make sure we have the Time.h library included at the beginning of the sketch... The Master Library I found on github actually generates two include statements when you build it into a sketch. I don't know why, but for know my sketches are small enough that I don't need to worry about excessive memory usage, so will leave them both there...
#include <Time.h>
#include <TimeLib.h>
...and then of course is the setTime command
setTime(hour,minute,second,2017,6,3);
You can see I've used the time parameters, but hard-coded today's date as it is simply not required for my quickie LM35 test sketch.
By the way, actually using the input ability of the Serial Monitor utilises the single line panel at the bottom of the monitor. Note there is the option to describe how you will signal the end of each data item - in this instance, I used the 'newline ending' option suggested in the Instructable article.
Now that the functionality in the Time library has a starting point and can 'keep' time, we can access the time at any point, and it will be stored in a special 'customised' data type, called time_t. This data type contains all the elements of the date and time in a way that can be accessed by a number of other functions in the library. For example...
time_t t = now();
stores the the time NOW in a variable of the time_t data type, called t.
Serial.print(hour(t));
will print the hour component of the current time.
So that's the basics of using the Serial Monitor to initialise values for use in a sketch - it doesn't have to be time and date values, it could be setting a seed value to use for random number generation, or... well anything - the sky's the limit!
Subscribe to:
Comments (Atom)




