Загрузка данных
#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);
}