Community project

UIP-6 Control Panel

ESP32
Photo of UIP-6 Control Panel
Generated with AI

vikstroyklim

Published August 29, 2026

The UIP-6 Control Panel is a touchscreen interface built around an ESP32 microcontroller and a 2.8-inch ILI9341 display with XPT2046 capacitive touch sensing. This guide provides everything needed to assemble a responsive control panel that can monitor and manage multiple system areas including process control, motion detection, and safety functions.

Builders will receive a complete wiring diagram showing SPI connections for both the display and touch controller, a full parts list with pinout specifications, and ready-to-compile Arduino firmware featuring button rendering, touch event handling, and multi-zone screen layout management. Assembly takes just a few steps: preparing the display module, mounting the panel in a secure location, and verifying the screen layout responds correctly to touch input.

Wiring diagram

Assemble it in 3 steps

1. Подготовьте экранный модуль

Возьмите плату Sunton ESP32-2432S028R — это готовая плата с цветным экраном и сенсорным стеклом. Для этого макета больше никаких деталей, проводов к станку или к шкафу 380 В не подключайте.

  • Снимите защитную плёнку с экрана только после установки платы в корпус, чтобы не поцарапать стекло.
  • Не подключайте эту плату к клеммам станка, контакторам или сети 380 В: текущая версия служит только интерактивным макетом интерфейса.

2. Установите панель в безопасном месте

Закрепите плату за монтажные отверстия в пластиковом корпусе или временно положите её на сухую непроводящую поверхность экраном вверх. Оставьте доступ к USB-разъёму — через него плата получает питание и загружается программа.

  • Для показа интерфейса рядом со станком используйте отдельный пластиковый корпус с прозрачным окном перед экраном.
  • Не кладите плату внутрь существующего силового шкафа: рядом с проводами 380 В она может быть повреждена и создаст опасную ситуацию.

3. Проверьте экранный макет

Подключите плату USB-кабелем к компьютеру. После загрузки программы касайтесь кнопок «VODA», «KLEY», «+» и «−», а также стрелок: они меняют только надписи и цвета на экране. Красная кнопка STOP выключает только нарисованные состояния; «SBROS» возвращает макет в ожидание.

  • Начальное значение подачи клея — 50%; кнопки «+» и «−» меняют его шагом 10%.
  • Экранная кнопка STOP не является аварийной остановкой станка и не должна заменять отдельную физическую грибовидную кнопку аварийного останова.

Deploy the firmware

#include <Arduino.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <XPT2046_Touchscreen.h>

// Built-in screen and touch wiring on the Sunton ESP32-2432S028R board.

struct Button { int16_t x, y, w, h; };


// Forward declarations
bool inside(const Button &b, int16_t x, int16_t y);
void labelCentered(const String &text, int16_t x, int16_t y, int16_t w, uint8_t size, uint16_t color);
void drawRoundedButton(const Button &b, uint16_t fill, const String &top, const String &bottom);
void drawHeader();
void drawProcessArea();
void drawMotionArea();
void drawSafetyArea();
void drawScreen();
void updateScreen();
bool readTouch(int16_t &screenX, int16_t &screenY);
void handleTap(int16_t x, int16_t y);

constexpr int TFT_CS = 15;
constexpr int TFT_DC = 2;
constexpr int TFT_RST = -1;
constexpr int TFT_SCK = 14;
constexpr int TFT_MISO = 12;
constexpr int TFT_MOSI = 13;
constexpr int TOUCH_CS = 33;
constexpr int TOUCH_IRQ = 36;
constexpr int TOUCH_SCK = 25;
constexpr int TOUCH_MISO = 39;
constexpr int TOUCH_MOSI = 32;

SPIClass tftSPI(HSPI);
SPIClass touchSPI(VSPI);
// The real ESP32 uses its dedicated HSPI instance. The browser facade only
// exposes Adafruit's explicit-pin constructor, so keep that compatibility
// path separate without changing the device-side wiring.
#if defined(ARDUINO_ARCH_ESP32)
Adafruit_ILI9341 tft(&tftSPI, TFT_CS, TFT_DC, TFT_RST);
#else
Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCK, TFT_RST, TFT_MISO);
#endif
XPT2046_Touchscreen touch(TOUCH_CS, TOUCH_IRQ);

constexpr uint16_t NAVY = 0x0841;
constexpr uint16_t PANEL = 0x18C3;
constexpr uint16_t CYAN = 0x05FF;
constexpr uint16_t GREEN = 0x05A0;
constexpr uint16_t RED = 0xF800;
constexpr uint16_t ORANGE = 0xFD20;
constexpr uint16_t WHITE = ILI9341_WHITE;
constexpr uint16_t GREY = 0x7BEF;

bool waterOn = false;
bool glueOn = false;
bool emergencyStop = false;
int glueLevel = 50;
String motion = "ОЖИДАНИЕ";


const Button waterButton = {8, 55, 94, 42};
const Button glueButton = {110, 55, 94, 42};
const Button minusButton = {212, 55, 48, 42};
const Button plusButton = {264, 55, 48, 42};
const Button upButton = {134, 119, 52, 35};
const Button leftButton = {76, 158, 52, 35};
const Button downButton = {134, 158, 52, 35};
const Button rightButton = {192, 158, 52, 35};
const Button stopButton = {252, 119, 60, 74};
const Button resetButton = {252, 202, 60, 28};

bool inside(const Button &b, int16_t x, int16_t y) {
  return x >= b.x && x < b.x + b.w && y >= b.y && y < b.y + b.h;
}

void labelCentered(const String &text, int16_t x, int16_t y, int16_t w, uint8_t size, uint16_t color) {
  tft.setTextSize(size);
  tft.setTextColor(color);
  int16_t textWidth = text.length() * 6 * size;
  tft.setCursor(x + (w - textWidth) / 2, y);
  tft.print(text);
}

void drawRoundedButton(const Button &b, uint16_t fill, const String &top, const String &bottom = "") {
  tft.fillRoundRect(b.x, b.y, b.w, b.h, 7, fill);
  tft.drawRoundRect(b.x, b.y, b.w, b.h, 7, WHITE);
  if (bottom.length() == 0) {
    labelCentered(top, b.x, b.y + (b.h - 16) / 2, b.w, 2, WHITE);
  } else {
    labelCentered(top, b.x, b.y + 6, b.w, 1, WHITE);
    labelCentered(bottom, b.x, b.y + 21, b.w, 1, WHITE);
  }
}

void drawHeader() {
  tft.fillRect(0, 0, 320, 47, NAVY);
  tft.setTextColor(WHITE);
  tft.setTextSize(2);
  tft.setCursor(10, 7);
  tft.print("UIP-6");
  tft.setTextSize(1);
  tft.setCursor(10, 29);
  tft.print("TELEZHKA KLEYA I VODY");
  tft.fillRoundRect(228, 10, 82, 25, 6, emergencyStop ? RED : GREEN);
  labelCentered(emergencyStop ? "STOP" : "MAKET", 228, 17, 82, 1, WHITE);
}

void drawProcessArea() {
  drawRoundedButton(waterButton, waterOn ? GREEN : PANEL, "VODA", waterOn ? "VKLUCHENO" : "VYKL");
  drawRoundedButton(glueButton, glueOn ? GREEN : PANEL, "KLEY", glueOn ? "VKLUCHENO" : "VYKL");
  drawRoundedButton(minusButton, PANEL, "-");
  drawRoundedButton(plusButton, PANEL, "+");

  tft.fillRoundRect(212, 102, 100, 12, 5, PANEL);
  int fillWidth = map(glueLevel, 0, 100, 0, 96);
  tft.fillRoundRect(214, 104, fillWidth, 8, 4, ORANGE);
  tft.setTextSize(1);
  tft.setTextColor(WHITE);
  tft.setCursor(212, 38);
  tft.print("PODACHA KLEYA");
  tft.fillRect(212, 48, 100, 7, NAVY);
  labelCentered(String(glueLevel) + "%", 212, 61, 100, 1, WHITE);
}

void drawMotionArea() {
  tft.fillRoundRect(8, 108, 236, 122, 8, PANEL);
  tft.setTextColor(CYAN);
  tft.setTextSize(1);
  tft.setCursor(15, 114);
  tft.print("DVIJENIE TELEZHKI");
  drawRoundedButton(upButton, NAVY, "VERH");
  drawRoundedButton(leftButton, NAVY, "NAZAD");
  drawRoundedButton(downButton, NAVY, "VNIZ");
  drawRoundedButton(rightButton, NAVY, "VPERED");
  tft.fillRoundRect(15, 202, 222, 21, 5, NAVY);
  labelCentered(motion, 15, 208, 222, 1, WHITE);
}

void drawSafetyArea() {
  drawRoundedButton(stopButton, RED, "STOP", "MAKET");
  drawRoundedButton(resetButton, emergencyStop ? ORANGE : PANEL, "SBROS");
}

void drawScreen() {
  tft.fillScreen(ILI9341_BLACK);
  drawHeader();
  drawProcessArea();
  drawMotionArea();
  drawSafetyArea();
}

void updateScreen() {
  // This UI redraws only the regions whose displayed state changed.
  tft.fillRect(0, 0, 320, 47, ILI9341_BLACK);
  drawHeader();
  tft.fillRect(8, 55, 304, 59, ILI9341_BLACK);
  drawProcessArea();
  tft.fillRect(8, 108, 304, 122, ILI9341_BLACK);
  drawMotionArea();
  drawSafetyArea();
}

bool readTouch(int16_t &screenX, int16_t &screenY) {
  if (!touch.touched()) return false;
  TS_Point point = touch.getPoint();
  // Calibration for the built-in XPT2046 touch panel in landscape orientation.
  screenX = constrain(map(point.y, 3800, 240, 0, 320), 0, 319);
  screenY = constrain(map(point.x, 200, 3700, 0, 240), 0, 239);
  return true;
}

void handleTap(int16_t x, int16_t y) {
  if (inside(stopButton, x, y)) {
    emergencyStop = true;
    waterOn = false;
    glueOn = false;
    motion = "OSTANOVLENO";
  } else if (inside(resetButton, x, y) && emergencyStop) {
    emergencyStop = false;
    motion = "OZHIDANIE";
  } else if (!emergencyStop && inside(waterButton, x, y)) {
    waterOn = !waterOn;
  } else if (!emergencyStop && inside(glueButton, x, y)) {
    glueOn = !glueOn;
  } else if (!emergencyStop && inside(minusButton, x, y)) {
    glueLevel = max(0, glueLevel - 10);
  } else if (!emergencyStop && inside(plusButton, x, y)) {
    glueLevel = min(100, glueLevel + 10);
  } else if (!emergencyStop && inside(upButton, x, y)) {
    motion = "KOMANDA: VERH";
  } else if (!emergencyStop && inside(downButton, x, y)) {
    motion = "KOMANDA: VNIZ";
  } else if (!emergencyStop && inside(leftButton, x, y)) {
    motion = "KOMANDA: NAZAD";
  } else if (!emergencyStop && inside(rightButton, x, y)) {
    motion = "KOMANDA: VPERED";
  } else {
    return;
  }
  updateScreen();
}

void setup() {
  tftSPI.begin(TFT_SCK, TFT_MISO, TFT_MOSI, TFT_CS);
  tft.begin();
  tft.setRotation(1);
  touchSPI.begin(TOUCH_SCK, TOUCH_MISO, TOUCH_MOSI, TOUCH_CS);
  touch.begin(touchSPI);
  touch.setRotation(1);
  drawScreen();
}

void loop() {
  static bool wasTouched = false;
  static uint32_t lastTapMs = 0;
  int16_t x, y;
  bool isTouched = readTouch(x, y);
  if (isTouched && !wasTouched && millis() - lastTapMs > 180) {
    handleTap(x, y);
    lastTapMs = millis();
  }
  wasTouched = isTouched;
  delay(15);
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik