ich bin komplett neu im Arduino-Bereich und wollte für einen Tag der offenen Tür in meinem ein Simon Says Spiel bauen. Ich habe dafür einen vorhandenen Code aus dem Internet verwendet und das ganze funktioniert auch schon ganz gut. Das Mechanische funktioniert auch soweit.
Die letzte Schwierigkeit besteht darin ein 4 Ziffern 7-Segment-Display so einzubinden, dass die Runde im "play_memory"-Modus angezeigt wird.
Hier einmal die Simulation: Simon says - Wokwi ESP32, STM32, Arduino Simulator
Ich hoffe der Link mit der Simulation funktioniert. Separat in einem Programm ohne den Spielcode kriege ich das Display vernünftig angesteuert, so dass auch die Zahl angezeigt wird, die ich haben möchte. Mit dem Programmcode des Spiels werden mir aber nicht die Zahlen auf dem Display angezeigt, sondern es leuchten nur einzelne Segmente auf.
Ich hätte gerne, dass die derzeitige Runde auf dem Display angezeigt wird. Ich habe mir gedacht, dass man dies einfach in der while-Schleife unter boolean play_memory(void) mithilfe von sevseg.setNumber (gameRound) anzeigen lassen kann. Das klappt so aber nicht.
Vermutlich ist das nicht die letzte Schwierigkeit.
Schreib doch einen Testsketch, der nur die sevseg - Anzeige ansteuert. Dann siehst du, ob du die Anzeige überhaupt richtig verdrahtet hast.
Deine tausend Zeilen Tondefinition verhindern übrigens, dass ich mir den Rest überhaupt ansehe.
sevseg.refreshDisplay(); muss so schnell wie möglich ausgeführt werden, um die Anzeige aktiv zu halten, wenn Sie mehrere Ziffern haben, da die Bibliothek tatsächlich jeweils nur eine Ziffer aktiviert und dann schnell zur nächsten wechselt, damit alle Ziffern so aussehen, als wären sie gleichzeitig eingeschaltet gleiche Zeit.
Die Sorge besteht darin, dass der Code längere Zeit voller blockierender Schleifen ist und Sie nicht oft auf die loop() zurückkommen. Sie könnten den Befehl also in jede innere loop (while, for, ...) aufteilen, um die visuelle Anzeige beizubehalten, was möglicherweise nicht ausreicht.
Ich würde Ihnen empfehlen, ein besseres 4-stelliges Display mit eigener On-Board-Logik zu wählen, beispielsweise ein 4-stelliges Modul mit tm1637 oder Max7219.
leider ist der zugrundeliegende Sketch nicht so geschrieben, dass er die einfache Einbindung einer direkt angesteuerten Siebensegmentanzeige unterstützt.
Eine einzelne Siebensegmentziffer belegt (inkl. Dezimalpunkt) 8 Pins, das wären dann bei 4 Ziffern 32 Pins. Statt so viele Anschlüsse zu "opfern" (die häufig gar nicht verfügbar sind), wird ein Kunstgriff eingesetzt: Die vergleichbaren Segmente aller vier Ziffern sind parallel am selben Pin, die einzelnen Anzeigen werden jedoch zeitlich nacheinander kurz mit Spannung versorgt, die angesteuerten Segmente leuchten auf, und schon wird zur nächsten Ziffer weitergeschaltet. Dieser Schaltvorgang muss so häufig pro Sekunde erfolgen, dass das menschliche Auge kein Flimmern wahrnimmt.
Die zahlreichen delay()s verhindern und die while(1)-Schleifen erschweren dies:
Die diversen while()-Schleifen erfordern, dass man in jede dieser Schleifen an passender Stelle den Aufruf sevseg.refreshDisplay();. einfügen muss.
Die delay()s blockieren den Controller, so dass die Anzeige nicht häufig genug angesteuert wird.
Um die Sache vielleicht doch noch - wenn auch mit einem "Schweinetrick" - zu retten, kann man versuchen, die Anzeige über einen Timerinterrupt sicherzustellen. Das ist nicht die empfohlene Lösung, das wäre Umschreiben, scheint aber zumindest in der Simulation zu funktionieren. Risiken und Nebenwirkungen wären zu prüfen
/*
Forum: https://forum.arduino.cc/t/simon-says-4-ziffern-7-segment-anzeige-einbinden/1161404
Wokwi: https://wokwi.com/projects/373929451032638465
*/
#include "sound.h"
#include "SevSeg.h"
SevSeg sevseg;
#define CHOICE_OFF 0 //Used to control LEDs
#define CHOICE_NONE 0 //Used to check buttons
#define CHOICE_RED (1 << 0)
#define CHOICE_GREEN (1 << 1)
#define CHOICE_BLUE (1 << 2)
#define CHOICE_YELLOW (1 << 3)
#define LED_RED 10
#define LED_GREEN 3
#define LED_BLUE 13
#define LED_YELLOW 5
// Button pin definitions
#define BUTTON_RED 9
#define BUTTON_GREEN 2
#define BUTTON_BLUE 12
#define BUTTON_YELLOW 6
// Buzzer pin definitions
#define BUZZER1 4
#define BUZZER2 7
// Define game parameters
#define ROUNDS_TO_WIN 13 //Number of rounds to succesfully remember before you win. 13 is do-able.
#define ENTRY_TIME_LIMIT 5000 //Amount of time to press a button before game times out. 3000ms = 3 sec
#define MODE_MEMORY 0
#define MODE_BATTLE 1
#define MODE_BEEGEES 2
// Game state variables
byte gameMode = MODE_MEMORY; //By default, let's play the memory game
byte gameBoard[32]; //Contains the combination of buttons as we advance
byte gameRound = 0; //Counts the number of succesful rounds the player has made it through
void setup()
{
//
Serial.begin(115200);
//Setup hardware inputs/outputs. These pins are defined in the hardware_versions header file
byte numDigits = 4;
byte digitPins[] = {24, 25, 26, 27};
byte segmentPins[] = {28, 29, 30, 31, 32, 33, 34, 35};
bool resistorsOnSegments = false; // 'false' means resistors are on digit pins
byte hardwareConfig = COMMON_ANODE; // See README.md for options
bool updateWithDelays = false; // Default 'false' is Recommended
bool leadingZeros = false; // Use 'true' if you'd like to keep the leading zeros
bool disableDecPoint = false; // Use 'true' if your decimal point doesn't exist or isn't connected. Then, you only need to specify 7 segmentPins[]
sevseg.begin(hardwareConfig, numDigits, digitPins, segmentPins, resistorsOnSegments,
updateWithDelays, leadingZeros, disableDecPoint);
//Enable pull ups on inputs
pinMode(BUTTON_RED, INPUT_PULLUP);
pinMode(BUTTON_GREEN, INPUT_PULLUP);
pinMode(BUTTON_BLUE, INPUT_PULLUP);
pinMode(BUTTON_YELLOW, INPUT_PULLUP);
pinMode(LED_RED, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_BLUE, OUTPUT);
pinMode(LED_YELLOW, OUTPUT);
pinMode(BUZZER1, OUTPUT);
pinMode(BUZZER2, OUTPUT);
//Mode checking
gameMode = MODE_MEMORY; // By default, we're going to play the memory game
// Check to see if the lower right button is pressed
if (checkButton() == CHOICE_YELLOW) play_beegees();
// Check to see if upper right button is pressed
if (checkButton() == CHOICE_GREEN)
{
gameMode = MODE_BATTLE; //Put game into battle mode
//Turn on the upper right (green) LED
setLEDs(CHOICE_GREEN);
toner(CHOICE_GREEN, 150);
setLEDs(CHOICE_RED | CHOICE_BLUE | CHOICE_YELLOW); // Turn on the other LEDs until you release button
while (checkButton() != CHOICE_NONE) ; // Wait for user to stop pressing button
//Now do nothing. Battle mode will be serviced in the main routine
}
/* Timerroutine zum regelmäßigen Ansteuern der vierstelligen Siebensegment-Anzeige
um ein komplettes Umschreiben des Sketches zu vermeiden, da dieser blockierenden
Code enthält, z.B.
delay()s und
while(1)-Schleifen (etc.)
Grundsätzlich sollte das Schreiben blockierenden Codes vermieden werden!
Das funktioniert nicht in allen Fällen, da die Interruptroutine den
Controller möglichst nur kurzzeitig belasten sollte, um andere Abläufe
nicht zu belasten!
Vermeiden (bzw. in diesem Fall Umschreiben) ist auf Dauer die bessere Lösung!
*/
// Setzen der Anzeige auf 0
sevseg.setNumber(0);
// Starten der Timerroutine (Timer1)
initTimer();
play_winner(); // After setup is complete, say hello to the world
}
void loop()
{
attractMode(); // Blink lights while waiting for user to press a button
// Indicate the start of game play
setLEDs(CHOICE_RED | CHOICE_GREEN | CHOICE_BLUE | CHOICE_YELLOW); // Turn all LEDs on
delay(1000);
setLEDs(CHOICE_OFF); // Turn off LEDs
delay(250);
if (gameMode == MODE_MEMORY)
{
// Play memory game and handle result
if (play_memory() == true)
play_winner(); // Player won, play winner tones
else
play_loser(); // Player lost, play loser tones
}
if (gameMode == MODE_BATTLE)
{
play_battle(); // Play game until someone loses
play_loser(); // Player lost, play loser tones
}
}
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//The following functions are related to game play only
// Play the regular memory game
// Returns 0 if player loses, or 1 if player wins
boolean play_memory(void)
{
randomSeed(millis()); // Seed the random generator with random amount of millis()
gameRound = 0; // Reset the game to the beginning
while (gameRound < ROUNDS_TO_WIN)
{
sevseg.setNumber(gameRound + 1);
add_to_moves(); // Add a button to the current moves, then play them back
playMoves(); // Play back the current game board
// Then require the player to repeat the sequence.
for (byte currentMove = 0 ; currentMove < gameRound ; currentMove++)
{
byte choice = wait_for_button(); // See what button the user presses
if (choice == 0) return false; // If wait timed out, player loses
if (choice != gameBoard[currentMove]) return false; // If the choice is incorect, player loses
}
delay(1000); // Player was correct, delay before playing moves
}
return true; // Player made it through all the rounds to win!
}
// Play the special 2 player battle mode
// A player begins by pressing a button then handing it to the other player
// That player repeats the button and adds one, then passes back.
// This function returns when someone loses
boolean play_battle(void)
{
gameRound = 0; // Reset the game frame back to one frame
while (1) // Loop until someone fails
{
byte newButton = wait_for_button(); // Wait for user to input next move
gameBoard[gameRound++] = newButton; // Add this new button to the game array
// Then require the player to repeat the sequence.
for (byte currentMove = 0 ; currentMove < gameRound ; currentMove++)
{
byte choice = wait_for_button();
if (choice == 0) return false; // If wait timed out, player loses.
if (choice != gameBoard[currentMove]) return false; // If the choice is incorect, player loses.
}
delay(100); // Give the user an extra 100ms to hand the game to the other player
}
return true; // We should never get here
}
// Plays the current contents of the game moves
void playMoves(void)
{
for (byte currentMove = 0 ; currentMove < gameRound ; currentMove++)
{
toner(gameBoard[currentMove], 150);
// Wait some amount of time between button playback
// Shorten this to make game harder
delay(150); // 150 works well. 75 gets fast.
}
}
// Adds a new random button to the game sequence, by sampling the timer
void add_to_moves(void)
{
byte newButton = random(0, 4); //min (included), max (exluded)
// We have to convert this number, 0 to 3, to CHOICEs
if (newButton == 0) newButton = CHOICE_RED;
else if (newButton == 1) newButton = CHOICE_GREEN;
else if (newButton == 2) newButton = CHOICE_BLUE;
else if (newButton == 3) newButton = CHOICE_YELLOW;
gameBoard[gameRound++] = newButton; // Add this new button to the game array
}
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//The following functions control the hardware
// Lights a given LEDs
// Pass in a byte that is made up from CHOICE_RED, CHOICE_YELLOW, etc
void setLEDs(byte leds)
{
if ((leds & CHOICE_RED) != 0)
digitalWrite(LED_RED, HIGH);
else
digitalWrite(LED_RED, LOW);
if ((leds & CHOICE_GREEN) != 0)
digitalWrite(LED_GREEN, HIGH);
else
digitalWrite(LED_GREEN, LOW);
if ((leds & CHOICE_BLUE) != 0)
digitalWrite(LED_BLUE, HIGH);
else
digitalWrite(LED_BLUE, LOW);
if ((leds & CHOICE_YELLOW) != 0)
digitalWrite(LED_YELLOW, HIGH);
else
digitalWrite(LED_YELLOW, LOW);
}
// Wait for a button to be pressed.
// Returns one of LED colors (LED_RED, etc.) if successful, 0 if timed out
byte wait_for_button(void)
{
long startTime = millis(); // Remember the time we started the this loop
while ( (millis() - startTime) < ENTRY_TIME_LIMIT) // Loop until too much time has passed
{
byte button = checkButton();
if (button != CHOICE_NONE)
{
toner(button, 150); // Play the button the user just pressed
while (checkButton() != CHOICE_NONE) ; // Now let's wait for user to release button
delay(10); // This helps with debouncing and accidental double taps
return button;
}
}
return CHOICE_NONE; // If we get here, we've timed out!
}
// Returns a '1' bit in the position corresponding to CHOICE_RED, CHOICE_GREEN, etc.
byte checkButton(void)
{
if (digitalRead(BUTTON_RED) == 0) return (CHOICE_RED);
else if (digitalRead(BUTTON_GREEN) == 0) return (CHOICE_GREEN);
else if (digitalRead(BUTTON_BLUE) == 0) return (CHOICE_BLUE);
else if (digitalRead(BUTTON_YELLOW) == 0) return (CHOICE_YELLOW);
return (CHOICE_NONE); // If no button is pressed, return none
}
// Light an LED and play tone
// Red, upper left: 440Hz - 2.272ms - 1.136ms pulse
// Green, upper right: 880Hz - 1.136ms - 0.568ms pulse
// Blue, lower left: 587.33Hz - 1.702ms - 0.851ms pulse
// Yellow, lower right: 784Hz - 1.276ms - 0.638ms pulse
void toner(byte which, int buzz_length_ms)
{
setLEDs(which); //Turn on a given LED
//Play the sound associated with the given LED
switch (which)
{
case CHOICE_RED:
buzz_sound(buzz_length_ms, 1136);
break;
case CHOICE_GREEN:
buzz_sound(buzz_length_ms, 568);
break;
case CHOICE_BLUE:
buzz_sound(buzz_length_ms, 851);
break;
case CHOICE_YELLOW:
buzz_sound(buzz_length_ms, 638);
break;
}
setLEDs(CHOICE_OFF); // Turn off all LEDs
}
// Toggle buzzer every buzz_delay_us, for a duration of buzz_length_ms.
void buzz_sound(int buzz_length_ms, int buzz_delay_us)
{
// Convert total play time from milliseconds to microseconds
long buzz_length_us = buzz_length_ms * (long)1000;
// Loop until the remaining play time is less than a single buzz_delay_us
while (buzz_length_us > (buzz_delay_us * 2))
{
buzz_length_us -= buzz_delay_us * 2; //Decrease the remaining play time
// Toggle the buzzer at various speeds
digitalWrite(BUZZER1, LOW);
digitalWrite(BUZZER2, HIGH);
delayMicroseconds(buzz_delay_us);
digitalWrite(BUZZER1, HIGH);
digitalWrite(BUZZER2, LOW);
delayMicroseconds(buzz_delay_us);
}
}
// Play the winner sound and lights
void play_winner(void)
{
setLEDs(CHOICE_GREEN | CHOICE_BLUE);
winner_sound();
setLEDs(CHOICE_RED | CHOICE_YELLOW);
winner_sound();
setLEDs(CHOICE_GREEN | CHOICE_BLUE);
winner_sound();
setLEDs(CHOICE_RED | CHOICE_YELLOW);
winner_sound();
}
// Play the winner sound
// This is just a unique (annoying) sound we came up with, there is no magic to it
void winner_sound(void)
{
// Toggle the buzzer at various speeds
for (byte x = 250 ; x > 70 ; x--)
{
for (byte y = 0 ; y < 3 ; y++)
{
digitalWrite(BUZZER2, HIGH);
digitalWrite(BUZZER1, LOW);
delayMicroseconds(x);
digitalWrite(BUZZER2, LOW);
digitalWrite(BUZZER1, HIGH);
delayMicroseconds(x);
}
}
}
// Play the loser sound/lights
void play_loser(void)
{
setLEDs(CHOICE_RED | CHOICE_GREEN);
buzz_sound(255, 1500);
setLEDs(CHOICE_BLUE | CHOICE_YELLOW);
buzz_sound(255, 1500);
setLEDs(CHOICE_RED | CHOICE_GREEN);
buzz_sound(255, 1500);
setLEDs(CHOICE_BLUE | CHOICE_YELLOW);
buzz_sound(255, 1500);
}
// Show an "attract mode" display while waiting for user to press button.
void attractMode(void)
{
while (1)
{
setLEDs(CHOICE_RED);
delay(500);
if (checkButton() != CHOICE_NONE) return;
setLEDs(CHOICE_BLUE);
delay(500);
if (checkButton() != CHOICE_NONE) return;
setLEDs(CHOICE_GREEN);
delay(500);
if (checkButton() != CHOICE_NONE) return;
setLEDs(CHOICE_YELLOW);
delay(500);
if (checkButton() != CHOICE_NONE) return;
}
}
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// The following functions are related to Beegees Easter Egg only
// Notes in the melody. Each note is about an 1/8th note, "0"s are rests.
int melody[] = {
NOTE_G4, NOTE_A4, 0, NOTE_C5, 0, 0, NOTE_G4, 0, 0, 0,
NOTE_E4, 0, NOTE_D4, NOTE_E4, NOTE_G4, 0,
NOTE_D4, NOTE_E4, 0, NOTE_G4, 0, 0,
NOTE_D4, 0, NOTE_E4, 0, NOTE_G4, 0, NOTE_A4, 0, NOTE_C5, 0
};
int noteDuration = 115; // This essentially sets the tempo, 115 is just about right for a disco groove :)
int LEDnumber = 0; // Keeps track of which LED we are on during the beegees loop
// Do nothing but play bad beegees music
// This function is activated when user holds bottom right button during power up
void play_beegees()
{
//Turn on the bottom right (yellow) LED
setLEDs(CHOICE_YELLOW);
toner(CHOICE_YELLOW, 150);
setLEDs(CHOICE_RED | CHOICE_GREEN | CHOICE_BLUE); // Turn on the other LEDs until you release button
while (checkButton() != CHOICE_NONE) ; // Wait for user to stop pressing button
setLEDs(CHOICE_NONE); // Turn off LEDs
delay(1000); // Wait a second before playing song
digitalWrite(BUZZER1, LOW); // setup the "BUZZER1" side of the buzzer to stay low, while we play the tone on the other pin.
while (checkButton() == CHOICE_NONE) //Play song until you press a button
{
// iterate over the notes of the melody:
for (int thisNote = 0; thisNote < 32; thisNote++) {
changeLED();
tone(BUZZER2, melody[thisNote], noteDuration);
// to distinguish the notes, set a minimum time between them.
// the note's duration + 30% seems to work well:
int pauseBetweenNotes = noteDuration * 1.30;
delay(pauseBetweenNotes);
// stop the tone playing:
noTone(BUZZER2);
}
}
}
// Each time this function is called the board moves to the next LED
void changeLED(void)
{
setLEDs(1 << LEDnumber); // Change the LED
LEDnumber++; // Goto the next LED
if (LEDnumber > 3) LEDnumber = 0; // Wrap the counter if needed
}
/*
Timerroutine zur Ansteuerung der Siebensegmentanzeige
*/
volatile int timerCount = 0;
void initTimer() {
noInterrupts();
TCCR1A = (1 << WGM01); //Set the CTC mode
OCR1A = 0xF9;
TIMSK1 |= (1 << OCIE1A); //Set the interrupt request
TCCR1B |= (1 << CS01); //Set the prescale 1/64 clock
TCCR1B |= (1 << CS00);
interrupts();
}
ISR(TIMER1_COMPA_vect) { //This is the interrupt request
timerCount++;
if (timerCount >= 4) {
sevseg.refreshDisplay();
timerCount = 0;
}
}
Multiplexen ist ein gängiges Vorgehen um viele LED anzusteuern bzw Tasten auszulesen.
Statt direkt zu multiplexen würde ich einen MAX7219 nehmen. Der braucht nur 3 Pin und Multiplext selbständig die LED-Matrix bzw 7-Segment Anzeige. In 7 Segemnt modalität braucht es nur die Ziffer und nicht die Bildmap.
Grüße Uwe
Richtig, unabhängige Anzeigen sind sicher zu bevorzugen ...
Da diese oben vorgegeben war, habe ich sie als Grundlage genommen.
Ich bin dabei noch auf die Library "TimerOne" gestoßen, die das Ganze noch etwas gefälliger macht:
/*
Forum: https://forum.arduino.cc/t/simon-says-4-ziffern-7-segment-anzeige-einbinden/1161404
Wokwi: https://wokwi.com/projects/373957696765247489
*/
#include "TimerOne.h"
#include "sound.h"
#include "SevSeg.h"
SevSeg sevseg;
#define CHOICE_OFF 0 //Used to control LEDs
#define CHOICE_NONE 0 //Used to check buttons
#define CHOICE_RED (1 << 0)
#define CHOICE_GREEN (1 << 1)
#define CHOICE_BLUE (1 << 2)
#define CHOICE_YELLOW (1 << 3)
#define LED_RED 10
#define LED_GREEN 3
#define LED_BLUE 13
#define LED_YELLOW 5
// Button pin definitions
#define BUTTON_RED 9
#define BUTTON_GREEN 2
#define BUTTON_BLUE 12
#define BUTTON_YELLOW 6
// Buzzer pin definitions
#define BUZZER1 4
#define BUZZER2 7
// Define game parameters
#define ROUNDS_TO_WIN 13 //Number of rounds to succesfully remember before you win. 13 is do-able.
#define ENTRY_TIME_LIMIT 5000 //Amount of time to press a button before game times out. 3000ms = 3 sec
#define MODE_MEMORY 0
#define MODE_BATTLE 1
#define MODE_BEEGEES 2
// Game state variables
byte gameMode = MODE_MEMORY; //By default, let's play the memory game
byte gameBoard[32]; //Contains the combination of buttons as we advance
byte gameRound = 0; //Counts the number of succesful rounds the player has made it through
/*
Timerroutine zur Ansteuerung der Siebensegmentanzeige
*/
void sevenSegment() { //This is the interrupt request
sevseg.refreshDisplay();
}
void setup()
{
//
Serial.begin(115200);
//Setup hardware inputs/outputs. These pins are defined in the hardware_versions header file
byte numDigits = 4;
byte digitPins[] = {24, 25, 26, 27};
byte segmentPins[] = {28, 29, 30, 31, 32, 33, 34, 35};
bool resistorsOnSegments = false; // 'false' means resistors are on digit pins
byte hardwareConfig = COMMON_ANODE; // See README.md for options
bool updateWithDelays = false; // Default 'false' is Recommended
bool leadingZeros = false; // Use 'true' if you'd like to keep the leading zeros
bool disableDecPoint = false; // Use 'true' if your decimal point doesn't exist or isn't connected. Then, you only need to specify 7 segmentPins[]
sevseg.begin(hardwareConfig, numDigits, digitPins, segmentPins, resistorsOnSegments,
updateWithDelays, leadingZeros, disableDecPoint);
//Enable pull ups on inputs
pinMode(BUTTON_RED, INPUT_PULLUP);
pinMode(BUTTON_GREEN, INPUT_PULLUP);
pinMode(BUTTON_BLUE, INPUT_PULLUP);
pinMode(BUTTON_YELLOW, INPUT_PULLUP);
pinMode(LED_RED, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_BLUE, OUTPUT);
pinMode(LED_YELLOW, OUTPUT);
pinMode(BUZZER1, OUTPUT);
pinMode(BUZZER2, OUTPUT);
//Mode checking
gameMode = MODE_MEMORY; // By default, we're going to play the memory game
// Check to see if the lower right button is pressed
if (checkButton() == CHOICE_YELLOW) play_beegees();
// Check to see if upper right button is pressed
if (checkButton() == CHOICE_GREEN)
{
gameMode = MODE_BATTLE; //Put game into battle mode
//Turn on the upper right (green) LED
setLEDs(CHOICE_GREEN);
toner(CHOICE_GREEN, 150);
setLEDs(CHOICE_RED | CHOICE_BLUE | CHOICE_YELLOW); // Turn on the other LEDs until you release button
while (checkButton() != CHOICE_NONE) ; // Wait for user to stop pressing button
//Now do nothing. Battle mode will be serviced in the main routine
}
/* Timerroutine zum regelmäßigen Ansteuern der vierstelligen Siebensegment-Anzeige
um ein komplettes Umschreiben des Sketches zu vermeiden, da dieser blockierenden
Code enthält, z.B.
delay()s und
while(1)-Schleifen (etc.)
Grundsätzlich sollte das Schreiben blockierenden Codes vermieden werden!
Das funktioniert nicht in allen Fällen, da die Interruptroutine den
Controller möglichst nur kurzzeitig belasten sollte, um andere Abläufe
nicht zu belasten!
Vermeiden (bzw. in diesem Fall Umschreiben) ist auf Dauer die bessere Lösung!
*/
// Setzen der Anzeige auf 0
sevseg.setNumber(0);
// Starten der Timerroutine (Timer1)
Timer1.initialize(10000);
Timer1.attachInterrupt(sevenSegment);
play_winner(); // After setup is complete, say hello to the world
}
void loop()
{
attractMode(); // Blink lights while waiting for user to press a button
// Indicate the start of game play
setLEDs(CHOICE_RED | CHOICE_GREEN | CHOICE_BLUE | CHOICE_YELLOW); // Turn all LEDs on
delay(1000);
setLEDs(CHOICE_OFF); // Turn off LEDs
delay(250);
if (gameMode == MODE_MEMORY)
{
// Play memory game and handle result
if (play_memory() == true)
play_winner(); // Player won, play winner tones
else
play_loser(); // Player lost, play loser tones
}
if (gameMode == MODE_BATTLE)
{
play_battle(); // Play game until someone loses
play_loser(); // Player lost, play loser tones
}
}
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//The following functions are related to game play only
// Play the regular memory game
// Returns 0 if player loses, or 1 if player wins
boolean play_memory(void)
{
randomSeed(millis()); // Seed the random generator with random amount of millis()
gameRound = 0; // Reset the game to the beginning
while (gameRound < ROUNDS_TO_WIN)
{
sevseg.setNumber(gameRound + 1);
add_to_moves(); // Add a button to the current moves, then play them back
playMoves(); // Play back the current game board
// Then require the player to repeat the sequence.
for (byte currentMove = 0 ; currentMove < gameRound ; currentMove++)
{
byte choice = wait_for_button(); // See what button the user presses
if (choice == 0) return false; // If wait timed out, player loses
if (choice != gameBoard[currentMove]) return false; // If the choice is incorect, player loses
}
delay(1000); // Player was correct, delay before playing moves
}
return true; // Player made it through all the rounds to win!
}
// Play the special 2 player battle mode
// A player begins by pressing a button then handing it to the other player
// That player repeats the button and adds one, then passes back.
// This function returns when someone loses
boolean play_battle(void)
{
gameRound = 0; // Reset the game frame back to one frame
while (1) // Loop until someone fails
{
byte newButton = wait_for_button(); // Wait for user to input next move
gameBoard[gameRound++] = newButton; // Add this new button to the game array
// Then require the player to repeat the sequence.
for (byte currentMove = 0 ; currentMove < gameRound ; currentMove++)
{
byte choice = wait_for_button();
if (choice == 0) return false; // If wait timed out, player loses.
if (choice != gameBoard[currentMove]) return false; // If the choice is incorect, player loses.
}
delay(100); // Give the user an extra 100ms to hand the game to the other player
}
return true; // We should never get here
}
// Plays the current contents of the game moves
void playMoves(void)
{
for (byte currentMove = 0 ; currentMove < gameRound ; currentMove++)
{
toner(gameBoard[currentMove], 150);
// Wait some amount of time between button playback
// Shorten this to make game harder
delay(150); // 150 works well. 75 gets fast.
}
}
// Adds a new random button to the game sequence, by sampling the timer
void add_to_moves(void)
{
byte newButton = random(0, 4); //min (included), max (exluded)
// We have to convert this number, 0 to 3, to CHOICEs
if (newButton == 0) newButton = CHOICE_RED;
else if (newButton == 1) newButton = CHOICE_GREEN;
else if (newButton == 2) newButton = CHOICE_BLUE;
else if (newButton == 3) newButton = CHOICE_YELLOW;
gameBoard[gameRound++] = newButton; // Add this new button to the game array
}
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//The following functions control the hardware
// Lights a given LEDs
// Pass in a byte that is made up from CHOICE_RED, CHOICE_YELLOW, etc
void setLEDs(byte leds)
{
if ((leds & CHOICE_RED) != 0)
digitalWrite(LED_RED, HIGH);
else
digitalWrite(LED_RED, LOW);
if ((leds & CHOICE_GREEN) != 0)
digitalWrite(LED_GREEN, HIGH);
else
digitalWrite(LED_GREEN, LOW);
if ((leds & CHOICE_BLUE) != 0)
digitalWrite(LED_BLUE, HIGH);
else
digitalWrite(LED_BLUE, LOW);
if ((leds & CHOICE_YELLOW) != 0)
digitalWrite(LED_YELLOW, HIGH);
else
digitalWrite(LED_YELLOW, LOW);
}
// Wait for a button to be pressed.
// Returns one of LED colors (LED_RED, etc.) if successful, 0 if timed out
byte wait_for_button(void)
{
long startTime = millis(); // Remember the time we started the this loop
while ( (millis() - startTime) < ENTRY_TIME_LIMIT) // Loop until too much time has passed
{
byte button = checkButton();
if (button != CHOICE_NONE)
{
toner(button, 150); // Play the button the user just pressed
while (checkButton() != CHOICE_NONE) ; // Now let's wait for user to release button
delay(10); // This helps with debouncing and accidental double taps
return button;
}
}
return CHOICE_NONE; // If we get here, we've timed out!
}
// Returns a '1' bit in the position corresponding to CHOICE_RED, CHOICE_GREEN, etc.
byte checkButton(void)
{
if (digitalRead(BUTTON_RED) == 0) return (CHOICE_RED);
else if (digitalRead(BUTTON_GREEN) == 0) return (CHOICE_GREEN);
else if (digitalRead(BUTTON_BLUE) == 0) return (CHOICE_BLUE);
else if (digitalRead(BUTTON_YELLOW) == 0) return (CHOICE_YELLOW);
return (CHOICE_NONE); // If no button is pressed, return none
}
// Light an LED and play tone
// Red, upper left: 440Hz - 2.272ms - 1.136ms pulse
// Green, upper right: 880Hz - 1.136ms - 0.568ms pulse
// Blue, lower left: 587.33Hz - 1.702ms - 0.851ms pulse
// Yellow, lower right: 784Hz - 1.276ms - 0.638ms pulse
void toner(byte which, int buzz_length_ms)
{
setLEDs(which); //Turn on a given LED
//Play the sound associated with the given LED
switch (which)
{
case CHOICE_RED:
buzz_sound(buzz_length_ms, 1136);
break;
case CHOICE_GREEN:
buzz_sound(buzz_length_ms, 568);
break;
case CHOICE_BLUE:
buzz_sound(buzz_length_ms, 851);
break;
case CHOICE_YELLOW:
buzz_sound(buzz_length_ms, 638);
break;
}
setLEDs(CHOICE_OFF); // Turn off all LEDs
}
// Toggle buzzer every buzz_delay_us, for a duration of buzz_length_ms.
void buzz_sound(int buzz_length_ms, int buzz_delay_us)
{
// Convert total play time from milliseconds to microseconds
long buzz_length_us = buzz_length_ms * (long)1000;
// Loop until the remaining play time is less than a single buzz_delay_us
while (buzz_length_us > (buzz_delay_us * 2))
{
buzz_length_us -= buzz_delay_us * 2; //Decrease the remaining play time
// Toggle the buzzer at various speeds
digitalWrite(BUZZER1, LOW);
digitalWrite(BUZZER2, HIGH);
delayMicroseconds(buzz_delay_us);
digitalWrite(BUZZER1, HIGH);
digitalWrite(BUZZER2, LOW);
delayMicroseconds(buzz_delay_us);
}
}
// Play the winner sound and lights
void play_winner(void)
{
setLEDs(CHOICE_GREEN | CHOICE_BLUE);
winner_sound();
setLEDs(CHOICE_RED | CHOICE_YELLOW);
winner_sound();
setLEDs(CHOICE_GREEN | CHOICE_BLUE);
winner_sound();
setLEDs(CHOICE_RED | CHOICE_YELLOW);
winner_sound();
}
// Play the winner sound
// This is just a unique (annoying) sound we came up with, there is no magic to it
void winner_sound(void)
{
// Toggle the buzzer at various speeds
for (byte x = 250 ; x > 70 ; x--)
{
for (byte y = 0 ; y < 3 ; y++)
{
digitalWrite(BUZZER2, HIGH);
digitalWrite(BUZZER1, LOW);
delayMicroseconds(x);
digitalWrite(BUZZER2, LOW);
digitalWrite(BUZZER1, HIGH);
delayMicroseconds(x);
}
}
}
// Play the loser sound/lights
void play_loser(void)
{
setLEDs(CHOICE_RED | CHOICE_GREEN);
buzz_sound(255, 1500);
setLEDs(CHOICE_BLUE | CHOICE_YELLOW);
buzz_sound(255, 1500);
setLEDs(CHOICE_RED | CHOICE_GREEN);
buzz_sound(255, 1500);
setLEDs(CHOICE_BLUE | CHOICE_YELLOW);
buzz_sound(255, 1500);
}
// Show an "attract mode" display while waiting for user to press button.
void attractMode(void)
{
while (1)
{
setLEDs(CHOICE_RED);
delay(500);
if (checkButton() != CHOICE_NONE) return;
setLEDs(CHOICE_BLUE);
delay(500);
if (checkButton() != CHOICE_NONE) return;
setLEDs(CHOICE_GREEN);
delay(500);
if (checkButton() != CHOICE_NONE) return;
setLEDs(CHOICE_YELLOW);
delay(500);
if (checkButton() != CHOICE_NONE) return;
}
}
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// The following functions are related to Beegees Easter Egg only
// Notes in the melody. Each note is about an 1/8th note, "0"s are rests.
int melody[] = {
NOTE_G4, NOTE_A4, 0, NOTE_C5, 0, 0, NOTE_G4, 0, 0, 0,
NOTE_E4, 0, NOTE_D4, NOTE_E4, NOTE_G4, 0,
NOTE_D4, NOTE_E4, 0, NOTE_G4, 0, 0,
NOTE_D4, 0, NOTE_E4, 0, NOTE_G4, 0, NOTE_A4, 0, NOTE_C5, 0
};
int noteDuration = 115; // This essentially sets the tempo, 115 is just about right for a disco groove :)
int LEDnumber = 0; // Keeps track of which LED we are on during the beegees loop
// Do nothing but play bad beegees music
// This function is activated when user holds bottom right button during power up
void play_beegees()
{
//Turn on the bottom right (yellow) LED
setLEDs(CHOICE_YELLOW);
toner(CHOICE_YELLOW, 150);
setLEDs(CHOICE_RED | CHOICE_GREEN | CHOICE_BLUE); // Turn on the other LEDs until you release button
while (checkButton() != CHOICE_NONE) ; // Wait for user to stop pressing button
setLEDs(CHOICE_NONE); // Turn off LEDs
delay(1000); // Wait a second before playing song
digitalWrite(BUZZER1, LOW); // setup the "BUZZER1" side of the buzzer to stay low, while we play the tone on the other pin.
while (checkButton() == CHOICE_NONE) //Play song until you press a button
{
// iterate over the notes of the melody:
for (int thisNote = 0; thisNote < 32; thisNote++) {
changeLED();
tone(BUZZER2, melody[thisNote], noteDuration);
// to distinguish the notes, set a minimum time between them.
// the note's duration + 30% seems to work well:
int pauseBetweenNotes = noteDuration * 1.30;
delay(pauseBetweenNotes);
// stop the tone playing:
noTone(BUZZER2);
}
}
}
// Each time this function is called the board moves to the next LED
void changeLED(void)
{
setLEDs(1 << LEDnumber); // Change the LED
LEDnumber++; // Goto the next LED
if (LEDnumber > 3) LEDnumber = 0; // Wrap the counter if needed
}
Die Library macht es einfach (hier nur die relevanten Teile):
#include "TimerOne.h"
// ...
// Starten der Timerroutine (Timer1) im setup()
Timer1.initialize(10000); // Aufruf alle 10 [ms]
Timer1.attachInterrupt(sevenSegment);
//...
/*
Timerroutine zur Ansteuerung der Siebensegmentanzeige
*/
void sevenSegment() { //This is the interrupt request
sevseg.refreshDisplay();
}