Arduino Workshop
Workshop, Projekte · · MichaelAm 07.10. hat in Europa die CodeWeek begonnen und bei Code For Heilbronn standen an diesem Wochenende die Türen offen für die Teilnahme an einem Arduino-Workshop für Einsteiger und Experten. Dabei waren die Teilnehmer hart im Nehmen: Grundkenntnisse C++, Elektronik und dann auch noch Programmierung eines Tic-Tac-Toe Spiels. Dafür haben sie es sich aber verdient, dass sie die fertige Platine nach dem Workshop behalten durften.
Und Was haben wir gemacht?
Angefangen hat der Workshop auf rein theoretischer Ebene, bei der die Grundkenntnisse der Programmiersprache C++ übermittelt wurden. Weil unsere Teilnehmer alle noch sehr jung waren und darin, haben wir uns Mühe gegeben, alles so anzustellen, dass es auch jeder versteht: unkompliziert und idiotensicher.
Gleich danach konnten die frisch geschlüpften Programmierer das anwenden, was sie nach der Theoriestunde noch im Kopf hatten. Für die Praxisphase wurden dann der Einstieg mit 2 kleinen Programmierübungen vereinfacht: ein Programm um die gedrückte Taste einzulesen und ein Programm um die Status-LED der Platine blinken zu lassen.
Danach wurde es Ernst: wir haben gemeinsam ein Tic-Tac-Toe Spiel programmiert. Wer das Spiel fertig bekommen hat (glücklicherweise alle, weil wir keinen Programmiereinsteiger auf der Strecke liegen lassen), durfte die Platine mit dem fertigen Tic-Tac-Toe Spiel mit Heim nehmen.
Platine? Sagt bloß …
Ja, wir haben das Spiel auf eine Platine gepackt. Mittig war das Herz der Platine: ein Arduino Nano. Unser Spielfeld war eine 3x3 RGB LED Matrix, über der sich noch eine weitere RGB LED als Status-LED befang. Warum RGB? - Naja, man braucht für 2 Spieler 2 Farben und wer möchte, kann sein Programm so konfigurieren, dass er seine Lieblingsfarben für jeden Spieler verwendet. Bei den Tasten haben wir ein wenig mit den Pins vom Arduino gespart: ein Spannungsteiler, bei den über den AD-Wandler eingelesen wurde, welche Taste gedrückt wurde. Gerade für Anfänger ist es nicht schwer, zu verstehen, wie ein Spannungsteiler und ein Mutiplexer funktioniert.
Da wir 20 Platinen für genauso viele Teilnehmer bestellt haben, sind wir natürlich überaus froh, Meet and Code als Sponsor bekommen zu haben. Andererseits hätten die Teilnehmer ihre Platinen nicht mit heim nehmen können.
Die von uns erstellte Platine ist nicht nur zum Tic-Tac-Toe Spielen gedacht, sondern ein richtiges Multitalent. Sie wurde so entwickelt, dass die Teilnehmer des Workshops nach ihrer eigenen Kreativität Spiele dafür entwickeln können, wie z.B. das Spiel Senso oder ein Programm, mit dem man die Platine als Würfel verwenden kann.
Und das fertige Spiel?
Das Programm wurde so entwickelt, dass es gerade für Einsteiger leichter zu verstehen ist. Natürlich ist es Open Source und für jeden frei verwendbar.
int Spielfeld[9];
int Spieler;
int Gewinner;
// Alle Pins, die mit den LEDs verbunden sind, auf Ausgang setzen
void Init(void)
{
for (int i = 2; i < 15; i++) {
pinMode(i, OUTPUT);
}
}
// Um alle LEDs aus zu schalten, müssen die damit verbundenen Pins auf LOW gesetzt werden.
void AllesAus(void)
{
for (int i = 2; i < 15; i++) {
// Der Pin 11 muss abeer auf HIGH gesetzt werden, weil er nicht mit einem Transistor verbunden ist
if (i != 11) {
digitalWrite(i, HIGH);
}
else {
digitalWrite(i, LOW);
}
}
}
// Zu Beginn eines Spiels werden alle Variablen auf ihren Standardwert gesetzt
void InitVariablen(void)
{
// Alle Elemente des Arrays Spielfeld werden mit 0 belegt
for (int i = 0; i < 9; i++) {
Spielfeld[i] = 0;
}
// Spieler 1 darf mit dem Spiel beginnen
Spieler = 1;
// Noch hat niemand gewonnen. Deshalb wird diese Variable mit 0 belegt
Gewinner = 0;
}
// Diese Funktion liefert zurück, welche Taste atuell gedrückt wird.
// Alle Tasten bilden einen Spannungsteiler, mit dem das Verhältnis der Widerstände zueinander über einen AD-Wandler ausgelesen wird.
// Falls keine Tate gedrückt wird, zieht der Pulldown-Widerstand die Spannung auf 0V
int TasteGedrueckt(void)
{
// 10 bedeutet, dass keine Taste gedrückt wird
int Taste = 10;
int ADC_Wert = analogRead(A7);
// Im Folgenen werden die Werte des AD-Wandlers verglichen. daraus wird dann ermittelt, welche taste gedrückt wird
if ((ADC_Wert > 360) && (ADC_Wert < 370)) {
Taste = 0;
}
if ((ADC_Wert > 560) && (ADC_Wert < 570)) {
Taste = 1;
}
if ((ADC_Wert > 635) && (ADC_Wert < 645)) {
Taste = 2;
}
if ((ADC_Wert > 390) && (ADC_Wert < 400)) {
Taste = 3;
}
if ((ADC_Wert > 500) && (ADC_Wert < 515)) {
Taste = 4;
}
if ((ADC_Wert > 725) && (ADC_Wert < 735)) {
Taste = 5;
}
if ((ADC_Wert > 415) && (ADC_Wert < 430)) {
Taste = 6;
}
if ((ADC_Wert > 460) && (ADC_Wert < 470)) {
Taste = 7;
}
if ((ADC_Wert > 845) && (ADC_Wert < 860)) {
Taste = 8;
}
//Serial.println( Taste );
// Der ermittelte wert für die Taste wird anschließend zurückgegeben
return Taste;
}
// Diese Funktion kümmert sich um die Ausgabe der Status-LED
void AusgabeStatusLED(void)
{
// Je nachdem, welcher Spieler gerade dran ist, leuchtet die LED in einer bestimmten Farbe
switch (Spieler) {
case 0: // Allle Farben aus
digitalWrite(5, HIGH);
digitalWrite(7, HIGH);
digitalWrite(8, HIGH);
break;
case 1: // Rot
digitalWrite(5, HIGH);
digitalWrite(7, HIGH);
digitalWrite(8, LOW);
break;
case 2: // Gruen
digitalWrite(5, HIGH);
digitalWrite(7, LOW);
digitalWrite(8, HIGH);
break;
case 3: // Blau
digitalWrite(5, LOW);
digitalWrite(7, HIGH);
digitalWrite(8, HIGH);
break;
}
// Setze die gemeinsame Kathode auf HIGH und schalte die LED somit an
digitalWrite(11, HIGH);
// Warte eine Millisekunde
delay(1);
// Schalte die LED aus
digitalWrite(11, LOW);
AllesAus();
}
// Diese Funktion gibt den Inhalt des Arrays Spielfeld auf der LED Matrix aus.
void AusgabeLEDs()
{
// Das wird für alle 3 Zeilen widerholt
for (int i = 0; i < 3; i++) {
// Prüfe die LEDs 0, 3 und 6
switch (Spielfeld[3 * i]) {
case 0: // alle Farben aus
digitalWrite(2, HIGH);
digitalWrite(3, HIGH);
digitalWrite(4, HIGH);
break;
case 1: // Rot
digitalWrite(2, LOW);
digitalWrite(3, HIGH);
digitalWrite(4, HIGH);
break;
case 2: // Gruen
digitalWrite(2, HIGH);
digitalWrite(3, LOW);
digitalWrite(4, HIGH);
break;
case 3: // Blau
digitalWrite(2, HIGH);
digitalWrite(3, HIGH);
digitalWrite(4, LOW);
break;
}
// Pruefe die LEDs 1, 4 und 7
switch (Spielfeld[3 * i + 1]) {
case 0: // Allle Farben aus
digitalWrite(5, HIGH);
digitalWrite(7, HIGH);
digitalWrite(8, HIGH);
break;
case 1: // Rot
digitalWrite(5, HIGH);
digitalWrite(7, HIGH);
digitalWrite(8, LOW);
break;
case 2: // Gruen
digitalWrite(5, HIGH);
digitalWrite(7, LOW);
digitalWrite(8, HIGH);
break;
case 3: // Blau
digitalWrite(5, LOW);
digitalWrite(7, HIGH);
digitalWrite(8, HIGH);
break;
}
// Pruefe die LEDs 2, 5 und 8
switch (Spielfeld[3 * i + 2]) {
case 0: // alle Farben aus
digitalWrite(12, HIGH);
digitalWrite(13, HIGH);
digitalWrite(14, HIGH);
break;
case 1: // Rot
digitalWrite(12, LOW);
digitalWrite(13, HIGH);
digitalWrite(14, HIGH);
break;
case 2: // Gruen
digitalWrite(12, HIGH);
digitalWrite(13, HIGH);
digitalWrite(14, LOW);
break;
case 3: // Blau
digitalWrite(12, HIGH);
digitalWrite(13, LOW);
digitalWrite(14, HIGH);
break;
}
// Schalte die dazugehörigr gemeinsame Kathode an.
// Die Zustände an den Kathoden werden durch den Transistor invertiert
switch (i) {
case 0:
digitalWrite(10, LOW);
break;
case 1:
digitalWrite(9, LOW);
break;
case 2:
digitalWrite(6, LOW);
break;
}
// warte eine Millisekunde
delay(1);
// Schalte die LED Matrix wieder aus
AllesAus();
}
}
// Nach jedem Spielzug muss geprüft werden ob es einen Gewinner gibt
// Dazu werden einfach die Werte in dem Array Spielfeld zeilenweise uns spaltenweise multipliziert
void PruefeGewinner(void)
{
int Produkt = 1;
// Pruefe zuerst, ob es ein Unentschieden gibt
for (int i = 0; i < 9; i++) {
Produkt *= Spielfeld[i];
}
if (Produkt != 0) {
Gewinner = -1;
}
// Pruefe alle 3 Zeilen und Spalten
for (int i = 0; i < 3; i++) {
Produkt = Spielfeld[0 + i] * Spielfeld[3 + i] * Spielfeld[6 + i];
if (Produkt == 1) {
Gewinner = 1;
}
if (Produkt == 8) {
Gewinner = 2;
}
Produkt = Spielfeld[0 + i * 3] * Spielfeld[1 + 3 * i] * Spielfeld[2 + 3 * i];
if (Produkt == 1) {
Gewinner = 1;
}
if (Produkt == 8) {
Gewinner = 2;
}
}
// Pruefe die 2 Diagonalen
Produkt = Spielfeld[0] * Spielfeld[4] * Spielfeld[8];
if (Produkt == 1) {
Gewinner = 1;
}
if (Produkt == 8) {
Gewinner = 2;
}
Produkt = Spielfeld[2] * Spielfeld[4] * Spielfeld[6];
if (Produkt == 1) {
Gewinner = 1;
}
if (Produkt == 8) {
Gewinner = 2;
}
}
// Falls das Spiel gewonnen wurde, zeige eine dazugehörige Animation
void SpielGewonnen(void)
{
bool neuesSpiel = false;
// Zuerst bleibt die Anzeig für 2 Sekunden auf dem aktuellen Spielstand
unsigned long Counter = millis();
while ((millis() - 2000) < Counter) {
AusgabeLEDs();
}
// Danach wird noch zwischengespeichert, welcher Spieler als nächstes an der Reihe wäre
// Das ist notwendig, weil später dieser Wert verloren geht
int naechsterSpieler = Gewinner + 1;
if (naechsterSpieler > 2) {
naechsterSpieler = 1;
}
// Solange keine Taste gedrückt wird, wird nun diese Animation angezeigt
do {
// Teil 1
for (int i = 0; i < 9; i++) {
Spielfeld[i] = Gewinner;
}
Spielfeld[4] = 0;
Counter = millis();
while ((millis() - 1000) < Counter) {
AusgabeLEDs();
if (TasteGedrueckt() != 10) {
neuesSpiel = true;
}
}
// Teil 2
for (int i = 0; i < 9; i++) {
Spielfeld[i] = 0;
}
Spielfeld[4] = Gewinner;
Counter = millis();
while ((millis() - 1000) < Counter) {
AusgabeLEDs();
if (TasteGedrueckt() != 10) {
neuesSpiel = true;
}
}
} while (!neuesSpiel);
// eine Taste wurde gedrückt. Das Spiel kann weitergehen
InitVariablen();
Spieler = naechsterSpieler;
}
// Falls das Spiel mit unentschieden endet, zeige eine dazugehörige Animation
void SpielUnentschieden(void)
{
bool neuesSpiel = false;
// Zuerst bleibt die Anzeig für 2 Sekunden auf dem aktuellen Spielstand
unsigned long Counter = millis();
while ((millis() - 2000) < Counter) {
AusgabeLEDs();
}
// Danach wird noch zwischengespeichert, welcher Spieler als nächstes an der Reihe wäre
// Das ist notwendig, weil später dieser Wert verloren geht
int naechsterSpieler = Spieler;
// Solange keine Taste gedrückt wird, wird nun diese Animation angezeigt
do {
// Teil 1
Spielfeld[0] = 1;
Spielfeld[2] = 1;
Spielfeld[6] = 1;
Spielfeld[8] = 1;
Spielfeld[4] = 0;
Spielfeld[1] = 2;
Spielfeld[3] = 2;
Spielfeld[5] = 2;
Spielfeld[7] = 2;
Counter = millis();
while ((millis() - 1000) < Counter) {
AusgabeLEDs();
if (TasteGedrueckt() != 10) {
neuesSpiel = true;
}
}
// Teil 2
Spielfeld[0] = 2;
Spielfeld[2] = 2;
Spielfeld[6] = 2;
Spielfeld[8] = 2;
Spielfeld[4] = 0;
Spielfeld[1] = 1;
Spielfeld[3] = 1;
Spielfeld[5] = 1;
Spielfeld[7] = 1;
Counter = millis();
while ((millis() - 1000) < Counter) {
AusgabeLEDs();
if (TasteGedrueckt() != 10) {
neuesSpiel = true;
}
}
} while (!neuesSpiel);
InitVariablen();
Spieler = naechsterSpieler;
}
// Die Setup-Funktion wird beim ersten Start aufgerufen
void setup()
{
Init();
AllesAus();
InitVariablen();
//Serial.begin(9600);
}
// Die Loop Funktion wird endlos durchlaufen
void loop()
{
// ermittle, ob eine Tastee geddrückt wurde
int Taste = TasteGedrueckt();
// Falls eine Taste gedrückt wurde und dieses Feld noch unbelegt ist, markiere es mit dem aktuellen Spieler
if ((Taste != 10) && (Spielfeld[Taste] == 0)) {
Spielfeld[Taste] = Spieler;
Spieler++;
}
// Nach jedem Spielzug ist der neue Spieler an der Reihe. Dafür wird der Spieler zuerst inkrementiert und danach ein Übrelauf geprüft
if (Spieler > 2) {
Spieler = 1;
}
// An der Status-LED wird gezeigt, welcher Spieler gerade an der Reihe ist
AusgabeStatusLED();
// Der aktuelle Spielstand wird auf der LED Matrix ausgegeben
AusgabeLEDs();
// Es wird geprüft, ob es schon einen Gewinner gibt
PruefeGewinner();
//Serial.println(Gewinner);
// Falle es einen Gewinner gibt, wird die dazugehörige Animation aktiviert
if (Gewinner > 0) {
SpielGewonnen();
}
// Falls das Spiel unentscheiden steht, wird die dazugrhörige Animation aktiviert
if (Gewinner == -1) {
SpielUnentschieden();
}
/*
for(int i = 0; i < 9; i++)
{
Serial.print(Spielfeld[i]);
Serial.print("; ");
}
Serial.print(" ");
Serial.print(analogRead(A7));
Serial.print(" ");
Serial.print(Taste);
Serial.print(" ");
Serial.print(Spieler);
Serial.println(" ");
*/
}