// ═══════════════════════════════════════════════════════════════
// SIMPLIFIED STEPPER MOTOR CONTROL SYSTEM
// 4 Asosiy Menyu: Tezlik, Harorat, Start, Stop
// ═══════════════════════════════════════════════════════════════
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SPI.h>
#include <EEPROM.h>
// ============== PIN KONFIGURATSIYASI ==============
// TFT Display (ILI9341 240x320)
#define TFT_CS 10
#define TFT_DC 8
#define TFT_RST 9
// Encoder KY-040
#define ENC_CLK 6
#define ENC_DT 7
#define ENC_SW 2
// A4988 Stepper Driver
#define STEP_PIN 3
#define DIR_PIN 4
// NTC Temperature Sensor
#define NTC_PIN A0
// Relay Module
#define RELAY_PIN 5
// ============== EEPROM MANZILLAR ==============
#define EEPROM_SPEED 0
#define EEPROM_TEMP_MIN 2
#define EEPROM_TEMP_MAX 4
// ============== OBYEKTLAR ==============
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
// ============== HOLAT MASHINA ==============
enum SystemState {
MAIN_MENU,
SPEED_SETTING,
TEMP_SETTING,
RUNNING
};
SystemState currentState = MAIN_MENU;
// ============== MENYU ==============
const int mainMenuItems = 4;
String mainMenu[mainMenuItems] = {
"TEZLIK SOZLASH",
"HARORAT SOZLASH",
"ISHNI BOSHLASH",
"TO'XTATISH"
};
uint16_t menuColors[mainMenuItems] = {
ILI9341_CYAN,
ILI9341_ORANGE,
ILI9341_GREEN,
ILI9341_RED
};
// ============== GLOBAL O'ZGARUVCHILAR ==============
// Encoder
volatile int encoderPos = 0;
int lastEncoderPos = 0;
int menuIndex = 0;
bool buttonPressed = false;
unsigned long lastButtonPress = 0;
// Motor
int motorSpeed = 200; // RPM (10-500)
bool motorRunning = false;
bool motorDirection = true;
unsigned long stepDelay = 0;
// Harorat
float currentTemp = 0;
float lastDisplayedTemp = -999;
int tempMin = 150;
int tempMax = 250;
bool editingMin = true; // true=Min, false=Max
// Relay
bool relayState = false;
// Display
unsigned long lastUpdate = 0;
int lastDisplayedSpeed = -1;
// ============== RANGLAR ==============
#define COLOR_BG ILI9341_BLACK
#define COLOR_TEXT ILI9341_WHITE
#define COLOR_HEADER ILI9341_NAVY
#define COLOR_ACCENT ILI9341_CYAN
#define COLOR_SUCCESS ILI9341_GREEN
#define COLOR_WARNING ILI9341_YELLOW
#define COLOR_ERROR ILI9341_RED
#define COLOR_SELECTED ILI9341_ORANGE
// ============== SETUP ==============
void setup() {
Serial.begin(115200);
// Pin rejimlarini o'rnatish
pinMode(ENC_CLK, INPUT);
pinMode(ENC_DT, INPUT);
pinMode(ENC_SW, INPUT_PULLUP);
pinMode(STEP_PIN, OUTPUT);
pinMode(DIR_PIN, OUTPUT);
pinMode(RELAY_PIN, OUTPUT);
// Encoder interrupt
attachInterrupt(digitalPinToInterrupt(ENC_SW), buttonISR, FALLING);
// TFT ni ishga tushirish
tft.begin();
tft.setRotation(3); // Landscape mode (320x240)
tft.fillScreen(COLOR_BG);
// EEPROM dan o'qish
loadSettings();
// Boot screen
showBootScreen();
delay(2000);
// Asosiy menyuga o'tish
drawMainMenu();
}
// ============== MAIN LOOP ==============
void loop() {
// Encoder o'qish
handleEncoder();
// Button o'qish
handleButton();
// Motor harakati
if (motorRunning) {
runMotor();
}
// Sensor ma'lumotlarini yangilash (har 1000ms)
if (millis() - lastUpdate > 1000) {
updateSensors();
updateDisplay();
lastUpdate = millis();
}
}
// ============== ENCODER HANDLER ==============
void handleEncoder() {
static int lastCLK = HIGH;
int currentCLK = digitalRead(ENC_CLK);
if (currentCLK != lastCLK && currentCLK == LOW) {
if (digitalRead(ENC_DT) == HIGH) {
encoderPos++;
} else {
encoderPos--;
}
}
lastCLK = currentCLK;
if (encoderPos != lastEncoderPos) {
int direction = (encoderPos > lastEncoderPos) ? 1 : -1;
lastEncoderPos = encoderPos;
switch (currentState) {
case MAIN_MENU:
menuIndex = constrain(menuIndex + direction, 0, mainMenuItems - 1);
drawMainMenu();
break;
case SPEED_SETTING:
adjustSpeed(direction);
drawSpeedSetting();
break;
case TEMP_SETTING:
adjustTemp(direction);
drawTempSetting();
break;
case RUNNING:
// Ishlab turganida encoder ishlamaydi
break;
}
}
}
// ============== BUTTON HANDLER ==============
void buttonISR() {
static unsigned long lastInterrupt = 0;
unsigned long interruptTime = millis();
if (interruptTime - lastInterrupt > 200) {
buttonPressed = true;
}
lastInterrupt = interruptTime;
}
void handleButton() {
if (buttonPressed) {
buttonPressed = false;
switch (currentState) {
case MAIN_MENU:
selectMenu(menuIndex);
break;
case SPEED_SETTING:
saveSettings();
showSaveMessage();
currentState = MAIN_MENU;
menuIndex = 0;
drawMainMenu();
break;
case TEMP_SETTING:
if (editingMin) {
// Min sozlandi, Max ga o'tish
editingMin = false;
drawTempSetting();
} else {
// Max ham sozlandi, saqlash
saveSettings();
showSaveMessage();
editingMin = true;
currentState = MAIN_MENU;
menuIndex = 0;
drawMainMenu();
}
break;
case RUNNING:
// Running holatida faqat Stop menyudan to'xtatish mumkin
break;
}
}
}
// ============== SENSOR FUNKSIYALARI ==============
void updateSensors() {
// NTC Harorat (Steinhart-Hart)
int rawValue = analogRead(NTC_PIN);
if (rawValue > 0) {
float resistance = 10000.0 * (1023.0 / rawValue - 1.0);
float steinhart = resistance / 10000.0;
steinhart = log(steinhart);
steinhart /= 3950.0;
steinhart += 1.0 / (25.0 + 273.15);
steinhart = 1.0 / steinhart;
currentTemp = steinhart - 273.15;
}
// Auto relay boshqaruv (faqat ishlab turganida)
if (motorRunning) {
if (currentTemp > tempMax && !relayState) {
setRelay(true);
} else if (currentTemp < tempMin && relayState) {
setRelay(false);
}
}
}
// ============== MOTOR FUNKSIYALARI ==============
void runMotor() {
digitalWrite(STEP_PIN, HIGH);
delayMicroseconds(stepDelay);
digitalWrite(STEP_PIN, LOW);
delayMicroseconds(stepDelay);
}
void startMotor() {
motorRunning = true;
digitalWrite(DIR_PIN, motorDirection ? HIGH : LOW);
stepDelay = 60000000L / (motorSpeed * 200);
currentState = RUNNING;
drawRunningScreen();
}
void stopMotor() {
motorRunning = false;
digitalWrite(STEP_PIN, LOW);
setRelay(false);
currentState = MAIN_MENU;
menuIndex = 0;
drawMainMenu();
}
void adjustSpeed(int delta) {
motorSpeed = constrain(motorSpeed + delta * 10, 10, 500);
if (motorRunning) {
stepDelay = 60000000L / (motorSpeed * 200);
}
}
// ============== HARORAT FUNKSIYALARI ==============
void adjustTemp(int delta) {
if (editingMin) {
tempMin = constrain(tempMin + delta * 5, 150, tempMax - 10);
} else {
tempMax = constrain(tempMax + delta * 5, tempMin + 10, 250);
}
}
// ============== RELAY FUNKSIYALARI ==============
void setRelay(bool state) {
relayState = state;
digitalWrite(RELAY_PIN, state ? HIGH : LOW);
}
// ============== EEPROM ==============
void loadSettings() {
motorSpeed = EEPROM.read(EEPROM_SPEED) | (EEPROM.read(EEPROM_SPEED + 1) << 8);
tempMin = EEPROM.read(EEPROM_TEMP_MIN) | (EEPROM.read(EEPROM_TEMP_MIN + 1) << 8);
tempMax = EEPROM.read(EEPROM_TEMP_MAX) | (EEPROM.read(EEPROM_TEMP_MAX + 1) << 8);
if (motorSpeed == 0 || motorSpeed > 500) motorSpeed = 200;
if (tempMin < 150 || tempMin > 250) tempMin = 150;
if (tempMax < 150 || tempMax > 250 || tempMax <= tempMin) tempMax = 250;
}
void saveSettings() {
EEPROM.write(EEPROM_SPEED, motorSpeed & 0xFF);
EEPROM.write(EEPROM_SPEED + 1, (motorSpeed >> 8) & 0xFF);
EEPROM.write(EEPROM_TEMP_MIN, tempMin & 0xFF);
EEPROM.write(EEPROM_TEMP_MIN + 1, (tempMin >> 8) & 0xFF);
EEPROM.write(EEPROM_TEMP_MAX, tempMax & 0xFF);
EEPROM.write(EEPROM_TEMP_MAX + 1, (tempMax >> 8) & 0xFF);
}
// ============== BOOT SCREEN ==============
void showBootScreen() {
tft.fillScreen(COLOR_BG);
tft.setTextSize(4);
tft.setTextColor(COLOR_ACCENT);
tft.setCursor(40, 60);
tft.print("STEPPER");
tft.setTextSize(3);
tft.setTextColor(COLOR_TEXT);
tft.setCursor(60, 100);
tft.print("CONTROL");
tft.setTextSize(2);
tft.setCursor(100, 140);
tft.print("v3.0");
// Progress bar
for (int i = 0; i < 280; i += 20) {
tft.fillRect(20, 190, i, 15, COLOR_ACCENT);
delay(50);
}
}
// ============== ASOSIY MENYU ==============
void drawMainMenu() {
tft.fillScreen(COLOR_BG);
drawHeader("ASOSIY MENYU");
int startY = 50;
int itemHeight = 45;
for (int i = 0; i < mainMenuItems; i++) {
int y = startY + i * itemHeight;
if (i == menuIndex) {
// Tanlangan element
tft.fillRoundRect(10, y - 5, 300, 38, 8, menuColors[i]);
tft.drawRoundRect(8, y - 7, 304, 42, 8, COLOR_TEXT);
tft.setTextColor(COLOR_BG);
} else {
tft.setTextColor(menuColors[i]);
}
tft.setTextSize(2);
tft.setCursor(30, y + 5);
tft.print(mainMenu[i]);
}
drawFooter("BTN: Tanlash | ENC: Harakat");
}
void selectMenu(int index) {
switch (index) {
case 0: // Tezlik sozlash
currentState = SPEED_SETTING;
lastDisplayedSpeed = -1;
drawSpeedSetting();
break;
case 1: // Harorat sozlash
currentState = TEMP_SETTING;
editingMin = true;
drawTempSetting();
break;
case 2: // Ishni boshlash
if (!motorRunning) {
startMotor();
}
break;
case 3: // To'xtatish
if (motorRunning) {
stopMotor();
} else {
// Agar ishlamasa xabar berish
tft.fillRect(20, 210, 280, 20, COLOR_BG);
tft.setTextSize(2);
tft.setTextColor(COLOR_ERROR);
tft.setCursor(60, 210);
tft.print("ISHLAMAYAPTI!");
delay(1500);
drawMainMenu();
}
break;
}
}
// ============== TEZLIK SOZLASH ==============
void drawSpeedSetting() {
tft.fillScreen(COLOR_BG);
drawHeader("TEZLIK SOZLASH");
// Joriy tezlik
tft.setTextSize(3);
tft.setTextColor(COLOR_TEXT);
tft.setCursor(30, 60);
tft.print("Joriy tezlik:");
// Katta raqam
tft.fillRect(50, 100, 220, 60, COLOR_BG);
tft.setTextSize(6);
tft.setTextColor(COLOR_ACCENT);
tft.setCursor(60, 110);
tft.print(motorSpeed);
tft.setTextSize(3);
tft.setTextColor(COLOR_TEXT);
tft.print(" RPM");
// Progressbar
int barWidth = map(motorSpeed, 10, 500, 0, 280);
tft.drawRect(20, 180, 280, 20, COLOR_TEXT);
tft.fillRect(21, 181, barWidth, 18, COLOR_ACCENT);
drawFooter("ENC: +/- | BTN: Saqlash");
lastDisplayedSpeed = motorSpeed;
}
// ============== HARORAT SOZLASH ==============
void drawTempSetting() {
tft.fillScreen(COLOR_BG);
drawHeader("HARORAT SOZLASH");
// MIN harorat
tft.setTextSize(2);
tft.setTextColor(editingMin ? COLOR_WARNING : COLOR_TEXT);
tft.setCursor(20, 60);
tft.print(editingMin ? "> " : " ");
tft.print("MIN harorat:");
tft.setTextSize(4);
tft.setTextColor(editingMin ? COLOR_ACCENT : COLOR_TEXT);
tft.setCursor(100, 90);
tft.print(tempMin);
tft.setTextSize(2);
tft.print(" C");
// MAX harorat
tft.setTextSize(2);
tft.setTextColor(!editingMin ? COLOR_WARNING : COLOR_TEXT);
tft.setCursor(20, 140);
tft.print(!editingMin ? "> " : " ");
tft.print("MAX harorat:");
tft.setTextSize(4);
tft.setTextColor(!editingMin ? COLOR_ACCENT : COLOR_TEXT);
tft.setCursor(100, 170);
tft.print(tempMax);
tft.setTextSize(2);
tft.print(" C");
// Yordam matni
if (editingMin) {
drawFooter("ENC: Min +/- | BTN: Keyingi");
} else {
drawFooter("ENC: Max +/- | BTN: Saqlash");
}
}
// ============== RUNNING SCREEN ==============
void drawRunningScreen() {
tft.fillScreen(COLOR_BG);
drawHeader("ISHLAMOQDA");
// Tezlik
tft.setTextSize(2);
tft.setTextColor(COLOR_TEXT);
tft.setCursor(20, 50);
tft.print("Tezlik:");
tft.setTextSize(3);
tft.setTextColor(COLOR_ACCENT);
tft.setCursor(180, 45);
tft.print(motorSpeed);
tft.setTextSize(2);
tft.print(" RPM");
// Harorat
tft.setTextSize(2);
tft.setTextColor(COLOR_TEXT);
tft.setCursor(20, 90);
tft.print("Harorat:");
tft.setTextSize(4);
tft.setTextColor(COLOR_ACCENT);
tft.setCursor(180, 85);
tft.print(currentTemp, 1);
tft.setTextSize(2);
tft.print(" C");
// Chegara
tft.setTextSize(2);
tft.setTextColor(COLOR_TEXT);
tft.setCursor(20, 130);
tft.print("Chegara: ");
tft.setTextColor(COLOR_WARNING);
tft.print(tempMin);
tft.print(" - ");
tft.print(tempMax);
tft.print(" C");
// Relay holati
tft.setTextSize(2);
tft.setTextColor(COLOR_TEXT);
tft.setCursor(20, 170);
tft.print("Relay:");
tft.fillCircle(120, 178, 12, relayState ? COLOR_SUCCESS : COLOR_ERROR);
tft.setTextColor(relayState ? COLOR_SUCCESS : COLOR_ERROR);
tft.setTextSize(3);
tft.setCursor(145, 165);
tft.print(relayState ? "ON " : "OFF");
drawFooter("MENYU > TO'XTATISH");
lastDisplayedTemp = currentTemp;
}
void updateRunningScreen() {
// Haroratni yangilash
if (abs(currentTemp - lastDisplayedTemp) > 1.0) {
tft.fillRect(180, 85, 130, 35, COLOR_BG);
tft.setTextSize(4);
tft.setTextColor(COLOR_ACCENT);
tft.setCursor(180, 85);
tft.print(currentTemp, 1);
tft.setTextSize(2);
tft.print(" C");
lastDisplayedTemp = currentTemp;
}
// Relay holatini yangilash
static bool lastRelayState = false;
if (relayState != lastRelayState) {
tft.fillCircle(120, 178, 12, relayState ? COLOR_SUCCESS : COLOR_ERROR);
tft.fillRect(145, 165, 100, 25, COLOR_BG);
tft.setTextColor(relayState ? COLOR_SUCCESS : COLOR_ERROR);
tft.setTextSize(3);
tft.setCursor(145, 165);
tft.print(relayState ? "ON " : "OFF");
lastRelayState = relayState;
}
}
// ============== UI ELEMENTLAR ==============
void drawHeader(String title) {
tft.fillRect(0, 0, 320, 35, COLOR_HEADER);
tft.drawFastHLine(0, 35, 320, COLOR_ACCENT);
tft.setTextSize(2);
tft.setTextColor(COLOR_TEXT);
tft.setCursor(10, 10);
tft.print(title);
}
void drawFooter(String text) {
tft.drawFastHLine(0, 215, 320, COLOR_TEXT);
tft.setTextSize(1);
tft.setTextColor(COLOR_TEXT);
tft.setCursor(10, 225);
tft.print(text);
// Uptime
tft.setCursor(240, 225);
tft.print(millis() / 1000);
tft.print("s");
}
void showSaveMessage() {
tft.fillRect(60, 100, 200, 40, COLOR_SUCCESS);
tft.drawRect(58, 98, 204, 44, COLOR_TEXT);
tft.setTextSize(3);
tft.setTextColor(COLOR_BG);
tft.setCursor(80, 110);
tft.print("SAQLANDI!");
delay(1000);
}
// ============== DISPLAY UPDATE ==============
void updateDisplay() {
if (currentState == RUNNING) {
updateRunningScreen();
}
}