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

15 февраля 2017

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

Код прошивки вы уже могли ранее видеть на 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.

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.