ARDUINO эмулятор "Волшебного экрана"

Sergey228

✩✩✩✩✩✩✩
24 Май 2026
0
0
TFT дисплей - сам экран. Там будет точка, которую можно перемещать двумя энкодерами или потенциометрами. В качестве дополнения можно сделать выбор между цветами с помощью кнопки.
 

wizard suleiman

✩✩✩✩✩✩✩
13 Окт 2023
73
3
@Sergey228, а вы похоже такой же ленивый как я,))) лень было собирать волшебный экран с механикой, и решил рисовать на дисплее))

надеюсь не перепутал код, дисплей занят, и проверить не могу, если работать не будет, другой поищу...


C++:
#include <TFT_eSPI.h>
#include <SPI.h>
#include <SD.h>

TFT_eSPI tft = TFT_eSPI();

// Пины SD-карты (для ESP32)
#define SD_CS    5
#define SD_SCLK 18
#define SD_MOSI 23
#define SD_MISO 19

// Цвета
#define LIGHT_YELLOW TFT_YELLOW  // Светло-желтый фон
#define DARK_YELLOW  0x8400      // Темно-желтый для контура
#define TRACE_COLOR  TFT_RED     // Красный след
#define CIRCLE_COLOR LIGHT_YELLOW // Круг цвета фона (чтобы не закрашивал)

// Радиус круга
#define CIRCLE_RADIUS 2

// Размеры дисплея
#define DISPLAY_WIDTH  128
#define DISPLAY_HEIGHT 160

// Переменные для движения
int currentX = 0, currentY = 0;
int targetX = 0, targetY = 0;
float posX = 0, posY = 0;
float speed = 1.5; // Скорость движения

File dataFile;
bool hasMorePoints = true;
int pointCount = 0;

// Границы исходных координат (предполагаем, что в файле координаты от 0 до 100)
#define SOURCE_MIN 0
#define SOURCE_MAX 168

void setup() {
  Serial.begin(115200);
 
  // Инициализация дисплея
  tft.init();
  tft.setRotation(2); // Для 160x128 обычно rotation 1 или 3
 
  // Проверяем размеры
  Serial.print("Display width: ");
  Serial.println(tft.width());
  Serial.print("Display height: ");
  Serial.println(tft.height());
 
  tft.fillScreen(LIGHT_YELLOW);
 
  // Подсветка
  pinMode(TFT_BL, OUTPUT);
  digitalWrite(TFT_BL, HIGH);
 
  // Инициализация SD карты
  SPI.begin(SD_SCLK, SD_MISO, SD_MOSI, SD_CS);
  if (!SD.begin(SD_CS)) {
    Serial.println("SD card failed!");
    showError("SD Error");
    return;
  }
  Serial.println("SD card OK");
 
  // Открываем файл с координатами
  dataFile = SD.open("/trace.txt", FILE_READ);
  if (!dataFile) {
    Serial.println("File not found!");
    showError("No trace.txt");
    return;
  }
  Serial.println("File opened");
 
  // Пропускаем первую строку (комментарий)
  String firstLine = dataFile.readStringUntil('\n');
  Serial.print("Skipped: ");
  Serial.println(firstLine);
 
  // Читаем первую точку
  if (readNextPoint()) {
    posX = targetX;
    posY = targetY;
    currentX = targetX;
    currentY = targetY;
    
    // Рисуем начальный круг
    drawTracePoint(currentX, currentY);
    Serial.println("Ready to draw!");
  } else {
    Serial.println("No points in file");
    showError("Empty file");
  }
}

// Функция центрирования координат
void centerCoordinates(int &x, int &y) {
  // Масштабируем координаты с сохранением пропорций
  // Находим максимальное значение из исходных координат
  int maxSource = SOURCE_MAX;
 
  // Вычисляем масштаб по каждой оси
  float scaleX = (float)(DISPLAY_WIDTH - 20) / maxSource;  // отступ 10 пикселей с каждой стороны
  float scaleY = (float)(DISPLAY_HEIGHT - 20) / maxSource;
 
  // Используем меньший масштаб чтобы сохранить пропорции
  float scale = min(scaleX, scaleY);
 
  // Вычисляем центрированные координаты
  int scaledX = x * scale;
  int scaledY = y * scale;
 
  // Смещаем для центрирования
  int offsetX = (DISPLAY_WIDTH - (maxSource * scale)) / 2;
  int offsetY = (DISPLAY_HEIGHT - (maxSource * scale)) / 2;
 
  x = scaledX + offsetX;
  y = scaledY + offsetY;
 
  // Для отладки
  Serial.print("Original: (");
  Serial.print(x - offsetX);
  Serial.print(", ");
  Serial.print(y - offsetY);
  Serial.print(") -> Scaled: (");
  Serial.print(x);
  Serial.print(", ");
  Serial.print(y);
  Serial.println(")");
}

// Альтернативная функция центрирования с ручной настройкой отступов
void centerCoordinatesManual(int &x, int &y, int margin = 10) {
  // Масштабируем с заданным отступом от краев
  int usableWidth = DISPLAY_WIDTH - (2 * margin);
  int usableHeight = DISPLAY_HEIGHT - (2 * margin);
 
  float scaleX = (float)usableWidth / SOURCE_MAX;
  float scaleY = (float)usableHeight / SOURCE_MAX;
  float scale = min(scaleX, scaleY);
 
  x = margin + (x * scale);
  y = margin + (y * scale);
}

bool readNextPoint() {
  if (!dataFile || !dataFile.available()) {
    hasMorePoints = false;
    return false;
  }
 
  String line = dataFile.readStringUntil('\n');
  line.trim();
 
  // Пропускаем пустые строки
  if (line.length() == 0) {
    return readNextPoint();
  }
 
  // Парсим X,Y
  int commaIndex = line.indexOf(',');
  if (commaIndex == -1) {
    return readNextPoint();
  }
 
  String xStr = line.substring(0, commaIndex);
  String yStr = line.substring(commaIndex + 1);
 
  targetX = xStr.toInt();
  targetY = yStr.toInt();
 
  // Сохраняем оригинальные координаты для отладки
  int originalX = targetX;
  int originalY = targetY;
 
  // Центрируем координаты
  centerCoordinates(targetX, targetY);
  // centerCoordinatesManual(targetX, targetY, 15); // Альтернативный вариант с отступом 15
 
  // Ограничиваем координаты
  targetX = constrain(targetX, 0, DISPLAY_WIDTH - 1);
  targetY = constrain(targetY, 0, DISPLAY_HEIGHT - 1);
 
  pointCount++;
 
  Serial.print("Point ");
  Serial.print(pointCount);
  Serial.print(": (");
  Serial.print(originalX);
  Serial.print(",");
  Serial.print(originalY);
  Serial.print(") -> (");
  Serial.print(targetX);
  Serial.print(",");
  Serial.print(targetY);
  Serial.println(")");
 
  return true;
}

void drawTracePoint(int x, int y) {
  // Рисуем точку следа (красную)
  tft.drawPixel(x, y, TRACE_COLOR);
}

void drawMovingCircle(int x, int y) {
  // Рисуем круг цвета фона (чтобы не закрашивать след)
  tft.fillCircle(x, y, CIRCLE_RADIUS, LIGHT_YELLOW);
  // Рисуем контур темно-желтым
  tft.drawCircle(x, y, CIRCLE_RADIUS, DARK_YELLOW);
}

void showError(String msg) {
  tft.fillScreen(TFT_RED);
  tft.setTextColor(TFT_WHITE);
  tft.setTextSize(2);
  tft.setCursor(20, 50);
  tft.print(msg);
  while(1);
}

void loop() {
  if (!hasMorePoints) {
    // Конец рисования - мигаем
    for (int i = 0; i < 5; i++) {
      tft.fillCircle(currentX, currentY, CIRCLE_RADIUS + 2, TFT_WHITE);
      delay(200);
      drawMovingCircle(currentX, currentY);
      delay(200);
    }
    while(1) {
      delay(1000);
    }
  }
 
  // Плавное движение к цели
  float dx = targetX - posX;
  float dy = targetY - posY;
  float distance = sqrt(dx*dx + dy*dy);
 
  if (distance < speed) {
    // Достигли цели
    posX = targetX;
    posY = targetY;
    
    // Рисуем точку в текущей позиции
    drawTracePoint((int)posX, (int)posY);
    
    // Обновляем текущие координаты для круга
    currentX = (int)posX;
    currentY = (int)posY;
    
    // Рисуем круг в новой позиции
    drawMovingCircle(currentX, currentY);
    
    // Читаем следующую точку
    if (!readNextPoint()) {
      Serial.println("Finished!");
      Serial.print("Total points drawn: ");
      Serial.println(pointCount);
    }
  } else {
    // Двигаемся к цели
    posX += (dx / distance) * speed;
    posY += (dy / distance) * speed;
    
    int newX = (int)posX;
    int newY = (int)posY;
    
    // Если позиция изменилась
    if (newX != currentX || newY != currentY) {
      // Рисуем линию от старой позиции к новой
      tft.drawLine(currentX, currentY, newX, newY, TRACE_COLOR);
      
      // Обновляем текущую позицию
      currentX = newX;
      currentY = newY;
      
      // Рисуем круг на новой позиции
      drawMovingCircle(currentX, currentY);
    }
  }
 
  delay(10); // Скорость анимации
}
рисунок с координатами на сайте возьмите, если еще не будет работать попробуйте удалить первую строчку координат из документа trace.txt
если захотите сами сделать... вроде хоть я и не умею объяснять, но разобраться можно наверное...