SAB линукс жив потому что кто то не сумел скупить все мозги, и закрыть все проекты)))
а рисование граффити является самоутверждением ?
для самоутверждения у меня есть библиотека с кодами, личная!)))
но как вариант, может и стоит того…
SAB линукс жив потому что кто то не сумел скупить все мозги, и закрыть все проекты)))
а рисование граффити является самоутверждением ?
для самоутверждения у меня есть библиотека с кодами, личная!)))
но как вариант, может и стоит того…
@lilik Вот ещё пример смешивания тетриса с другой игрой.
Тут скорей пародия вообще на геймплей, и на тетрис в частности ![]()
В мировосприятии или по диплому?
Тяжело ответить на вопрос зачем ![]()
…Колом колено колет вены, В надежде тайну разгадать — зачем я сажаю алюминиевые огурцы, а-а на брезентовом поле…аааа ![]()
Ну вот мы и нашли разгадку:
оказывается, интерес что-то делать бескорыстно непосредственно связан с наличием мозгов.
Ну вот они, тетрашки. ИИ написал скетч со второго раза, дальше пара моих пожеланий. Почему то как и BABOS вначале генератор сделал курсор, которым надо идти к пусто-местам. Но исправился с одной подсказки.
Сомневался потянет ли он неизвестные правила в реализации и сначала потренировался на тетрисе.
скетч тетрашек в исполнении ИИ
#include <Adafruit_GFX.h> // подключаем библиотеку Adafruit GFX для рисования
#include "Adafruit_ST7735.h" // подключаем драйвер дисплея ST7735
#include <SPI.h> // подключаем SPI для общения с дисплеем
#define TFT_CS 10 // пин CS (Chip Select) дисплея
#define TFT_DC 9 // пин DC (Data/Command) дисплея
#define TFT_RST 8 // пин RESET дисплея
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); // создаём объект дисплея
const int JOY_X_PIN = A1; // аналоговый пин для оси X джойстика
const int JOY_Y_PIN = A0; // аналоговый пин для оси Y джойстика
const int JOY_SW_PIN = 2; // цифровой пин для кнопки джойстика
const int COLS = 5; // число колонок игрового поля
const int ROWS = 6; // число строк игрового поля
const int CELL = 20; // размер одной ячейки в пикселях
const int BOARD_X = 8; // отступ поля слева в пикселях
const int BOARD_Y = 12; // отступ поля сверху в пикселях
const uint16_t COLOR_EMPTY = ST77XX_BLACK; // цвет для пустой ячейки (чёрный)
const uint16_t CHERRY = 0xD8A7; // вишнёвый цвет (RGB565 приблизительно для (220,20,60))
const uint16_t COLORS[8] = { // массив цветов для значений 0..7 (0 = пустая)
COLOR_EMPTY, // 0 — пусто
ST77XX_RED, // 1 — красный
ST77XX_GREEN, // 2 — зелёный
ST77XX_BLUE, // 3 — синий
ST77XX_CYAN, // 4 — циан
ST77XX_MAGENTA, // 5 — пурпурный
ST77XX_YELLOW, // 6 — жёлтый
CHERRY // 7 — вишнёвый (заменяет оранжевый)
};
uint8_t board[ROWS][COLS]; // матрица состояния поля (значения 0..7)
uint8_t drawn[ROWS][COLS]; // буфер того, что уже отрисовано (для минимальной перерисовки)
int emptR[2], emptC[2]; // координаты двух пустот: emptR[i], emptC[i]
int selectedEmpty = 0; // индекс выбранной пустоты (0 или 1)
unsigned long lastMoveTime = 0; // время последнего шага перемещения пустоты
const unsigned long moveRepeatDelay = 400; // задержка между шагами перемещения пустоты в мс (увеличена)
bool lastBtnState = false; // предыдущее состояние кнопки (для определения нажатия/отпускания)
unsigned long btnPressTime = 0; // время нажатия кнопки (для определения удержания)
const unsigned long holdShuffleMs = 800; // удержание >= 800 мс => Shuffle
const unsigned long holdResetMs = 1800; // удержание >= 1800 мс => Reset
const int SHUFFLE_MOVES = 300; // количество случайных перемещений при перемешивании
unsigned long moveCount = 0; // счётчик ходов (количество успешных перемещений пустот)
// функция инициализации поля — создаёт 7 групп по 4 клетки и 2 пустоты
void initBoard() { // функция инициализации поля
uint8_t flat[ROWS * COLS]; // временный массив для заполнения
int idx = 0; // индекс для плоского массива
for (uint8_t g = 1; g <= 7; ++g) { // проходим по 7 группам
for (int k = 0; k < 4; ++k) { // каждая группа содержит 4 клетки
flat[idx++] = g; // записываем идентификатор группы
}
}
flat[idx++] = 0; // добавляем первую пустоту
flat[idx++] = 0; // добавляем вторую пустоту
idx = 0; // сбрасываем индекс для заполнения матрицы
for (int r = 0; r < ROWS; ++r) { // по строкам
for (int c = 0; c < COLS; ++c) { // по столбцам
board[r][c] = flat[idx++]; // заполняем поле по строкам
drawn[r][c] = 0xFF; // пометим как "неотрисованное" для первой отрисовки
}
}
findEmpties(); // найдём позиции пустот
selectedEmpty = 0; // по умолчанию выбрана первая пустота
moveCount = 0; // сбрасываем счётчик ходов при инициализации
}
// функция поиска пустот в поле
void findEmpties() { // функция поиска пустот в поле
int cnt = 0; // счётчик найденных пустот
for (int r = 0; r < ROWS; ++r) { // по всем строкам
for (int c = 0; c < COLS; ++c) { // по всем столбцам
if (board[r][c] == 0) { // если ячейка пустая
if (cnt < 2) { emptR[cnt] = r; emptC[cnt] = c; } // сохраним координаты (до 2-х)
cnt++; // увеличим счётчик
}
}
}
if (cnt < 2) { // если по какой-то причине меньше 2 пустот
for (int r = 0; r < ROWS && cnt < 2; ++r) { // найдем клетки и сделаем их пустыми
for (int c = 0; c < COLS && cnt < 2; ++c) {
if (board[r][c] != 0) { // возьмём первую непустую
board[r][c] = 0; // сделаем её пустой
emptR[cnt] = r; emptC[cnt] = c; // сохраним координаты
cnt++; // увеличим счётчик
}
}
}
}
}
// рисует одну ячейку поля (r,c)
void drawCell(int r, int c) { // рисует одну ячейку поля (r,c)
uint8_t val = board[r][c]; // значение в ячейке
uint16_t col = (val < 8) ? COLORS[val] : COLOR_EMPTY; // выбираем цвет
int x = BOARD_X + c * CELL; // вычисляем x пиксель клетки
int y = BOARD_Y + r * CELL; // вычисляем y пиксель клетки
tft.fillRect(x, y, CELL, CELL, col); // заполняем прямоугольник цветом
tft.drawRect(x, y, CELL, CELL, ST77XX_BLACK); // рисуем тонкую чёрную рамку
if (val == 0) { // если текущая ячейка пустая
if (r == emptR[selectedEmpty] && c == emptC[selectedEmpty]) { // если это выбранная пустота
tft.drawRect(x+1, y+1, CELL-2, CELL-2, ST77XX_YELLOW); // рисуем внешнюю жёлтую рамку
tft.drawRect(x+2, y+2, CELL-4, CELL-4, ST77XX_WHITE); // и внутреннюю белую рамку
} else { // если это не выбранная пустота, но любая пустота
tft.drawRect(x+1, y+1, CELL-2, CELL-2, ST77XX_YELLOW); // рисуем одну жёлтую рамку
}
}
drawn[r][c] = board[r][c]; // обновляем буфер отрисованного
}
// вывод счётчика ходов внизу дисплея
void drawMoveCount() { // функция отрисовки счётчика ходов
int tx = BOARD_X; // позиция X для текста (под полем)
int ty = BOARD_Y + ROWS * CELL + 4; // позиция Y чуть ниже поля
tft.fillRect(tx, ty, COLS * CELL, 12, ST77XX_BLACK); // очищаем область под счётчик
tft.setCursor(tx, ty); // устанавливаем курсор текста
tft.setTextColor(ST77XX_WHITE); // цвет текста белый
tft.setTextSize(1); // размер текста 1
tft.print("Moves: "); // подпись "Moves: "
tft.print(moveCount); // печатаем текущее число ходов
}
// полная перерисовка поля и рамки
void drawGrid() { // полная перерисовка поля и рамки
tft.fillRect(BOARD_X - 2, BOARD_Y - 2, COLS * CELL + 4, ROWS * CELL + 4, ST77XX_BLACK); // очищаем область поля
tft.drawRect(BOARD_X - 2, BOARD_Y - 2, COLS * CELL + 4, ROWS * CELL + 4, ST77XX_WHITE); // рисуем рамку стакана
for (int r = 0; r < ROWS; ++r) { // цикл по строкам
for (int c = 0; c < COLS; ++c) { // цикл по столбцам
drawCell(r, c); // отрисуем каждую ячейку
}
}
drawMoveCount(); // нарисуем счётчик ходов внизу
}
// чтение аналоговой оси джойстика, выдаёт -1/0/1
int readJoyAxis(int pin) { // чтение аналоговой оси джойстика, выдаёт -1/0/1
int v = 1023-analogRead(pin); // читаем значение 0..1023
if (v < 300) return -1; // ниже нижнего порога = -1
if (v > 700) return 1; // выше верхнего порога = 1
return 0; // в середине = 0
}
// пытаемся переместить пустоту ei в направлении dr,dc; возвращаем true если успешно
bool tryMoveEmpty(int ei, int dr, int dc) { // пытаемся переместить пустоту ei в направлении dr,dc
int er = emptR[ei]; // текущая строка пустоты
int ec = emptC[ei]; // текущий столбец пустоты
int nr = er + dr; // новая строка после движения
int nc = ec + dc; // новый столбец после движения
if (nr < 0 || nr >= ROWS || nc < 0 || nc >= COLS) return false; // проверка границ
uint8_t tmp = board[nr][nc]; // возьмём значение соседней клетки
board[nr][nc] = 0; // на её место ставим пустоту
board[er][ec] = tmp; // а в старую позицию пустоты ставим этот блок
emptR[ei] = nr; emptC[ei] = nc; // обновляем координаты пустоты
int other = 1 - ei; // индекс другой пустоты
if (emptR[other] == nr && emptC[other] == nc) { // редкий случай: если другая пустота оказалась там
emptR[other] = er; emptC[other] = ec; // меняем её координаты (они просто поменяются местами)
}
drawCell(er, ec); // перерисуем старую клетку (теперь с блоком)
drawCell(nr, nc); // перерисуем новую клетку (теперь пустота)
moveCount++; // увеличиваем счётчик ходов после успешного перемещения
drawMoveCount(); // обновляем отображение счётчика ходов
return true; // успешно
}
// перемешиваем поле допустимыми перемещениями пустот
void shuffleBoard(int moves) { // перемешиваем поле допустимыми перемещениями пустот
findEmpties(); // заново найдём позиции пустот
for (int i = 0; i < moves; ++i) { // делаем moves попыток
int ei = random(0, 2); // выбираем случайную пустоту
int dir = random(0, 4); // случайное направление 0..3
int dr = 0, dc = 0; // дельты
if (dir == 0) { dr = -1; dc = 0; } // вверх
if (dir == 1) { dr = 1; dc = 0; } // вниз
if (dir == 2) { dr = 0; dc = -1; } // влево
if (dir == 3) { dr = 0; dc = 1; } // вправо
tryMoveEmpty(ei, dr, dc); // пытаемся переместить (если не выйдет — пропустим)
}
for (int r = 0; r < ROWS; ++r) for (int c = 0; c < COLS; ++c) drawn[r][c] = 0xFF; // пометим для полной перерисовки
drawGrid(); // полная перерисовка после перемешивания
}
// обработка джойстика и кнопки
void handleJoystick() { // обработка входов джойстика и кнопки
unsigned long now = millis(); // текущее время в мс
bool btn = digitalRead(JOY_SW_PIN) == LOW; // читаем кнопку (LOW при нажатии)
if (btn && !lastBtnState) { // если только что нажали
btnPressTime = now; // запоминаем время начала нажатия
}
if (!btn && lastBtnState) { // если только что отпустили кнопку
unsigned long held = now - btnPressTime; // время удержания
if (held >= holdResetMs) { // если удерживали достаточно долго для Reset
initBoard(); // инициализация поля заново
shuffleBoard(60); // лёгкое перемешивание после reset
for (int r = 0; r < ROWS; ++r) for (int c = 0; c < COLS; ++c) drawn[r][c] = 0xFF; // пометим всё как неотрисованное
drawGrid(); // перерисуем поле
lastBtnState = btn; // обновим состояние кнопки
return; // выйдем из обработки
} else if (held >= holdShuffleMs) { // если удержали для Shuffle
shuffleBoard(SHUFFLE_MOVES); // перемешаем поле
lastBtnState = btn; // обновим состояние кнопки
return; // выйдем
} else { // короткое нажатие — переключаем выбранную пустоту
selectedEmpty = 1 - selectedEmpty; // переключаем индекс выбранной пустоты
for (int r = 0; r < ROWS; ++r) for (int c = 0; c < COLS; ++c) drawn[r][c] = 0xFF; // пометим всё для перерисовки
drawGrid(); // перерисуем поле с новым выделением
}
}
lastBtnState = btn; // обновим предыдущее состояние кнопки
int ax = readJoyAxis(JOY_X_PIN); // читаем ось X джойстика (-1/0/1)
int ay = readJoyAxis(JOY_Y_PIN); // читаем ось Y джойстика (-1/0/1)
if (now - lastMoveTime > moveRepeatDelay) { // проверяем таймер между шагами перемещения пустоты
int dr = 0, dc = 0; // инициализируем направления
if (ax == -1) dr = -1; // физическая ось X вверх -> dr = -1
else if (ax == 1) dr = 1; // физическая ось X вниз -> dr = +1
else if (ay == -1) dc = -1; // физическая ось Y влево -> dc = -1
else if (ay == 1) dc = 1; // физическая ось Y вправо -> dc = +1
if ((dr != 0 || dc != 0)) { // если есть команда на движение
tryMoveEmpty(selectedEmpty, dr, dc); // переместим выбранную пустоту
lastMoveTime = now; // обновим время последнего шага
}
}
}
// настройка режимов пинов
void setupPins() { // настроим режимы пинов
pinMode(JOY_SW_PIN, INPUT_PULLUP); // кнопка — вход с внутренним pull-up
pinMode(JOY_X_PIN, INPUT); // аналоговый вход X
pinMode(JOY_Y_PIN, INPUT); // аналоговый вход Y
}
// Arduino setup()
void setup() { // Arduino setup()
setupPins(); // инициализация пинов джойстика
Serial.begin(115200); // запуск Serial для отладки (не обязателен)
tft.initR(INITR_BLACKTAB); // инициализация дисплея (возможны другие табы)
tft.setRotation(0); // ориентация экрана (меняйте по необходимости)
tft.fillScreen(ST77XX_BLACK); // очистка экрана
randomSeed(analogRead(A3) ^ micros()); // seed для random
initBoard(); // подготовка поля
drawGrid(); // рисуем поле первый раз (и счётчик)
}
// основной цикл Arduino
void loop() { // основной цикл Arduino
handleJoystick(); // обрабатываем вводы, перемещаем выбранную пустоту по запросу
// никаких дополнительных процессов — всё управление ручное через пустоты
}
Символично, 256 строк.
…надо ещё ИИ тренировать в символичности.
lilik а что происходит при нажатии на кнопку ?)))
я еще не разобрался, и дисплей кажется забыл как подключать, с пеленой смотрю…
но на кнопку начинается какое то движение,(вроде не долгое нажатие) и потенциометры вроде через раз работают….
но в целом хорошо наверное, просто я не разобрался, или 10 ток запросов и соберет)))
lilik а сможете чуть переделать свой код ?))
через потенциометры и правда не удобно управлять))) не подумал об этом сразу, но ком порт это не совсем то…
однако сможете запустить мой exe файл ?))) я сам не люблю их запускать, там может всякая гадость скрываться… ( 2,97 МБ (3 116 544 байт) )
https://ru.files.me/u/pakcuz35zj дайте знать если не скачивается, у меня такое часто бывает…
#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>
#define MATRIX_PIN 6
#define MATRIX_WIDTH 8
#define MATRIX_HEIGHT 8
Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(
MATRIX_WIDTH, MATRIX_HEIGHT, MATRIX_PIN,
NEO_MATRIX_TOP + NEO_MATRIX_LEFT +
NEO_MATRIX_ROWS + NEO_MATRIX_PROGRESSIVE,
NEO_GRB + NEO_KHZ800
);
int pixelX = 3;
int pixelY = 3;
const uint16_t PIXEL_COLOR = matrix.Color(0, 255, 0);
const uint16_t BG_COLOR = matrix.Color(0, 0, 0);
void setup() {
Serial.begin(115200);
matrix.begin();
matrix.setBrightness(30);
matrix.fillScreen(BG_COLOR);
matrix.drawPixel(pixelX, pixelY, PIXEL_COLOR);
matrix.show();
// Ждем стабилизации
delay(1000);
Serial.println("READY"); // Сигнал готовности
}
void loop() {
if (Serial.available() > 0) {
String command = Serial.readStringUntil('\n');
command.trim();
if (command == "UP") {
movePixel(0, -1);
} else if (command == "DOWN") { movePixel(0, 1);
} else if (command == "LEFT") { movePixel(-1, 0);
} else if (command == "RIGHT"){ movePixel(1, 0);
} else if (command == "RESET") { resetPixel(); }
// Быстрый ответ
Serial.println("OK");
}
}
void movePixel(int dx, int dy) {
int oldX = pixelX;
int oldY = pixelY;
pixelX = constrain(pixelX + dx, 0, MATRIX_WIDTH - 1);
pixelY = constrain(pixelY + dy, 0, MATRIX_HEIGHT - 1);
if (oldX != pixelX || oldY != pixelY) {
matrix.drawPixel(oldX, oldY, BG_COLOR);
matrix.drawPixel(pixelX, pixelY, PIXEL_COLOR);
matrix.show();
}
}
void resetPixel() {
matrix.drawPixel(pixelX, pixelY, BG_COLOR);
pixelX = 3;
pixelY = 3;
matrix.drawPixel(pixelX, pixelY, PIXEL_COLOR);
matrix.show();
}
а вот это код для уно, для теста на адресной матрицы 8 на 8,(матрица к 6 пину) и тогда мы сможем управлять с клавиатуры пк…(клавиши w,s,a,d,r а букву q - не нажимать!))) ) правда отладка программы отпадет, так как ком порт занят будет… но управление будет вроде удобным))) нажатие клавиш без энтера
вроде не плохой инструмент…
по знаниям из сети, без корочек, от того и умею много чего)))
звучит двусмысленно, и не понятно какую сторону смысла вы тут заняли)))
что то сегодня форум жестко лагает, страница иногда минуту открывается…. а редактировать сообщение вообще ад))) не понятно отредактировалось или нет…
Передача движения от одного пусто-места другому.
Хороший, делал на процессинге и в HiAsm тоже делал.
Надо через джойстик.
#define TFT_CS 10 // CS пин дисплея
#define TFT_DC 9 // DC пин дисплея
#define TFT_RST 8 // RST пин дисплея
//ST7735 SCL / SCK -> D13 (SCK)
//ST7735 SDA / MOSI -> D11 (MOSI)
Резисторы на выходы УНО стоят.
…
буду пробовать игру змейка ![]()
Вот наивный первичный текст запроса:
Напиши код для дисплея st7735 и джойстика игры змейка с пояснениями всех строк кода.
Игровое поле тёмно-зелёное определи 16x16 ячеек с размером 8x8 пикселей каждая ячейка.
Змейка становится длиннее с каждым съеденным яблоком (ячейка красного цвета).
Игра заканчивается и начинается снова после столкновения змейки с границами поля или с самой собой.
Змейка серого цвета, а голова с белыми глазками черного. Кнопка джойстика делает паузу в игре.
Под игровым полем счёт игры по скушанным яблокам, ориентация дисплея – вертикальная.
lilik змейка это слишком просто…
да подключение такое, драйвер может чуть другой… но на библиотеки espi работает идеально))) а тут после перезагрузки четко все… а так пелена, фиг с ним, лень вникать
а sd карту и загрузку анимации + на esp32 не хотите попробовать сделать ?)))
“дипа мне сказали что ты код выдаешь с первого раза, а мне с попытки 10, я в шоке с тебя)))“
а он мне Понимаю! Давайте сделаем качественную змейку для вашего дисплея.))) и выдал самую простую как вы описали, но рабочую с первого раза!))))
а квейк заново делать с полного 0 наверное бредово?)))
а если хранитель подземелья, или ред алерт, нет желания попытаться сделать с полного 0 ?))) текстуры наверное есть в сети, но еще не проверял…
еще помню игру на своем первом пк вот эту)))
https://www.drillingbilly.com/ но тут совсем не уверен что текстуры отыскать…
главная сложность, слишком мало озу… не потянет esp32 такое ?))) хотя бы в урезанной версии
Вот Qwen ответил на запрос:
Ты программист ардуино. Напиши код игры змейка для дисплея st7735, платы bluepill и джойстика с пояснениями всех строк кода.
Игровое поле тёмно-зелёное определи 16x16 ячеек с размером 8x8 пикселей каждая ячейка.
Змейка становится длиннее с каждым съеденным яблоком (ячейка красного цвета).
Игра заканчивается и начинается снова после столкновения змейки с границами поля или с самой собой.
Змейка серого цвета, а голова с белыми глазками черного. Изначально змейка располагается в центре игрового поля и имеет длину две ячейки. Кнопка джойстика запускает игру и делает паузу в игре.
Под игровым полем счёт игры по скушанным яблокам, ориентация дисплея – вертикальная.
Ниже приведён полный код игры "Змейка" для платы BluePill (STM32F103C8T6) с использованием дисплея ST7735 и аналогового джойстика (с кнопкой).
Код написан на Arduino-совместимом C++ с использованием библиотеки Adafruit_GFX и Adafruit_ST7735.
🔧 Требования к подключению:
Дисплей ST7735:
SCL → PA5
SDA → PA7
RS/DC → PA6
RST → PA4
CS → PA3
VCC → 3.3V
GND → GND
Джойстик:
VRx → PA0 (аналог)
VRy → PA1 (аналог)
SW (кнопка) → PB0 (с подтяжкой к VCC, нажатие = LOW)
⚠️ Убедитесь, что вы выбрали в Arduino IDE плату "Generic STM32F103C series", и установлены соответствующие ядра (например, от STM32duino).
📦 Необходимые библиотеки:
Adafruit_GFX
Adafruit_ST7735
Adafruit_BusIO (автоматически установится с Adafruit_GFX)
🎮 Полный код с пояснениями:
cpp
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
// Подключаем библиотеки
if (x < mid - threshold && direction.x != 1) {
direction = {-1, 0}; // Влево
} else if (x > mid + threshold && direction.x != -1) {
direction = {1, 0}; // Вправо
}
// Вертикальное движение
else if (y < mid - threshold && direction.y != 1) {
direction = {0, -1}; // Вверх
} else if (y > mid + threshold && direction.y != -1) {
direction = {0, 1}; // Вниз
}
}
// Функция: экран окончания игры
void drawGameOver() {
tft.fillScreen(ST7735_BLACK);
tft.setTextColor(ST7735_RED);
tft.setTextSize(2);
tft.setCursor(20, 50);
tft.println("GAME OVER");
tft.setTextSize(1);
tft.setCursor(30, 80);
tft.print("Score: ");
tft.println(score);
tft.setCursor(10, 100);
tft.println("Press button");
tft.println("to restart");
}
// Основная функция настройки
void setup() {
// Инициализация пинов
pinMode(JOY_BTN, INPUT_PULLUP); // Кнопка с подтяжкой
// Инициализация дисплея
tft.initR(INITR_BLACKTAB); // Для большинства ST7735 с чёрной вкладкой
tft.setRotation(1); // Портретная ориентация (128x160)
// Инициализация генератора случайных чисел
randomSeed(analogRead(PA2)); // Используем "шум" с неподключённого пина
// Стартовый экран
tft.fillScreen(ST7735_BLACK);
tft.setTextColor(ST7735_WHITE);
tft.setTextSize(1);
tft.setCursor(10, 50);
tft.println("SNAKE GAME");
tft.setCursor(10, 70);
tft.println("Press button");
tft.println("to start");
}
// Основной цикл
void loop() {
handleInput();
if (gameRunning) {
updateGame();
drawScene();
} else {
drawGameOver();
}
delay(10); // Небольшая задержка для стабильности
}
📝 Пояснения ключевых частей:
1. Инициализация дисплея
cpp
1
2
tft.initR(INITR_BLACKTAB);
tft.setRotation(1);
INITR_BLACKTAB — тип дисплея (часто встречается у недорогих ST7735).
setRotation(1) — поворот на 90°, чтобы получить портретную ориентацию (128×160).
2. Игровое поле
16×16 ячеек × 8 пикселей = 128×128 пикселей — идеально помещается в 128×160.
Оставшиеся 32 пикселя внизу используются для счёта.
3. Змейка
Хранится как массив Point.
Голова — белая, с чёрными глазками, ориентированными по направлению движения.
Тело — серое.
4. Управление
Джойстик: аналоговые оси → определяют направление.
Кнопка: запуск/пауза/рестарт.
5. Коллизии
Проверка выхода за границы (<0 или >=16).
Проверка пересечения с собственным телом.
6. Случайное яблоко
Генерируется в свободной ячейке (не на змейке).
7. Автоматический рестарт
После проигрыша игра не перезапускается автоматически, а ждёт нажатия кнопки — это удобнее для игрока.
✅ Советы по отладке:
Если дисплей не работает — проверьте тип (INITR_BLACKTAB / INITR_REDTAB).
Если джойстик "не слушается" — подстройте threshold и mid.
Убедитесь, что используется 10-битный АЦП (по умолчанию в STM32duino).
```
добавь звук через пищалку Разные звуки для начала игры, съедания яблока и окончания игры!
Отлично! Добавим звуковую индикацию через пассивную пищалку (buzzer), подключённую к одному из цифровых пинов BluePill (например, PA8).
Мы реализуем три разных звука:
Старт игры — короткая мелодия (например, восходящая гамма).
Съедено яблоко — короткий “пик”.
Игра окончена — трагичный звук (низкая нота с затуханием).
Подключение пищалки:
Пассивная пищалка (не активная!):
Один вывод → PA8
Другой → GND
Активная пищалка (с встроенным генератором) не подойдёт — она издаёт только один тон. Нам нужна пассивная, чтобы управлять частотой.
Обновлённый код с звуком
Добавим функции воспроизведения звука и вызовы в нужных местах.
cpp
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
⌄
⌄
⌄
⌄
⌄
⌄
⌄
⌄
// === ДОБАВЛЕНО: ПИН ПИЩАЛКИ ===
paused = false;
//
Проигрываем звук старта
playStartSound();
}
// === ОБНОВЛЁННАЯ ФУНКЦИЯ updateGame ===
void updateGame() {
if (paused || !gameRunning) return;
if (millis() - lastMoveTime < moveDelay) return;
lastMoveTime = millis();
Point newHead = {snake[0].x + direction.x, snake[0].y + direction.y};
// Проверка столкновения с границами или собой
if (newHead.x < 0 || newHead.x >= GRID_WIDTH || newHead.y < 0 || newHead.y >= GRID_HEIGHT ||
isSnake(newHead.x, newHead.y)) {
gameRunning = false;
//
Звук проигрыша
playGameOverSound();
return;
}
// Двигаем змейку
for (int i = snakeLength - 1; i > 0; i–) {
snake[i] = snake[i - 1];
}
snake[0] = newHead;
// Проверка поедания яблока
if (newHead.x == apple.x && newHead.y == apple.y) {
snakeLength++;
score++;
spawnApple();
//
Звук яблока
playAppleSound();
}
}
// === SETUP: добавляем инициализацию пищалки ===
void setup() {
pinMode(JOY_BTN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT); // ← НОВОЕ: настраиваем пищалку как выход
digitalWrite(BUZZER_PIN, LOW);
tft.initR(INITR_BLACKTAB);
tft.setRotation(1);
randomSeed(analogRead(PA2));
tft.fillScreen(ST7735_BLACK);
tft.setTextColor(ST7735_WHITE);
tft.setTextSize(1);
tft.setCursor(10, 50);
tft.println(“SNAKE GAME”);
tft.setCursor(10, 70);
tft.println(“Press button”);
tft.println(“to start”);
}
// loop остаётся без изменений
void loop() {
handleInput();
if (gameRunning) {
updateGame();
drawScene();
} else {
drawGameOver();
}
delay(10);
}
Что делает каждый звук:
Старт игры
4 ноты вверх: до-ре-ми-соль (весело и бодро)
Яблоко
Два коротких высоких тона — “пик-пик!”
Game Over
Три понижающихся низких тона + длинный финальный гул (как в Zelda)
Настройка под вашу пищалку
Если звук слишком тихий или не воспроизводится:
Убедитесь, что пищалка пассивная.
Попробуйте увеличить duration или уменьшить паузы.
На некоторых платах PA8 может быть занят (например, для тактирования). Если есть проблемы — переназначьте на другой пин, например PB1, и обновите #define BUZZER_PIN PB1.
lilik а самые быстрые сенсорные дисплеи ILI9341 ?
из за того что имеют 256 кб какой то своей памяти…
с максимальным расширением 480x320…
а скорее 240x320(что бы была sd карта еще)
и не считая ILI9488 из за 16 битного паралейного интерфеса…)))
какой дисплей посоветуете взять ?))) ILI9488 займет все пины…
https://ya.ru/video/preview/519726501504225858
хочу попробовать как раз https://www.drillingbilly.com/ переписать с 0, но хотелось бы провести время с дисплеем с пользой))) что бы потом уверенно его использовать в проектах, ILI9488 слишком много пинов использует, и в большинстве проектов потом его не использовать, но скорость там просто отличная…
в общем терзаюсь выбором)))
p.s. до конца не вник, кажется надо брать ili9488 там просто перемычкой переключаются 8-16 bit
lilik а я все еще ищу и пытаюсь понять)))
как я понимаю вот это фейк, и перемычка там не поможет, тупо 8 бит так и будет(но это не точно)
https://aliexpress.ru/item/1005004308953310.html
а 16 будет только на этом (подозрительно что только 1 ссылку на товар выдал поиск))))
https://aliexpress.ru/item/1005008337880346.html
и лучше тогда сразу это наверное взять
https://aliexpress.ru/item/1005006269829705.html
потому что за 3 дня как раз доставят какой у вас примерно, остальное только в китае, а что от него ожидать вообще не знаю))) но по пинам это точно не 16bit + своей памяти очень мало может быть…
то что я раньше брал для меги, и за дешево,(до 1000) не могу найти, и модуля этого не осталось…
ILI9341 по SPI держит максимум 60 мегагерц, а вот ST те аж 80 мегагерц
И что, это работает? ![]()
В общем со змейкой у ИИ затык, пришлось самому искать ошибку, благо ИИ все свои размышления подробно описывает. Надо ещё разобраться почему хвост не круглый, в скетче он круглый, в реальности нет ![]()
Ошибся он видимо из-за чрезмерного ума и желания оптимизировать код. Сделал чтение джойстика и кнопки одной функцией. В итоге змея систематически ползёть вспять и гейм овер ! Прошли почти сутки пока я понял ошибку ИИ.
#include <Adafruit_GFX.h> // подключаем Adafruit GFX
#include "Adafruit_ST7735.h" // подключаем драйвер ST7735
#include <SPI.h> // SPI для дисплея
// пины дисплея
#define TFT_CS 10 // CS
#define TFT_DC 9 // DC
#define TFT_RST 8 // RST
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); // объект дисплея
// джойстик / кнопка
const int JOY_X_PIN = A0; // ось X
const int JOY_Y_PIN = A1; // ось Y
const int JOY_SW_PIN = 2; // кнопка (на GND при нажатии)
// поле 16x16, клетка 8x8 пикселей
const int CELL = 8; // размер клетки в пикселях
const int COLS = 16; // количество колонок
const int ROWS = 16; // количество строк
const int FIELD_W = COLS * CELL; // ширина поля в пикселях
const int FIELD_H = ROWS * CELL; // высота поля в пикселях
const int BOARD_X = 0; // отступ слева
const int BOARD_Y = 0; // отступ сверху
// цвета (RGB565)
const uint16_t COLOR_CELL = 0x03A0; // тёмно-зелёный фон клетки
const uint16_t COLOR_APPLE = ST77XX_RED; // яблоко — красный
const uint16_t COLOR_SNAKE = 0x6B4D; // более серый цвет тела (пример)
const uint16_t COLOR_HEAD = 0xA514; // светло-серый для головы (пример)
const uint16_t COLOR_WHITE = ST77XX_WHITE; // белый
const uint16_t COLOR_BLACK = ST77XX_BLACK; // чёрный
// массивы для змеи (максимум COLS*ROWS сегментов)
int snakeX[COLS * ROWS]; // X координаты сегментов (в клетках)
int snakeY[COLS * ROWS]; // Y координаты сегментов (в клетках)
int snakeLen = 0; // текущая длина змеи
int dirX = 0, dirY = 0; // направление движения (вправо стартом)
// яблоко
int appleX = 0, appleY = 0; // координаты яблока (в клетках)
// таймеры, флаги и счёт
unsigned long lastStep = 0; // время последнего шага
unsigned long stepInterval = 400; // интервал между шагами (мс)
bool paused = false; // пауза флаг
bool gameOver = false; // флаг конца игры
unsigned long gameOverTime = 0; // момент game over
const unsigned long restartDelay = 1500; // задержка перед автоперезапуском
int score = 0; // счёт (кол-во съеденных яблок)
// кнопка: дебаунс
bool lastBtnStable = false; // последнее стабилизированное состояние кнопки
bool lastBtnRaw = false; // последнее сырое состояние
unsigned long lastBtnChange = 0; // время последнего изменения
const unsigned long debounceMs = 50; // дебаунс (мс)
// прототипы функций
void setupBoardCells(); // отрисовка фона поля (без сетки)
void spawnApple(); // поставить яблоко (не на змее и не на границе)
void resetGame(); // старт / сброс игры
void drawCellNoGrid(int cx, int cy, uint16_t color); // заполнение клетки цветом
void drawBodySeg(int x, int y); // рисование сегмента тела (прямоугольник)
void drawTailSeg(int x, int y); // рисование закруглённого хвоста (круг)
void drawHead(int hx, int hy); // рисование головы с глазками
void clearCell(int cx, int cy); // очистка клетки (заливка фоном)
void drawSnakeFull(); // полная отрисовка змеи (при старте)
void stepSnake(); // шаг змеи (логика и минимальная отрисовка)
void readInput(); // чтение кнопки
void readInput_dj(); // чтение джойстика
int readJoyAxis(int pin); // чтение аналоговой оси
void drawScore(); // отрисовать счёт (вызывается только при нужде)
void drawGameOver(); // отрисовать Game Over
void serialDumpState(const char *reason, int hx, int hy); // дамп состояния для отладки
// ---------------- setup ----------------
void setup() {
pinMode(JOY_SW_PIN, INPUT_PULLUP); // кнопка с внутренней подтяжкой
Serial.begin(9600); // Serial для отладки
tft.initR(INITR_BLACKTAB); // инициализация дисплея (можно другой INIT)
tft.setRotation(0); // вертикальная ориентация
tft.fillScreen(COLOR_BLACK); // очистка экрана
randomSeed(analogRead(A3) ^ micros()); // seed RNG
setupBoardCells(); // отрисовать фон поля (без сетки)
resetGame(); // старт игры
}
// ---------------- loop ----------------
void loop() {
readInput(); // читаем кнопку
unsigned long now = millis(); // текущее время
if (!paused && !gameOver && (now - lastStep >= stepInterval)) { // если не на паузе и пора шагать
readInput_dj(); // читаем джойстик
lastStep = now; // обновляем метку времени
stepSnake(); // делаем шаг (минимальная перерисовка)
// счёт обновляем только при съеденном яблоке — drawScore вызывается внутри stepSnake при поедании
}
if (gameOver) { // если игра окончена
drawGameOver(); // показываем сообщение
if (millis() - gameOverTime >= restartDelay) resetGame(); // автоперезапуск
}
}
// ---------------- отрисовка фона (без сетки) ----------------
void setupBoardCells() {
for (int r = 0; r < ROWS; ++r) { // по строкам
for (int c = 0; c < COLS; ++c) { // по столбцам
drawCellNoGrid(c, r, COLOR_CELL); // заливаем клетку тёмно-зелёным
}
}
// НЕ рисуем сетку / линии между клетками (по требованию)
// Первичную отрисовку счёта делаем при resetGame, не здесь.
}
// заполнить клетку цветом (без рамки / сетки)
void drawCellNoGrid(int cx, int cy, uint16_t color) {
int px = BOARD_X + cx * CELL; // левый верх пиксель X
int py = BOARD_Y + cy * CELL; // левый верх пиксель Y
tft.fillRect(px, py, CELL, CELL, color); // заполнить прямоугольник
// не рисуем рамку
}
// ---------------- яблоко (круг) ----------------
void spawnApple() {
int tries = 0;
while (true) {
// запрещаем границы: x in [1, COLS-2], y in [1, ROWS-2]
int ax = random(1, COLS - 1);
int ay = random(1, ROWS - 1);
bool occupied = false;
for (int i = 0; i < snakeLen; ++i) { // проверяем, не занята ли клетка змейкой
if (snakeX[i] == ax && snakeY[i] == ay) { occupied = true; break; }
}
if (!occupied) {
appleX = ax; appleY = ay;
// рисуем яблоко круглым: центр = клетка центр, радиус немного меньше половины CELL
int cx = BOARD_X + appleX * CELL + CELL / 2; // центр пикселей X
int cy = BOARD_Y + appleY * CELL + CELL / 2; // центр пикселей Y
int r = max(1, CELL / 2 - 1); // радиус круга
tft.fillCircle(cx, cy, r, COLOR_APPLE); // залитый круг яблока
// небольшой ёффект блика (маленький белый кружок)
tft.fillCircle(cx - r/2, cy - r/2, max(1, r/4), COLOR_WHITE);
break;
}
if (++tries > 2000) break; // защита
}
}
// ---------------- рисование сегментов змеи ----------------
void drawBodySeg(int x, int y) { // рисуем сегмент тела прямоугольником
drawCellNoGrid(x, y, COLOR_SNAKE);
}
void drawTailSeg(int x, int y) { // рисуем хвост закруглённым (круг)
int cx = BOARD_X + x * CELL + CELL / 2; // центр пикселей X
int cy = BOARD_Y + y * CELL + CELL / 2; // центр пикселей Y
int r = max(1, CELL / 2 - 1); // радиус круга
tft.fillCircle(cx, cy, r, COLOR_SNAKE); // залитый круг — хвост
}
void drawHead(int hx, int hy) { // рисуем голову со "глазками"
drawCellNoGrid(hx, hy, COLOR_HEAD); // сначала заливка головы
int px = BOARD_X + hx * CELL; // пиксель X левого верхнего угла головы
int py = BOARD_Y + hy * CELL; // пиксель Y левого верхнего угла головы
int eyeSize = max(1, CELL / 4); // размер глазка
// рисуем белые глазки
tft.fillRect(px + 1, py + 1, eyeSize, eyeSize, COLOR_WHITE);
tft.fillRect(px + CELL - 1 - eyeSize, py + 1, eyeSize, eyeSize, COLOR_WHITE);
// зрачки чёрные
int pupil = max(1, eyeSize / 2);
tft.fillRect(px + 1 + pupil, py + 1 + pupil, pupil, pupil, COLOR_BLACK);
tft.fillRect(px + CELL - 1 - eyeSize + pupil, py + 1 + pupil, pupil, pupil, COLOR_BLACK);
//дорисовка рта
tft.drawLine(px+1, py+6,px+6 ,py+6 ,COLOR_BLACK );
}
// очистка клетки (вернуть фон), без рисования сетки
void clearCell(int cx, int cy) {
drawCellNoGrid(cx, cy, COLOR_CELL); // просто заливаем фоном
}
// полная отрисовка змеи (при старте)
void drawSnakeFull() {
if (snakeLen <= 0) return;
for (int i = 1; i < snakeLen - 1; ++i) drawBodySeg(snakeX[i], snakeY[i]); // тело (без головы и хвоста)
if (snakeLen >= 2) drawTailSeg(snakeX[snakeLen - 1], snakeY[snakeLen - 1]); // хвост (круг)
drawHead(snakeX[0], snakeY[0]); // голова
}
// ---------------- шаг змеи (логика и минимальная отрисовка) ----------------
void stepSnake() {
// защита длины
if (snakeLen <= 0 || snakeLen > COLS * ROWS) {
serialDumpState("invalid_length", snakeX[0], snakeY[0]);
resetGame(); return;
}
int newX = snakeX[0] + dirX; // новая X головы
int newY = snakeY[0] + dirY; // новая Y головы
// проверка границ — если вылет, game over
if (newX < 0 || newX >= COLS || newY < 0 || newY >= ROWS) {
serialDumpState("wall_collision", newX, newY);
gameOver = true; gameOverTime = millis(); return;
}
// проверяем, будет ли рост (съедание яблока)
bool willGrow = (newX == appleX && newY == appleY);
// проверка самопересечения: попадание в любой сегмент тела — ошибка,
// кроме случая попадания в хвост, если хвост освободится (!willGrow)
for (int i = 0; i < snakeLen; ++i) { //////////////////////////////////////////////////////////////////////
if (snakeX[i] == newX && snakeY[i] == newY) {
if (i == snakeLen - 1 && !willGrow) {
// допустимо (входим в старую позицию хвоста, который освободится)
break;
} else {
serialDumpState("self_collision", newX, newY);
gameOver = true; gameOverTime = millis(); return;
}
}
}
// помним старый хвост и предыдущую голову
int oldTailX = snakeX[snakeLen - 1];
int oldTailY = snakeY[snakeLen - 1];
int prevHeadX = snakeX[0];
int prevHeadY = snakeY[0];
// сдвигаем все сегменты назад
for (int i = snakeLen - 1; i > 0; --i) {
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
// ставим новую голову
snakeX[0] = newX;
snakeY[0] = newY;
// если съели яблоко
if (willGrow) {
if (snakeLen < COLS * ROWS) { // рост
snakeLen++;
// добавляем новый последний сегмент на место старого хвоста
snakeX[snakeLen - 1] = oldTailX;
snakeY[snakeLen - 1] = oldTailY;
}
score++; // увеличиваем счёт
spawnApple(); // спавним новое яблоко (draw)
// обновляем только изменённые клетки:
drawBodySeg(prevHeadX, prevHeadY); // предыдущая голова -> тело
drawHead(newX, newY); // новая голова
drawTailSeg(snakeX[snakeLen - 1], snakeY[snakeLen - 1]); // новый хвост (круг)
drawScore(); // обновляем счёт только при съеденном яблоке
} else {
// обычный шаг: очистим старый хвост, превратим prev head в тело, нарисуем новую голову и новый хвост
clearCell(oldTailX, oldTailY); // очистить освобождённую клетку
drawBodySeg(prevHeadX, prevHeadY); // предыдущая голова становится телом
// новый хвост теперь расположен в snakeX[snakeLen-1], snakeY[snakeLen-1]
drawTailSeg(snakeX[snakeLen - 1], snakeY[snakeLen - 1]); // рисуем хвост (круг)
drawHead(newX, newY); // рисуем новую голову
}
}
// ---------------- ввод: джойстик + кнопка (пауза) ----------------
void readInput() {
unsigned long now = millis();
bool btnRaw = (digitalRead(JOY_SW_PIN) == LOW); // сырое состояние кнопки
if (btnRaw != lastBtnRaw) { // если поменялось сырое состояние
lastBtnChange = now; // пометим время
lastBtnRaw = btnRaw;
} else {
if ((now - lastBtnChange) > debounceMs && btnRaw != lastBtnStable) { // стабилизировалось
lastBtnStable = btnRaw; // новое стабилизированное состояние
if (lastBtnStable) { // при нажатии (LOW)
paused = !paused; // переключаем паузу
}
}
}
}
void readInput_dj() {
// оси (аналог) -> -1/0/1
int ax = readJoyAxis(JOY_X_PIN); // -1 = влево, 1 = вправо
int ay = readJoyAxis(JOY_Y_PIN); // -1 = вверх, 1 = вниз
// запрет 180° разворота
if (ax == -1 && dirX != 1) { dirX = -1; dirY = 0; } ////////////////////////////////////////////////////////////////////
else if (ax == 1 && dirX != -1) { dirX = 1; dirY = 0; }
else if (ay == -1 && dirY != 1) { dirX = 0; dirY = -1; }
else if (ay == 1 && dirY != -1) { dirX = 0; dirY = 1; }
}
// чтение аналоговой оси с порогами
int readJoyAxis(int pin) {
int v = 1023-analogRead(pin); // 0..1023
if (v < 300) return -1;
if (v > 700) return 1;
return 0;
}
// отрисовка счёта ПРИ УПОМИНАНИИ: только при resetGame и когда яблоко съедено
void drawScore() {
int tx = BOARD_X; // позиция X под полем
int ty = BOARD_Y + FIELD_H + 2; // позиция Y под полем
tft.fillRect(tx, ty, FIELD_W, 12, COLOR_BLACK); // очищаем область счёта
tft.setCursor(tx, ty); // ставим курсор
tft.setTextColor(COLOR_WHITE); // белый текст
tft.setTextSize(1); // размер 1
tft.print("Score: "); // подпись
tft.print(score); // текущее значение счёта
}
// Game Over сообщение
void drawGameOver() {
int gx = BOARD_X + FIELD_W / 4;
int gy = BOARD_Y + FIELD_H / 2 - 8;
int gw = FIELD_W / 2;
int gh = 16;
tft.fillRect(gx, gy, gw, gh, COLOR_BLACK);
tft.drawRect(gx, gy, gw, gh, COLOR_WHITE);
tft.setCursor(gx + 4, gy + 4);
tft.setTextColor(ST77XX_RED);
tft.setTextSize(1);
tft.print("GAME OVER");
}
// дамп состояния в Serial для отладки причин Game Over
void serialDumpState(const char *reason, int hx, int hy) {
Serial.println();
Serial.print("GAME OVER REASON: "); Serial.println(reason);
Serial.print("Attempted head pos: "); Serial.print(hx); Serial.print(','); Serial.println(hy);
Serial.print("dir: "); Serial.print(dirX); Serial.print(','); Serial.println(dirY);
Serial.print("snakeLen: "); Serial.println(snakeLen);
Serial.print("score: "); Serial.println(score);
Serial.println("Segments:");
for (int i = 0; i < snakeLen; ++i) {
Serial.print(i); Serial.print(": "); Serial.print(snakeX[i]); Serial.print(','); Serial.println(snakeY[i]);
}
Serial.print("Apple at: "); Serial.print(appleX); Serial.print(','); Serial.println(appleY);
Serial.println("--- end dump ---");
}
// сброс/старт игры
void resetGame() {
snakeLen = 3; // стартовая длина
int midX = COLS / 2; // центр по X
int midY = ROWS / 2; // центр по Y
for (int i = 0; i < snakeLen; ++i) { // размещаем сегменты горизонтально
snakeX[i] = midX - i; // голова в i=0
snakeY[i] = midY;
}
dirX = 1; dirY = 0; // направление вправо
score = 0; // сброс счёта
paused = false; gameOver = false;
lastStep = millis();
setupBoardCells(); // фон поля без сетки
spawnApple(); // первое яблоко (не по границе)
drawSnakeFull(); // полная отрисовка змеи
drawScore(); // отрисовать счёт 0 при старте
}
Всё-таки сколько труда вложил генератор в скетч. Конечно сам я так не напишу, но в то же время сам могу найти его ошибки и понять природу их совершения. В общем в тандеме можно с ИИ работать.
…теперь можно шары и колбы попробовать в исполнении ИИ и на zumu замахнуться ![]()
ты скажи, что с ИИ вырастет совсем другое поколение ![]()
С форматированием точно не напишешь.
Эта микросхема управления сенсорным дисплеем данного модуля. Вся своя память дисплея это его озу кадра. Объём считаем как 240х320х18 бит.
…на старом форуме была тема человека который научился читать на мк данные из дисплея, кажется на чипе st7735, на примере игры шарик выбивает фишки. Его работу обосрали. А так конечно направление перспективное, но ИИ не потянет, да и опытные программисты тоже
Тут потребуется работы паяльником.
…из проблем - дисплей скорее всего хранит пиксель в 18 битах, а получает данные в 16.
…аппаратно у него может отсутствовать вывод отдачи данных.
…библиотек толковых на эту тему нет в принципе.
…люди не понимают зачем это нужно.