Игрушка на Arduino: Саймон сказал

Хотите чем-либо занять ребёнка? Машинки и куклы надоели, хочется что-нибудь по-веселее? Предлагаю сделать простую электронную игрушку своими руками. В магазинах есть интересная игра под названием: «Саймон сказал»:

Суть игры заключается в том, чтобы игрок повторил код, который показал компьютер. Нечто подобное мы и будем собирать сегодня сами.

Для начала разберёмся с основными частями проекта:

  • 4 кнопки и 4 светодиода — это основа. Посредством светодиодов компьютер будет показывать код, а с помощью кнопок игрок будет его повторять.
  • Динамик будет озвучивать нажатие на кнопки и включение лампочек.
  • Семисегментный индикатор с драйвером 74HC595. Будет отображать текущий уровень сложности.
  • Фоторезистор. Для генератора случайных чисел. (Позже объясню).

Подключаем к Arduino по такой схеме:

  • Светодиоды подключены к 8, 6, 4, 2 выходам Arduino через резисторы по 220 Ом (номинал можно изменить от 150 до 330 Ом);
  • Кнопки к 9, 7, 5, 3 выходам со стягивающими резисторами 10 кОм (номинал не важен, важно его наличие);
  • Динамик(Я взял пассивный) подключен напрямую к 10 пину (поддерживает ШИМ);
  • Контакты семисегмента (через резистор) идут к выходам регистра.
  • Сам регистр подключен к 13-му, 12-му и 11-му контакту (такт, данные и защёлка); Подробнее в этой статье.
  • Ну, и фоторезистор к аналоговому входу 1;

Я всё сделал на Breadboard. Получилось так:

В дальнейшем планирую сделать печатную плату, добавить питание от USB и поместить в какой-либо корпус. Плату уже нарисовал (скачать можно внизу). Там 2 платы. 1-я — это верхняя(лицевая) часть с кнопками, светодиодами, индикатором и. т. д.. А 2-я (нижняя) — это микроконтроллер с обвязкой (вместо неё можно использовать Arduino);

Теперь о коде: (Скачать код полностью можно внизу по ссылке)

В первую очередь пишем глобальные переменные:

#define CLOCK 13
#define DATA 12
#define LATCH 11

— это выходы регистра;

#define buzzer 10
const int leds[4] = {8, 6, 4, 2};
const int buttons[4] = {9, 7, 5, 3};

— выходы динамика (пищалки), светодиодов и входы кнопок.

const int notes[4] = {262, 330, 392, 523};

— а в это массиве хранятся ноты, которые будут проигрываться при нажатии кнопок. При желании их можно сделать одинаковыми. Я их брал из файла pitches.h. Откройте пример Digital > toneMelody. К нему прикреплён это файл:

В setup-е назначаем пины регистра, динамика и светодиодов выходами, а кнопки входами; ставим высокий уровень на защёлку.

pinMode(CLOCK, OUTPUT);
pinMode(DATA, OUTPUT);
pinMode(LATCH, OUTPUT);
pinMode(buzzer, OUTPUT);
for(int i=0; i<4; i++)
pinMode(leds[i], OUTPUT),
pinMode(buttons[i], INPUT);

digitalWrite(LATCH, HIGH);

Теперь разберём функции:

  • для семисегмента я сначала написал 2 функции. 1-я будет выводить на него цифры (от 0 до 9. 10 — это 9 с точкой):

void segWrite(int number){
byte numbers[11] = {0b11111100,0b01100000,0b11011010,
0b11110010,0b01100110,0b10110110,0b10111110,
0b11100000,0b11111110,0b11110110,0b11110111};
digitalWrite(LATCH, LOW);
shiftOut(DATA, CLOCK, LSBFIRST, numbers[number]);
digitalWrite(LATCH, HIGH);
}

А 2-я будет выключать индикатор:

void segOff(){
digitalWrite(LATCH, LOW);
shiftOut(DATA, CLOCK, LSBFIRST, 0);
digitalWrite(LATCH, HIGH);
}

  • Потом решил добавить 3-ю, которая будет показывать небольшую анимацию перед началом игры:

void animation(){
byte add = 1;
byte value = 0;
for(int i=0; i<6; i++){
value+=add;
digitalWrite(LATCH, LOW);
shiftOut(DATA, CLOCK, MSBFIRST, value);
digitalWrite(LATCH, HIGH);
add*=2;
delay(100);
}
}

  • Следующая называется showled. Она будет показывать код игроку:

void showled(int count){
tone(buzzer, notes[count]);
digitalWrite(leds[count], HIGH);
delay(300);
digitalWrite(leds[count], LOW);
noTone(buzzer);
delay(300);
}

  • Следующие 2 функции противоположны друг-другу. Одна будет исполнятся, когда игрок выиграл:

void soundwin(){
//262, 294, 330, 392; 330, 392;
tone(buzzer, 262);
digitalWrite(leds[0], HIGH);
delay(100);
tone(buzzer, 294);
digitalWrite(leds[1], HIGH);
delay(100);
tone(buzzer, 330);
digitalWrite(leds[2], HIGH);
delay(100);
tone(buzzer, 392);
digitalWrite(leds[3], HIGH);
delay(100);
noTone(buzzer);
for(int i=0; i<4; i++) digitalWrite(leds[i], LOW);
delay(100);
tone(buzzer, 330);
delay(100);
tone(buzzer, 392);
for(int i=0; i<4; i++) digitalWrite(leds[i], HIGH);
delay(500);
noTone(buzzer);
for(int i=0; i<4; i++) digitalWrite(leds[i], LOW);
}

(ноты брал из того же файла);

Другая — когда проиграл:

void soundlose(){
tone(buzzer, 1480);
for(int i=0; i<4; i++) digitalWrite(leds[i], HIGH);
delay(350);
for(int i=0; i<4; i++) digitalWrite(leds[i], LOW);
tone(buzzer, 1047);
delay(350);
tone(buzzer, 1480);
for(int i=0; i<4; i++) digitalWrite(leds[i], HIGH);
delay(500);
noTone(buzzer);
for(int i=0; i<4; i++) digitalWrite(leds[i], LOW);
}

  • Функции для работы с кнопками: button() — будет ожидать нажатия любой кнопки и возвращать её номер.

int button(){
int pressed = -1;
do{
for(int i=0; i<4; i++){
if(digitalRead(buttons[i])==HIGH){
pressed = i;
break;
}
}
} while(pressed<0);
return pressed;
}

readButton() — а эта будет проверять, правильная ли кнопка нажата и, соответственно, возвращать: да или нет.

boolean readbutton(int count){
int pressed = button();
delay(5);
tone(buzzer, notes[pressed]);
digitalWrite(leds[pressed], HIGH);
delay(100);
while(digitalRead(buttons[pressed])!=LOW);
noTone(buzzer);
digitalWrite(leds[pressed], LOW);
delay(100);
if(pressed==count) return true;
else return false;
}

(И, кстати, данная функция не будет ничего возвращать, пока человек не отпустит кнопку. Таким образом, последнее состояние кнопки будет всегда LOW, потому для борьбы с дребезгом мы ограничимся лишь задержкой на 5 мс).

  • И последняя функция (самая интересная) будет делать случайные числа максимально случайными 🙂

void setRandom(){
int analog = analogRead(0);
int light = analogRead(1);
unsigned long time = millis();
unsigned long results = analog * light * time;
do results = results / 10;
while(results>=1000);
results+=random(0, 300);
randomSeed(results);
}

Всё что она делает — это формирует число, зависящее от освещённости (для этого и нужен фоторезистор), показаний с пустого аналог. входа и времени. А потом отправляет его в randomSeed();

Теперь сам цикл. (Для меня это было самым сложным, т. к. пришлось использовать метки)

— Вначале стоят 4 переменные, отвечающие за кол-во жизней, длину кода, текущий уровень и сам код.

int lives = 2;
int codelength = 10;
int level = 0;
int code[codelength];

(обращу ваше внимание, что Arduino будет показывать игроку сначала одну цифру кода, потом первую + вторую, + 3-ю, 4-ю и. т. д. Потому весь код надо разбивать на части, а лучше сразу сделать массив переменных, как это сделал я);

— Затем показываем анимацию и ожидаем нажатия любой кнопки для начала игры. Ну, и чуть-чуть ждём:

animation();
button();
delay(1000);

— Кода игра началась, сразу заполняем ячейки кода random-ом:

for(int i=0; i<codelength; i++){
setRandom();
code[i] = random(4);
}

— Вся игра будет крутиться в цикле, пока игрок не пройдёт все уровни, либо пока жизни не закончатся. Потому заводим цикл for():

for(level=0; level < codelength; level++){

— В цикле ставим метку show, отображаем на индикаторе текущий уровень и проверяем, не закончились ли жизни:

show:
segWrite(level+1);
if(lives==0) break;

— Потом крутим ячейки нашего кода и показываем их игроку (кол-во таких ячеек должно быть равно текущему уровню):

//показываем код(одно за другим)
for(int count=0; count<=level; count++) showled(code[count]);

— Затем заводим ещё один цикл for(), в котором выводим на сегмент количество кнопок, которое ещё необходимо нажать, и проверяем нажатие кнопок. Если нажата не та кнопка, то играем печальную мелодию о проигрыше и переходим в начало первого цикла (заново показываем код):

//проверяем код(одно за другим)
for(int count=0; count<=level; count++){
segWrite(level+1-count);
// если правильно — идём дальше
// если неправильно, уменьшаем жизни
if(!readbutton(code[count])){
segOff();
soundlose();
delay(1000);
lives—;
goto show;
}
}

— И, если данный цикл пройден, значит игрок нажал всё правильно. Поздравляем его и переходим на следующий уровень:

segWrite(0);
delay(100);
soundwin();
delay(1000);

Надеюсь, я понятно объяснил. Если есть вопросы — пишите в комментариях. Буду рад ответить.


Список радиоэлементовОбозначение
Тип
Номинал
Количество
ПримечаниеМагазинМой блокнот

Arduino или ATmega8 с обвязкой
МК AVR 8-битATmega81

Кварцевый резонатор16 МГц1

Конденсатор22 пФ2

Конденсатор100 мФ1

Светодиод1
ON
Резистор220 Ом1

Тактовая кнопка1
RESET
Резистор1 — 10 КОм1

РазъёмUSB1
ПериферияIC1
Сдвиговый регистрCD74HC5951
SEG1
Семисегмент1
LED1-LED4
Светодиод4
У меня 2 красных и 2 жёлтыхR1-8, R13-16
Резистор220 Ом12
R9-12
Резистор1 — 10 КОм4
S1-S4
Тактовая кнопка4
SP1
Динамик (пищалка)активный/пассивный1
PH1
Фоторезистор1
Добавить все

Скачать список элементов (PDF)

Прикрепленные файлы:

Добавить комментарий

Ваш адрес email не будет опубликован.