/* ALL DISPLAY SETTING CHANGED TO OPERATE ON AN ILI9341
* Trying to modify button routine using interrupts
*
*
Converted: 3D Printing Filament Dehydrator Control Module
* "Thermostat_OLED_WIP.ino"
* Created by:
* Olivier Royer-Tardif (alias MirageC)
* October 12th, 2018
*
* to: ESP32 Wrover Dev Board,
* Changed to a DHT22 Temperature / Humidity,
* Changed button routines to interrups,
* Changed display to ST7789,
* Changed Heater routine to PID
*
* Glen E. Calhoun
* ________________________________________________________________
* Revision Log:
*
* V 0.1 05-Jan-2026 Converted to ESP32
* V 0.2 06-Jan-2026 Changed DS18B20 to DHT22
* V 0.21 11-Jan-2026 Changed to interrups on buttons
*
* "Set Timer" menu needs to be implemented
*
* Connect the TFT display to the ESP32 using SPI:
* TFT_CS 5
* TFT_RST 4
* TFT_DC 15
* TFT_MOSI 23 // SDA
* TFT_SCLK 18 // SCL
*
* Connect DS18B20 temp sensor to pin 5
*
* Connect Buttons as follows:
* UP - 32
* DWN - 33
* SEL - 25
*/
//Disable Brownout
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
//#include <Adafruit_ST7789.h>
#include <Adafruit_ILI9341.h>
// Setup ST7789 display
//#define ST7789_DRIVER // FOR WOKWI
//#define TFT_WIDTH 240
//#define TFT_HEIGHT 240
/*
// Define ESP32 pins for the ST7789 TFT Display
#define TFT_CS 5
#define TFT_RST 4
#define TFT_DC 15
#define TFT_MOSI 23 // SDA
#define TFT_SCLK 18 // SCL
*/
#define TFT_DC 2
#define TFT_CS 15
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
//Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
//#include "MyDryerMenu.h"
// Use portENTER_CRITICAL_ISR() and portEXIT_CRITICAL_ISR() for safe access to shared variables in ESP32
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; // For critical sections on ESP32
// Reading DHT22 with ESP32
#include <Adafruit_Sensor.h>
#include <DHT.h>
#define DHTPIN 22
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
// ---------- Config Button Pins ----------------
#define BTN_UP 33 // GPIO pin for UP button
#define BTN_DOWN 32 // GPIO pin for DOWN button
#define BTN_SELECT 25 // GPIO pin for SELECT button
#define DEBOUNCE_MS 50 // debounce time in ms
/*-----------------GLOBAL VARIABLES--------------------------*/
struct MenuItems {
char desc[20];
char shortdesc[5];
int mode;
int drytemp;
int drytime;
};
MenuItems Menu1[] = {
// Menu Item, filament, Mode, Temp, Time
{"Filament ", "", 2, 0, 0},
{"Manual ", "", 1, 0, 0}, // future
{"Exit ", "", 1, 0, 0}
};
MenuItems Menu2[] = {
// Menu Item, filament, Mode, Temp, Time
{"Return ", " ", 0, 0, 0},
{"PLA 45C 4h", "PLA ", 1, 45, 240},
{"ABS 60C 2h", "ABS ", 1, 60, 120},
{"PETG 65C 2h", "PETG", 1, 65, 120},
{"NYLN 70C 12h", "NYLN", 1, 70, 720},
{"PVA 45C 4h", "PVA ", 1, 45, 240},
{"TPU 50C 4h", "TPU ", 1, 50, 240},
{"ASA 60C 4h", "ASA ", 1, 60, 240},
{"PP 55C 6h", "PP ", 1, 55, 360},
{"Desc 65C 3h", "Dsct", 1, 65, 180},
{"Test 29C 1m", "Test", 1, 29, 60}
};
byte nItem1 = sizeof Menu1 / sizeof Menu1[0];
byte nItem2 = sizeof Menu2 / sizeof Menu2[0];
byte nItems;
volatile unsigned long lastPress0Time = 0;
volatile unsigned long lastPress1Time = 0;
volatile unsigned long lastPress2Time = 0;
byte menuMode = 1; // 0=Menu,1=Main Screen,2=Manual
byte setTemp = 50; // Target temperature from menu table
unsigned int setTime = 0; // Timer value from menu
unsigned long millisStart = 0;
char setKeyw[5]; // Filament keyword from menu
bool heaterStat = 0; // Heater Status: 1=off 0=on
bool dotHeat = 0; // Flash "*" while heating
unsigned long lastmillis = 0; // Flashing "*" timer
unsigned long lastmillisSensor = 0; // Temp/Hum sensor querry timer
bool BtnStat[] = {false, false, false}; // Status of sel, dwn, & up buttons
//MenuItems *mAddress;
int currentPage = 1;
int linesLeft = 0;
int currentPos = 0;
int dhtHum;
int dhtTemp;
// Button 0 intrrupt routine
void IRAM_ATTR handleButton0Interrupt() {
portENTER_CRITICAL_ISR(&mux);
unsigned long currentMillis = millis();
if ((currentMillis - lastPress0Time) > DEBOUNCE_MS) {
BtnStat[0] = true;
lastPress0Time = currentMillis;
}
portEXIT_CRITICAL_ISR(&mux);
}
// Button 1 intrrupt routine
void IRAM_ATTR handleButton1Interrupt() {
portENTER_CRITICAL_ISR(&mux);
unsigned long currentMillis = millis();
if ((currentMillis - lastPress1Time) > DEBOUNCE_MS) {
BtnStat[1] = true;
lastPress1Time = currentMillis;
}
portEXIT_CRITICAL_ISR(&mux);
}
// Button 2 intrrupt routine
void IRAM_ATTR handleButton2Interrupt() {
portENTER_CRITICAL_ISR(&mux);
unsigned long currentMillis = millis();
if ((currentMillis - lastPress2Time) > DEBOUNCE_MS) {
BtnStat[2] = true;
lastPress2Time = currentMillis;
}
portEXIT_CRITICAL_ISR(&mux);
}
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
pinMode(BTN_SELECT, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
pinMode(BTN_UP, INPUT_PULLUP);
// Attach interrupt 0
attachInterrupt(digitalPinToInterrupt(BTN_SELECT), handleButton0Interrupt, FALLING);
// Attach interrupt 1
attachInterrupt(digitalPinToInterrupt(BTN_DOWN), handleButton1Interrupt, FALLING);
// Attach interrupt 2
attachInterrupt(digitalPinToInterrupt(BTN_UP), handleButton2Interrupt, FALLING);
Serial.begin(115200);
dht.begin();
//tft.init(TFT_WIDTH, TFT_HEIGHT); // FOR WOKWI
tft.begin(); // FOR WOKWI
tft.setTextSize(3);
tft.setRotation(1);
tft.fillScreen(ILI9341_BLACK);
}
//
// ########### Display Menu ##################
//
void MenuSelection() {
int maxLines = 6; // max allowed # of Lines / page
int menuLines = 0; // # of Lines to be displayed-updated in code
int tmp_menuMode;
int tmp_setTemp;
unsigned int tmp_setTime;
char tmp_setKeyw[5];
if (BtnStat[1] == true && currentPos > 0 ) {
currentPos += -1;
} else if (BtnStat[2] == true && currentPos <= nItems) {
currentPos += 1;
}
if (currentPos == nItems){ // reload menu from start if past end of menu
currentPos =0;
currentPage =1;
}else if (currentPos == (maxLines * currentPage)){ // show next page if past end of current page
currentPage++;
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 0);
}
BtnStat[1] = false;
BtnStat[2] = false;
//int menuPage = (currentPos / maxLines) * maxLines; // used to compute *m offset
int totalPages = (int)ceil((float)nItems / maxLines);
MenuItems *m;
switch (menuMode) {
case 0: // Main Mwnu
nItems = nItem1;
m = Menu1;
break;
case 2: // Filament
nItems = nItem2;
m = Menu2;
break;
}
tft.setTextColor(ILI9341_WHITE,ILI9341_BLACK);
tft.setTextSize(3);
tft.setCursor(5, 10);
tft.print("Main Menu p");
tft.print(currentPage);
tft.print("/");
tft.println((int)ceil((float)nItems / maxLines)); // Total Pages
tft.setCursor(0, 40);
linesLeft = nItems - ((currentPage-1) * maxLines);
if (linesLeft >= maxLines){
menuLines = maxLines;
}else{
menuLines = linesLeft;
}
int mStart = (currentPage-1) * maxLines; //menuLines;
int mStop = mStart + menuLines;
for (int i = mStart; i < mStop; i++) {
if (i == currentPos){
tft.print(">");
tmp_menuMode = m[i].mode;
tmp_setTemp = m[i].drytemp;
tmp_setTime = m[i].drytime;
strcpy(tmp_setKeyw, m[i].shortdesc);
} else {
tft.print(" ");
}
tft.println(m[i].desc); // Print menu line item
}
//Serial.print("linesLeft= "); Serial.println(linesLeft);
//Serial.print("currentPos= "); Serial.println(currentPos);
//Serial.print("menuLines= "); Serial.print(menuLines);Serial.print("; currentPage= "); Serial.println(currentPage);
/*
// show next page if past end of current page
if (currentPos == (maxLines * currentPage)){
currentPage++;
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 0);
}
*/
tft.setCursor(0, 0);
// Update information if current line Selected
if (BtnStat[0] == true) {
BtnStat[0] = false;
currentPos = 0;
menuMode = tmp_menuMode;
setTemp = tmp_setTemp;
setTime = tmp_setTime;
strcpy(setKeyw,tmp_setKeyw);
millisStart = millis();
tft.fillScreen(ILI9341_BLACK);
}
return;
}
//
// ########### Display Menu ##################
//
void mainScreen() {
// If button pressed then clear Main screen & go to Menu display
if (BtnStat[0] == true || BtnStat[1] == true || BtnStat[2] == true) {
// delay(100);
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 0);
BtnStat[0] = false;
BtnStat[1] = false;
BtnStat[2] = false;
currentPage = 1;
menuMode = 0;
currentPos = 0;
return;
}
unsigned long tSec;
unsigned long dHour;
int dMin;
int dSec;
tft.setTextSize(3);
tft.setTextColor(ILI9341_YELLOW,ILI9341_BLACK);
tft.setCursor(25, 5);
if (heaterStat == 1) {
tft.println("..HEATING..");
}else{
tft.println("..Standby..");
}
tft.setTextColor(ILI9341_WHITE,ILI9341_BLACK);
tft.setTextSize(4);
tft.setCursor(70, 35);
// tft.setCursor(140, 35);
tft.println(setKeyw);
tft.setCursor(25, 70);
tft.println("PV: SV:");
if (dhtTemp << setTemp - 3) {
tft.setTextColor(ILI9341_YELLOW,ILI9341_BLACK);
} else if (dhtTemp >> setTemp + 3) {
tft.setTextColor(ILI9341_RED,ILI9341_BLACK);
} else {
tft.setTextColor(ILI9341_GREEN,ILI9341_BLACK);
}
if (dhtTemp < 100) {tft.setCursor(20, 105);}
if (dhtTemp < 10) {tft.setCursor(30, 105);}
tft.print(dhtTemp); //(int)round(dhtTemp));
tft.print("C");
if (setTemp < 100) {tft.setCursor(140, 105);}
if (setTemp < 10) {tft.setCursor(160, 105);}
tft.setTextColor(ILI9341_ORANGE,ILI9341_BLACK);
tft.print(setTemp);
tft.println("C");
tft.setTextSize(3);
tft.setTextColor(ILI9341_WHITE,ILI9341_BLACK);
tft.setCursor(20, 145);
tft.print("Humidity ");
tft.setCursor(200, 145);
tft.print(dhtHum);
tft.println("% ");
tft.setTextSize(4);
// Display Timer
if ((setTime * 60) <= ((millis() - millisStart) / 1000)) {
tSec = 0;
}else{
tSec = ((setTime * 60) - ((millis() - millisStart) / 1000));
}
dHour = tSec / 3600;
dMin = tSec / 60;
dMin = dMin % 60;
dSec = tSec % 60;
tft.setTextColor(ILI9341_GREEN,ILI9341_BLACK);
tft.setCursor(25, 180);
if (dHour < 10) { tft.print("0");}
tft.print(dHour);
tft.print(":");
if ((dMin)< 10){tft.print("0");}
tft.print(dMin);
tft.print(":");
if (dSec < 10){tft.print("0");}
tft.print(dSec);
tft.setTextColor(ILI9341_WHITE,ILI9341_BLACK);
}
void loop() {
if (millis() > lastmillisSensor + 1000) {
lastmillisSensor = millis();
dhtTemp = int(dht.readTemperature()); //round(dht.readTemperature() * 10.0) / 10.0; //reads °C
dhtHum = int(dht.readHumidity()); //round(dht.readHumidity() * 10.0) / 10.0;
}
/* Temporarily Disabled heater to test Menu features on ESP32
if ((heaterStat == 0) && (dhtTemp < (setTemp - 2))) {
digitalWrite(3, HIGH); // put PWM call here
heaterStat = 1;
} else if ((heaterStat == 1) && (dhtTemp >= setTemp)) {
digitalWrite(3, LOW); // put PWM call here
heaterStat = 0;
}
*/
switch (menuMode) {
case 0: // Main Menu
MenuSelection();
break;
case 1: // Run Screen
mainScreen();
break;
case 2: // Menu2 (Filament Menu)
MenuSelection();
break;
case 3: // Case 3 will be set timer
tft.setCursor(10, 20);
tft.print("Put timer routine here!");
break;
}
}