Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save codeandmake/8a26c2c7ed6f3b3d8e79e5000bca45e7 to your computer and use it in GitHub Desktop.
Save codeandmake/8a26c2c7ed6f3b3d8e79e5000bca45e7 to your computer and use it in GitHub Desktop.
This code accompanies our Code for Arduino Thermometer Display project: https://codeandmake.com/post/arduino-thermometer-display
/*
* Copyright 2021 Code and Make (codeandmake.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
* Code for Arduino Thermometer Display; an Arduino-based thermometer and humidity display
*
* This code accompanies the following project:
*
* https://codeandmake.com/post/arduino-thermometer-display
*/
#include <DHT.h>
#include <math.h>
#include <Adafruit_GFX.h>
#include <Adafruit_TFTLCD.h>
#include <MCUFRIEND_kbv.h>
#include <Fonts/FreeSansBold18pt7b.h>
#define DHTPIN 12
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
MCUFRIEND_kbv tft;
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RESET A4
// 16-bit colors
#define BLACK 0x0000
#define BLUE 0x001F
#define CYAN 0x07FF
#define DARKCYAN 0x05F7
#define DARKGREEN 0x05E0
#define DARKORANGE 0xC3E0
#define DARKRED 0x7800
#define DARKYELLOW 0x7BE0
#define GREEN 0x07C0
#define MAGENTA 0xF81F
#define ORANGE 0xFD00
#define PURPLE 0x780F
#define RED 0xF800
#define WHITE 0xFFFF
#define YELLOW 0xFFE0
#define POLLING_INTERVAL 3000
#define POLLS_PER_GRAPH_UPDATE 60 // about 12 hours on screen
#define TFT_WIDTH 240
#define TFT_HEIGHT 320
const boolean rotateScreen = false;
const boolean darkMode = true;
const unsigned long refreshInterval = POLLING_INTERVAL;
unsigned long previousTime = 0;
byte pollTick = 0;
byte graphTick = 0;
const float a = 17.62;
const float b = 243.12;
float previousC = -128;
float previousHic = -128;
float previousH = -128;
float previousDewC = -128;
char temps[TFT_WIDTH];
char feels[TFT_WIDTH];
char hums[TFT_WIDTH];
char dews[TFT_WIDTH];
uint16_t tempGraphColor = (darkMode ? RED : DARKRED);
uint16_t feelGraphColor = (darkMode ? YELLOW : DARKYELLOW);
uint16_t humidityGraphColor = (darkMode ? MAGENTA : PURPLE);
uint16_t dewPointGraphColor = (darkMode ? CYAN : DARKCYAN);
void drawCentreString(const char *buf, int x, int y)
{
int16_t x1, y1;
uint16_t w, h;
tft.getTextBounds(buf, x, y, &x1, &y1, &w, &h); //calc width of new string
tft.setCursor(x - (w / 2), y);
tft.print(buf);
}
void drawValue(float value, char *unit, byte precision, GFXfont *font, uint16_t color, float x, float y) {
char buf[(precision ? 8 : 6)];
dtostrf(value, 1, precision, buf);
strcat(buf, unit);
drawText(buf, font, color, x, y);
}
void drawText(char *text, GFXfont *font, uint16_t color, float x, float y) {
tft.setTextColor(color);
tft.setFont(font);
if(font == NULL) {
tft.setTextSize(2);
drawCentreString(text, TFT_WIDTH * x, (TFT_HEIGHT * y) - (6 * 2));
tft.setTextSize(1);
} else {
drawCentreString(text, TFT_WIDTH * x, TFT_HEIGHT * y);
}
}
// Clear section
void clearSection(float x, float y) {
tft.fillRect(TFT_WIDTH * (x - 0.25), TFT_HEIGHT * (y - 0.085),
TFT_WIDTH * 0.5, TFT_HEIGHT * 0.17, darkMode ? BLACK : WHITE);
}
// Draw number section with number sub-section
void drawSection(float value, char *unit, byte precision, float subValue, char *subUnit, uint16_t color, float x, float y) {
clearSection(x, y);
drawValue(value, unit, precision, &FreeSansBold18pt7b, color, x, y);
drawValue(subValue, subUnit, precision, NULL, color, x, (y + 0.07));
}
// Draw number section with text sub-section
void drawSectionTextSub(float value, char *unit, byte precision, char *subValue, uint16_t color, float x, float y) {
clearSection(x, y);
drawValue(value, unit, precision, &FreeSansBold18pt7b, color, x, y);
drawText(subValue, NULL, color, x, y + 0.07);
}
// Draw text section with no sub-section
void drawSectionText(char *text, uint16_t color, float x, float y) {
clearSection(x, y);
drawText(text, &FreeSansBold18pt7b, color, x, y);
}
uint16_t getTemperatureColor(int value) {
if(value < 0) {
return (darkMode ? BLUE : DARKCYAN);
}
if(value < 20) {
return blend((darkMode ? BLUE : DARKCYAN), (darkMode ? GREEN : DARKGREEN), value, 0, 20);
}
if(value < 40) {
return blend((darkMode ? GREEN : DARKGREEN), (darkMode ? RED : DARKRED), value, 20, 40);
}
return (darkMode ? RED : DARKRED);
}
uint16_t getHumidityColor(int value) {
if(value < 25) {
return (darkMode ? RED : DARKRED);
}
if(value < 30) {
return blend((darkMode ? RED : DARKRED), (darkMode ? YELLOW : DARKYELLOW), value, 25, 30);
}
if(value < 45) {
return blend((darkMode ? YELLOW : DARKYELLOW), (darkMode ? GREEN : DARKGREEN), value, 30, 45);
}
if(value < 60) {
return blend((darkMode ? GREEN : DARKGREEN), (darkMode ? YELLOW : DARKYELLOW), value, 45, 60);
}
if(value < 70) {
return blend((darkMode ? YELLOW : DARKYELLOW), (darkMode ? RED : DARKRED), value, 60, 70);
}
return (darkMode ? RED : DARKRED);
}
char* getHumidityText(int value) {
if(value < 25) {
return "V. Dry";
}
if(value < 30) {
return "Dry";
}
if(value < 60) {
return "Good";
}
if(value < 70) {
return "Humid";
}
return "V. Humid";
}
unsigned short blendChannel(unsigned short v1, unsigned short v2, float mix) {
return ((v2 * mix) + (v1 * (255 - mix))) / 255;
}
unsigned short blend(unsigned short color1, unsigned short color2, float mix) {
// split color1 into channels
unsigned color1r = color1 >> 11;
unsigned color1g = (color1 >> 5) & ((1u << 6) - 1);
unsigned color1b = color1 & ((1u << 5) - 1);
// split color2 into channels
unsigned color2r = color2 >> 11;
unsigned color2g = (color2 >> 5) & ((1u << 6) - 1);
unsigned color2b = color2 & ((1u << 5) - 1);
// blend each channel
unsigned mixedR = blendChannel(color1r, color2r, mix);
unsigned mixedG = blendChannel(color1g, color2g, mix);
unsigned mixedB = blendChannel(color1b, color2b, mix);
// pack
return (unsigned short) ((mixedR << 11) | (mixedG << 5) | mixedB);
}
unsigned short blend(unsigned short color1, unsigned short color2, float value, float lower, float upper) {
unsigned char mix = (value - lower) / (upper - lower) * 255;
return blend(color1, color2, mix);
}
void setup() {
for(int i = 0; i < TFT_WIDTH ; i++) {
temps[i] = -128;
feels[i] = -128;
hums[i] = -128;
dews[i] = -128;
}
dht.begin();
tft.reset();
tft.begin(tft.readID());
tft.fillScreen(darkMode ? BLACK : WHITE);
tft.setTextWrap(false);
tft.setRotation((rotateScreen == false ? 0 : 2));
// text
drawText("Temp", NULL, blend(BLACK, WHITE, 128), 0.25, 0.07);
drawText("Feel", NULL, blend(BLACK, WHITE, 128), 0.75, 0.07);
drawText("Humidity", NULL, blend(BLACK, WHITE, 128), 0.25, 0.41);
drawText("Dew Point", NULL, blend(BLACK, WHITE, 128), 0.75, 0.41);
// footer
drawText("codeandmake.com", NULL, blend(BLACK, WHITE, (darkMode ? 64 : 192)), 0.5, 0.97);
// key dots
tft.setTextSize(2);
drawText("-", NULL, tempGraphColor, 0.25, 0.32);
drawText("-", NULL, feelGraphColor, 0.75, 0.32);
drawText("-", NULL, humidityGraphColor, 0.25, 0.66);
drawText("-", NULL, dewPointGraphColor, 0.75, 0.66);
tft.setTextSize(1);
// initial update
update();
}
void loop() {
unsigned long now = millis();
if (now - previousTime >= refreshInterval) {
update();
previousTime = now;
}
}
void update() {
// status indicator
tft.fillCircle(TFT_WIDTH - 6, TFT_HEIGHT - 6, 3, (darkMode ? ORANGE : DARKORANGE));
// read values from sensor
float h = dht.readHumidity();
float c = dht.readTemperature();
float f = dht.readTemperature(true);
// clear status indicator
tft.fillCircle(TFT_WIDTH - 6, TFT_HEIGHT - 6, 3, darkMode ? BLACK : WHITE);
// check if any reads failed and exit early (to try again).
if (isnan(h) || isnan(c) || isnan(f)) {
tft.fillCircle(TFT_WIDTH - 6, TFT_HEIGHT - 6, 3, (darkMode ? RED : DARKRED));
return;
}
float hif = dht.computeHeatIndex(f, h);
float hic = dht.computeHeatIndex(c, h, false);
// compute dew point
float alpha = log(h / 100) + (a * c / (b + c));
float dewC = (b * alpha) / (a - alpha);
float dewF = (dewC * (9.0/5)) + 32;
// temp
if(previousC != c) {
previousC = c;
drawSection(c, "C", 1, f, "F", getTemperatureColor(c), 0.25, 0.19);
}
// feel
if(previousHic != hic) {
previousHic = hic;
drawSection(hic, "C", 1, hif, "F", getTemperatureColor(hic), 0.75, 0.19);
}
// humidity
if(previousH != h) {
previousH = h;
drawSectionTextSub(h, "%", 0, getHumidityText(h), getHumidityColor(h), 0.25, 0.53);
}
// dew point
if(previousDewC != dewC) {
previousDewC = dewC;
if(!isnan(dewC)) {
drawSection(dewC, "C", 1, dewF, "F", getTemperatureColor(dewC), 0.75, 0.53);
} else {
drawSectionText("N/A", (darkMode ? WHITE : BLACK), 0.75, 0.52);
}
}
// graph
if(pollTick == 0) {
temps[graphTick] = (!isnan(c) && c >= -50 && c <= 100 ? c : -128);
feels[graphTick] = (!isnan(hic) && hic >= -50 && hic <= 100 ? hic : -128);
hums[graphTick] = (!isnan(h) && h >= -50 && h <= 100 ? h : -128);
dews[graphTick] = (!isnan(dewC) && dewC >= -50 && dewC <= 100 ? dewC : -128);
for (byte x = 0; x < TFT_WIDTH; x++) {
int screenX = (TFT_WIDTH + (x - graphTick) - 1) % TFT_WIDTH;
tft.drawFastVLine(screenX, TFT_HEIGHT * 0.89, -TFT_HEIGHT * 0.2, darkMode ? BLACK : WHITE);
// vertical graph lines
if(screenX != 0 && screenX % (TFT_WIDTH / 12) == 0) {
tft.drawFastVLine(screenX, TFT_HEIGHT * 0.89, -TFT_HEIGHT * 0.2, blend(BLACK, WHITE, (darkMode ? 64 : 192)));
}
// horizontal graph lines
for (byte y = 0; y < 7; y++) {
if(y == 2 && screenX % 2 == 0) {
// dotted line at 0
tft.drawPixel(screenX, (TFT_HEIGHT * 0.89) - (TFT_HEIGHT * 0.2 * (y / 6.0)), blend(BLACK, WHITE, 128));
} else {
tft.drawPixel(screenX, (TFT_HEIGHT * 0.89) - (TFT_HEIGHT * 0.2 * (y / 6.0)), blend(BLACK, WHITE, (darkMode ? 64 : 192)));
}
}
// calculate colors
int dewsY;
uint16_t dewColor;
int humsY;
uint16_t humsColor;
int feelsY;
uint16_t feelsColor;
int tempsY;
uint16_t tempsColor;
if(dews[x] > -128) {
dewColor = dewPointGraphColor;
dewsY = (TFT_HEIGHT * 0.89) - (TFT_HEIGHT * 0.2 * ((dews[x] + 50) / 150.0));
}
if(hums[x] > -128) {
humsColor = humidityGraphColor;
humsY = (TFT_HEIGHT * 0.89) - (TFT_HEIGHT * 0.2 * ((hums[x] + 50) / 150.0));
}
if(feels[x] > -128) {
feelsColor = feelGraphColor;
feelsY = (TFT_HEIGHT * 0.89) - (TFT_HEIGHT * 0.2 * ((feels[x] + 50) / 150.0));
}
if(temps[x] > -128) {
tempsColor = tempGraphColor;
tempsY = (TFT_HEIGHT * 0.89) - (TFT_HEIGHT * 0.2 * ((temps[x] + 50) / 150.0));
}
// draw
if(x % 4 == 0) {
if(dews[x] > -128) {
tft.drawPixel(screenX, dewsY, dewColor);
}
if(hums[x] > -128) {
tft.drawPixel(screenX, humsY, humsColor);
}
if(feels[x] > -128) {
tft.drawPixel(screenX, feelsY, feelsColor);
}
if(temps[x] > -128) {
tft.drawPixel(screenX, tempsY, tempsColor);
}
}
if(x % 4 == 1) {
if(temps[x] > -128) {
tft.drawPixel(screenX, tempsY, tempsColor);
}
if(feels[x] > -128) {
tft.drawPixel(screenX, feelsY, feelsColor);
}
if(hums[x] > -128) {
tft.drawPixel(screenX, humsY, humsColor);
}
if(dews[x] > -128) {
tft.drawPixel(screenX, dewsY, dewColor);
}
}
if(x % 4 == 2) {
if(feels[x] > -128) {
tft.drawPixel(screenX, feelsY, feelsColor);
}
if(temps[x] > -128) {
tft.drawPixel(screenX, tempsY, tempsColor);
}
if(dews[x] > -128) {
tft.drawPixel(screenX, dewsY, dewColor);
}
if(hums[x] > -128) {
tft.drawPixel(screenX, humsY, humsColor);
}
}
if(x % 4 == 3) {
if(hums[x] > -128) {
tft.drawPixel(screenX, humsY, humsColor);
}
if(dews[x] > -128) {
tft.drawPixel(screenX, dewsY, dewColor);
}
if(temps[x] > -128) {
tft.drawPixel(screenX, tempsY, tempsColor);
}
if(feels[x] > -128) {
tft.drawPixel(screenX, feelsY, feelsColor);
}
}
}
graphTick++;
if(graphTick >= TFT_WIDTH) {
graphTick = 0;
}
}
pollTick++;
if(pollTick >= POLLS_PER_GRAPH_UPDATE) {
pollTick = 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment