Showing posts with label sketch. Show all posts
Showing posts with label sketch. Show all posts

Tuesday, 31 October 2017

A fortuitous ebay mistake

Today, my ultrasonic sensor arrived with the 4 digit display.  BUT - instead of the bare 4 digit display, they sent me a display module with a 4 pin interface, which is good, but it is a clock module with a colon in the middle, rather than normal digits - each with a decimal point.
After a little digging around, I have found a library to drive the module and figured out how to use it.  As it happens - since I wouldn't need a decimal point, I could use this module in the water tank gauge project, but I have already pointed out the mistake to the seller, in the hope he will send me a new display of the type shown in the ebay listing.

But as I now have an LED display geared up for a clock type display, I thought - why not pair it up with my new RTC module and have a little clock.  Ok - so it only shows hours and minutes, but it will help me to test the longevity of the 2032 battery in the DS3231.

Here is my code for the clock

 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
#include <DS3231.h>
#include <TM1637Display.h>


// Module connection pins (Digital Pins)
#define CLK 9
#define DIO 8

// Initialise LED
TM1637Display display(CLK, DIO);

// Initialise RTC & RTC variables
DS3231 Clock;
bool Century=false;
bool h12;
bool PM;
byte ADay, AHour, AMinute, ASecond, ABits;
bool ADy, A12h, Apm;

// other variables
long counter;
uint8_t mask = 0b00010000;


void setup() {

// turn off annoying LED on 13
  pinMode(13, OUTPUT);
  digitalWrite(13,LOW);

// set brightness of LED
  display.setBrightness(0x0f);

// set all segments off for all digits
  uint8_t data[] = { 0x00, 0x00, 0x00, 0x00 };
  display.setSegments(data);

// store current millis()
  counter = millis();
}

void loop() {
// get Time from RTC
  int hrs = Clock.getHour(h12,PM);
  int mins = Clock.getMinute();

  if (hrs > 12) hrs -= 12;
  
// display the hours with colon enabled/disabled as each second passes
  // mask for colon on digit 2
  // 0x80>>3 = 0b00010000 (I have no idea WHY this works!)

  if (millis() - counter > 1000) {
    if (mask == 0b00010000) {
      mask = 0b00000000;
    } else {
      mask = 0b00010000;
    }
    counter = millis();
  }
  display.showNumberDecEx(hrs,mask,false,2,0);

// display the minutes
  display.showNumberDec(mins,true,2,2);

}

I have tried forcing the hours to am/pm mode, as the RTC always seems to show it in 24 hour format (I expect there is a way to do it using the library functions, but I didn't find it yet), and then apply leading zero suppression to the hours, so that it shows as 9:00 for example, rather than 09:00.  However, I'm not sure what'll happen between midnight and 1am - perhaps the hour will not show at all...?  I won't be awake to see it though - at least not tonight!

I have no clue why the mask that controls the colon being displayed works... it is a bitwise shift right by three places, but by my reckoning, as the left most bit in the mask is a shift of 0, then the mask should be using a shift of either 1 or 2 places (depending if the colon is linked to the right side of digit 2 or the left side of digit 3.  But - I got it working by trial and error, and have even managed to make it flash every second.

Tomorrow, hopefully, I may start playing with the ultrasonic sensor unit.  Exciting!!

Update - 2 days later:
The RTC is still holding the time well.  I did originally have some issues with it randomly resetting itself, but I think was down to my dodgy prototyping with modules just hanging off the Uno on the end of DuPont cables - things were possibly touching other things and shorting out, etc.  After I blu-tacked all the modules down on a base board - no more problems.
By the way - even with leading zero suppression, times between midnight and 1 o'clock DO show with a single zero for the hour, as I hoped.

Update - 3 weeks later:
It is still holding the time, but given that my display is only hours and minutes, I haven't confirmed just how accurate the time is.  But the fact it still has a valid time that is even roughly correct is a big win.  

Monday, 10 July 2017

Testing tone();

At the weekend, I quickly cobbled together another of my little break-out boards, this time for a piezo crystal transducer (or a buzzer to you and me). 

One thing this taught me is that although we all call these things buzzers - actually, they are just 'clickers'.  If you apply voltage to it, it just goes click.  The tone comes from applying and disconnecting power 1000 times a second, to get a 1KHz tone for example.  A real ''buzzer' has components and circuitry built into it, so that all you need to do is apply power, and you get a nice tone generated.

Anyway - I digress... That was not really the point of this post.  I found out something else, which wasn't really immediately evident to me from the Arduino Reference manual, and even looking at it again with hindsight - I still didn't find anything about it...

To generate a tone on an Arduino, simply connect a 'clicker' thingy (I'm just going to call it a buzzer) to an output enabled pin and GND (NB - the buzzers are polarity sensitive, and have the positive pin marked on the casing, or indicated with a red lead), and then there is a nice easy tone command that does the work of generating the square wave for you.  Typically, the code looks something like this...

// start sending the tone of toneFrequency Hz to the device on buzzerPin
    tone(buzzerPin, toneFrequency);
// keep doing it for the period toneDuration milliseconds
    delay(toneDuration);
// at the end of the toneDuration delay, stop sending the tone
    noTone(buzzerPin);


I was using this code in a little sketch to make a hospital heartbeat monitor type 'beep', and also flashing the LED in time with the beep.  It all worked fine, and I should really have left well alone at this point (if it ain't broke, don't fix it!)

But then, I found an alternate version of the tone command, that includes the duration as a third parameter.  Why would anyone go to the lengths of typing 3 lines of code, when one will do..?  So I tried it...

// Same function, but all in one command
  tone(buzzerPin, toneFrequency, toneDuration);
// No need for the delay() or noTone() commands!


It worked!  Well, at first glance, it seemed to have worked, but then I noticed something odd - my LED was no longer flashing.  Actually, it was, but only for the briefest of moments, and unless you covered up the adjacent power LED (yeah - ALL the LEDs are clustered together on my clone - and very bright - it's a bit of a pain sometimes) you really wouldn't notice it.

I wondered about the potential for the buzzer to be drawing too much voltage and not leaving enough for the LED, so I disconnected the the buzzer.  The LED was still only giving the faintest of flickers.  I wondered if the 100ms period was too quick, though my gut was already saying "of course it's not".  I went back to basics and installed Blink, and changed it to 100ms.  Yes it was fast, but the LED lit up properly.  So I went back to my sketch and changed the toneDuration to 5000 millis. No change in the LED, but something else odd was now happening - the normal round-trip duration of the loop() section of my sketch was about 3 seconds, including a 1 second delay at the end. I was expecting my tone to now run for 5 seconds, followed by 1 second pause, but it was running constantly.

Then, the penny dropped - including the toneDuration as a parameter on the tone command, appears to switch it to background operation.  That means that the sketch starts generating the tone, and can carry on with the rest of the lines of code while the tone is being sounded.  So, what was happening is that my LED was switching on, staying on for probably just a few microseconds while the tone command was being evaluated, and then switched straight off again.  furthermore, if the toneDuaration value exceeds the time it takes for the rest of the code in loop() to run through - including any delay() commands, then the 'next' tone starts sounding before the first has finished, so it sounds like it is going constantly.

So - just be a little bit careful with the two flavours of the tone command - they are not the same beast at all, and make sure you know whether you want the duration of your tone to play an integral part in the progression of your code, or if it can be set off and left to run independently while your code goes and does something else...  If you choose the latter option, make sure there is room in your loop() code for the tone to finish before the loop does.

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.


Friday, 26 May 2017

Experimenting with the LM35 Temp sensor

A few days ago, my breadboard and power supply arrived, so I have already been doing some testing with the LM35 sensor I had rescued.  You may remember I mentioned it was on a circuit board, and the output from the sensor was being pushed through an op-amp chip.  I have no idea what that chip was doing to the signal, and hooking the output from that up to my multimeter was giving some very inconclusive readings.  So I figured the safest bet was to remove the LM35 sensor from the board and use it on its own - at least then I have the specs and can understand what it is supposed to be doing.

Now that I have my Arduino as well (though no display as yet), last night I put together a little test sketch just to see what the LM35 on its own provides.

The sketch is a simple loop that takes a reading, converts it to temp, checks against min and max values, and then prints the results out to the serial monitor.  However - my first attempt resulted in 20.0C over and over again.  I put in some extra print lines to troubleshoout the raw value coming from the A0 pin, and found it to be showing slightly different values each time as I would have expected, but after a little research I found the problem... I was using the simpler map() function to translate my analog value between 0 and 1023 into a temperature between 0 and 110C, rather than messy mathematics.

          float celsius = map(pinVal,0,1023,0,110);

It turns out that the map() function ONLY produces INT values, so the small variations I was seeing on the analog pin, were not big enough to generate a whole degree of difference.

I reverted back to the mathematical approach, and started to get much better results, showing different readings varying by tenths of a degree (though adjacent readings separated by just 5 seconds could sometimes be half a degree different - so I question the accuracy of the sensor when in the open - perhaps it needs to be in an enclosure to protect it from drafts, etc.

          float celsius = (pinVal/1024.0) * 110.0;

Now, with a steady stream of data arriving through the serial console and rapidly scrolling off the top, I thought it might be nice to see how long the sketch has been running, and initially just included the millis count (oh - by the way, I have discovered that the millis count doesn't reset every 9 hours or so as I originally thought - apparently that figure was a throwback to an old version from around 2007!!  The current millis counter will run a little short of 50 DAYS before wrapping around to 0 again).

Then I figured it would be nice to show the elapsed time in an hh:mm:ss format, so set about converting it, like this...
Take the elapsed millis and divide it by the number of millis in an hour, to get an (int) hrs value
Subtract (hrs times the number of millis in an hour) from elapsed millis
Take the modified elapsed millis and divide it by the number of millis in a minute,
Subtract (mins times the number of millis in a minute) from elapsed millis
Take modified elapsed millis and divide by 1000 to get an (int) secs value
Finally, display each of the elements on the console.
I found that there is seemingly no easy way in Arduino code to do something like
   print(hrs + ":" + min + ":" + secs)
so the simplest solution looks like being a whole bunch of Serial.print(); statements in my sketch.  It ain't pretty - but at least it is easy to follow.

So finally, here is my little test sketch...

 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
int LM35Out = 0;           // Analogue pin 0 
float min = 100;
float max = 0;
int counter = 0;

void setup() {
  // put your setup code here, to run once:

     Serial.begin(9600);
     analogReference(INTERNAL);    // use 1.1V reference to get greater granularity on analogRead
}

void loop() {
  // Get reading and increment counter
     int pinVal = analogRead(LM35Out);
     counter++;

  // Convert analog value to temp (don't use MAP as it calculates int values only)      
     // float celsius = map(pinVal,0,1023,0,110);
     float celsius = (pinVal/1024.0) * 110.0;

  // Check and set max and min values
     if (celsius > max) max=celsius;
     if (celsius < min) min=celsius;

  // Keep note of elapsed time
     unsigned long elapsed = millis();

  // Convert elapsed millis into hh:mm:ss
     int hrs = elapsed/3600000;
     elapsed = elapsed - (hrs*3600000);
     int mins = elapsed/60000;
     elapsed = elapsed - (mins*60000);
     int secs = elapsed/1000;

  // Print results to serial monitor
     Serial.print("Reading: ");
     Serial.println(counter);
     
     Serial.print("Elapsed time = ");
     if(hrs<10) Serial.print("0");
     Serial.print(hrs);
     Serial.print(":");
     if(mins<10) Serial.print("0");
     Serial.print(mins);
     Serial.print(":");
     if(secs<10) Serial.print("0");
     Serial.println(secs);
     
     Serial.print("Temp is now ");
     Serial.print(celsius,1);
     Serial.println("C");
     
     Serial.print("Max was ");
     Serial.print(max,1);
     Serial.println("C");
     
     Serial.print("Min was ");
     Serial.print(min,1);
     Serial.println("C");
     Serial.println();
     
  // Wait 5 seconds between readings   
     delay(5000);
}
...and the output in the serial monitor window.


Sunday, 21 May 2017

SK001 - in theory... (pt 4)

Hi again,
time for the final part of the puzzle - loop().  This is where all the preparations come together to actually do something.  Then once it has done that something... it does it again... and then guess what?  It does it again.  A bit repetitive - yes?  Well - not necessarily.  Remember those variables we set up at the beginning, and how we said that they were 'global', so were available to all of the functions in the sketch?  Well, that includes from loop to loop of loop().  This means that one time through, we might set a variable to be a certain value, but the next time we go through loop, that value acts as a trigger to make something different happen - like this...


 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
str trafficLight;

void setup()
{
     trafficLight = "RED";
}

void loop()
{
     if (trafficLight == "RED") 
     {
          trafficLight = "RED and AMBER";
     }
     else if (trafficLight == "RED and AMBER")
     {
          trafficLight = "GREEN";
     }
     else if (trafficLight == "GREEN")
     {
          trafficLight = "AMBER";
     }
     else
     {
          trafficLight = "RED";
     }
}
Disclaimer - the code above may not actually work if you try and load it into your Arduino - it was intended only to illustrate a point.
The first time we go through loop(), the light is already set to red, and so it is changed to red and amber.  The next time through, red and amber are changed to green.  The next iteration will set it back to amber, and then finally back to red, and so the cycle starts all over again.  Add in some delays, and you have a set of (UK) traffic lights.  So you can see that in this instance, there are four distinct and different operations performed by loop().  However, even these follow a set pattern, and could all be in one large chunk of code inside of a single iteration of loop().

Now bring some real world randomness into the picture, using the Arduino to react to outside influences, such as the temperature.  Each time through the loop, the current temperature is read from a sensor.  If it is greater than the previously stored maximum, then it replaces it - or if it is lower than the previous minimum, it replaces that.  If the light is on, and 10 seconds has passed since it was switched on, then switch it off.  Hey, does all this sound a bit familiar to you?  Yeah - me too... so let's see it in action!

  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
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
void loop() 
{
  // Read analog value (0-1023) on LM35 output connected to A0
  int analogVal=analogRead(LM35out);

  // convert analog value (0-1023) to millivolts (0-1100) to celsius
  //float celsius=(1.1 * analogVal * 100.0) / 1023.0;
  float celsius=map(analogVal,0,1023,0,110);

  // Display "Temp now" with 1 decimal
  lcd.setCursor(13,0);
  if (celsius < 10) 
    lcd.print("0");      // Add leading 0 if < 10C
  lcd.print(celsius,1);

  // 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(13,1);
       if (maxTemp < 10) 
        lcd.print("0");      // Add leading 0 if < 10C
      lcd.print(maxTemp,1);
  }
  
  // if "Temp now" is a new low...
  if (celsius < minTemp)
  {
    //store current value as new low
    minTemp=celsius;

    // and change value on display
      lcd.setCursor(13,2);
      if (minTemp < 10) 
        lcd.print("0");      // Add leading 0 if < 10C
      lcd.print(minTemp,1);
  }

  // Backlight Operation
  // -------------------
  
  // check if backlight is on
  if (lightState == "on")
  {
    // switch backlight off after 10 seconds
    // NB - If you happen to switch light on just before the clock hits max and resets to 0, then light may only stay on for 1 or 2 three second period.
    // But since the millis() count only resets every 9 hours, it is unlikely you will hit just that 10 second period.
    // I could code around it, but can't be bothered to spend the time for something so unlikely.
    if (abs(millis() - lightOnTime) > 10000)  
    {
      // switch backlight off immediately
      // lcd.noBacklight();
      // or fade to off over 1 second
      for (int i=255; i>=0; i--)
      {
        lcd.setBacklight(i);
        delay(4);
      }
      lightState="off";
      lightOnTime=0;
    }
  }

  // check for backlight off, and pushbutton pressed
  if (lightState == "off")
  {
    // check for pushbutton press
    if (digitalRead(lightSwitch) == HIGH)
    {
      // switch backlight on immediately
      // lcd.Backlight();
      // or fade on over 0.5 second
      for (int i=0; i<256; i++)
      {
        lcd.setBacklight(i);
        delay(2);
      }
      lightState = "on";
      lightOnTime = millis();
    }
  }
  
  // Averaging Calcs
  // ---------------
  
  //    increment counter
  fiveMinCount++;
  
  //    accumulate value
  fiveMinAccum = fiveMinAccum + celsius;
  
  //    calculate and display average for current 5 min period
  fiveMinAve = fiveMinAccum / fiveMinCount;
  lcd.setCursor(13,3);
      if (fiveMinAve < 10) 
        lcd.print("0");      // Add leading 0 if < 10C
      lcd.print(fiveMinAve,1);
 
  //  write temp, max, and min to serial console
    Serial.print("Current temp = ");
    Serial.print(celsius,1);
    Serial.println(char(176) + 'C');

    Serial.print("Max temp = ");
    Serial.print(maxTemp,1);
    Serial.println(char(176) + 'C');

    Serial.print("Min temp = ");
    Serial.print(minTemp,1);
    Serial.println(char(176) + 'C');

    
  //    if end of a 5 minute period, then write average temp to serial console, reset and start again.
  if (fiveMinCount >= ((5 * 60 * 1000) / loopPeriod))
  {
    Serial.print("Latest 5 minute period average = ");
    Serial.print(fiveMinAve,1);
    Serial.print(" at ");
    Serial.println(millis());
    fiveMinAccum=0;
    fiveMinCount=0;
    fiveMinAve=0;
  }
  
  // wait a few seconds before retesting
  delay(loopPeriod);

}

WOW - that's a lot of code, but in my defence, a lot of it is comments ;-)  So let's break it down into its component parts.

Line 4 simply reads the signal from the LM35 attached to analog pin 0.  Remember, this will be a voltage somewhere between 0 and 5 volts (hopefully lower than 1.1 volts so it can be evaluated against the INTERNAL reference voltage).  The Arduino translates this into a number between 0 and 1023.

Line 8 takes the possible numbers 0 to 1023, and maps those to 1024 increments between 0 and 110 to match our expected temperature range. The actual incremental point is stored in a floating point variable called celsius.

Lines 11 to 14 basically write the celsius value as a two digit plus 1 decimal value at position 13 on line 0 of the display. If the temperature is lower than 10 degrees, then the code will insert a leading zero, so the decimal point always stays in the same place (yes - I realise I will have problems when it reaches 100C... but given that this is designed to read ambient air temperature in my office, if it ever reaches 100C, then the position of the decimal point is not likely to be of immediate interest to me).

Line 17 checks to see if this is a new maximum temperature.
If it is, then line 20 stores the current temperature as the new maxTemp, and lines 23 to 26 print the value (just like above) on line 1 of the display.  Notice that the maxTemp value is ONLY updated on the display when the value actually changes - this is to eliminate the possibility of a flicker caused by the value being overwritten with same value every single time it goes through the loop.

Lines 30 to 39 do the same again, but this time checking for a new minimum temperature, and writing it to line 2 (if required).

Line 46 checks to see if the backlight on the display has been switched on (zoom forward to line 68 to see how it is switched on, and what happens then...)
If the backlight IS on, then line 52 looks at the millisecond counter value now, and compares it with the millisecond value when the light was switched on.  If the difference is > 10,000 (i.e. 10 seconds), then lines 57 to 63 switch the backlight off again (notice - I used the 'fade' style of code here, that I mentioned way back at the start when I flash the display on and off a few times when I first switch it on).  I also set the backlight status variable to "off", and set the millisecond 'on' time to 0.

In case I hadn't already mentioned it, the millisecond counter just counts milliseconds (wow - really?  Yes - really!) since the sketch started running.  It can keep going for around 9 hours or so, and then rolls back to 0 and starts all over again.  Note my comments about the potential problem if the light was switched on just before the counter rolls back to 0...

Now we come to the code that handles switching the light ON.
Line 68 checks to see if the light is off.
If it is, then line 71 checks to see if the button attached to digital pin 8 has been pressed (connecting it to Vcc or +5v and giving a HIGH value on the pin).
If it has... then lines 76 to 80 fade the light up, line 81 sets the backlight status variable to "on", and then line 82 makes a note of the current millisecond count.

Remember - the backlight status variable, and the stored millisecond count, will both be available to be used in the next iteration of loop(), and the next, and the next, until eventually, 10 seconds will have passed, and then lines 46 to 63 will turn the light off again.

Now we come to another instance of variable values that get changed over a number of passes through loop(), until eventually, a trigger causes an action.  In this case, it is the accumulation of average temperature data which will be re-calculated every few seconds, then eventually written to an external device at the end of each period of 5 minutes, and the values reset and calculations started afresh.  In this case - the 'external device' is simply the serial console, but could eventually be an SD card for data logging, or plotting on a graph, etc.

In line 90, a counter is incremented.  This was initially set to 0 right back at the beginning of the sketch, and is used to count each iteration through loop().

Line 93 adds the current temperature (celsius) to an accumulator, which was also set to zero at the start.

Line 96 recalculates the average temperature, by dividing the cumulative temperature, by the number of readings (the count of loop iterations), and lines 97 to 100 print the averaged value to line 3 of the display in the now familiar fashion.

Lines 103 to 113 simply replicate all the LCD values displayed above (currently with the exception of the averaged value, but I will include that one too), on to the serial console.  This functionality was added a) to demonstrate how 'debugging' information can be sent to the console to help monitor the progress of the sketch's logic and variable values, and b) just in case my display is delayed and I want to actually try to run this sketch before it arrives!

Line 117 takes the number of milliseconds in a 5 minute period, and divides that by the 'delay' period per loop.  This gives the number of iterations through loop() that can be made in 5 minutes.  This number is compared with the actual loop counter (see line 90), and once the 5 minute period is exceeded... (it is unlikely to be accurately hit, due to the variable cost in milliseconds of all the operations within each iteration of loop() which can vary based on the logic and cumulative values) ...then lines 119 to 122 write the average value to the serial console (substitute other external device here when data logging or plotting function is required), and lines 123 to 125 reset all the variables used during the 5 minute average calculations, ready for another go.

Finally, line 129 sets a short pause delay at the end of loop(), before the whole process starts over again, and a new temperature reading is taken.

PHEW!!  If you are still here - congratulations - you have survived my first and pretty lengthy description of a (still theoretical) real world application of the Arduino.  Now that is what I call a starter sample - but maybe not for the absolute starter!!  Even if you haven't fully understood some of the syntax, or the logic, I hope it has given you some insight, and whetted your appetite.

Now, as soon as my Arduino arrives, and I can spend the time putting this circuit into operation, I will post "SK001 - in practice", which will include any changes I have to make to my code in order to get it to actually work, as opposed to this theoretical version.

Tuesday, 16 May 2017

SK001 - in theory... (pt 3)

Now we come to the setup function.  This is where we do all the one-time initialisation, getting everything ready to go...


 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
void setup()
{

  Serial.begin(9600);         // initialize serial comms at 9600 baud
  lcd.init();                 // initialise the display

  // flash backlight for 1 second on startup
  for (int i=1; i <= 4; i++)
  {
    lcd.backlight();        // may be an alias for lcd.setBacklight(255);  Try adjusting brightness with values 1-254.
    delay(125);
    lcd.noBacklight();      // may be an alias for lcd.setBacklight(0);
    delay(125);
  }

  // turn off cursor options
  lcd.cursor_off();
  lcd.blink_off();
  

  // setup backlight pushbutton
  lightState="off";
  pinMode(lightSwitch, INPUT);
  lightOnTime = 0;

  // write constants to display (Coordinates = col,row)
  lcd.setCursor(2,0);
  lcd.print("Temp now = --.-");
  lcd.print((char)223);
  lcd.print("C");
  lcd.setCursor(2,1);
  lcd.print("Max temp = --.-");
  lcd.print((char)223);
  lcd.print("C");
  lcd.setCursor(2,2);
  lcd.print("Min temp = --.-");
  lcd.print((char)223);
  lcd.print("C");
  lcd.setCursor(2,3);
  lcd.print("5min ave = --.-");
  lcd.print((char)223);
  lcd.print("C");

  // set Analog Input to use internal 1.1V reference rather than VCC 5V
  // this reduces upper limit to 110C, but gives greater resolution of nearly 0.1C vs 0.5C.
  analogReference(INTERNAL);
 
}

Line 4 sets up the ability to have serial communications with the controlling PC.  It isn't really required in this specific sketch, but it is generally a good idea to have this ready for use for troubleshooting  when things go wrong (as they will).  I'll cover troubleshooting in a later post, but in a nutshell, serial communications allow the Arduino to talk to the PC across the USB cable.  This can be as simple as writing the value of a variable out to the serial console (visible in the Arduino programming IDE), or writing data to the hard disk (or reading it from the hard disk), amongst other things.

Line 5 initialises the lcd.

Line 8 through 14 is what is known as a 'for...next loop'.
Line 8 sets up the loop control.  It creates an integer variable called 'i', and starts by setting it to 1. The next part of the loop control sets the ending condition for how many times the loop is run - all the while i is less than or equal to 4, then the loop will keep running.  Finally, at the end of each loop, the value in i is incremented by 1 (that is what i++ does).
The loop itself is all the instructions between the curly brackets.
Line 10 calls a function called backlight from the external code library, via the object called lcd. This switches the lcd backlight on.  Notice in the comment, it appears that you can actually put a brightness level in the brackets as a parameter to the setBacklight command - if that is the case, then a nice touch might be to create another for...next loop with values 0 to 255, and turn the lcd backlight on by incrementally 'fading it up'.
Line 11 sets a delay before the next instruction is started.  The parameter value of 125 is the number of milliseconds for the delay.
Lines 12 and 13 turn the backlight off again, and wait for another 125 milliseconds
So, effectively, one iteration of the loop will take a total of about 1/4 second (250 milliseconds), and the loop will run 4 times.  The upshot of this block of code flashes the backlight on and off 4 times in a second.  It will actually take very slightly more than 1 second as each instruction may take a few milliseconds to execute - and external devices may take some additional time to react.

Lines 17 and 18 make sure the block and underline cursor options are switched off.  I don't want the display to have any cursor at all - just text and numbers that change as required.

Lines 22 to 24 are setting up the pushbutton for the control of the backlight.  First we have a variable which just stores the current state of the light (starting with 'off').  The next line tells the Arduino microcontroller that the pin we are connecting the switch to (pin 8) is to be treated as an input to be read from, as opposed to an output that can be written.  The final line of the three sets up a variable that will be used to store the milliseconds counter at the point the button gets pressed.

Lines 27 to 42 are 4 sets of 4 commands that set the cursor position on the display (char, row); write out a line of text (note - the hyphens will be overwritten with the actual values later); write character #223 from the 256 standard ASCII character set (actually, only 128 characters are official standard characters, followed by 128 unofficial character slots) - I have seen 223 listed as the degree symbol (°) for the character set used in the LCD controller, but also seen 176 used for it elsewhere, so this will need some testing when the actual display comes,  The final line in each group of 4 writes the C for Celsius.

Line 46 - OK, this might take some explaining!
When an analogue input pin is read, it is normal (actually, it is required) for the voltage on that pin to be somewhere in the range of 0 to 5 volts.  As the chip itself is also running from 5 volts, then that is a handy thing for it to be able to use as a comparison for the voltage present at the analogue input.
The voltage seen at the analogue pin is expressed in 1024ths of the reference 5v - i.e. an integer 0 to 1023, where 0 = 0v and 1023 = 5v.  This gives the smallest increment a value of 5v/1024 = 4.9mV.
Since the LM35 produces an output of 10mV per degree C, then this effectively means that our thermometer can only differentiate temperature in half degree increments.  However, also note that our LM35 will only ever put out a maximum of 1.5 volts, and the 'normal' usage range in this instance will have it somewhere between 0 and 0.5 volts - so comparing it with a 5 volt reference with half a degree accuracy is somewhat wasteful.
The great thing is that internally, the chip also has a 1.1v reference available to it.  If that could be used instead of the normal 5 volts, then each of the 1024 increments now becomes slightly over 1mV, meaning that our thermometer can now read in 0.1 degree increments.  However, if the temperature goes above 110C (i.e. the voltage on the analogue pin is higher than 1.1v), then the thermometer will still register it as 110C - that has become the highest recordable temperature.
So analogReference(INTERNAL) is simply telling the chip to use its internal 1.1v reference for analogue inputs, instead of the normal 5v.

NB - there is also an analogReference(EXTERNAL) option, whereby you can provide whatever reference voltage you want (up to 5v max of course) and connect it to the chip's AREF pin.  So, if I had a regulated 1.5v reference, then I could use the full range of the LM35's measuring range, or alternatively, a 0.5v reference would severely constrain the range I could measure, but give a much greater level of granularity with each increment being about half a millivolt!

If you want to read more about the way the analogRead and analogReference are related, then the Arduino Reference is a good place to start (and they explain it far more succinctly than me).

Next up - SK001 - in theory...(pt 4) - the process loop.

Sunday, 14 May 2017

SK001 - in theory... (pt 2)

Here is the sketch, as it stands at the moment, broken down into manageable chunks that I will endeavour to explain, at least from a logical perspective.  This is a biggie to start your Arduino journey with, but having looked at dozens of 'getting started' sketches, I wanted to show something with a real world application.

You may not understand every single line of code, but that's OK - you can look up individual commands in the Arduino Programming Language Reference, or get in touch with me and I'll do my best to explain to the best of my understanding.  So, let's dive in...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// include I2C library for LiquidCrystal (www.sunfounder.com)
// check the specific display against http://playground.arduino.cc/Code/LCDi2c for correct I2C library
#include <LiquidCrystal_I2C.h>

// declare variables - pin connections
int LM35out=0;                // LM35 output -> pin A0
int lightSwitch=8;            // pin D8 for backlight pushbutton

// declare variables - Min/Max
float minTemp=99.9;           // initially set very high
float maxTemp=0.0;            // initially set very low

// declare variables - Backlight
unsigned long lightOnTime;    // to hold millis() value when pushbutton pressed
char lightState = "off";      // holds current state of backlight (on/off)

// declare variables - Five Min Averages
const int loopPeriod=3000;    // set period as a constant for use in average calcs as well as controlling loop
float fiveMinAccum=0;            // set accumulator to 0
int fiveMinCount=0;              // set counter to 0
float fiveMinAve=0;              // set average to 0

// declare the LCD device variable (7-bit address, chars, lines)
LiquidCrystal_I2C lcd(0x27,20,4);

So, remember that all the lines starting with // are simply comments, and comments can also follow the line of code - on the same line.  I have a terrible memory, so I put LOTS of comments in my sketches.  You might find them helpful to read - I know I do!  Notice that each line of code ends with a semi-colon (;)  If you forget to type them in when you start your own coding, be ready for all sorts of strange problems!

Line 3 is the one that gets the external code library for the LCD display.
The library is called LiquidCrystal_I2C.h

Lines 6 and 7 declare a couple of variables that each hold an integer type of number.  These are actually just to identify 2 of the I/O pins on the Arduino Uno's chip.  It is easier in the program to refer to these pins using a name with some context, rather than having to remember what 'pin 8' is used for, for example.

Lines 10 and 11 set up 'float' type variables.  A 'float' can store a number with decimal places, rather than just an integer. I have initially set the minimum temperature to a high value and the maximum to a low value. The first time I read the actual temperature it is likely to be somewhere between these two, and so both will get overwritten with the actual temperature then.

Line 14 is setting a 'long' numeric variable.  Note that it is also marked as being unsigned.  By default, all numeric variables are signed, which means they can hold negative numbers.  Every numeric type (float, integer, long, and double) has a specific range of numbers it can count to - and for signed types, 0 comes in the middle with numbers spreading out equally on the positive and negative sides.  For example, an 'int' type number can hold the values -32,768 to +32,767.  However, an unsigned int cannot hold negative numbers, but it does still have the same number of numbers, just starting at 0.  So an unsigned int can hold any number from 0 to 65,535. OK, back to line 14 again - this variable is going to hold the count of milliseconds since the sketch started running, when the button is pressed to turn on the backlight.

Line 15 is a 'string' variable.  Instead of numbers, this holds a text value - in this case, it will be simply 'ON' or 'OFF' to indicate to the program if the backlight is on or off.

Line 18 is another integer value, but notice that this one has the word 'const' in front of it.  This turns the variable into a 'read-only' constant value.  Once it is declared here, the value in it cannot be changed elsewhere in the sketch.  In this case, it is the delay to be used at the end of the loop function, before it goes back and starts again.

Lines 19-21 declare some variable to be used when calculating the 5 minute average temperature, which I will explain when we get to that part of the code.

Finally - Line 24..!  What the heck is this doing?
Firstly, note that it is somehow linked to the external code library we  pulled in (line 3) - notice how the line starts with the name of that library (minus the dot-h suffix)?  Next comes a name lcd followed by parentheses with some strange looking numbers separated by commas.  A bit like the parentheses on the function definition we saw earlier, these ones are also holding some parameters.

Remember that the external code library takes lots of complex coding and breaks it out into easier functions for us to use (like my examples 'turn on the backlight' or 'clear the screen')? OK, now what this command is doing is creating a way of linking all those functions to something in the sketch (let's call it an 'object') that we will call lcd, and then telling lcd where my actual lcd is (0x27 is the address of the lcd controller), and how big it is (20 characters, 4 rows).

So now the object we have called lcd has links to all those functions in the library (we call them methods), and also has some parameters that link it to the physical device (we call these attributes).  Once this line has been run, then we can call any of the methods in the external code library, and send them to the lcd like this...

1
2
3
4
5
// turn backlight on
lcd.backlight();

// turn backlight off
lcd.nobacklight();


Phew - that's been quite a bit for me to type, and you to take in - so let's take a break here, and I will post the setup() function in part 3, very soon.

Saturday, 13 May 2017

SK001 - in theory... (pt 1)

At the moment, pending the arrival of my Arduino Uno R3 look-alike from China, this sketch is a theoretical draft or work in progress... it looks good on paper, but who knows if it will actually work!

There are a number of unknowns, such as, will my LM35 temperature sensor (while installed in the module that I salvaged from a burned out UPS) still produce 0-1.5 volts over the range 0-150C, or does that op-amp chip actually change the voltage?  Will it even work at all?  Will the LCD library I found work with the LCD I purchased (I've seen dozen's of forum messages from people unable to get their LCDs to function - which usually comes down to addressing or library compatibility problems).

The sketch has gone through a number of iterations, and evolved somewhat from the original... but the nuts and bolts of the original concept were...
  1. Include LCD library, and declare variables.
  2. Setup
    1. Initialise LCD
    2. Write static information to LCD
  3. Loop
    1. Check temperature - write it to LCD
    2. If it is higher than Max, then replace the value in Max and write to LCD
    3. If it is lower than Min, then replace the value in Min and write to LCD
    4. Wait a few seconds, then go back to start of Loop
Hopefully, this is not too difficult to follow.  Arduino sketches all have this basic structure of three segments.  

The first is where any external code libraries are identified to the sketch.  A code library handles all the complex instructions for a device we want to use (such as the LCD), and breaks them out into simpler functions (like 'turn the backlight on' or 'clear the display') that we can use more easily in our own code.

Variables can also be declared here.  Variables are just named buckets to hold a particular piece of information, like the current temperature, or the highest temperature that has been seen so far.  If you declare a variable here, it can be used by code anywhere in the sketch. 

The next two segments handle the setup of everything that is needed to make your sketch work, and then a repeating loop that runs the actual process itself.  Arduino sketches use a code construct called a 'function' for these two segments.  A function is a named block of code, designed for a specific purpose, that can be called from other sections of code.  The block of code can receive input parameters, and/or return an output value at the end - but both of these are optional, and for these two special functions in every Arduino sketch, neither option is used.

This is how you start to define a function...

1
2
3
output_value function_name(input_parameters) {
   // Your code goes here
}

Because these two special functions have no input parameters, there is nothing between the parentheses, and as there is no output value, then this is noted by using the word 'void'.  So, the two special functions look like this when you first open a new sketch... (in case you hadn't figured it out, the lines starting with // are comments).

Here you can see two functions - one called 'setup' and one called 'loop'.  An Arduino sketch always expects these two functions, even if they have nothing in them!  Setup is run just once at the beginning, and then loop is run.  At the end of all the code in loop, it will 'loop' back round to the start and run all the code again, and again, and... well you get the picture.  Pretty much the only way to stop it, is to pull the plug!

You can write your own functions in addition to setup and loop, but we'll get to that concept a bit later.  Note that variables can also be declared inside a function, but if they are, then they are only available for use within that particular function.

My original code was written with a 16 character by 2 line LCD in mind.  But when trying to fit the current, minimum, and maximum temperatures on the display, each with meaningful labels, I realised that 16 by 2 provides a very limited amount of display real-estate. That was what triggered me to opt for the larger 20 by 4 LCD in my shopping list instead, and that was the first of my many code changes.

I then found how I could improve the accuracy of the mechanism to read the temperature value; how to format the output to 1 decimal place (and include a leading 0 if required); added a button press trigger to turn on the backlight for 10 seconds without interrupting the real-time reading of the sensor; and finally, added a function to calculate an average value over 5 minutes.  This last improvement was done ready for the next phase of my experimentation, which is to use this as a data-logger and either write data to an SD card, or directly back to my PC (where it could be displayed graphically, for example).  I've also found an alternate mechanism to turn the backlight on and off by degrees of brightness - but I don't know if my display will actually support it.

Well - this post has rambled on rather, so I'm going to call it a day for now.  I'll post part 2 in the next day or two, which will include the whole sketch as it exists at his point (remember - it is untested!!), and will break the code down with some explanations of what each bit does - or is intended to do.