Решив использовать последние дни отпуска для приведения имеющихся у меня запасов электронных компонентов к некоторому подобию порядка я наткнулся на неизвестную михросхему SOT-23-6 с еле читаемой маркировкой.

Микроскоп и гугл помогли идентифицировать микосхему как Attiny10 с 1024 байтами FLASH и 32 байтами SRAM.

Увидев такие весьма ограниченные характеристики я уже хотел было убрать Attiny10 обратно в коробку но почувствовал настальгию. Дело в том, что свой путь в микроэлектронике я начинал именно с микроконтроллеров серии AVR. Мне захотелось сделать на базе микроконтроллера какой нибудь полноценный девайс. В наличии у меня так же оказался OLED LCD Display размером 128x64.

Таким образом идея тетриса и сформировалась окончательно.

Электронная часть:

Я решил подключить LCD дисплей по протоколу 3-SPI. Сам протокол отличается от обычного SPI тем, что значение D/C отвечающее за выбор передачи данных или комманд для дисплея, передается дополнительным нулевым битом в SPI транзакции. Тем самым позволяя не подключать D/C вывод.

Схема подключения дисплея получилась следующая:

После подключения дисплея у микроконтроллера остается всего один свободный вывод PB3, на который требуется подключить как минимум четыре кнопки. Решением в даной ситуации является использование PB3 как вход ADC для следующей схемы:

Наличие делителя обусловлено тем, что PB3 так же является сигналом reset и напряжение на нем при штатной работе не должно падать ниже 0.9V. Конечно, можно было отключить внешний ресет с помощью фьюза RSTDISBL, но в таком случае я бы потерял возможность перепрошивать микроконтроллер, который у меня к слову был всего один.

В итоге получилась следующая общая схема:

Питание осуществляется от крошечной lipo батарейки 3.7V, 180mAh подключаемой к разъему P1. Батарейка оказался единственным элементом который мне пришлось докупить для этого проекта и по заверениям продавца имеет встроенный контроль за over discharge. По этой причине на схеме отсутствует какой либо контроль за разрядкой источника питани��. Так же на схеме нет отдельного разъема для программирование, не считая PLS штырька для RESET. Часть сигналов программирования (PB0 — TPIDATA, PB1 — TPICLK) подключается через разъем дисплея P2. Сам дисплей съемный и стыкуется через PLS.

Вот так выглядит плата после разводки:

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

После монтажа всех компонентов получилось следующее:

Программная часть

Учитывая ограниченный объем имеющихся ресурсов Attiny10 вся программная часть реализована на ассемблере без использования сторонних библиотек.

Первое что я сделал это получил значения ADC для нажатия кнопок. Для этого написал отдельную программу суть которой постоянно считывать значения ADC и выводитьэти значения на gpio в формате 8 битной посылки. Дабы не тратить время на прием данных я просто подключился осциллографом к нужным gpio и таким образом считал нужные значения ADC.

Для примера вот так выглядит посылка при нажатии кнопки UP:

 значение ADC: 0b10000000
значение ADC: 0b10000000

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

Работа с дисплеем

Все объекты отображаемые на дисплее хранятся в SRAM микроконтроллера и учитывая объем в 32 байта тут не обойтись без масштабирования. Поэтому минимальной единицей отображения на дисплее будет квадрат 8х8 пикселей в результате игровое поле получилось размером 8х16 квадратов. Поскольку дисплей монохромный то для хранения значения квадрата хватит одного бита. В итоге, все объекты отображаемые на дисплее укладываются в массив из 16 байт. Чтобы было понятней приведу пример:

0 — 00010000

1 — 00111000

2 — 00 000 000

……………...

15 — 00 000 000

uint8_t t_field[] = {0x10, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

Нулевой байт массива соответствует первой строчке игрового поля, 15 — последней. Вот как выглядит это на дисплее:

Сам дисплей работает в horizontal addressing mode, что позволяет легко обновлять изображение просто последовательно передавая данные, инкрементация страниц происходит в самом дисплее.

Отрисовка элементов

Чтобы максимально упростить задачу отрисовки каждый элемент я задаю 8 битным паттерном. Например для элемента

паттерны будут следующие:

Каждый внутренний квадрат содержит номер бита в паттерне. В центре находится точка вращения. Структура описывающая текущий элемент содержит следующие поля:

brick_x координата x

brick_y координата y

brick_rot положение относительно точки вращения

brick_type тип элемента

Плюс такого подхода в том что существенно сокращается объем кода так как все элементы обрабатываются одинаково.

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

Псевдо рандом и некоторые детали реализации

Рандом я сделал на основе комбинировании событий от нажатия кнопок и free run счетчика изменяющегося по таймеру. Получилось не идеально но вполне играбельно.

После включения питания на дисплее отображается заставка с надписью PUSH, разница во времени от появления заставки до нажатия произвольной клавиши обеспечивает случайность в выборе начальной фигуры. Предусмотрен счетчик для подсчета результатов. Каждый раз когда происходит удаление заполненной линии, счетчик инкрементирует на единицу. Изначально я хотел выводить результат в последней строке в виде двоичного кода, но в итоге от этого отказался, выглядело странно и сбивало игрока с толку. Счетчику нашлось другое применение: пауза между смещениями элемента вниз изначально равна 0.7 секунд, по мере набора очков пауза меняется следующим образом:

счетчик: 0..15 пауза 0.7

счетчик:16..31 пауза 0.6

счетчик:32..63 пауза 0.5

счетчик: > 63 пауза 0.4

В результате по мере прогресса в игре скорость падения элементов ускоряется внося дополнительную сложность.

Исходники собирались в Atmel Studio 6. Вся прошивка занимает 898 байт.

Корпус и сборка

Для полноценного девайса нужен корпус и этот корпус стал наверное самой сложной частью проекта. Я перепробовал несколько вариантов, истратил метры филламента и в итоге удалось создать приемлемый вариант:

Для создания 3д модели корпуса я использовал Fusion 360, печатал на Flying Bear Ghost 6, материал PLA.

Процесс сборки

Все исходники и материалы к проекту лежат по ссылке

Посмотреть как все выглядит в процессе работы можно на видео:

UPD: Добавил элемент "прямая линия из 4х квадратов". Репозиторий обновил. Всем спасибо за отзывы.