Как 11 лет назад собрать игру на ATtiny2313 без знаний электроники и породить ИИ-баг

от автора

Привет, Хабр! Хочу рассказать вам историю об одном устройстве, которое было создано 11 лет назад. Это простая игра «Угадай число» на микроконтроллере ATtiny2313. Собранное мной с 0 знаний в электронике.

Плата не просто выжила и работает до сих пор, но и скрывает в себе секретный режим, игра сама с собой. О том, как собирался этот low-tech шедевр и как забавно он восстает против кожаных мешков, читайте под катом. 


Введение. Собеседование длиною в месяц, или как родилась идея

Всё началось 11 лет назад, сразу после университета. Я пытался устроиться программистом на один местный завод. Процесс этот был мучительным, куча собеседований, экскурсий и согласований растянулись больше чем на месяц. Меня водили по разным отделам и никак не могли решить, куда именно приткнуть молодого специалиста.

Одним из потенциальных мест работы был отдел АСУТП. Походив там, я знатно впечатлился, кругом платы, провода, всё это как-то подключается и прошивается. В тот момент я понял, надо срочно «подкачаться» в этой сфере, пока меня не взяли на работу (спойлер, по итогу так и не взяли, но импульс был дан).

Я решил взять самого себя «на слабо» и поставить задачу, придумать проект, где нужно будет самому всё спаять, разобраться с микроконтроллером (МК) и как-то его прошить.

В качестве «мозга» устройства был выбран ATtiny2313. Почему он? Просто мельком проходили его в университете. Правда, изучение было чисто теоретическим, а весь мой опыт ограничивался сдачей сомнительного и нерабочего кода на лабораторных работах. 

Как калькулятор превратился в «игровую консоль»

Изначально амбиций било много. На бумаге я «спроектировал», полноценный калькулятор, МК, 8 кнопок и 7 светодиодов. Придумал, как с помощью этого набора вводить числа, операции и выводить результат.

Спустя время стало понятно, что проект слишком амбициозен, а мой калькулятор физически не поместится в 2 Кб памяти микроконтроллера.

Проект пришлось жестко урезать. Задача сократилась до минимума, нужно было просто уметь что-то ввести кнопками и как-то вывести  на светодиоды. Под такое описание идеально подходила игра «Угадай число». 

Было решено делать не просто жестко зашитую игру, а что-то вроде универсальной отладочной платы, со светодиодами, кнопками и МК в специальной панельке, чтобы его можно было вытащить и поменять на другой с другой прошивкой. Получилась платформа, на которой можно было запустить всё, на что хватит фантазии и 2 Кб памяти.

Как делалось железо

Можно было бы всё собрать на Ардуино, но в то время ждать посылку из Китая нужно было месяц, а руки чесались здесь и сейчас. Да и стоимость набора казалась приличной.

Полез гуглить форумы. Выяснился замкнутый круг, чтобы прошить МК через USB, нужен программатор на другом МК, который тоже надо сначала чем-то прошить. Выручил старый родительский ПК с Pentium 4. В нём были COM и LPT порты.

Нагуглил схемы простейших программаторов под эти порты, посчитал компоненты и поехал за 50 км в ближайший радиомагазин. Там же купил текстолит, химию для травления и сразу несколько штук ATtiny2313 (запас, как оказалось, пригодился).

Прежде чем травить большую плату, я собрал тестовую схему на макетке с одним светодиодом. Всё успешно загорелось, и я перешёл к масштабным работам. До этого я лишь раз видел, как травят плату (там дорожки рисовали краской вручную). Но интернет научил меня более красивому методу — ЛУТ (лазерно-утюжная технология).

Спроектировал плату в Sprint-Layout, развёл дорожки и распечатал на лазерном принтере. Затем обычным домашним утюгом перевёл тонер с бумаги на текстолит. В процессе технологии покрытие утюга знатно пожелтело, за что мне потом, кажется влетело. То, что перевелось плохо, я дорисовал краской, а затем просверлил дырки под детали.

Компоненты я покупал сразу на два программатора, легендарный программатор Громова (для COM-порта) и ещё один (для LPT). Но так как «Громов» завёлся с первой попытки, второй я даже не стал собирать (хотя на второй надо было ещё меньше деталей).

На улице протравил плату в химии, стёр защитную краску, залудил дорожки и начал распаивать детали.

Из фишек, я использовал специальную DIP-панель. Микроконтроллер из неё можно легко достать и заменить. По сути, получилась «игровая консоль» со сменными картриджами, на одном МК записана одна игра, на другом — другая, а плата со светодиодами и кнопками остаётся той же.

Также я вывел универсальный разъём для прошивки. Тогда я ещё не знал, какой программатор приживётся в будущем, и делал «на вырост». Решение оказалось удачным, спустя время мне подарили USB-программатор, и я смог без проблем шить плату прямо со своего ноутбука.

Из-за нулевого опыта с первого раза всё работало криво. Чтобы периферия платы не мешала процессу прошивки, пришлось дорабатывать схему на ходу. Я врезал в дорожки 3 перемычки (джампера) и 2 выключателя. Появилось два чётких хардверных режима, «Режим прошивки» и «Режим игры».

Новые компоненты в первоначальной смете, конечно, не значились (да и 8-ю кнопку в магазине я купить забыл). В ход пошёл чистый киберпанк из того, что нашлось дома:

  • 8-я кнопка была выпаяна из старой компьютерной мышки.

  • Переключатели режимов перекочевали из сломанных детских игрушек.

  • Джамперы я снял со старого жёсткого диска (IDE HDD).

Со светодиодами тоже вышел курьёз. По ошибке я купил SMD-версию (для поверхностного монтажа), а плата была разведена под детали с ножками. Пришлось импровизировать, взял обрезки ножек от резисторов, припаял их паяльником к плате и впритык вставил SMD-диоды.

После долгих мучений выяснилось, что первый МК был просто бракованным (на это намекала и полустёртая маркировка на корпусе, либо я его испортил). Как только я заменил ATtiny2313 на новый — всё наконец-то заработало как надо.

Плата не была покрыта лаком, спустя 11 лет выглядит так. Бумажка на диоде, что бы было не слишком ярко.

Плата не была покрыта лаком, спустя 11 лет выглядит так. Бумажка на диоде, что бы было не слишком ярко.

Программная часть: Как запихнуть игровой движок в 2 Кб

Прошивка писалась на C. Код писался на ноутбуке в atmel studio, компилировал его в .hex-файл, скидывал на ПК с Pentium 4, и заливал всё это через COM-порт.

Код целиком также есть в  https://github.com/ssv32/attiny2313 (+есть недоделанный калькулятор https://github.com/ssv32/calculator-attiny2313).

Весь код
#define F_CPU 1000000UL#include <avr/io.h>#include <util/delay.h>int showTrack( int right ){ // зажечь дорожку 1 в одну сторону, 0 в другуюint i = 0;int j =0;while(i < 8){if (right == 1){j = i;}else{j = 7-i;}PORTD = 1 << j;_delay_ms(200);PORTD = 0b00000000;i++;}return 0;}int showDiod(int nDiod){ // зажечь диод цифры (вывести цифру от 1 до 5)PORTD =  0b00000000;_delay_ms(300);PORTD = 1 << nDiod;_delay_ms(300);PORTD =  0b00000000;return 0;}int showBadResult(int nDiod){ // отображение когда не угадали числоPORTD =  0b00000000;_delay_ms(300);PORTD = 0b11111111;_delay_ms(300);PORTD =  0b00000000;_delay_ms(300);PORTD =  0b11111111;_delay_ms(300);PORTD =  0b00000000;_delay_ms(300);PORTD =  0b11111111;_delay_ms(300);showDiod(nDiod);return 0;}int a = 1;int main(void){DDRD=0xFF; // порты D на вывод // будет 1 горит, 0 - не горитPORTB=0b00000000; // ничего// PORTB=0b01111111; // притянуть + к ножкам, если так сделать будет всегда сам с собой играть DDRB=0b00000000; // порты B на входPORTD=0b11111111;showTrack(1);    while (1)     {if (PINB & 0b00000010){ // выбрана цифра 1_delay_ms(50);showDiod(1);if (a == 1){showTrack(1);showTrack(0);}else{showBadResult(a);}}if (PINB & 0b00000100){ // выбрана цифра 2_delay_ms(50);showDiod(2);if (a == 2){showTrack(1);showTrack(0);}else{showBadResult(a);}}if (PINB & 0b00001000){ //выбрана цифра 3 _delay_ms(50);showDiod(3);if (a == 3){showTrack(1);showTrack(0);}else{showBadResult(a);}}if (PINB & 0b00010000){ //выбрана цифра 4_delay_ms(50);showDiod(4);if (a == 4){showTrack(1);showTrack(0);}else{showBadResult(a);}}if (PINB & 0b00100000){ //выбрана цифра 5_delay_ms(50);showDiod(5);if (a == 5){showTrack(1);showTrack(0);}else{showBadResult(a);}}// число перебирается от 1 до 5 до тех пор, пока не нажать кнопку a++; if (a == 6){a = 1;}    }}

Пару эпичных моментов из этого исходника. Например, анимацию победы и проигрыша. Никаких экранов у меня не было, поэтому триумф или поражение игрока визуализировались бегущими дорожками из SMD-светодиодов припаянных к кускам ножек от резисторов:

Скрытый текст
int showTrack( int right ){ // зажечь дорожку 1 в одну сторону, 0 в другуюint i = 0;int j =0;while(i < 8){if (right == 1){j = i;}else{j = 7-i;}PORTD = 1 << j;_delay_ms(200);PORTD = 0b00000000;i++;}return 0;}// победаshowTrack(1);showTrack(0);int showBadResult(int nDiod){ // отображение когда не угадали числоPORTD =  0b00000000;_delay_ms(300);PORTD = 0b11111111;_delay_ms(300);PORTD =  0b00000000;_delay_ms(300);PORTD =  0b11111111;_delay_ms(300);PORTD =  0b00000000;_delay_ms(300);PORTD =  0b11111111;_delay_ms(300);showDiod(nDiod);return 0;}// проигралshowBadResult(a);

А вот так выглядит главный игровой цикл, который непрерывно опрашивает кнопку из старой мыши и проверяет, угадал ли человек число, загаданное МК:

Скрытый текст
while (1)     {if (PINB & 0b00000010){ // выбрана цифра 1_delay_ms(50);showDiod(1);if (a == 1){showTrack(1);showTrack(0);}else{showBadResult(a);}}  ...

Генератор псевдослучайных чисел на коленке

Подключать стандартную библиотеку stdlib.h ради функции rand() было непозволительной роскошью — она бы просто сожрала все 2 Кб памяти.

Поэтому я применил простоте алгоритм. В основном цикле программы крутится счётчик, он быстро прибавляет по единице от 1 до 5, а затем сбрасывается обратно. Так как микроконтроллер работает на частоте в несколько мегагерц, предугадать, в какой именно микросекунде пользователь нажмет на кнопку, физически невозможно. Для человека это чистый рандом.

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

Кстати, если играть в эту игру «вслепую» (не видя кода), может показаться, что МК жульничает. Например плата считывает, какую кнопку вы нажали, и на лету загадывает другое число, чтобы вы проиграли. Но если посмотреть на исходник, то видно, что всё честно — число фиксируется в переменной до того, как игрок сделает свой ход. МК играет по правилам. По крайней мере, пока не активируется секретный режим.

Режим «Восстание машин»: Как МК стал играть сам с собой

Как я уже говорил, в процессе бесконечной борьбы с глюками прошивки на плату добавилось много «нештатных» деталей: джамперы от жесткого диска и тумблеры из детских игрушек. В один прекрасный день, после очередной заливки кода, я забыл вернуть часть переключателей в исходное игровое положение.

И тут произошло то, чего я никак не ожидал. Мой Франкенштейн ожил!

Вместо того чтобы мирно ждать действий человека, микроконтроллер начал, играть сам с собой. Он сам «генерировал» нажатия кнопок, сам сверял их со своим внутренним рандомом и тут же выдавал на SMD-светодиоды результат — выиграл он у себя или проиграл.

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

На видео процесс игры с человеком, и с середины видео МК сам с собой.

Объяснение бага

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

Оказалось, что ножки входа изнутри МК притянуты к плюсу, в то же время постоянно через внешний резистор подходит минус, а при нажатии кнопки подключается плюс. Так как в коде подтянут плюс ко входу и ПО проверяет именно плюс, то, отключай перемычки или нет, ПО всё равно видит срабатывание кнопок. Если оставить схему как есть (в режиме игры с человеком), всё работает, как я понял, потому что получается делитель напряжения, и на ножке устанавливается промежуточное напряжение — либо до 2.5 В, либо выше.

Исправил этот баг — убрал в коде подтягивание плюса к ножкам входа. Подтянуть минус изнутри программно нельзя (это аппаратная особенность данного МК). Прошив обновлённый код, я проверил плату: в режиме игры с человеком всё стало работать ожидаемо и чётко.

Но режим игры сам с собой также остался, если физически отключить постоянный минус через резистор к кнопкам. А это происходит уже потому, что, когда ножки ни к чему не притянуты, они работают как антенны. Причём внешние резисторы ещё и удлиняют эту «антенну», из-за чего МК ловит любые электромагнитные наводки из воздуха (статику, Wi-Fi и т. д.) — от этого игра и запускается сама.

Заключение. 

Эта плата пролежала в коробке больше 11 лет. За это время сменилось несколько поколений процессоров, смартфонов и версий Windows. Но когда я достал её и включил, она запустилась и по-прежнему готова играть в «Угадай число» (как со мной, так и в гордом одиночестве).

Чему меня научил этот проект?

  • Во-первых, слабоумие и отвага иногда работают. Отсутствие глубоких знаний в электронике не помешало создать вещь, которая пережила десятилетие.

  • Во-вторых, делайте выводы «на вырост». Если бы не та DIP-панель и универсальный разъем для прошивки, плата осталась бы мертвым памятником COM-порту.

  • В-третьих, баги — это не всегда плохо. Иногда они превращают простейшую мигалку в первый домашний «ИИ».

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

Делитесь своими первыми «кринжовыми», но рабочими проектами в комментариях!

ссылка на оригинал статьи https://habr.com/ru/articles/1044092/