Система для «Своєї гри»



Привіт, Хабр! Так вийшло, що одним із моїх захоплень є інтелектуальні ігри. Це «Що? Де? Коли?», «Своя гра», «Ерудит-квартет», «Брейн-ринг» та інші. І ось, одного разу мені захотілося зробити своїми руками систему для цієї гри. Якщо вам цікавий процес створення з нуля такого пристрою — запрошую під кат.

Передісторія

Коротко про правила «Своєї гри»:
Група гравців (як правило, до 4-х осіб) сідає за гральні місця. Оголошується тема, яка складається з п'яти питань. Питання йдуть за зростанням складності і відповідно оцінюються — від 10 до 50 балів. Ведучий оголошує номінал питання і починає його зачитувати. У будь-який момент читання питання гравець подає сигнал, після якого ведучий припиняє читати питання і починає відлік часу — подав сигнал гравцеві дається 3-5 секунд на відповідь. У разі правильного та своєчасного відповіді, гравець отримує кількість балів, що дорівнює номіналу питання. В іншому випадку у гравця віднімається кількість балів (Підсумковий рахунок може бути глибоко негативним).
Сигналом в цій грі може бути все що завгодно — від бавовни в долоні в найпростішому випадку, до натискання кнопки спеціалізованої системи для «Своєї гри». Переважна більшість таких систем є саморобними (не настільки інтелектуальні ігри популярні, щоб ставити цю справу на потік). Крім того, ці системи часто володіють різними вадами.

Перерахую деякі з недоліків в системах, з якими мені доводилося мати справу:
  • великі габарити;
  • робота тільки від мережі 220В;
  • робота тільки у зв'язці з комп'ютером (взаємодія зі спеціальним Windows-додатком);
  • неякісні, липкі кнопки (наприклад, від дверних дзвінків);
У нашому місті тренування з інтелектуальних ігор проходять щотижня, по неділях. Причому, в залежності від пори року і погоди, ми можемо зібратися в місцевому парку — це набагато приємніше, ніж коптитися в задушливому приміщенні. З-за своїх недоліків, наявні в нашому розпорядженні готові системи було просто лінь приносити з дому. Тому часто доводилося грати «на бавовну». Точність визначення першого, встиг подати сигнал, залишала бажати кращого. Часто бувало, що думки присутніх з цього приводу поділялися — здавалося, що першим ляснув той, хто стоїть ближче до тебе. Всі ці фактори наштовхнули мене на думку про створення своєї реалізації ігрової системи. Та й взагалі, було цікаво отримати досвід створення пристрою з нуля — від проекту до готового виробу.

Проект

Для початку, я вирішив визначитися з основної концепції пристрою. Для себе відзначив принципові пункти, які повинні бути реалізовані в системі:
  • Зручні пульти з надійними кнопками і світлодіодним індикацією (підтвердження гравцеві, що він перший натиснув на кнопку);
  • Роз'ємне з'єднання пультів і пристрої. Вирішено було зробити роз'єми як на пультах, так і на самій системі. Був обраний роз'єм 4P4C (RJ11, зазвичай застосовується в телефонних апаратах). Такий вибір був зроблений з-за невеликих розмірів і простоти ремонту кабелю при наявності обтискного інструменту і коннекторів проблема вирішується в лічені хвилини;
  • Два можливих джерела живлення — зовнішній і внутрішній. Забігаючи вперед — саме в реалізації схеми харчування я зробив найбільше число помилок;
  • Система повинна мати роз'єм внутрішньосхемного програмування (ISP), щоб можна було без зайвих клопотів міняти прошивку мікроконтролера;
  • Не повинно бути прив'язки до будь-яких інших пристроїв (комп'ютер тощо);
  • Невеликі габарити.
В якості «мізків» пристрою я вибрав надзвичайно популярний у радіоаматорів мікроконтролер від Atmel — Atmega8. Хоча для функціоналу системи з головою вистачило б якого-небудь Attiny, у нього був фатальний недолік — він не лежав у мене в столі. На відміну від вищезгаданої Atmega.

Потім я нашвидкуруч накидав приблизний принципову схему:



В якості зовнішнього джерела живлення був обраний валявся без діла блок живлення від старого модему — на виході він має 12 В змінного напруги. В ролі внутрішнього джерела я вибрав батарейки типу «Крона» — помилка №1. Для вироблення необхідних для мікроконтролера 5 В я поставив лінійний стабілізатор LM7805 — помилка №2. Про те, що це помилки, я на практиці дізнався вже після складання і тестування пристрою. Виявилося, що у «Крони» досить низька ємність, та ще й майже половина її йшла в нікуди — на підігрів лінійного стабілізатора. Однак, про це пізніше.

Кнопки і роз'єми, які я вибрав для пристрою, довелося купувати на ebay — аж надто високі ціни просили місцеві торговці. Правда, 10-міліметрові світлодіоди таки довелося придбати на радіоринку — ні на ebay, ні на aliexpress я не знайшов індикаторних різнокольорових світлодіодів потрібного діаметру.

Одним з найважливіших і визначають подальший дію кроків був вибір корпусу. В своєму місті нічого підходящого мені за якістю знайти не вдалося — довелося скористатися торговими майданчиками в Інтернеті. Влаштовує мене варіант знайшовся в Києві, там і були замовлені 5 корпусів — для основного блоку і для пультів.

І ось, маючи на руках всі необхідні елементи, я став продумувати дизайн пристрою.

Друковані плати

Маючи на руках корпусу, я приступив до розробки друкованих плат. Ось, що у мене вийшло:

Друкована плата основного блоку
Друкована плата блоку світлодіодів
Друкована плата кнопки

Макети друкованих плати створювалися в програмі Sprint Layout, в кінці будуть посилання на джерело. Використовуйте, модифікуйте — загалом, робіть з ними все що хочете.

Самі друковані плати я робив методом ЛУТ (лазерно-утюжная технологія) — на мою скромну думку, це найбільш простий і доступний простому смертному спосіб зробити друковану плату прийнятної якості.

Код

Тут, власне, лістинг програми мікроконтролера. Можливо, код не самий витончений, але він працює і свою функцію виконує на ура.

Код програми на C++
#define F_CPU 4000000UL

#define f 349
#define a 440
#define cH 523

#include <avr/io.h> // Введення/виведення, все стандартно
#include <util/delay.h> // Нам будуть потрібні тимчасові затримки
#include <avr/interrupt.h> // Без переривань теж нікуди

// Гасимо засвітилися світлодіоди

void clear_led(void)
{
PORTB = 0b00110000;
PORTC = 0xFF;
}

int blink_led(int num)
{
clear_led();
_delay_ms(100);
PORTB |= (1<<num);
_delay_ms(100);
clear_led();
_delay_ms(100);
PORTB |= (1<<num);
_delay_ms(100);
clear_led();
_delay_ms(100);
PORTB |= (1<<num);
_delay_ms(100);
clear_led();
_delay_ms(100);
PORTB |= (1<<num);
_delay_ms(100);
clear_led();
_delay_ms(100);
PORTB |= (1<<num);
_delay_ms(100);
return 0;
}

// Голос нашої програми, аргументи функції - тривалість і тон відповідно

void song(void)
{
_delay_ms(500);
int T = 1000000/440;
int k = 500000/T;
int i = 0;
int tempo = 1;
while(i<k)
{
PORTD = 0b11111111; // Тут ми робимо меандр: ставимо високий рівень на ніжці
_delay_us(T/2); // Чекаємо деякий час
PORTD = 0b01111111; // Ставимо низький рівень
_delay_us(T/2); // Знову чекаємо. Ось і готова послідовність прямокутних імпульсів
i++;
}
PORTB |= (1<<0);
PORTB |= (1<<1);
PORTB |= (1<<2);
PORTB |= (1<<3); 
PORTD = 0b01111111;
_delay_ms(500/tempo); // Завершити все ж краще тишею :)


T = 1000000/440;
k = 500000/T;
i = 0;
while(i<k)
{
PORTD = 0b11111111; // Тут ми робимо меандр: ставимо високий рівень на ніжці
_delay_us(T/2); // Чекаємо деякий час
PORTD = 0b01111111; // Ставимо низький рівень
_delay_us(T/2); // Знову чекаємо. Ось і готова послідовність прямокутних імпульсів
i++;
}
clear_led(); 
_delay_ms(500/tempo); // Завершити все ж краще тишею :)

T = 1000000/440;
k = 500000/T;
i = 0;
while(i<k)
{
PORTD = 0b11111111; // Тут ми робимо меандр: ставимо високий рівень на ніжці
_delay_us(T/2); // Чекаємо деякий час
PORTD = 0b01111111; // Ставимо низький рівень
_delay_us(T/2); // Знову чекаємо. Ось і готова послідовність прямокутних імпульсів
i++;
}
PORTB |= (1<<0);
PORTB |= (1<<1);
PORTB |= (1<<2);
PORTB |= (1<<3); 
PORTD = 0b01111111;
_delay_ms(500/tempo); // Завершити все ж краще тишею :)

T = 1000000/349;
k = 350000/T;
i = 0;
while(i<k)
{
PORTD = 0b11111111; // Тут ми робимо меандр: ставимо високий рівень на ніжці
_delay_us(T/2); // Чекаємо деякий час
PORTD = 0b01111111; // Ставимо низький рівень
_delay_us(T/2); // Знову чекаємо. Ось і готова послідовність прямокутних імпульсів
i++;
}
PORTD = 0b01111111;
_delay_ms(350/tempo); // Завершити все ж краще тишею :)

T = 1000000/523;
k = 150000/T;
i = 0;
while(i<k)
{
PORTD = 0b11111111; // Тут ми робимо меандр: ставимо високий рівень на ніжці
_delay_us(T/2); // Чекаємо деякий час
PORTD = 0b01111111; // Ставимо низький рівень
_delay_us(T/2); // Знову чекаємо. Ось і готова послідовність прямокутних імпульсів
i++;
}
PORTD = 0b01111111;
_delay_ms(150/tempo); // Завершити все ж краще тишею :)

T = 1000000/440;
k = 500000/T;
i = 0;
while(i<k)
{
PORTD = 0b11111111; // Тут ми робимо меандр: ставимо високий рівень на ніжці
_delay_us(T/2); // Чекаємо деякий час
PORTD = 0b01111111; // Ставимо низький рівень
_delay_us(T/2); // Знову чекаємо. Ось і готова послідовність прямокутних імпульсів
i++;
}
PORTD = 0b01111111;
_delay_ms(500/tempo); // Завершити все ж краще тишею :)

T = 1000000/349;
k = 350000/T;
i = 0;
while(i<k)
{
PORTD = 0b11111111; // Тут ми робимо меандр: ставимо високий рівень на ніжці
_delay_us(T/2); // Чекаємо деякий час
PORTD = 0b01111111; // Ставимо низький рівень
_delay_us(T/2); // Знову чекаємо. Ось і готова послідовність прямокутних імпульсів
i++;
}
PORTD = 0b01111111;
_delay_ms(350/tempo); // Завершити все ж краще тишею :)

T = 1000000/523;
k = 150000/T;
i = 0;
while(i<k)
{
PORTD = 0b11111111; // Тут ми робимо меандр: ставимо високий рівень на ніжці
_delay_us(T/2); // Чекаємо деякий час
PORTD = 0b01111111; // Ставимо низький рівень
_delay_us(T/2); // Знову чекаємо. Ось і готова послідовність прямокутних імпульсів
i++;
}
PORTD = 0b01111111;
_delay_ms(150/tempo); // Завершити все ж краще тишею :)

T = 1000000/440;
k = 1000000/T;
i = 0;
while(i<k)
{
PORTD = 0b11111111; // Тут ми робимо меандр: ставимо високий рівень на ніжці
_delay_us(T/2); // Чекаємо деякий час
PORTD = 0b01111111; // Ставимо низький рівень
_delay_us(T/2); // Знову чекаємо. Ось і готова послідовність прямокутних імпульсів
i++;
}
PORTB |= (1<<0);
PORTB |= (1<<1);
PORTB |= (1<<2);
PORTB |= (1<<3); 
PORTD = 0b01111111;
_delay_ms(1000); // Завершити все ж краще тишею :)
clear_led();

} 

int beep(int k, int j) //функція біпа
{
int i = 0;
while(i<k)
{
PORTD = 0b11111111; // Тут ми робимо меандр: ставимо високий рівень на ніжці
_delay_us(j); // Чекаємо деякий час
PORTD = 0b01111111; // Ставимо низький рівень
_delay_us(j); // Знову чекаємо. Ось і готова послідовність прямокутних імпульсів
i++;
}
PORTD = 0b01111111; // Завершити все ж краще тишею :)
return 0;
} 

void reset_wait(void)
{
short t = 0;

while(t==0)
{
if(!(PIND & (1<<PIND4)))
{
_delay_ms(5);
if(!(PIND & (1<<PIND4)))
{
t = 1;
clear_scr();
clear_led();
}
}
}
}


int bond007(void) // Функція-шпигун для відстеження натискання клавіш
{
short i = 0;
short p = 0;
int t = 300;
int tone = 100;

while(i==0) // Цикл відстеження натискання кнопок
{ // Відкривають дужки циклу відстеження натискань кнопок
if(!(PIND & (1<<PIND0)) || !(PIND & (1<<PIND1)) || !(PIND & (1<<PIND2)) || !(PIND & (1<<PIND3)) || !(PIND & (1<<PIND6))) 
//Якщо хоча б одна із кнопок не натиснута гравців
{ // Відкривають дужки "Якщо хоча б одна з кнопок гравців натиснута"
_delay_ms(5); // Захист від брязкоту
if(!(PIND & (1<<PIND0))) // Перевірка натискання кнопки гравця 1
{ // Відкриваюча дужка "Перевірка натискання кнопки гравця 1"
p = beep(t, tone); // Пищимо
i=1; // Прапор завершення циклу відстеження натискання кнопки
clear_led();
p = blink_led(0);
reset_wait();
i=0;
} // Закриває дужка "Перевірка натискання кнопки гравця 1"
else
{
if(!(PIND & (1<<PIND1))) //Перевірка натискання кнопки гравця 2, все інше аналогічно
{
p = beep(t, tone);
i=1;
clear_led();
p = blink_led(1);
reset_wait();
i=0;
}
else
{
if(!(PIND & (1<<PIND2))) //Перевірка натискання кнопки гравця 3
{
p = beep(t, tone);
i=1;
clear_led();
p = blink_led(2);
reset_wait();
i=0;
}
else
{
if(!(PIND & (1<<PIND3))) //Перевірка натискання кнопки гравця 4
{
p = beep(t, tone);
i=1;
clear_led();
p = blink_led(3);
reset_wait();
i=0;
}
}
} 
} 
} // Закриваючі дужки "Якщо хоча б одна з кнопок гравців натиснута"
} // Закриваючі дужки циклу відстеження натискань кнопок 
return i;
}


int main(void) // Головна функція
{ // Відкривають дужки головної функції
DDRC = 0xFF; // Порт на вихід З
PORTC = 0xFF; // Підтягуючі резистори вкл. на порт З
DDRD = 0b10000000; // порт Д на вхід, крім 8ї ніжки, вона на вихід у нас, там пищалка
PORTD = 0b01111111; // підтягуючі резистори на весь порт Д, окрім 8го біта, бо на ньому динамік у нас.
DDRB = 0xFF; // Порт Б на вихід
PORTB = 0b00110000; // Підтягуючі резистори на 5 і 6 біти порту Б

song();

short i = 0;
while(1) // Вічний цикл пам'яті загиблих на Клендату
{ // Відкривають дужки вічного циклу
i = bond007();
} // Закриваючі дужки вічного циклу
return 0;
}

Якщо раптом трапиться, що хтось відкриє кат і подивиться на код, у цієї людини напевно виникне питання — що за дурниця займає велику його частину. Справа в тому, що мені захотілося внести в пристрій якусь родзинку, і в якості цієї родзинки я вибрав привітання при включенні. Відразу після того, як перші електрони побіжать ланцюга, ця чорна коробочка з різнокольоровими світлодіодами і червоною кнопкою починає весело грати уривок з відомого всім «Імперського маршу».

Ось як це виглядає:


Крім самої мелодії, на цьому короткому відео можна побачити приклад роботи системи: по натисненню кнопки загоряється відповідний світлодіод на основному пристрої і індикатор. У такому стані система блокується до натискання провідним червоної кнопки скидання системи. Ось, власне, і вся логіка роботи.

Помилки в проекті

Як я вже писав вище, основні помилки на етапі проектування стосувалися системи живлення пристрою. Жахливий ККД лінійного стабілізатора, який буквально перетворює в тепло «зайві» 4В при живленні від «Крони» — апофеоз энергонеэффективности. Та й сама «Крона» — далеко не кращий вибір. Цей елемент живлення володіє малою ємністю (близько 600 мА*год), і його вистачає дуже ненадовго. З такою схемою живлення система під час першого тестування в бойових умовах пропрацювала не більше години. Мене це абсолютно не влаштовувало, тому довелося переробляти цю частину схеми.

Незадовго до цього проекту я познайомився з літій-іонними акумуляторами форм-фактора 18650. Вони зарекомендували себе з найкращого боку. Як мінімум, навіть у дешевих китайських ємність акумуляторів в перерахунку на одиницю об'єму була значно вище, ніж у «Крони».

Однак, за вибором цих елементів живлення відразу поставала інша проблема. Номінальна напруга на такому акумуляторі — 3.7 Ст. А цього недостатньо, щоб живити Atmega8. На допомогу знову прийшли спритні китайці — щоб отримати заповітні 5 В я взяв лежав без діла підвищуючий перетворювач на LM2577. Вирвавши з коренем нещасливу LM7805 (залишилися в платі ніжки можна буде побачити на фото, розміщені далі), я впровадив в систему живлення акуратну схемку, створену працьовитими жителями КНР.

Крім того, на час тестування цього варіанту схеми я вирішив відмовитися від можливості підключення зовнішнього джерела живлення. Польові тести пройшли на ура — після багатьох годин експлуатації не було ніяких ознак розряду акумулятора або осідання напруги («Крона» осідання давала — мабуть, не могла віддавати необхідний схемою струм). Я вирішив продовжувати тести до тих пір, поки мій доблесний noname 18650 не відмовиться запускати схему.

До речі, в описі лота при купівлі була заявлена ємність близько 3700 мА*год — дуже самовпевнено навіть для китайців, враховуючи вартість однієї банки в районі 3$. Але вийшло так, що за кілька місяців роботи (що говорить ще і про низький саморазряде акумуляторів) батарейка так і не сіла. Тому я здався раніше і зарядив акумулятор для безвідмовної роботи пристрою на одному важливому заході, про який буде згадано пізніше.

Фотографії

Тут наводжу фото отриманого апарату, в тому числі і вид зсередини. Це для того, щоб було видно, що все зроблено по-чесному — ніякої неонки всередині там немає.















Підсумок

Система вийшла працездатна, яка виконує свою функцію на 100%. Крім тренувань, вона була протестована на ЧУСІ-2013 (Чемпіонат України зі «Своєї гри» 2013-го року), на якому ваш покірний слуга був одним з провідних.

Це був чудовий досвід, який, як відомо, син помилок важких. У підсумковій реалізації пристрій втратило частину функцій, які замислювалися спочатку. Наприклад, я відмовився від можливості підключення зовнішнього блоку харчування — навіть при активному використанні системи посереднього акумулятора вистачає на багато місяців.

Нижче додаю посилання, за яким можна скачати макети друкованих плат (у форматі .lay) і готову прошивку для мікроконтролера. Буду радий, якщо комусь знадобляться мої напрацювання.

Друкована плата основної частини;
Друкована плата блоку світлодіодів;
Друкована плата кнопки;
Прошивка.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.