#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); // Скорость анимации
}