От макетной платы к печатной: игра Simon Says — Часть 1
Компоненты и расходные материалы
| Arduino UNO | ×1 |
| Макетная плата полноразмерная | ×1 |
| Светодиоды (generic) | ×4 |
| Тактильные кнопки 12 мм (SparkFun) | ×4 |
| Зуммер (buzzer) | ×1 |
| Перемычки | ×12 |
| Резисторы 10 кОм (pull-down для кнопок) | ×4 |
| Резисторы 330 Ом (для LED) | ×4 |
| USB-кабель A-B | ×1 |
Приложения и онлайн-сервисы
- Arduino IDE
О проекте
Введение
Все знают старую игру Simon Says — нужно повторять последовательность вспышек, нажимая соответствующие кнопки. Для Hackster Live мы продемонстрировали инструмент проектирования PCB — Upverter. Это веселый и простой проект для создания и превращения в печатную плату. Ниже — базовая версия с железом и кодом.
Серия из трёх частей:
- Часть 1: Сборка Simon Says на макетной плате (эта статья)
- Часть 2: Проектирование PCB в Upverter на базе этой игры
- Часть 3: Фрезеровка собственной PCB по проекту из Upverter на станке с ЧПУ
Чему вы научитесь в этой серии
- Размещению базового проекта на макетной плате
- Проектированию PCB в Upverter
- Фрезеровке собственной платы на ЧПУ
Программное обеспечение
Если нет Arduino IDE — скачайте последнюю версию для своей ОС здесь.
Сборка оборудования
Ориентировался на пример из SparkFun Inventor's Kit с небольшими изменениями. См. диаграмму Fritzing ниже.


Код
В этой игре масса вариаций. Железо базируется на примере SparkFun, код написал с нуля. Если не хотите писать свой — используйте мой ниже, адаптируйте код SparkFun или поищите другие реализации Simon Says под ваше железо.
В коде подробные комментарии и отладочные Serial.print — видно, что происходит в мониторе порта. Видео работы игры ниже!
Видео Simon Says
Примечание: в видео условие победы — 5 правильных повторений подряд. Можно увеличить для сложности, но видео хотел короче :).
Теперь вы готовы превратить макетную плату в PCB! Учимся в Breadboard to PCB Part 2 — Designing a PCB using Upverter.
Код
Simon Says Arduino Code
int ledArray[] = {12, 9, 5, 2}; // 0=зелёный, 1=красный, 2=жёлтый, 3=синий
int buttonArray[] = {13, 10, 6, 3}; // то же соответствие
int pinCount = 4;
int buzzer = 8;
int ledState = 0;
int gameState = 0;
#define NUMBERTOWIN 5 // сколько вспышек повторить для победы — меняйте для сложности
int gameValues[NUMBERTOWIN];
int roundNum = 0;
unsigned long previousMillis = 0;
unsigned long interval1 = 500;
void setLed(int ledNum);
void displayClue(int roundNum);
boolean waitForInput(int buttonNum);
void setup() {
Serial.begin(9600);
Serial.println("setup");
for (int Pin = 0; Pin < pinCount; Pin++) {
pinMode(ledArray[Pin], OUTPUT);
}
for (int Pin = 0; Pin < pinCount; Pin++) {
pinMode(buttonArray[Pin], INPUT_PULLUP); // используем встроенные pull-up
// автор добавил внешние 10к после написания кода — можно переделать, но не обязательно
}
randomSeed(analogRead(0)); // шум на А0 для случайности
}
void loop() {
if (gameState == 0) {
int b0 = digitalRead(buttonArray[0]);
int b1 = digitalRead(buttonArray[1]);
int b2 = digitalRead(buttonArray[2]);
int b3 = digitalRead(buttonArray[3]);
if (b0 && b1 && b2 && b3) { // ни одна не нажата (pull-up = HIGH)
pre_game();
} else {
Serial.println("button Pressed");
setLed(-1); // все LED выкл
roundNum = 0;
delay(1000);
gameState = 1;
}
}
if (gameState == 1) gamePlay();
if (gameState == 2) { // Победа
delay(500);
winning_sound();
winDisplay(); winDisplay(); winDisplay();
loseDisplay(); // для перехода
Serial.println("winDisplay");
delay(1000);
gameState = 0;
}
if (gameState == 3) { // Проигрыш
delay(500);
losing_sound();
loseDisplay(); loseDisplay(); loseDisplay();
Serial.println("loseDisplay");
delay(1000);
gameState = 0;
}
}
void pre_game() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis > interval1) {
if (ledState == 0) { Serial.println("pre green"); setLed(0); ledState = 1; }
else if (ledState == 1) { Serial.println("pre red"); setLed(1); ledState = 2; }
else if (ledState == 2) { Serial.println("pre yellow"); setLed(2); ledState = 3; }
else if (ledState == 3) { Serial.println("pre blue"); setLed(3); ledState = 0; }
previousMillis = currentMillis;
}
}
void gamePlay() {
Serial.println("gamePlay");
for (int i = 0; i < NUMBERTOWIN; i++) {
gameValues[i] = floor(random(0, 4));
Serial.println(gameValues[i], DEC);
}
Serial.print("Starting Round "); Serial.println(roundNum, DEC);
for (int r = 0; r < NUMBERTOWIN; r++) {
displayClue(roundNum);
for (int i = 0; i <= roundNum; i++) {
Serial.print("Waiting for input "); Serial.print(i+1, DEC);
Serial.print(" of "); Serial.print(roundNum+1, DEC); Serial.println("");
if (waitForInput(gameValues[i])) {
if (i == roundNum) {
Serial.println("Correct");
if (roundNum == NUMBERTOWIN - 1) {
Serial.println("You Win!");
gameState = 2; return;
} else { Serial.println("Continue"); }
}
} else {
Serial.println("You Lose!");
gameState = 3; return;
}
}
roundNum++;
delay(1000);
}
}
void setLed(int ledNum) {
if (ledNum < 0) {
for (int i = 0; i < pinCount; i++) digitalWrite(ledArray[i], LOW);
} else {
for (int i = 0; i < pinCount; i++) {
digitalWrite(ledArray[i], (i == ledNum) ? HIGH : LOW);
}
}
}
void displayClue(int roundNum) {
setLed(-1);
for (int i = 0; i <= roundNum; i++) {
setLed(gameValues[i]); delay(750);
setLed(-1); delay(250);
}
}
boolean waitForInput(int buttonNum) {
while (true) {
int b0 = digitalRead(buttonArray[0]);
int b1 = digitalRead(buttonArray[1]);
int b2 = digitalRead(buttonArray[2]);
int b3 = digitalRead(buttonArray[3]);
if (!b0 || !b1 || !b2 || !b3) { // нажата хоть одна (pull-up = LOW)
delay(250); // антидребезг
if (buttonNum == 0) { digitalWrite(ledArray[0], HIGH); delay(250); digitalWrite(ledArray[0], LOW); }
else if (buttonNum == 1) { digitalWrite(ledArray[1], HIGH); delay(250); digitalWrite(ledArray[1], LOW); }
else if (buttonNum == 2) { digitalWrite(ledArray[2], HIGH); delay(250); digitalWrite(ledArray[2], LOW); }
else if (buttonNum == 3) { digitalWrite(ledArray[3], HIGH); delay(250); digitalWrite(ledArray[3], LOW); }
if (buttonNum == 0 && b0 == 0) { Serial.println("0 Pressed"); return true; }
else if (buttonNum == 1 && b1 == 0) { Serial.println("1 Pressed"); return true; }
else if (buttonNum == 2 && b2 == 0) { Serial.println("2 Pressed"); return true; }
else if (buttonNum == 3 && b3 == 0) { Serial.println("3 Pressed"); return true; }
else return false;
}
}
}
void winDisplay() {
for (int i = 0; i < 4; i++) {
digitalWrite(ledArray[i], HIGH); delay(75); digitalWrite(ledArray[i], LOW); delay(75);
}
for (int i = 2; i >= 0; i--) {
digitalWrite(ledArray[i], HIGH); delay(75); digitalWrite(ledArray[i], LOW); delay(75);
}
}
void winning_sound() {
Serial.println("buzzer1");
tone(buzzer, 294, 250); delay(200);
tone(buzzer, 294, 250); delay(200);
tone(buzzer, 294, 250); delay(200);
tone(buzzer, 392, 500); delay(500);
tone(buzzer, 392, 250); delay(200);
tone(buzzer, 440, 250); delay(200);
tone(buzzer, 392, 250); delay(200);
tone(buzzer, 440, 250); delay(200);
tone(buzzer, 494, 500);
}
void loseDisplay() {
for (int i = 0; i < 4; i++) digitalWrite(ledArray[i], HIGH);
delay(200);
for (int i = 0; i < 4; i++) digitalWrite(ledArray[i], LOW);
delay(200);
}
void losing_sound() {
Serial.println("losing_sound");
tone(buzzer, 98, 250); delay(250);
tone(buzzer, 93, 250); delay(250);
tone(buzzer, 87, 250); delay(250);
}
Схемы
simon_says_fritzing_Yb8EdKPqD4.fzz (файл Fritzing)
Производственный процесс
- Создание игры в реальном времени на Arduino Nano с гироскопом MPU-6050 и OLED-дисплеем
- Цифровые кости на Arduino: генератор случайных чисел с LCD-дисплеем и кнопкой
- Создание рулетки на 37 светодиодов с Arduino Nano
- DIY USB-геймпад на Arduino: собственный высокопроизводительный контроллер
- Интерактивная игра с джойстиком и LED-обратной связью на Arduino
- Портативный дальномер на Arduino Nano: HC-SR04, питание 18650, 3D-корпус
- Pixel Chaser: игра на один тап с NeoPixel Ring и Arduino Nano
- Медное гальваническое покрытие: умная система на Arduino UNO
- Авиастроение для малых цехов: практическое руководство
- Полное руководство по производству 4-слойных печатных плат: пошаговый процесс