This project was done to showcase the Arduino abilities to students taking part in Futureintech's "Introduction to Arduino".
It consists of an :-
- Analogue Input
- Digital Input
- DS18B20 temperature sensor
- I2C LCD display
- Buzzer
- Relay
- LED (onboard)
All these parts work together to provide a :-
"User settable temperature controlled relay with an audio alert that can be silenced and displays the maximum and minimum temperature".
Trying to find a suitable name was just too difficult but putting it altogether got me BARTLAD
Buzzer
Arduino
Relay
Temperature
LCD
Analogue
Digital
Digital Input
A pushbutton is used to put the software into "Temperature Set" mode which sets the temperature when the alarm will trigger.
The button also cancels the audible alarm, so it has a dual purpose and depends on the length of time it is pressed.
Analogue Input
A potentiometer sets the temperature that the alarm will trigger at.
This is read while in "Temp Set" mode, but is ignored at other times.
DS18B20
This is a Dallas "One Wire" device that communicates with the micro to provide temperature information.
These are extremely accurate and wide ranging, and there is no calibration.
A unique ID number etched during manufacture means you can have several on a single I/O pin.
Buzzer
This makes noise when you provide power to it.
We chose to add tape over the opening as they are rather loud in a classroom.
The buzzer operates at the same time as the relay is triggered, but the noise can be stopped by pressing the button.
Relay
A 5v relay is triggered when the temperature climbs above the "Temp Set" point.
In a real world application this could trigger a cooling fan, warning system or any other switched device.
LED
We use the onboard LED because the kits we provided had only 5 leads.
You could shift the LED pin or drive an external LED in the real world.
I2C LCD
A 2 line 16 character LCD is used to show the actual temperature, set point temperature and the minimum and maximum temperatures.
I also scroll 8 characters to show a message that would otherwise be too long.
The idea was you can make up error code messages, and scroll all of them, and every other guide only shows how to scroll the whole line.
I2C means there are only 4 wires to worry about.
Arduino
For these lessons we use the brick connectors, and these have a Grove style plug and one end, and a three pin female header the other.
The boards have the normal Arduino connectors but they also have three pins that provide Power, Ground and Signal for each of the I/O pins.
For I2C they provide a 4 pin header as well making it neat and easy.
While you can get shields to do similar, why add more and extra cost.
An additional feature of these 3rd party boards is the switchable processor supply so you can have it running 5v or 3v3 and connect 3v3 devices without having level shifters.
When you're doing the development of some projects this makes it really easy.
Software
The software is where everything magic is done.
For this sketch the Loop is nearly all calls to other routines, even the two if statements have calls within them.
Obviously if we are scrolling characters, then delays cannot be used, so I make use of the Millis() timer.
There is built in hysteresis for the ON/OFF of the relay to prevent chattering when the temperature is right on the trigger point.
The software includes a feature so the user can check the devices are connected as it starts.
Each of the outputs is turned on briefly and the display provides some information, regardless of the other connections.
The display scrolling is achieved by taking the first 8 characters of the message and displaying them, the pointer is moved one character forward in the message and the next 8 characters are displayed, this repeats until the pointer reaches the end of the message and it starts again at the beginning.
The result is the appearance of a scrolling message.
This technique can be used on messages of any size, but common sense suggests if it's too long the reader may forget the first part of the message.
The speed of the scrolling is able to be configured as I found that Blue/White LCDs tend to ghost if they are scrolled too fast.
My theory is the white is where the pixel is Off and the other pixels are ON so it takes longer, whereas the Black is an ON pixel that blocks the light.
The rest of the display shows the minimum and maximum temperature, along with the current temperature.
As the temperature can be a single digit, formatting is done to ensure no remnants of previous figures remain.
I'm sure there may be a better method however it worked, could be understood by students, and the output was right.
Debouncing of the switch is done in software as these are not high quality switches, and it allows the long press detection used to put it in "Temp Set" mode.
Many of our modern devices use a long press for another function, so the concept is simple to understand.
Code
There are plenty of comments, and I've tried to make it simple to suit the students using it.
The connections are detailed so you don't need to hunt for another document.
I strongly recommend that you adopt this trick as it makes it very easy later when you come back to using it again.
/* LCD Display of max and min temperatures using an I2C display. Allows setting of the Temp Trigger between TempMin and TempMax using a pot. DS18B20 Temperature chip on pin 2 4k7 pullup between pin 2 and +5v. Reads temp and records the highest and lowest readings. Assumes a read time of 1sec, and goes back to loop to wait for the 1 sec to be up before reading the data. This allows the sketch to do other time critical tasks rather than using delay(1000) which holds up other process'es. Uses a 16x2 I2C LCD Backlight Display The LCD Connections are: GND to GND VCC to +5v SDA to A4 (Arduino) SCL to A5 (Arduino) Arduino Pin assignments Pin D0 Rx Pin D1 Tx Pin D2 Temp sensor DS18B20 data pin with 4k7 external pullup resistor to +5v Pin D3 Buzzer (Low to operate) Pin D4 Pin D5 Pin D6 Pin D7 Pin D8 Pin D9 Pin D10 Pin D11 Relay (High to operate) Pin D12 Pushbutton (with onboard pullup) Pin D13 On board Led Pin A0 Rotary sensor (pot) Pin A4 (SDA) to SDA Pin A5 (SCL) to SCL Created by Mark Beckett References: ------------------------------------------------ http://www.arduino.cc/en/Tutorial/LiquidCrystal ------------------------------------------------ Version 0.1 Initial Code started Apr 2014 (using previous code) 0.2 May 2014 Tidy outstanding issues --------------------------------------------------- To Do : */ // include the library code: #include <liquidcrystal_i2c.h> #include #include #include // initialize the library with the numbers of the interface pins LiquidCrystal_I2C lcd(0x27,16,2); // set the LCD address to 0x27 for a 16 chars and 2 line display //inputs const int TempSensor = 2; // Tempsensor name const int Pushbutton = 12; // Pushbutton const int RotarySensor = A0; // RotarySensor (Range = 0-1023) //Outputs const int Buzzer = 3; // Pin 3 Buzzer Output const int Relay = 11; // Pin 11 Relay Output const int LED = 13; // Pin 13 On Board LED // DS18B20 Temperature chip i/o OneWire ds(TempSensor); // TempSensor on pin 2 byte i; byte present = 0; byte data[12]; byte addr[8]; word TReading; byte HighByte, LowByte; int SignBit, Tc_dec, Tc_100, Whole, Fract; int HWhole, HFract; //High reading int LWhole, LFract; //Low reading boolean Sign, LSign, HSign; //sign for the readings 1= positive long ReadStartTime; long ConvTime = 1000; //Time for a conversion to take place at 9 bit resolution boolean ConvIssued = false; //Signals a reading has been started // General Purpose boolean FirstReading = true; // Flag used to set temps to the first reading int TempReading; // Temp reading as + or - figure int TempMin = 10; // min temp you can set using the rotary sensor int TempMax = 40; // max temp you can set using the rotary sensor //LCD Scrolling message char LCDMessage[8] ; // 7 char (0-6) scrolling message plus a null. byte MessageStartPosition =0; byte MessageStringCount; byte LCDPosition =0; char MessageString[] = "Current Temperature "; // message to be scrolled int ScrollDelay = 300; // Delay for the char scrolling. long LastScroll =0; // timer used in the delay //Settings int HighTempOn = 29; // High Temperature On setting int HighTempOff = (HighTempOn -3); // High Temperature Off setting (4 deg below the HighTempOn) boolean SetMode = LOW; // SetMode goes HIGH when setting temp boolean BuzzerMode = LOW; // Buzzer Mode is ON, Goes HIGH to turn OFF // Button handling variables boolean ButtonState = HIGH; // records the Button State unsigned long LastButtonCheck = 0; // Time the Buttons were last checked. unsigned long ButtonPressTime = 0; // Time the last button was pressed. int ButtonCount = 0; // Button counter int ButtonTimeout = 10000; // Timeout after button is pressed. (10 secs) //------------------------------------------------ void setup() { // set up the LCD's number of rows and columns: lcd.init(); // initialize the lcd Wire.begin(); // for the One Wire device //Serial.begin(57600); // remove the '//' at the start to output to serial lcd.clear(); lcd.noCursor(); // Tell the library not to flash the cursor lcd.backlight(); // Turn on the backlight //Define the pins pinMode(TempSensor, INPUT); pinMode(Pushbutton, INPUT); pinMode(RotarySensor, INPUT); pinMode(Buzzer, OUTPUT); digitalWrite(Buzzer, HIGH); // Turn OFF the Buzzer pinMode(Relay, OUTPUT); digitalWrite(Relay, LOW); // Turn OFF the Relay pinMode(LED, OUTPUT); digitalWrite(LED, LOW); // Turn OFF the LED //digitalWrite(LCDBack, HIGH); // Print a message to the LCD. lcd.setCursor(0,0); // First position (0) and first line (0) lcd.print (" Initialising"); lcd.setCursor(0,1); // First position (0) and second line (1) lcd.print (" M.Beckett 2014"); delay(1800); // check the outputs digitalWrite(Buzzer, LOW); digitalWrite(Relay, HIGH); digitalWrite(LED, HIGH); delay(200); digitalWrite(Buzzer, HIGH); digitalWrite(Relay, LOW); digitalWrite(LED, LOW); findDevices(); // Call the routine to check if there are any One Wire devices LastScroll = millis(); // check the time for the LCD message to be seen ReadTemp(); delay(5000-((millis())- (LastScroll))); // this should end up as 1.5 secs lcd.clear(); LastScroll=0; LCDScrollingMessage(); } void loop() /* This runs after setup and continuously loops or runs Code can be entered here, or as in this example a means of calling the other routines. */ { ReadTemp(); // Sets ConvIssued. Compares millis() with [ReadStartTime] and [ConvTime] before collecting the reading. Check_Buttons(); // Check for a button press (it will call the ProcessButton routine) RelayControl(); // Turn Relay on/off LEDControl(); // Turn LED on/off BuzzerControl(); // Turn the Buzzer on/off DisplayTemp(); // Display the current temperature if (SetMode == HIGH) // change the Temp setting { Read_RotarySensor(); // Read the potentiometer value DisplaySetTemp(); // Display the set points if ((millis()-ButtonPressTime) > ButtonTimeout) { SetMode = LOW; // SetMode is 'unset' since the button was pressed long ago lcd.clear(); // clear the message that was bigger than 7 characters } } else { MaxMinUpdate(); // Update the High and Low temperature HighLowTemp(); // Display the High and Low temperatures DisplayMessage(); // Display the scrolling message LCDScrollingMessage(); // The construct for the message } } void ReadTemp() { /* The process of reading the temp involves telling the device to process a reading There is a setup time between the command and the data being available at the register. This depends on the resolution, but with the 9 bit resolution it is approx 750mS. Rather than slow everything down, we issue the command and then return. When we come back, we check if it has been (long ConvTime = 1000 in mS) between command and current. If it has we read the register. */ if (ConvIssued != true) { ds.reset(); ds.select(addr); ds.write(0x44,1); // start conversion ConvIssued = true; // We have asked for data ReadStartTime = millis(); // note the time we asked for temperature (so we know when to come back) } if (ConvIssued && (millis() - ReadStartTime) >= ConvTime) { /* This is the delay....The program should do the reset, and come back when the time is up. */ present = ds.reset(); ds.select(addr); ds.write(0xBE); // Read Scratchpad } else { return; //either there was no conversion issued, or time is too soon. } for ( i = 0; i < 9; i++) // we need 9 bytes { data[i] = ds.read(); } LowByte = data[0]; HighByte = data[1]; TReading = (HighByte << 8) + LowByte; SignBit = TReading & 0x8000; // test most sig bit if (SignBit) // negative { Sign = 1; TReading = (TReading ^ 0xffff) + 1; // 2's comp } Tc_100 = (6 * TReading) + TReading / 4; // multiply by (100 * 0.0625) or 6.25 if (SignBit) // negative { TempReading = (0 - Tc_100); } else { TempReading = Tc_100; } Whole = Tc_100 / 100; // separate off the whole and fractional portions Fract = Tc_100 % 100; // the two lines below force the fraction portion to one (1) decimal place ie 23.7 rather than 23.73 Fract =Fract /10; Fract = Fract *10; ConvIssued = false; // means we got a reading back } void MaxMinUpdate() { /* Update the Max and Min settings using the last reading If the temp is negative, then the comparison flips More work required on the Highest reading if the previous High was in negative figures, and it goes above zero. Perhaps converting the High/Low temp storage to use the sign int, rather than a two part figure. */ if (FirstReading == true && Tc_100 != 0000) //Temp is negative ; make LWhole and LFrac into 4 digit number { //Serial.print (Whole,DEC); // if you uncomment this it will go to the serial monitor //Serial.print (Fract, DEC); // if you uncomment this it will go to the serial monitor LWhole = Whole; LFract = Fract; LSign = SignBit; HWhole = Whole; HFract = Fract; HSign = SignBit; FirstReading = false; } if (SignBit) //negative { if (Tc_100 > ((LWhole *100) + LFract)) { LWhole = Whole; LFract = Fract; LSign = SignBit; } if (Tc_100 < ((HWhole *100) + HFract)) { HWhole = Whole; HFract = Fract; HSign = SignBit; } } if (SignBit == false) //positive { if (Tc_100 < ((LWhole *100) + LFract)) { LWhole = Whole; LFract = Fract; LSign = SignBit; } if (Tc_100 > ((HWhole *100) + HFract)) { HWhole = Whole; HFract = Fract; HSign = SignBit; } } } void HighLowTemp() /* This displays the highest and lowest temp Displays over two lines | L -xx.x| |-xx.xx H -xx.x| */ { // Top line lcd.setCursor(10,0); lcd.print("L"); // first position (10) and first line (0) displays | Temp L -xx.x| lcd.setCursor(11,0); // 12th position (11) and first line (0) if (LSign) { lcd.print("-"); } else { lcd.print(" "); } lcd.setCursor(12,0); // 13th position (12) and first line (0) if (LWhole <10) { lcd.print(" "); } lcd.print(LWhole); lcd.setCursor(14,0); // 15th position (14) and first line (0) lcd.print("."); lcd.setCursor(15,0); // 16th position (15) and first line (0) if (LFract <10) { lcd.print("0"); } // Bottom line lcd.print(LFract); lcd.setCursor(10,1); // 11th position (10) and second line (1) lcd.print("H "); lcd.setCursor(11,1); // 12th position (11) and second line (1) if (HSign) { lcd.print("-"); } else { lcd.print(" "); } lcd.setCursor(12,1); // 13th position (12) and second line (1) if (HWhole <10) { lcd.print(" "); } lcd.print(HWhole); lcd.setCursor(14,1); // 15th position (14) and second line (1) lcd.print("."); lcd.setCursor(15,1); // 16th position (15) and second line (1) if (HFract <10) { lcd.print("0"); } lcd.print(HFract); } void DisplayTemp() // This displays the current temp { lcd.setCursor(0,1); // First position (0) and second line (1) if (Sign) { lcd.print("-"); } else { lcd.print(" "); } lcd.setCursor(1,1); // 2nd position (1) and second line (1) if (Whole <10) { lcd.print(" "); } lcd.print(Whole); lcd.setCursor(3,1); // 4th position (3) and second line (1) lcd.print("."); lcd.setCursor(4,1); // 5th position (4) and second line (1) if (Fract <10) { lcd.print("0"); } lcd.print(Fract); } void DisplayMessage() // This displays the 7 chars of the message on the first line { lcd.setCursor(0,0); lcd.print(LCDMessage); } void RelayControl() { //Relays are ON when the ouput pin is HIGH unless we are setting the temp using the rotarysensor if (SetMode == LOW) // only control the Relay when we aren't setting temp { if (Whole >= HighTempOn) // reading is greater or equal to the ON setting { digitalWrite(Relay,HIGH); } if (Whole <= HighTempOff) // reading is lower or equal to the OFF setting { digitalWrite(Relay,LOW); } } else { digitalWrite(Relay,LOW); } } void LEDControl() { // This turns on the LED if (Whole >= HighTempOn) // reading is greater or equal to the ON setting { digitalWrite(LED,HIGH); // LED on } if (Whole <= HighTempOff) // reading is lower or equal to the OFF setting { digitalWrite(LED,LOW); // LED off } } void BuzzerControl() { //Buzzer is ON when the ouput pin is LOW unless we are setting the temp using the rotary sensor if (SetMode == LOW) // only control the Buzzer when we aren't setting temp { if (Whole >= HighTempOn) // reading is greater or equal to the ON setting { digitalWrite(Buzzer,LOW); // Buzzer is ON } if (Whole <= HighTempOff) // reading is lower or equal to the OFF setting { digitalWrite(Buzzer,HIGH); // Buzzer is OFF BuzzerMode = LOW; // Reset the Buzzer mode as the temp is below the OFF setting } } else { digitalWrite(Buzzer,HIGH); // Buzzer is OFF } if (BuzzerMode == HIGH) // turn off the buzzer as the button was pressed to silence it. { digitalWrite(Buzzer,HIGH); // Buzzer is OFF } } void Check_Buttons() /* This routine checks the timer (LastButtonCheck) and if necessary reads the button input If the button is detected, it waits until it counts it 5 times at the set time rate. This prevents button bounce from triggering and ensures the button is pressed. Once a valid button press has been detected, it starts a timer (ButtonPressTime) */ { if (millis() - LastButtonCheck > 5) // Reads button state every 5mS and then updates button counts { ButtonState = digitalRead(Pushbutton); LastButtonCheck = millis(); if (ButtonState == LOW) { ButtonCount ++; // Increment the Count by 1 if (ButtonCount > 5) // the button should be LOW for 5x5mS = 25mS { // Button has been pressed for longer than 25mS so its valid ButtonPressTime = millis(); // set the Button press timer ButtonCount = 0; ProcessButton(); // Do something with the valid button press } } else // Button is HIGH { ButtonCount =0; // Reset the counter as the button has been released } } } void ProcessButton() // this processes what the valid button press does. Called from Check_Buttons { if (BuzzerMode == LOW && (digitalRead(Buzzer)) == LOW) //Buzzer is ON { BuzzerMode = HIGH; return; } if (SetMode == LOW) { SetMode = HIGH; // SetMode is 'set' since the button press was valid } } void Read_RotarySensor() { if (SetMode == HIGH) // Reads the rotary position sensor { int val = analogRead(RotarySensor); HighTempOn = map(val, 0, 1023, TempMin, TempMax); // AnalogRead is between 0 and 1023, we need to set it between the HighTempon and HighTempOff HighTempOff = (HighTempOn -3); // The -3 means the Low Temp will be 3 degrees lower than the set temperature } } void LCDScrollingMessage() { /* This demonstrates how to scroll a portion of the LCD The basic principle is to construct a message, find the length of the message. You then grab xx chars from the message to display. Each time you loop, you shift along the message one place and grab xx chars, and repeat. When you reach the end of the message, start grabbing from the beginning. A space at the end of the message, makes the message look correct on the LCD as it loops. Don't let the loop run too fast. */ MessageStringCount = (sizeof(MessageString))-1; // Last character in a string is a null, which is counted. Serial.println(MessageStringCount,DEC); if ((millis()-LastScroll) >=ScrollDelay) { for (i = 0; i <7; i++) //there is room for 7 chars (i = 0 to 6) { LCDPosition = MessageStartPosition +i; if (LCDPosition >= MessageStringCount) { LCDPosition = LCDPosition - MessageStringCount; } LCDMessage[i] = MessageString[LCDPosition]; } LCDMessage[7] = 0; //terminate the string with a null MessageStartPosition +=1; if (MessageStartPosition >= MessageStringCount) { MessageStartPosition =0; } LastScroll = millis(); } } void DisplaySetTemp() /* This shows the highest and lowest temp Displays over two lines *** Counting starts at zero *** | L -xx.x| |-xx.xx H -xx.x| */ { lcd.setCursor(10,0); // first position (10) and first line (0) lcd.print("L"); // displays | Temp L -xx.x| lcd.setCursor(11,0); // 12th position (11) and first line (0) lcd.print(" "); lcd.setCursor(12,0); // 13th position (12) and first line (0) if (HighTempOff <10) { lcd.print(" "); } lcd.print(HighTempOff); // variable HighTempOff lcd.setCursor(14,0); // 15th position (14) and first line (0) lcd.print("."); lcd.print("0"); lcd.setCursor(10,1); lcd.print("H "); lcd.setCursor(11,1); lcd.print(" "); lcd.setCursor(12,1); // 13th position (12) and second line (1) lcd.print(HighTempOn); // variable HighTempOn lcd.setCursor(14,1); // 15th position (14) and second line (1) lcd.print("."); lcd.print("0"); // this displays the LCD message which is 7 characters long lcd.setCursor(0,0); lcd.print("*SetTemp*"); } void findDevices() { /* This routine is called once to find any One Wire devices and obtain their address. Once the address is found it is checked to to see if it is a Temperature Sensor (hex 28 or 0x28) */ if ( !ds.search(addr)) { ds.reset_search(); } if ( addr[0] != 0x28) { lcd.clear(); lcd.setCursor(0,0); lcd.print(" No Temp Sensor"); lcd.setCursor(0,1); lcd.print("Check connection"); delay(5000); return; } }
Video
The video shows the sequence from initial start to reading the ambient temperature.
The long press allows the trigger point to be set and shows the setting in the display.
The final portion shows when the temperature set point is exceeded and the relay and buzzer are triggered.
Pressing the pushbutton stops the noise, but leaves the LED and Relay ON.
I hope this helps demonstrate a few techniques and methods that can be used or adapted.
We use the "brick' style sensors from ITEAD studios as they are cost effective for the numbers we needed.
It removed any incorrect connection problems, but it doesn't stop the students plugging them in the wrong way or the wrong pin.
Cheers
Mark
Top Comments