When using a sensor with an Arduino board there are many ways to communicate measurements such as printing to a serial monitor, displaying data on an LCD screen, shining a light, or making a sound to name a few. But if multiple sensors are used in tandem an aggregate reading or a summary, might be most useful. This tutorial will walk through how to take multiple readings, aggregate them, and display a simple result on an LCD.
This setup has many applications, but this tutorial focuses on ideal environmental conditions for saplings and young plants. With that in mind, temperature and humidity will be used as inputs, but the same approach could work with any other sensor. Furthermore, this tutorial is part of a broader project that will utilize several more sensors measuring soil moisture, particulate matter, and soil pH among others, but for the sake of this post only humidity and temperature will be used.
In the first section, measurements will print directly to the LCD
screen. Then, the second section will walk through the code to print a
summary or aggregate using the same wiring. Finally, the third section will walkthrough custom characters as an alternative method for displaying an aggregate.
Section 1 – Printing Measurement to an LCD
Parts
- 1 x Arduino Uno Board
- 1 x 830 tie-points Breadboard
- 1 x LCD1602 modeul
- 1 x Potentiometer (10k)
- 16 x M-M wires
- 1 x DHT11 Temperature and Humidity module
- 3 x F-M wires
Wiring
The LCD1602 module has 16 pins and must be wired carefully. Orienting the LCD with the pins on top, from left to right:
- VSS: Connects to the ground
- VDD: Connects to a power supply (+5V)
- VO: Controls the contrast of the LCD. This is connected to the potentiometer.
- RS: Register Select, which connects to pin 7 in this tutorial and determines where the LCD memory is written.
- RW: Read/Write that selects read or writing mode, connected to the ground here.
- E: Enable, which directs the LCD module. Connected to pin 8.
- D0-7: Pins that read and write data. In this tutorial D4-D7 will be connected to pins 9-12 of the board.
- A: Controls the backlight, connected to the power supply.
- K: Controls the backlight, connected to the ground.
The DHT11 Temperature and Humidity module must also be wired carefully. It has three relevant pins from left to right:
- VDD: Connects to a power supply (3.5~5.5 V)
- DATA: Connects to a pin on the Ardunio, in this case pin 2
- NC: An empty pin not used in this tutorial and not included on all models of the DHT11
- GND: Ground
Code
This sketch is quite simple. It just takes a measurement using the DHT and prints it directly to the LCD. This utilizes the DHT library, which can be downloaded here.
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 |
/* DHT print measurements to LCD created 29 March 2022 by Ben Aiken Measure temperature and humidity using an DHT sensor. Print measurements on an LCD screen. This project extends ELEGOO Lessons 11 and 14 */ // Libraries #include <LiquidCrystal.h> #include <dht.h> // Define variables and pins LiquidCrystal lcd(7, 8, 9, 10, 11, 12); // LCD pins: BS E D4 D5 D6 D7 dht DHT; // set DHT abbreviation int DHT11_PIN = 2; void setup() { // Initialize LCD lcd.begin(16, 2); } void loop() { // Connect pins int chk = DHT.read11(DHT11_PIN); //Display Temperature lcd.setCursor(0, 0); lcd.print("Temp: C "); lcd.setCursor(6, 0); lcd.print(DHT.temperature); //Display Humidity lcd.setCursor(0,1); lcd.print("Humidity: %"); lcd.setCursor(10, 1); lcd.print(DHT.humidity); delay(500); } |
Section 2 – Calculating an aggregate and displaying it on an LCD
Next, we will adjust the code, but use the same components and wiring in order to display an aggregate.
As mentioned above, this Arduino setup is to determine how suitable an environment is for a plant that requires a temperate climate. The following tables show the ideal temperature and humidity for a sapling.
Temperature | Condition |
<40°F | Dangerously Cold |
40-59°F | Colder than optimal |
60-79°F | Ideal |
80-90°F | Hotter than optimal |
>90°F | Dangerously hot |
Humidity | Condition |
0-39% | Too dry |
40-69% | Ideal |
70-100% | Too moist |
It might be most helpful to have an LCD readout that simply says how suitable the environment is based on the given conditions. With that in mind, these variables are combined to form a health score out of 100.
In this example we will assume that temperature and humidity are equally important; therefore, we will calculate their sub-scores out of 50 such that when totaled the health score will be out of 100. If a third measure was included that was also considered of equal importance each sub-score would be out of 33, a fourth equally important measure would make each sub-score out of 25 and so on. If, however, sunlight, for example, is twice as important as temperature and humidity the weights would be adjusted such that sunlight accounts for half or 50 points, and the other two would be out of 25 each. Clearly, the formula is dependent on what you’re measuring and what you consider most important.
Code
The following code replaces the print outs of the temperature and humidity readings with a sentence stem “This environment is”. Then an if statement determines which word completes the sentence: ideal, decent, poor, or dangerous, depending on the health score. The health score is calculated using the following steps:
- Since 65 degrees is the optimal temperature in this example, any measurement over 65 is subtracted from 130 in order to make the measurements linear from dangerous to ideal.
- Similarly, optimal humidity is 55%, so all measurements over 55 are subtracted from 110.
- Both are constrained from 0 to 65 for temperature and 0 to 55 for humidity to eliminate any negative values.
- Then the resulting values are mapped from 0 to 50 and totaled to determine the health score.
Like the temperature and humidity ranges, the health score categories are somewhat arbitrary for the sake of this tutorial.
- Ideal: health score > 50
- Decent: 25 < health score ≤ 50
- Poor: 10 < health score ≤ 25
- Dangerous: health core ≤ 10
For an actual application of this device the categories would be much more intentional.
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 |
/* DHT print statement to LCD created 1 April 2022 by Ben Aiken Measure temperature and humidity using an DHT sensor. Develop a plant health score based on the temp and humidity and the optimal conditions of a plant. Categorize the score and print the category as a statement on an LCD screen. This project extends ELEGOO Lessons 11 and 14 */ // Libraries #include <LiquidCrystal.h> #include <dht.h> // Define variables and pins LiquidCrystal lcd(7, 8, 9, 10, 11, 12); // LCD pins: BS E D4 D5 D6 D7 dht DHT; // set DHT abbreviation int DHT11_PIN = 2; void setup() { // Initialize LCD lcd.begin(16, 2); } void loop() { // Connect pins and define variable types int chk = DHT.read11(DHT11_PIN); float temp_score; float humidity_score; float health_score; //Define temp_score if(DHT.temperature <= 65){ temp_score = DHT.temperature; } if(DHT.temperature > 65){ temp_score = (130 - DHT.temperature); } temp_score = constrain(temp_score, 0, 65); //eliminate negative values temp_score = map(temp_score, 0, 65, 0, 50); //adjust range to 0-50 //Define humidity score if(DHT.humidity <= 55){ humidity_score = DHT.humidity; } if(DHT.humidity > 55){ humidity_score = (110 - DHT.humidity); } humidity_score = constrain(humidity_score, 0, 55); //eliminate negative values humidity_score = map(humidity_score, 0, 55, 0, 50); //adjust range to 0-50 //Define health score health_score = temp_score + humidity_score; //Print sentence stem lcd.setCursor(0, 0); lcd.print("This environment"); lcd.setCursor(0, 1); lcd.print("is "); //Print condition word based on score lcd.setCursor(4, 1); if(health_score > 50){ lcd.print("ideal."); } if(health_score > 25 && health_score <= 50){ lcd.print("decent."); } if(health_score > 10 && health_score <= 25){ lcd.print("poor."); } if(health_score <= 10){ lcd.print("dangerous."); } delay(500); } |
Example
Section 3 – Using custom characters to display an aggregate
Finally, for a fun additional feature a face will be printed on the LCD
screen that represents the condition. Ultimately this device, with several more sensors, is intended to be mounted publicly to show the health of a young tree. In order to make the display accessible, the face will communicate with children and any non-english readers. Plus, a face is a bit more fun than a simple sentence.
Code
This sketch is far more complex than the first two sections since creating custom characters take quite a few lines of code. The first addition to this sketch from the last, are the byte definitions which write the shape of the custom character for the faces.
The faces consist of a several custom characters displayed consecutively on the LCD. The custom characters are created using the generator here. Essentially, each space on the LCD is an 8×5 array, and the byte definitions are arrays of eight strings – one for each row – of 0s or 1s. 0 keeps the cell off and 1 turns the cell on.
Since the LCD memory can only handle 8 custom characters at a time, the bytes are defined globally, but the characters are created in separate functions and a customclear function is necessary to wipe the memory of the LCD.
The health score is calculated just the same as in section 2, but instead of printing the sentence immediately, an if statement runs separate functions for each condition category.
Each condition category creates the custom characters based on the byte definitions and prints the face. The face is displayed for eight seconds before the sentence appears for two seconds. The following faces are used for each category:
- Ideal (health score > 50): Smile
- Decent (25 < health score ≤ 50): Indifferent (flat mouth)
- Poor (10 < health score ≤ 25): Frown
- Dangerous (health core ≤ 10): Frown and X eyes
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 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
/* DHT print to LCD, face and statement created 2 April 2022 by Ben Aiken Measure temperature and humidity using an DHT sensor. Develop a plant health score based on the temp and humidity and the optimal conditions of a plant. Categorize the score and print the category as a face and a statement on an LCD screen. This project extends ELEGOO Lessons 11 and 14 and utilizes the LCD custom character generator - https://maxpromer.github.io/LCD-Character-Creator/, the custom character walkthrough - https://minov.in/how-to-create-custom-character-for-lcd/, and the custom character clearing function - https://forum.arduino.cc/t/why-the-8-character-limit-with-liquidcrystal/48650/4. */ // Libraries #include <LiquidCrystal.h> #include <dht.h> // Define variables and pins LiquidCrystal lcd(7, 8, 9, 10, 11, 12); // LCD pins: BS E D4 D5 D6 D7 dht DHT; // set DHT abbreviation int DHT11_PIN = 2; int face_time = 8000; // Time face is displayed int sentence_time = 2000; // Time statentment is displayed // Define eye byte arrangements (X is for the dangerous condition) byte eye[8] = {B01110, B11111, B11111, B11111, B11111, B01110, B00000, B00000}; byte X[8] = {B10001, B11011, B01110, B01110, B11011, B10001, B00000, B00000}; // Define mouth byte arrangements byte smile1[8] = {B00000, B00000, B00100, B00110, B00011, B00001, B00000, B00000}; byte smile2[8] = {B00000, B00000, B00000, B00000, B00000, B10000, B11111, B01111}; byte smile3[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B11111, B11111}; byte smile4[8] = {B00000, B00000, B00000, B00000, B00000, B00001, B11111, B11110}; byte smile5[8] = {B00000, B00000, B00100, B01100, B11000, B10000, B00000, B00000}; byte flat[8] = {B00000, B00000, B00000, B00000, B00000, B11111, B11111, B00000}; byte frown1[8] = {B00000, B00000, B00000, B00000, B00001, B00011, B00110, B00100}; byte frown2[8] = {B00000, B00000, B01111, B11111, B10000, B00000, B00000, B00000}; byte frown3[8] = {B00000, B00000, B11111, B11111, B00000, B00000, B00000, B00000}; byte frown4[8] = {B00000, B00000, B11110, B11111, B00001, B00000, B00000, B00000}; byte frown5[8] = {B00000, B00000, B00000, B00000, B10000, B11000, B01100, B00100}; void setup() { // Initialize LCD lcd.begin(16, 2); } void loop() { // Connect pins and define variable types int chk = DHT.read11(DHT11_PIN); float temp_score; float humidity_score; float health_score; // Define temp_score if(DHT.temperature <= 65){ temp_score = DHT.temperature; } if(DHT.temperature > 65){ temp_score = (130 - DHT.temperature); } temp_score = constrain(temp_score, 0, 65); //eliminate negative values temp_score = map(temp_score, 0, 65, 0, 50); //adjust range to 0-50 // Define humidity score if(DHT.humidity <= 55){ humidity_score = DHT.humidity; } if(DHT.humidity > 55){ humidity_score = (110 - DHT.humidity); } humidity_score = constrain(humidity_score, 0, 55); //eliminate negative values humidity_score = map(humidity_score, 0, 55, 0, 50); //adjust range to 0-50 // Define health score health_score = temp_score + humidity_score; // Print condition based on score if(health_score > 50){ ideal(); } if(health_score > 25 && health_score <= 50){ decent(); } if(health_score > 10 && health_score <= 25){ poor(); } if(health_score <= 10){ dangerous(); } } void ideal() { // LCD only has memory for 8 custom character, must wipe and re-write memory for each face customclear(); // Create custom chracters lcd.createChar(0, eye); lcd.createChar(1, smile1); lcd.createChar(2, smile2); lcd.createChar(3, smile3); lcd.createChar(4, smile4); lcd.createChar(5, smile5); // Write smile face characters lcd.setCursor(6, 0); lcd.write(byte(0)); lcd.setCursor(9, 0); lcd.write(byte(0)); lcd.setCursor(5, 1); lcd.write(byte(1)); lcd.setCursor(6, 1); lcd.write(byte(2)); lcd.setCursor(7, 1); lcd.write(byte(3)); lcd.setCursor(8, 1); lcd.write(byte(3)); lcd.setCursor(9, 1); lcd.write(byte(4)); lcd.setCursor(10, 1); lcd.write(byte(5)); delay(face_time); lcd.clear(); // Print ideal sentence lcd.setCursor(0, 0); lcd.print("This environment"); lcd.setCursor(0, 1); lcd.print("is "); lcd.setCursor(4, 1); lcd.print("ideal."); delay(sentence_time); lcd.clear(); } void decent() { // LCD only has memory for 8 custom character, must wipe and re-write memory for each face customclear(); // Create custom chracters lcd.createChar(0, eye); lcd.createChar(1, flat); // Write flat face characters lcd.setCursor(6, 0); lcd.write(byte(0)); lcd.setCursor(9, 0); lcd.write(byte(0)); lcd.setCursor(5, 1); lcd.write(byte(1)); lcd.setCursor(6, 1); lcd.write(byte(1)); lcd.setCursor(7, 1); lcd.write(byte(1)); lcd.setCursor(8, 1); lcd.write(byte(1)); lcd.setCursor(9, 1); lcd.write(byte(1)); lcd.setCursor(10, 1); lcd.write(byte(1)); delay(face_time); lcd.clear(); // Print decent sentence lcd.setCursor(0, 0); lcd.print("This environment"); lcd.setCursor(0, 1); lcd.print("is "); lcd.setCursor(4, 1); lcd.print("decent."); delay(sentence_time); lcd.clear(); } void poor() { // LCD only has memory for 8 custom character, must wipe and re-write memory for each face customclear(); // Create custom chracters lcd.createChar(0, eye); lcd.createChar(1, frown1); lcd.createChar(2, frown2); lcd.createChar(3, frown3); lcd.createChar(4, frown4); lcd.createChar(5, frown5); // Write frown face characters lcd.setCursor(6, 0); lcd.write(byte(0)); lcd.setCursor(9, 0); lcd.write(byte(0)); lcd.setCursor(5, 1); lcd.write(byte(1)); lcd.setCursor(6, 1); lcd.write(byte(2)); lcd.setCursor(7, 1); lcd.write(byte(3)); lcd.setCursor(8, 1); lcd.write(byte(3)); lcd.setCursor(9, 1); lcd.write(byte(4)); lcd.setCursor(10, 1); lcd.write(byte(5)); delay(face_time); lcd.clear(); // Print poor sentence lcd.setCursor(0, 0); lcd.print("This environment"); lcd.setCursor(0, 1); lcd.print("is "); lcd.setCursor(4, 1); lcd.print("poor."); delay(sentence_time); lcd.clear(); } void dangerous() { // LCD only has memory for 8 custom character, must wipe and re-write memory for each face customclear(); // Create custom chracters lcd.createChar(0, X); lcd.createChar(1, frown1); lcd.createChar(2, frown2); lcd.createChar(3, frown3); lcd.createChar(4, frown4); lcd.createChar(5, frown5); // Write frown face characters with X eyes lcd.setCursor(6, 0); lcd.write(byte(0)); lcd.setCursor(9, 0); lcd.write(byte(0)); lcd.setCursor(5, 1); lcd.write(byte(1)); lcd.setCursor(6, 1); lcd.write(byte(2)); lcd.setCursor(7, 1); lcd.write(byte(3)); lcd.setCursor(8, 1); lcd.write(byte(3)); lcd.setCursor(9, 1); lcd.write(byte(4)); lcd.setCursor(10, 1); lcd.write(byte(5)); delay(face_time); lcd.clear(); // Print dangerous sentence lcd.setCursor(0, 0); lcd.print("This environment"); lcd.setCursor(0, 1); lcd.print("is "); lcd.setCursor(4, 1); lcd.print("dangerous."); delay(sentence_time); lcd.clear(); } void customclear() { // clears all custom chracter slots so new chracters can be defined byte blank[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B00000, B00000}; for(int i = 0; i < 8; i++) { lcd.createChar(i, blank); } } |
Example
References
- ELEGOO Lesson 11 – DHT11 Temperature and Humidity Sensor
- ELEGOO Lesson 14 – LCD Display
- DHT Sensor library – https://www.circuitbasics.com/how-to-set-up-the-dht11-humidity-sensor-on-an-arduino/
- LCD custom character generator – https://maxpromer.github.io/LCD-Character-Creator/
- Minov’s Custom Character walkthrough – https://minov.in/how-to-create-custom-character-for-lcd/
- Custom Character memory clear function posted in the Arduino forum – https://forum.arduino.cc/t/why-the-8-character-limit-with-liquidcrystal/48650/4