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.

No comments:

Post a Comment