Как я спаял электронные игральные кости на базе ATtiny85
15 февраля 2017
В этом посте мне хотелось бы чуть подробнее рассказать о проекте электронных игральных костей, который ранее был упомянут в заметке о мультиплексировании светодиодов и кнопок. Игральные кости плохо видно в темноте и они постоянно укатываются со стола на пол. Мне показалось, что их электронная версия является довольно полезным устройством, лишенного названных недостатков, и я принялся за проектирование платы.
Код прошивки вы уже могли ранее видеть на GitHub, но я продублирую его здесь еще раз:
#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 — для ввода.
Например, код:
PORTB |= (1 << 3);
… переключает пин PB3 на выход и подает на него высокое напряжение. В микроконтроллерах с большим числом пинов могут быть аналогичные регистры с другими буквами на конце — DDRC, PORTD и так далее. Наконец, стоит отметить, что когда пин настроен на ввод, соответствующие ему биты регистра PORTx не становятся бесполезными. Вместо этого их роль меняется — они включают / выключают подтягивающие регистры на этом пине. Если подтягивающий резистор выключен, пин имеет Z-состояние.
Как вы можете видеть по коду, генерация случайных чисел реализована путем инкремента двух счетчиков в бесконечном цикле. Это гарантирует равномерное распределения случайных чисел без использования операций умножения и деления. Генерируемые числа зависят от момента нажатия пользователем на кнопку, поэтому получаются чуть ли не истинно случайными, и меняются от одного включения устройства к другому. Анимация, которая как бы изображает перекатывание костей, на самом деле просто очень быстро выводит предыдущие 6 чисел, выпавших на каждой из костей.
Окончательный вид устройства:
Кости сделаны разными цветами специально. В играх, где нужна только одна кость, игроки могут договориться использовать только красную или только синюю. Устройство можно спокойно поворачивать, и никто из игроков (которые к тому же обычно садятся вкруг) не запутается, где левая кость, а где правая.
Плата односторонняя, если не считать нескольких проводов, и изготовлена при помощи пленочного фоторезиста. Об этом методе изготовления печатных плат я непременно расскажу в одном из будущих постов. С тем же успехом вы можете использовать и ЛУТ.
Все исходники к данному посту, включающие в себя код прошивки и проект платы, вы найдете на GitHub. Желающие заказать готовую плату могут сделать это на OSH Park. Как всегда, буду рад вашим вопросам и дополнениям.
Дополнение: В заметке Паяем таймер и матрицу из УФ-светодиодов для быстрой засветки фоторезиста вы найдете пример кода, работающего с EEPROM. Если же вас интересует пример использования аппаратного UART, он приводится в посте Микроконтроллеры AVR: пример работы с часами реального времени DS1302.
Метки: AVR, Электроника.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.