Тетрис на ATtiny10

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

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

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

195c06145465e108755af9a66fc0c39f.png

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

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

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

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

a47265a9bc0c4d6937e64ee76cec0b33.png

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

f8c0dd486c417f8e31a3b9e93bdae822.png

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

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

b7fae89741a797d523b13f5d55d91a55.png

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

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

65f57cf05c08362c873657c3d4c69bbb.png

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

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

bca9b4ab75605cfdf87567eb4daa9b5c.png

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

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

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

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

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

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

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

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

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

0 — 00010000

1 — 00111000

2 — 00000000

………………

15 — 00000000

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

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

bf01cba2a5efe1ba38ce98e5a16257b8.jpg

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

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

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

07de96d7859c83d6225c46f0c1839318.png

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

06620beebacfe54be504006c898243df.png

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

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 байт.

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

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

ba274ab0b4b95de5f628eed8b75381e2.png

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

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

5c3ee15784f6d02bb2ba67b1e411eff9.jpg

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

https://github.com/staticbear/attiny10_tetris

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

Habrahabr.ru прочитано 1659 раз