/*
Name: HW06_Temperature_Monitoring_Station___blizard.ino
Created: 10/7/2022 9:48:39 AM
Author: nblizard
*/
/*
Objective: Build a temperature monitoring station using
- the I2C backpack with an LCD,
- a thermistor,
- two buttons
that will track and display the current temperature as well as the highest, lowest, and average
since Reset button was pressed.
Operation:
The Scale button should be used to switch between Fahrenheit and Celsius on the display.
HINT: Toggle a flag to remember your current state.
Line 1 of the LCD should always display the current temperature in this format:
“Now: ###.#°F” (or °C depending on the current mode).
Line 2 of the LCD display should cycle between the highest, lowest, and average
temperatures recorded since the Reset button was pressed.
It should display each value for two seconds.
The values should be displayed in the following format:
“Label: ###.#°F” (or °C depending on the current mode),
the “Label” should be either “High”, “Low”, or “Avg”.
You may use a two point moving average to make it simple.
The Reset button should reset the High, Low, and Average readings.
Do not pad any of the numbers with leading zeros to make them fit the proposed format,
just display the number as it is, rounding to the nearest tenth.
Advice: Start by planning out the problem - what steps need to happen in what order.
Plan the code out on paper before you ever touch any hardware.
Then once you have broken down the problem, write your code in incremental milestones.
Setup the hardware, test the hardware with code,
then start adding features.
Add one feature at a time and test frequently after you add each feature.
*/
/*
algorithm
set up variables, pins, flags, lcd
char for degree symbol (223) - do not create, it exists in extended char set
call functions to:
- read button to reset if necessary
- if reset, call reset function to zero avg, high, low tems
- read button to change scale if necessary (toggle state) - remember to reset avg high low
- read current temperature raw data from thermistor
- convert raw reading to a temperature
- check celsius flag, change answer to fahreinheit if false
- use map function with longs, limits are 10x to allow decimal precision (divide by 10 in float)
- average with previous temp reading(s)
- update (pick) highest temp reading
- update (pick) lowest temp reading
- update line 1 of lcd with current temperature
- update line 2 of lcd with either avg, high or low temps (cycling every 2 seconds)
- add diagnostic serial printouts to troubleshoot
- build incrementally and test each function
- repeat loop
functions:
- reset
- scale (f to C or C to F - flag)
- read current temp
- average current temp (either 2 point or 10 point)
- highest temp (compare current vs last highest)
- lowest temp (compare current vs last lowest)
- update lcd line 1
- update lcd line 2 high temp
- update lcd line 2 low temp
- update lcd line 2 avg temp
- 2 seconds persist between line 2 updates
- debounce buttons
Test Cases for thermistor
- ambient room temperature
- ice cube contacting thermistor (put in bag to keep circuit dry!!!)
- thermistor between fingers
- press reset
- press mode (scale)
*/
// I2c library expects SCL = A5; SDA = A4
//create object lcd
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // create lcd object
//
//set up pins and leds
const byte thermistorPin = A0; // thermistor and 10K pulldown are connected to A1
int thermistorReading; // the analog reading from the sensor divider
const byte UPDATE_DISPLAY = 100;
const byte RESET_PIN = 3;
const byte SCALE_PIN = 4;
bool PRESSED = LOW;
const int DEBOUNCE_TIME = 50;
float temp_AVG = 50.; // initialize midrange
float temp_LOW = 100.; // initialize highest possible
float temp_HIGH = 0.; // initialize lowest possible
float temp_LAST = 0.; // prior measured temperature before avg
float temp_CURRENT = 0.; // current measurement
float temp = 0.;
bool testSampling = true; // this is a flag to test NUM_SAMPLES sampling average
float NUM_SAMPLES = 20.; // try this for sampling A0 N times
unsigned long futureTime = 0; // to track time for flipping display from avg to high to low
bool celsius_Flag = false; // true if Celcius, False if Fahrenheit
bool reset_Flag = false; // true if reset pressed
bool serial_Flag = false; // true to get diagnostic output to serial monitor
// measured these values experimentally for map function call in ConvertRawADCToTemperature
// multiply by 10 to retain 1 decimal place, divide by 10 before return
const int ADC_LOW = 465;
const int ADC_HIGH = 605;
const long CELSIUS_LOW = 210.3;
const long CELSIUS_HIGH = 335.9;
const long FAHREINHEIT_LOW = 698.5;
const long FAHREINHEIT_HIGH = 924.6;
const int CHANGE_DISPLAY = 2000; // 2 seconds to change line 2
byte displayIndex = 1; // initialize, limit between 1 and 3 for avg, high, low temp display
void setup() {
//lcd.begin();
lcd.init(); // only on Wokwi
lcd.backlight();
lcd.clear();
lcd.print("HW06 Temperature");
lcd.setCursor(0, 1);
lcd.print(" Monitor");
delay(CHANGE_DISPLAY);
lcd.clear();
Serial.begin(9600);
//initiate pins
pinMode(thermistorPin, INPUT);
pinMode(RESET_PIN, INPUT_PULLUP);
pinMode(SCALE_PIN, INPUT_PULLUP); // connect one side to ground
futureTime = millis() + CHANGE_DISPLAY; // get initial time to flip display
} // end of setup function
void loop(void) {
CheckTimeToFlipDisplay();
CheckIfReset_PB();
CheckIfScale_PB();
thermistorReading = ReadThermistor();
temp_CURRENT = ConvertRawADCToTemp(thermistorReading);
if (serial_Flag) {
printToSerialPort(thermistorReading, temp_CURRENT); // testcode
}
printCurrentTempToLCD(temp_CURRENT);
PickHighestTemperature(temp_CURRENT);
PickLowestTemperature(temp_CURRENT);
PickAverageTemperature(temp_CURRENT);
printAvgHighLowTempToLDC();
debounceDelay(UPDATE_DISPLAY);
} // end of loop function
/*
LCD should display each value for two seconds.
The values should be displayed in the following format:
“Label: ###.#°F” (or °C depending on the current mode),
the “Label” should be either “High”, “Low”, or “Avg”.
You may use a two point moving average to make it simple.
float temp_AVG = 0.;
float temp_LOW = 0.;
float temp_HIGH = 0.;
*/
void printAvgHighLowTempToLDC() {
//lcd.clear();
lcd.setCursor(0, 1);
if (displayIndex == 1) { // index = 1 changes lcd line 2 to HIGH temp output
displayHighTemp();
}
else if (displayIndex == 2) { // index = 2 changes lcd line 2 to LOW temp output
displayLowTemp();
}
else { // index = 3 changes lcd line 2 to AVG temp output
displayAvgTemp();
}
debounceDelay(DEBOUNCE_TIME); // FIXME -- see if this settles display}
} // end of print average high low temperatures to lcd (cycle)
//Line 1 of the LCD should always display the current temperature in this format:
//“Now: ###.#°F” (or °C depending on the current mode).
void printCurrentTempToLCD(float tempValue) {
lcd.setCursor(0, 0);
lcd.print("Now: ");
lcd.print(tempValue, 1); // one place decimal
printCorFPostscript();
debounceDelay(DEBOUNCE_TIME); // FIXME -- see if this settles display
} // end of print current temperature to lcd
void printToSerialPort(int ADCvalue, float tempValue) {
Serial.print("ADC value = ");
Serial.print(ADCvalue);
Serial.print(", Temp = ");
Serial.print(tempValue); //Print the value to the serial port
Serial.print(", celsius_Flag = ");
Serial.println(celsius_Flag);
} // end of print adc to serial port and c or f flag
//The Reset button function should reset the High, Low, and Average readings. TESTED OK
void CheckIfReset_PB() {
if (digitalRead(RESET_PIN) == PRESSED) {
if (celsius_Flag) {
temp_LOW = CELSIUS_HIGH/10.; // remember to divide by 10 while resetting
temp_HIGH = CELSIUS_LOW/10.;
temp_AVG = (temp_LOW + temp_HIGH) / 2.;
}
else {
temp_LOW = FAHREINHEIT_LOW / 10.; // remember to divide by 10 while resetting
temp_HIGH = FAHREINHEIT_LOW / 10.;
temp_AVG = (temp_LOW + temp_HIGH) / 2.;
}
if (serial_Flag) {
Serial.println("RESET PRESSED");
Serial.print("Temp_LOW = ");
Serial.print(temp_LOW);
Serial.print(", Temp_HIGH = ");
Serial.print(temp_HIGH);
Serial.print(", Temp_AVG = ");
Serial.print(temp_AVG);
Serial.print(", celsius_Flag = ");
Serial.println(celsius_Flag);
}
lcd.clear();
lcd.print("RESET PRESSED");
debounceDelay(CHANGE_DISPLAY); //
}
} // end of check reset button
// the Scale button function sets celSius flag to true or false (toggle state)
void CheckIfScale_PB() {
if (digitalRead(SCALE_PIN) == PRESSED) {
celsius_Flag = !celsius_Flag;
if (celsius_Flag) { // mode is Celsius, convert ADC readings to C
if (serial_Flag) {
Serial.println("MODE PRESSED, C");
}
lcd.clear();
lcd.print("MODE PRESSED");
lcd.setCursor(0, 1);
lcd.print("Celsius");
debounceDelay(CHANGE_DISPLAY); //
// reset initial readings to extremes
temp_LOW = CELSIUS_HIGH / 10.; // remember to divide by 10 while resetting
temp_HIGH = CELSIUS_LOW / 10.;
temp_AVG = (temp_LOW + temp_HIGH) / 2.;
}
else { // mode is Fahreinheit, convert ADC readings to F
if (serial_Flag) {
Serial.println("MODE PRESSED, F");
}
lcd.clear();
lcd.print("MODE PRESSED");
lcd.setCursor(0, 1);
lcd.print("Fahrenheit");
debounceDelay(CHANGE_DISPLAY); //
// reset initial readings to extremes
temp_LOW = FAHREINHEIT_HIGH / 10.; // remember to divide by 10 while resetting
temp_HIGH = FAHREINHEIT_LOW / 10.;
temp_AVG = (temp_LOW + temp_HIGH) / 2.;
}
if (serial_Flag) {
Serial.println("SCALE PRESSED");
Serial.print("Temp_LOW = ");
Serial.print(temp_LOW);
Serial.print(", Temp_HIGH = ");
Serial.print(temp_HIGH);
Serial.print(", Temp_AVG = ");
Serial.print(temp_AVG);
Serial.print(", celsius_Flag = ");
Serial.println(celsius_Flag);
}
}
} // end of check scale button
// read thermistor raw value, condition or limit if necessary
int ReadThermistor() {
int thermistorRawData = analogRead(thermistorPin);
return thermistorRawData;
} // end of read thermistor raw data
// note all ADC map conversions are multiplied by factor of 10 since result desired with 1 decimal place
// and map function supports only long integers, not floats
float ConvertRawADCToTemp(int rawADC) {
// if celsiusFlag = true convert ADC to celsius Temperature using map function
long RawADC = rawADC;
if (celsius_Flag) {
temp = map(RawADC, ADC_LOW, ADC_HIGH, CELSIUS_LOW, CELSIUS_HIGH);
}
//if celsiusFlag = false convert ADC to Fahrenheit Temperature using map function
else {
temp = map(rawADC, ADC_LOW, ADC_HIGH, FAHREINHEIT_LOW, FAHREINHEIT_HIGH);
}
return temp / 10.; // divide by 10 to retain precision to 1 decimal place
} // end of Convert Raw ADC to Temperature
// this routine checks the time and changes the flag for displaying avg high low temps
void CheckTimeToFlipDisplay() {
if (millis() > futureTime) {
displayIndex++;
if (displayIndex > 3) {
displayIndex = 1; // reset
}
futureTime = millis() + CHANGE_DISPLAY; // set new futureTime
if (serial_Flag) {
Serial.print("time to increment displayIndes = ");
Serial.println(displayIndex);
}
}
} // end of Check Time to Flip Display (every 2000 seconds
// display High Temp
void displayHighTemp() {
lcd.print("High: ");
lcd.print(temp_HIGH, 1);
printCorFPostscript();
} // end of display High Temp
//display Low Temp
void displayLowTemp() {
lcd.print("Low: ");
lcd.print(temp_LOW, 1);
printCorFPostscript();
} // end of display Low Temp
//display Avg Temp FIXME (MUST BE 2 POINT AVG)
void displayAvgTemp() {
lcd.print("Avg: ");
lcd.print(temp_AVG, 1);
printCorFPostscript();
} // end of displayAvgTemp
void debounceDelay(int delayTime) {
unsigned long futureDelayTime = millis() + delayTime;
while (millis() < futureDelayTime) {
// do nothing
}
} // end of debounceDelay
// this function prints degree symbol and C or F depending on state of celsius Flag
void printCorFPostscript() {
lcd.print((char)223); // prints the degree symbol
if (celsius_Flag) {
lcd.print("C "); // pad with blanks to clear right lcd screen
}
else {
lcd.print("F ");
}
} // end of printC or F Postscript symbol
// function to pick highest temperature
void PickHighestTemperature(float tempMeasured) {
if (tempMeasured > temp_HIGH) {
temp_HIGH = tempMeasured;
if (serial_Flag) {
Serial.println("temp_HIGH updated, = ");
Serial.println(temp_HIGH);
}
}
} // end of PickHighestTemperature
// function to pick lowest temperature
//
void PickLowestTemperature(float tempMeasured) {
if (temp_LOW > tempMeasured) {
temp_LOW = tempMeasured;
if (serial_Flag) {
Serial.println("temp_LOW updated, = ");
Serial.println(temp_LOW);
}
}
} // end of PickLowestTemperature
// this routine picks average temperature
void PickAverageTemperature(float tempMeasured) {
temp_AVG = (temp_LAST + tempMeasured) / 2.;
temp_LAST = tempMeasured;
if (serial_Flag) {
Serial.println("temp_AVG updated, = ");
Serial.println(temp_AVG);
}
// just for fun, do a 8 sampled average like in lecture
if (testSampling) {
long totalSample = 0;
for (byte j = 0; j < NUM_SAMPLES; j++) {
totalSample = ReadThermistor(); // this gets the raw data
}
temp_LAST = temp_AVG;
totalSample = totalSample / NUM_SAMPLES;
ConvertRawADCToTemp(totalSample);
temp_AVG = ConvertRawADCToTemp(thermistorReading);
}
} // end PickAverageTemperature
// end of program