← На главную

Как я спаял электронные игральные кости на базе ATtiny85

В этом посте мне хотелось бы чуть подробнее рассказать о проекте электронных игральных костей, который ранее был упомянут в заметке о мультиплексировании светодиодов и кнопок. Игральные кости плохо видно в темноте и они постоянно укатываются со стола на пол. Мне показалось, что их электронная версия является довольно полезным устройством, лишенного названных недостатков, и я принялся за проектирование платы.

Код прошивки вы уже могли ранее видеть на GitHub, но я продублирую его здесь еще раз:

#define F_CPU 1000000 #include <avr/io.h> #include <avr/interrupt.h> #include <avr/pgmspace.h> #include <util/delay.h> char dice1 = 5; char dice2 = 5; char rand1 = 1; char rand2 = 1; #define BTN_PIN 4 char button_pressed = 0; #define ANIMATION_LENGTH 6 char dice1_animation[ANIMATION_LENGTH] = { 5, 4, 1, 3, 2, 6 }; char dice2_animation[ANIMATION_LENGTH] = { 3, 6, 4, 1, 5, 2 }; char animation_playing = 0; char animation_index = 0; char animation_last_updated_index = 0; short animation_time = 0; void setup() { /* Do nothing */ } void out_if(char high, char low, char cond) { if(!cond) return; DDRB = (1 << low) | (1 << high); PORTB = (1 << high); _delay_ms(1); } void loop() { _delay_ms(1); /* Random number generation */ rand1++; if(rand1 > 6) { rand1 = 1; rand2++; if(rand2 > 6) { rand2 = 1; } } if(animation_playing) { animation_time++; if(animation_time >= 25) { if(animation_index >= ANIMATION_LENGTH) { /* End playing animation, display random numbers */ animation_playing = 0; dice1 = rand1; dice2 = rand2; } else { /* Play the next animation frame */ animation_time = 0; animation_index++; dice1 = dice1_animation[animation_index]; dice2 = dice2_animation[animation_index]; } } } else /* ! animation_playing */ { /* If button was pressed and then released, change dice1 and dice2 */ if((PINB & (1 << BTN_PIN)) == 0) button_pressed = 1; else if(button_pressed) { button_pressed = 0; /* Play animation first */ animation_playing = 1; animation_time = 0; animation_index = 0; dice1_animation[animation_last_updated_index] = dice1; dice2_animation[animation_last_updated_index] = dice2; animation_last_updated_index++; if(animation_last_updated_index >= ANIMATION_LENGTH) animation_last_updated_index = 0; } } /* Display dice1 */ out_if(1, 0, dice1 != 1); out_if(3, 2, dice1 == 4 || dice1 == 5 || dice1 == 6); out_if(3, 0, dice1 & 1); out_if(2, 1, dice1 == 6); /* Display dice2 */ out_if(0, 1, dice2 != 1); out_if(2, 3, dice2 == 4 || dice2 == 5 || dice2 == 6); out_if(0, 3, dice2 & 1); out_if(1, 2, dice2 == 6); } void main() { setup(); while(1) { loop(); } }

Программирование для AVR’ов на чистом C мы ранее не рассматривали, но тут все очень просто. Регистр DDRB определяет направление пинов (бит равен 1 – выход, 0 – вход), PORTB предназначен для вывода (бит равен 1 – высокое напряжение, 0 – низкое), а PINB – для ввода.

Например, код:

DDRB |= (1 << 3); PORTB |= (1 << 3);

… переключает пин PB3 на выход и подает на него высокое напряжение. В микроконтроллерах с большим числом пинов могут быть аналогичные регистры с другими буквами на конце – DDRC, PORTD и так далее. Наконец, стоит отметить, что когда пин настроен на ввод, соответствующие ему биты регистра PORTx не становятся бесполезными. Вместо этого их роль меняется – они включают / выключают подтягивающие регистры на этом пине. Если подтягивающий резистор выключен, пин имеет Z-состояние.

Как вы можете видеть по коду, генерация случайных чисел реализована путем инкремента двух счетчиков в бесконечном цикле. Это гарантирует равномерное распределения случайных чисел без использования операций умножения и деления. Генерируемые числа зависят от момента нажатия пользователем на кнопку, поэтому получаются чуть ли не истинно случайными, и меняются от одного включения устройства к другому. Анимация, которая как бы изображает перекатывание костей, на самом деле просто очень быстро выводит предыдущие 6 чисел, выпавших на каждой из костей.

Окончательный вид устройства:

Электронные игральные кости

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

Плата односторонняя, если не считать нескольких проводов, и изготовлена при помощи пленочного фоторезиста. Об этом методе изготовления печатных плат я непременно расскажу в одном из будущих постов. С тем же успехом вы можете использовать и ЛУТ.

Все исходники к данному посту, включающие в себя код прошивки и проект платы, вы найдете на GitHub. Желающие заказать готовую плату могут сделать это на OSH Park. Как всегда, буду рад вашим вопросам и дополнениям.

Дополнение: В заметке Паяем таймер и матрицу из УФ-светодиодов для быстрой засветки фоторезиста вы найдете пример кода, работающего с EEPROM. Если же вас интересует пример использования аппаратного UART, он приводится в посте Микроконтроллеры AVR: пример работы с часами реального времени DS1302.