Загрузка данных


#include <Stepper.h>
#include <math.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>

// --- Настройки шагового двигателя ---
const int stepsPerRev = 580; // Твое обновленное значение
Stepper stepper(stepsPerRev, 8, 9, 10, 11);

// --- Настройки пинов фоторезисторов ---
const int pinNorth = A5;
const int pinWest  = A4;
const int pinEast  = A3;
const int pinSouth = A2;

// --- Настройки дисплея ILI9341 (Software SPI) ---
#define TFT_CS    3
#define TFT_RST   4
#define TFT_DC    5
#define TFT_MOSI  6
#define TFT_CLK   7
#define TFT_MISO  12 // Пину MISO нужен любой свободный пин, даже если он не подключен

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST, TFT_MISO);

// --- Координаты и размеры графики (для экрана 320x240) ---
const int centerX = 160;     // Центр экрана по X
const int centerY = 120;     // Центр экрана по Y
const int motorRadius = 30;  // Радиус круга мотора
const int pointerLength = 45; // Длина стрелки

int currentStep = 0;
int oldPointerX = centerX + pointerLength; // Предыдущие координаты стрелки (для затирания)
int oldPointerY = centerY;

// Таймер для обновления шкал фоторезисторов
unsigned long lastBarUpdate = 0;
const unsigned long barUpdateInterval = 100; // Интервал обновления шкал (мс)

void setup() {
  Serial.begin(9600);
  stepper.setSpeed(20); 

  // Инициализация дисплея
  tft.begin();
  tft.setRotation(1); // Переворот в ландшафтный режим (320x240)
  tft.fillScreen(ILI9341_BLACK);

  // Рисуем статичный интерфейс
  drawStaticUI();
}

void loop() {
  // 1. Считываем датчики
  int valNorth = analogRead(pinNorth);
  int valWest  = analogRead(pinWest);
  int valEast  = analogRead(pinEast);
  int valSouth = analogRead(pinSouth);

  // 2. Вычисляем вектор направления
  float x = valEast - valWest;
  float y = valNorth - valSouth;

  // Логика движения мотора (выполняется только за пределами мертвой зоны)
  if (abs(x) >= 30 || abs(y) >= 30) {
    float angle = atan2(y, x);
    if (angle < 0) angle += 2 * PI;

    int targetStep = round((angle / (2 * PI)) * stepsPerRev);
    if (targetStep >= stepsPerRev) targetStep = 0;

    int diff = targetStep - currentStep;

    if (diff != 0) {
      if (diff > stepsPerRev / 2) diff -= stepsPerRev;
      else if (diff < -stepsPerRev / 2) diff += stepsPerRev;

      // Делаем 1 шаг
      if (diff > 0) {
        stepper.step(-1); 
        currentStep++;
      } else {
        stepper.step(1);
        currentStep--;
      }

      // Закольцовываем шаги
      if (currentStep >= stepsPerRev) currentStep = 0;
      else if (currentStep < 0) currentStep = stepsPerRev - 1;

      // СИНХРОНИЗАЦИЯ: Обновляем стрелку на экране вместе с шагом мотора
      updatePointer(currentStep);
      
      delay(5); // Пауза для стабильности симулятора
    }
  }

  // 3. Обновляем шкалы фоторезисторов по таймеру (чтобы мотор не лагал)
  if (millis() - lastBarUpdate >= barUpdateInterval) {
    lastBarUpdate = millis();
    updateBars(valNorth, valWest, valEast, valSouth);
  }
}

// Функция отрисовки начального экрана
void drawStaticUI() {
  // Корпус мотора в центре
  tft.drawCircle(centerX, centerY, motorRadius, ILI9341_BLUE);
  tft.drawCircle(centerX, centerY, motorRadius + 2, ILI9341_DARKGREY);
  
  // Рамки для датчиков (белые прямоугольники)
  tft.drawRect(130, 10, 60, 20, ILI9341_WHITE);   // Север
  tft.drawRect(130, 210, 60, 20, ILI9341_WHITE);  // Юг
  tft.drawRect(10, 90, 20, 60, ILI9341_WHITE);    // Запад
  tft.drawRect(290, 90, 20, 60, ILI9341_WHITE);   // Восток

  // Текст-подписи направлений
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(1);
  tft.setCursor(155, 35);  tft.print("N");
  tft.setCursor(155, 195); tft.print("S");
  tft.setCursor(35, 115);  tft.print("W");
  tft.setCursor(275, 115); tft.print("E");

  // Первоначальное положение стрелки (на Восток)
  tft.drawLine(centerX, centerY, oldPointerX, oldPointerY, ILI9341_RED);
}

// Функция плавного перемещения стрелки
void updatePointer(int step) {
  // Переводим текущий шаг в радианы
  float angle = (step / (float)stepsPerRev) * 2 * PI;
  
  // Рассчитываем координаты конца стрелки
  // Знак минус перед синусом нужен, так как на экранах координата Y идет сверху вниз
  int newX = centerX + pointerLength * cos(angle);
  int newY = centerY - pointerLength * sin(angle);

  // Перерисовываем только если положение изменилось
  if (newX != oldPointerX || newY != oldPointerY) {
    // 1. Стираем старую стрелку черным цветом
    tft.drawLine(centerX, centerY, oldPointerX, oldPointerY, ILI9341_BLACK);
    
    // Восстанавливаем синюю окружность мотора, которую могла пересечь старая стрелка
    tft.drawCircle(centerX, centerY, motorRadius, ILI9341_BLUE);

    // 2. Рисуем новую стрелку красным цветом
    tft.drawLine(centerX, centerY, newX, newY, ILI9341_RED);
    
    // Запоминаем текущие координаты для следующего шага
    oldPointerX = newX;
    oldPointerY = newY;
  }
}

// Функция заполнения прямоугольников датчиков
void updateBars(int n, int w, int e, int s) {
  // Карта значений: переводим 0..1023 из аналогового пина в пиксели (макс. длина заполнения 58)
  int barN = map(n, 0, 1023, 0, 58);
  int barS = map(s, 0, 1023, 0, 58);
  int barW = map(w, 0, 1023, 0, 58);
  int barE = map(e, 0, 1023, 0, 58);

  // --- СЕВЕР (заполнение слева направо) ---
  tft.fillRect(131, 11, barN, 18, ILI9341_GREEN);               // Заполненная часть
  tft.fillRect(131 + barN, 11, 58 - barN, 18, ILI9341_BLACK);   // Оставшаяся пустая часть

  // --- ЮГ (заполнение слева направо) ---
  tft.fillRect(131, 211, barS, 18, ILI9341_GREEN);
  tft.fillRect(131 + barS, 211, 58 - barS, 18, ILI9341_BLACK);

  // --- ЗАПАД (заполнение снизу вверх) ---
  tft.fillRect(11, 91 + (58 - barW), 18, barW, ILI9341_GREEN);
  tft.fillRect(11, 91, 18, 58 - barW, ILI9341_BLACK);

  // --- ВОСТОК (заполнение снизу вверх) ---
  tft.fillRect(291, 91 + (58 - barE), 18, barE, ILI9341_GREEN);
  tft.fillRect(291, 91, 18, 58 - barE, ILI9341_BLACK);
}