ARTICLE AD BOX
I'm working on a touchscreen fan controller using an Arduino Mega 2560 and a 4" Hosyond ST7796S SPI TFT display. I've run into two hardware/code issues I can't seem to resolve and would appreciate any guidance. I attached my pin configurations.
My Setup:
Arduino Mega 2560
Hosyond 4" ST7796S 480x320 SPI TFT touchscreen (XPT2046 touch controller)
DHT11 temperature and humidity sensor
Dual High-Power MOSFET module (5V-36V, 400W, 15A) for fan switching
12V external power supply (drill battery)
Fan 1 — Cooler Fan (Pin 2): A 12V brushed DC fan with only positive and negative terminals connected through the MOSFET module. PWM signal goes from pin 2 to the MOSFET signal input. This previously worked at variable speeds using analogWrite.
Fan 2 — Exhaust Fans (Pin 4): Two 140mm Thermalright 4-pin PWM fans. The blue PWM wire connects directly to pin 4 on the Mega. These fans run at the same speed regardless of the analogWrite value sent.
Hardware Components
Microcontroller
Arduino Mega 2560 (Elegoo clone)Display
Hosyond 4.0" TFT SPI Touchscreen — 480x320 resolution
Driver: ST7796S
Touch controller: XPT2046
Fans
Cooler Fan — 12V brushed DC fan controlled via MOSFET module
Exhaust Fans — 140mm 4-pin PC fans (2x) powered directly from 12V
Power
12V battery supply for fans
USB/barrel jack for Arduino Mega
Dual High-Power MOSFET module (400W, 15A) for cooler fan switching
Sensor
DHT11 Temperature and Humidity sensor (breakout board version)Software
Libraries Used
Adafruit ST7796S kbv — display driver
XPT2046 Touchscreen — touch input
Adafruit GFX — graphics
DHT — temperature sensor
UI Features
Dark navy themed interface
Team Branding
Live temperature and humidity display
System status indicator
Power button — kills all fans instantly
Cooler Fan card with 5 speed buttons (OFF/25/50/75/100%)
Exhaust Fan card with 5 speed buttons (OFF/25/50/75/100%)
-------------------------------------------------------------------------------------------------
#include <SPI.h> #include <Adafruit_GFX.h> #include <Adafruit_ST7796S_kbv.h> #include <XPT2046_Touchscreen.h> #include <DHT.h> #include <math.h> // --- PINS --- #define TFT_CS 10 #define TFT_DC 8 #define TFT_RST 9 #define TOUCH_CS 53 #define FAN1_PWM 2 // Cooler fan via MOSFET #define FAN2_PWM 4 // Exhaust fan #define DHT_PIN 7 #define DHT_TYPE DHT11 // --- TOUCH CALIBRATION --- #define TS_MINX 277 #define TS_MAXX 3929 #define TS_MINY 235 #define TS_MAXY 3875 #define TS_MINZ 200 // --- COLORS --- #define COL_BG 0x0208 #define COL_PANEL 0x0C2F #define COL_ORANGE 0xFC40 #define COL_WHITE 0xFFFF #define COL_BLACK 0x0000 #define COL_LTBLUE 0x4BDF #define COL_RED 0xF800 #define COL_YELLOW 0xFFE0 #define COL_GREEN 0x07E0 #define COL_DIMTEXT 0x8C71 #define COL_DIVIDER 0x0C1A #define COL_BTN 0x1A3A // --- LAYOUT --- #define HDR_H 60 #define TEMP_Y 60 #define TEMP_H 32 #define CARD1_Y 100 #define CARD1_H 108 #define CARD2_Y 218 #define CARD2_H 108 #define PWR_CX 450 #define PWR_CY 30 #define PWR_R 20 #define SBTN_W 72 #define SBTN_H 40 #define SBTN_Y_OFF 58 #define SBTN_GAP 8 #define SBTN_X0 16 // Speed steps — raw PWM values 0-255 const int pwmSteps[5] = {0, 64, 128, 192, 255}; const char* speedLabels[5] = {"OFF","25%","50%","75%","100%"}; Adafruit_ST7796S_kbv tft(TFT_CS, TFT_DC, TFT_RST); XPT2046_Touchscreen touch(TOUCH_CS); DHT dht(DHT_PIN, DHT_TYPE); int fan1Step = 0; int fan2Step = 0; bool masterOn = true; unsigned long lastPwrPress = 0; unsigned long lastBtnPress = 0; unsigned long lastDHTTime = 0; float temperature = 0; float humidity = 0; // ============================================================ // FAN CONTROL // ============================================================ void applyFan1(int step) { analogWrite(FAN1_PWM, masterOn ? pwmSteps[step] : 0); } void applyFan2(int step) { analogWrite(FAN2_PWM, masterOn ? pwmSteps[step] : 0); } void killFans() { analogWrite(FAN1_PWM, 0); analogWrite(FAN2_PWM, 0); } // ============================================================ // POWER BUTTON // ============================================================ void drawPowerButton() { uint16_t col = masterOn ? COL_GREEN : COL_RED; uint16_t bgcol = masterOn ? 0x0344 : 0x6000; tft.fillCircle(PWR_CX, PWR_CY, PWR_R+3, bgcol); tft.drawCircle(PWR_CX, PWR_CY, PWR_R+3, col); tft.drawCircle(PWR_CX, PWR_CY, PWR_R+4, col); int r = PWR_R - 4; for (float a = -200; a <= 20; a += 5) { float rad = a * 3.14159f / 180.0f; tft.drawLine(PWR_CX+(int)((r-1)*cos(rad)), PWR_CY+(int)((r-1)*sin(rad)), PWR_CX+(int)((r+1)*cos(rad)), PWR_CY+(int)((r+1)*sin(rad)), col); } tft.fillRect(PWR_CX-1, PWR_CY-r-1, 3, r+1, col); } // ============================================================ // HEADER // ============================================================ void drawHeader() { tft.fillRect(0, 0, 480, HDR_H, COL_BG); tft.setTextColor(COL_WHITE); tft.setTextSize(2); tft.setCursor(10, 10); tft.print("Fan Controller"); tft.setTextColor(COL_ORANGE); tft.setTextSize(1); tft.setCursor(10, 32); tft.print("Arduino Mega 2560 — ST7796S TFT"); tft.fillRect(0, HDR_H-1, 480, 1, COL_DIVIDER); drawPowerButton(); } // ============================================================ // TEMP BAR // ============================================================ void drawTempBar() { tft.fillRect(0, TEMP_Y, 480, TEMP_H, COL_PANEL); tft.fillCircle(16, TEMP_Y+17, 5, COL_RED); tft.fillRect(14, TEMP_Y+5, 4, 14, COL_WHITE); tft.fillRect(15, TEMP_Y+6, 2, 10, COL_RED); tft.setTextColor(COL_WHITE); tft.setTextSize(2); tft.setCursor(26, TEMP_Y+8); tft.print(temperature, 1); tft.print((char)247); tft.print("C"); uint16_t dot = temperature<30 ? COL_GREEN : temperature<40 ? COL_YELLOW : COL_RED; tft.fillCircle(114, TEMP_Y+16, 5, dot); tft.fillRect(128, TEMP_Y+5, 1, 20, COL_DIVIDER); tft.fillCircle(140, TEMP_Y+18, 5, COL_LTBLUE); tft.fillTriangle(140, TEMP_Y+6, 135, TEMP_Y+17, 145, TEMP_Y+17, COL_LTBLUE); tft.setTextColor(COL_WHITE); tft.setTextSize(2); tft.setCursor(152, TEMP_Y+8); tft.print(humidity, 0); tft.print("%"); tft.setTextColor(COL_DIMTEXT); tft.setTextSize(1); tft.setCursor(320, TEMP_Y+7); tft.print("SYSTEM STATUS"); tft.setCursor(320, TEMP_Y+19); if (masterOn) { tft.setTextColor(COL_GREEN); tft.print("● ACTIVE "); } else { tft.setTextColor(COL_RED); tft.print("● STOPPED "); } tft.fillRect(0, TEMP_Y+TEMP_H-1, 480, 1, COL_DIVIDER); } // ============================================================ // SPEED BUTTON // ============================================================ void drawSpeedBtn(int cardY, int btnIdx, int activeStep) { int bx = SBTN_X0 + btnIdx * (SBTN_W + SBTN_GAP); int by = cardY + SBTN_Y_OFF; bool active = (btnIdx == activeStep); uint16_t bg, fg, border; if (active) { if (btnIdx==0) { bg=0x2104; fg=COL_DIMTEXT; border=COL_DIMTEXT; } else if (btnIdx==1) { bg=0x0344; fg=COL_LTBLUE; border=COL_LTBLUE; } else if (btnIdx==2) { bg=0x3200; fg=COL_ORANGE; border=COL_ORANGE; } else if (btnIdx==3) { bg=0x6200; fg=COL_YELLOW; border=COL_YELLOW; } else { bg=0x6000; fg=COL_RED; border=COL_RED; } } else { bg=COL_BTN; fg=COL_DIMTEXT; border=0x2A4A; } tft.fillRoundRect(bx, by, SBTN_W, SBTN_H, 6, bg); tft.drawRoundRect(bx, by, SBTN_W, SBTN_H, 6, border); tft.setTextColor(fg); tft.setTextSize(1); int textX = bx + SBTN_W/2 - (strlen(speedLabels[btnIdx]) * 3); tft.setCursor(textX, by + SBTN_H/2 - 4); tft.print(speedLabels[btnIdx]); } // ============================================================ // FAN CARD // ============================================================ void drawFanCard(int fanNum) { int cardY = (fanNum==1) ? CARD1_Y : CARD2_Y; int cardH = (fanNum==1) ? CARD1_H : CARD2_H; int step = (fanNum==1) ? fan1Step : fan2Step; tft.fillRoundRect(4, cardY, 472, cardH, 10, COL_PANEL); tft.fillCircle(14, cardY+18, 4, COL_ORANGE); tft.setTextColor(COL_WHITE); tft.setTextSize(2); tft.setCursor(24, cardY+10); tft.print(fanNum==1 ? "COOLER FAN" : "EXHAUST FAN"); tft.setTextColor(step==0 ? COL_DIMTEXT : COL_ORANGE); tft.setTextSize(2); tft.setCursor(380, cardY+10); tft.print(speedLabels[step]); tft.setTextSize(1); tft.setCursor(390, cardY+30); if (!masterOn) { tft.setTextColor(COL_RED); tft.print("STOPPED"); } else if (step==0){ tft.setTextColor(COL_DIMTEXT); tft.print(" OFF "); } else { tft.setTextColor(COL_GREEN); tft.print("RUNNING"); } for (int i=0; i<5; i++) drawSpeedBtn(cardY, i, step); } // ============================================================ // FULL SCREEN // ============================================================ void drawScreen() { tft.fillScreen(COL_BG); drawHeader(); drawTempBar(); drawFanCard(1); drawFanCard(2); } // ============================================================ // SETUP // ============================================================ void setup() { pinMode(FAN1_PWM, OUTPUT); pinMode(FAN2_PWM, OUTPUT); analogWrite(FAN1_PWM, 0); analogWrite(FAN2_PWM, 0); delay(500); pinMode(TFT_RST, OUTPUT); digitalWrite(TFT_RST, LOW); delay(100); digitalWrite(TFT_RST, HIGH); delay(100); tft.begin(); tft.setRotation(3); touch.begin(); touch.setRotation(1); dht.begin(); delay(2000); float t = dht.readTemperature(); float h = dht.readHumidity(); if (!isnan(t) && !isnan(h)) { temperature=t; humidity=h; } drawScreen(); } // ============================================================ // LOOP // ============================================================ void loop() { unsigned long now = millis(); if (touch.touched()) { TS_Point p = touch.getPoint(); if (p.z >= TS_MINZ && (now-lastBtnPress > 300)) { int tx = map(p.x, TS_MINX, TS_MAXX, 0, 480); int ty = map(p.y, TS_MINY, TS_MAXY, 0, 320); tx = constrain(tx, 0, 480); ty = constrain(ty, 0, 320); int dx = tx-PWR_CX, dy = ty-PWR_CY; bool hitPwr = (ty<HDR_H) && (tx>410) && ((dx*dx+dy*dy) <= (PWR_R+8)*(PWR_R+8)); if (hitPwr && (now-lastPwrPress > 500)) { lastPwrPress = now; masterOn = !masterOn; if (masterOn) { applyFan1(fan1Step); applyFan2(fan2Step); } else { killFans(); } drawPowerButton(); drawTempBar(); drawFanCard(1); drawFanCard(2); return; } int c1By = CARD1_Y + SBTN_Y_OFF; if (!hitPwr && ty>=c1By && ty<=c1By+SBTN_H) { for (int i=0; i<5; i++) { int bx = SBTN_X0 + i*(SBTN_W+SBTN_GAP); if (tx>=bx && tx<=bx+SBTN_W) { lastBtnPress = now; fan1Step = i; applyFan1(fan1Step); drawFanCard(1); break; } } } int c2By = CARD2_Y + SBTN_Y_OFF; if (!hitPwr && ty>=c2By && ty<=c2By+SBTN_H) { for (int i=0; i<5; i++) { int bx = SBTN_X0 + i*(SBTN_W+SBTN_GAP); if (tx>=bx && tx<=bx+SBTN_W) { lastBtnPress = now; fan2Step = i; applyFan2(fan2Step); drawFanCard(2); break; } } } } } if (now-lastDHTTime >= 3000) { float t = dht.readTemperature(); float h = dht.readHumidity(); if (!isnan(t) && !isnan(h)) { temperature=t; humidity=h; drawTempBar(); } lastDHTTime = now; } }