
Привет, Хабр! Хочу рассказать вам историю об одном устройстве, которое было создано 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 на новый — всё наконец-то заработало как надо.
Программная часть: Как запихнуть игровой движок в 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/