Arduino Projekte – Pollux Labs https://polluxlabs.net Arduino, ESP32 & ESP8266 | Projekte & Tutorials Thu, 12 Dec 2024 10:58:56 +0000 de hourly 1 https://wordpress.org/?v=6.6.2 https://polluxlabs.net/wp-content/uploads/2020/05/cropped-pollux-labs-p-32x32.png Arduino Projekte – Pollux Labs https://polluxlabs.net 32 32 Die Zimmer lüften mit dem Arduino https://polluxlabs.net/arduino-projekte/die-zimmer-lueften-mit-dem-arduino/ Thu, 05 Dec 2024 09:22:06 +0000 https://polluxlabs.net/?p=18059 Die Zimmer lüften mit dem Arduino Weiterlesen »

]]>
Gehörst du zu den Leuten, die gerne die Zimmer lüften, aber dann vergessen, dass das Fenster offen ist? Mir passiert das regelmäßig – bis jetzt. In diesem Projekt überwacht ein Arduino Nano die Temperatur, sobald du das Fenster zum Lüften öffnest. Fällt die Temperatur im Raum um 0,5°C schlägt er Alarm. So kannst du sicher sein, dass der Raum oder sogar die ganze Wohnung nicht unnötig auskühlt.

Diese Bauteile benötigst du:

  • Arduino Nano
  • Temperatursensor DHT22
  • Piezo-Summer
  • Button
  • 2x 10kΩ Widerstände
  • Breadboard & Kabel

So funktioniert die Temperaturüberwachung

Wenn du das Fenster öffnest, drückst du einen Button am Arduino – damit wird der Startwert der Temperatur festgelegt und die Messung beginnt. Während nun (mal abgesehen vom Sommer) kühlere Luft ins Zimmer gelangt, fällt die Temperatur darin kontinuierlich. Sobald die Zimmertemperatur um 0,5°C gefallen ist, schlägt der Piezo-Summer Alarm und ruft dich ans geöffnete Fenster, um es zu schließen. Du drückst den Button erneut: der Alarm hört auf und der Arduino wird wieder in den Wartezustand zurückgesetzt – bis zum nächsten Lüften.

Natürlich kannst du den Wert von 0,5°C ändern und so einstellen, dass er zu deinen Wünschen, zur Positionierung des Arduinos im Zimmer etc. passt.

Aufgebauter Arduino Temperaturwächter

So baust du das Projekt zusammen

Orientiere dich beim Aufbau an der folgenden Skizze. Ich habe für dieses Projekt wegen seiner kompakten Größe einen Arduino Nano verwendet. Du kannst aber natürlich auch ein anderes Board verwenden, das du gerade zur Hand hast – also zum Beispiel einen Arduino UNO. Auch muss es nicht unbedingt der Sensor DHT22 sein – in diesem Tutorial lernst du, wie du den „kleinen Bruder“ DHT11 am Arduino anschließt und verwendest.

Aufbau des Projekts mit dem Arduino Nano und weiteren Bauteilen

Wenn du alles verkabelt hast, kann es mit dem Sketch weitergehen.

Der Sketch für das Projekt

Bevor du den folgenden Sketch kopierst und auf deinen Arduino hochlädst, benötigst du noch die passenden Bibliotheken für den Sensor DHT22 – falls du in der Arduino IDE noch nie ein Projekt mit diesem Sensor umgesetzt hast.

Öffne in diesem Fall den Bibliotheksmanager in der IDE und suche nach DHT sensor library und klicke auf Installieren. In einem Dialogfenster wirst du daraufhin gefragt, ob du die Bibliothek Adafruit Unified Sensor gleich mitinstallieren möchtest. Bestätige das mit einem Klick auf Ja.

Nun zum vollständigen Sketch:

___STEADY_PAYWALL___

#include <DHT.h>

// Pins
#define DHTPIN 7       // DHT22 an Pin D7
#define BUZZER_PIN 4   // Piezo-Summer an Pin D4
#define BUTTON_PIN 6   // Taster an Pin D6
#define LED_PIN 13     // Interne LED an Pin D13

// DHT Sensor
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

// Variablen
float startTemperature = 0.0;
bool measuring = false;
bool alarmActive = false;

void setup() {
  Serial.begin(9600);
  dht.begin();
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP); // Interner Pull-Up-Widerstand aktivieren
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(BUZZER_PIN, LOW);
  digitalWrite(LED_PIN, LOW); // LED initial ausgeschaltet
}

void loop() {
  // Taster prüfen (wechseln zwischen Messung und Leerlauf)
  if (digitalRead(BUTTON_PIN) == LOW) { // Taster gedrückt
    delay(50); // Entprellung
    // Sicherstellen, dass der Taster weiterhin gedrückt ist
    if (digitalRead(BUTTON_PIN) == LOW) {
      if (!measuring) {
        // Messung starten
        measuring = true;
        alarmActive = false; // sicherstellen, dass der Alarm deaktiviert ist
        startTemperature = dht.readTemperature();
        if (isnan(startTemperature)) {
          Serial.println("Fehler beim Lesen des DHT22!");
          startTemperature = 0.0; // Standardwert
        }
        Serial.print("Messung gestartet. Starttemperatur: ");
        Serial.println(startTemperature);
        digitalWrite(LED_PIN, HIGH); // LED einschalten
      } else {
        // In Leerlauf wechseln: Alarm und Messung stoppen
        noTone(BUZZER_PIN); // Alarmton stoppen
        digitalWrite(LED_PIN, LOW); // LED ausschalten
        alarmActive = false; 
        measuring = false;
        Serial.println("Alarm ausgeschaltet und Leerlauf aktiviert.");
      }
      // Warten bis der Button losgelassen wird
      while (digitalRead(BUTTON_PIN) == LOW);
      delay(50); // Entprellzeit nach Loslassen
    }
  }
  
  // Messlogik, wenn aktiv
  if (measuring) {
    float currentTemperature = dht.readTemperature();
    if (isnan(currentTemperature)) {
      Serial.println("Fehler beim Lesen des DHT22!");
      delay(2000);
      return;
    }

    // Temperaturänderung prüfen
    if (currentTemperature <= startTemperature - 0.5) {
      alarmActive = true; // Alarm auslösen
    }

    // Alarm ausführen
    if (alarmActive) {
      tone(BUZZER_PIN, 1000); // Alarmton
      Serial.println("Alarm: Temperatur ist gesunken!");
    } else {
      noTone(BUZZER_PIN); // Alarmton deaktivieren
    }

    // Debug-Ausgabe
    Serial.print("Aktuelle Temperatur: ");
    Serial.println(currentTemperature);

    delay(2000); // Messung alle 2 Sekunden
  }
}

So funktioniert der sketch

Lass uns einen genaueren Blick auf den Sketch werfen. Zunächst bindest du die Bibliothek für den Temperatursensor ein und legst die Anschlüsse der Bauteile fest:

#include <DHT.h>

#define DHTPIN 7       // DHT22 an Pin D7
#define BUZZER_PIN 4   // Piezo-Summer an Pin D4
#define BUTTON_PIN 6   // Taster an Pin D6
#define LED_PIN 13     // Interne LED an Pin D13

#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

Weiter geht es mit den benötigten Variablen. In startTemperature wird die Temperatur zu Beginn deiner Messung – also sobald du auf den Button drückst – erfasst. Die beiden Variablen measuring und alarmActive dienen später der Programmsteuerung.

float startTemperature = 0.0;
bool measuring = false;
bool alarmActive = false;

Setup-Funktion

Hier startest du den Seriellen Monitor und initialisierst den Sensor. Außerdem legst du die benötigten pinModes fest und aktivierst für den Button den internen Pullup-Widerstand des Arduinos. Zuletzt stellst du sicher, dass der Piezo-Summer und die interne LED ausgeschaltet sind.

void setup() {

  Serial.begin(9600);
  dht.begin();
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP); // Interner Pull-Up-Widerstand aktivieren
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(BUZZER_PIN, LOW);
  digitalWrite(LED_PIN, LOW); // LED initial ausgeschaltet
}

Loop-Funktion

Das ist der Hauptteil des Programms, der kontinuierlich läuft. Hier wird entschieden, ob die Messung durchgeführt wird oder nicht:

if (digitalRead(BUTTON_PIN) == LOW) {

  // Wurde der Button gedrückt? Dann beginnt die Messung, falls sie nicht bereits läuft.

}

Bei einem Druck auf den Button passiert Folgendes: Wenn die Messung nicht läuft (die Variable measuring ist false), wird sie gestartet. Die Variable measuring wird dann auf true gesetzt.

Wenn die Messung allerdings läuft, wird sie gestoppt. Der Alarm wird deaktiviert und die LED ausgeschaltet. Die Variable measuring wird wieder auf false gesetzt. Der Arduino befindet sich nun sozusagen im Leerlauf.

In der Messung selbst wird alle 2 Sekunden geprüft, ob die aktuelle Temperatur mehr als 0,5°C unterhalb der Starttemperatur liegt. Ist das der Fall, wird der Alarm aktiviert:

// Temperaturänderung prüfen
if (currentTemperature <= startTemperature - 0.5) {
  alarmActive = true; // Alarm auslösen
}

Hierfür wird die Variable alarmActive auf true gesetzt, was dazu führt, dass in der darauffolgenden Abfrage der Piezo-Summer aktiviert wird:

if (alarmActive) {
  tone(BUZZER_PIN, 1000); // Alarmton
  Serial.println("Alarm: Temperatur ist gesunken!");
} else {
  noTone(BUZZER_PIN); // Alarmton deaktivieren
}

Hier wird geprüft, ob alarmActive == true ist (in einer verkürzten Schreibweise) und der Piezo entsprechend aktiviert oder deaktiviert.

Wie geht es weiter?

Du hast nun einen Arduino, der laut Alarm schlägt, sobald die Temperatur entsprechend deiner Einstellung gefallen ist. Statt einen Piezo-Summers kannst du aber natürlich auch eine andere Benachrichtigung wählen. Wie wäre es z.B. mit einer Push Notification auf deinem Smartphone? Hierfür benötigst du allerdings einen Microcontroller mit WLAN, wie z.B. den ESP8266 oder ESP32.

]]>
Snake spielen auf dem Arduino (und dem ESP32) https://polluxlabs.net/arduino-projekte/snake-spielen-auf-dem-arduino-und-dem-esp32/ Sun, 27 Oct 2024 20:51:07 +0000 https://polluxlabs.net/?p=17469 Snake spielen auf dem Arduino (und dem ESP32) Weiterlesen »

]]>
Der Spieleklassiker Snake begleitet uns schon lange und auf ganz unterschiedlichen Geräten – auf dem PC, auf Taschenrechnern von Texas Instruments oder auch auf dem ehrwürdigen Nokia 3210. Dank des einfachen Prinzips ist das Spiel fast überall dort umzusetzen, wo etwas Prozessorleistung vorhanden ist. Also warum nicht auch auf einem Arduino?

In diesem Projekt baust du dir zunächst eine Snake-Version auf einem Arduino UNO R4. Als Display verwendest du hierbei ein OLED-Display mit 128×64 Pixeln. Steuern wirst du das Spiel mit einem kleinen Joystick. Da das Spiel zwar auf dem Arduino UNO läuft, für geschickte Spieler jedoch vielleicht etwas zu langsam ist, kommt später noch ein ESP32 zum Zug. Dieser verfügt über mehr Leistung und sorgt für ein flüssigeres Spielvergnügen.

Für dieses Projekt benötigst du:

Snake auf dem Arduino UNO

Zunächst also die Version auf dem Arduino UNO. Hier eignet sich der „neue“ UNO R4, da dieser deutlich mehr Leistung als sein Vorgänger R3 besitzt. Ob du die WiFi- oder die Minima-Version verwendest, ist für dieses Projekt egal. Falls du jedoch noch überlegst, dir einen R4 zuzulegen, empfehle ich dir auf jeden Fall erstere, da der R4 WiFi nur ein paar Euro mehr kostet und du damit mit deinen Projekten ins Internet kannst.

Nun aber zum Anschluss der Bauteile: Verbinde das OLED-Display und den den Joystick folgendermaßen mit dem Arduino UNO:

Anschluss von OLED-Display und Joystick am Arduino UNO für Snake

Je nachdem, welchen Joystick du verwendest, kann die Beschriftung der Pins variieren. So kann zum Beispiel statt VERT auch VRy zu lesen sein. Mehr Infos hierüber findest du im Tutorial zum Arduino Joystick. Wenn du alles verkabelt hast, kann es direkt mit dem Sketch weitergehen.

Die benötigten Bibliotheken

Damit du am Arduino dein OLED-Display verwenden kannst, benötigst du zwei Bibliotheken. Öffne in der Arduino IDE den Bibliotheksverwalter, suche und installiere dort diese beiden Bibliotheken von Adafruit:

Adafruit SSD1306
Adafruit GFX

Im Sketch wirst du gleich noch eine dritte Bibliothek sehen, Wire.h – diese ist jedoch bereits vorinstalliert, sodass du dich um diese nicht kümmern musst.

Der gesamte Sketch

Hier nun der vollständige Sketch für Snake. Kopiere dir den folgenden Code, erstelle einen neuen Sketch und lade ihn auf deinen Arduino hoch.

// Snake spielen auf dem Arduino
// Pollux Labs

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>

// OLED Display Konfiguration
#define SCREEN_WIDTH 128  // Breite des OLED-Displays
#define SCREEN_HEIGHT 64  // Höhe des OLED-Displays
#define OLED_RESET    -1  // OLED-Reset-Pin (nicht verwendet)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Joystick Konfiguration
#define VRX_PIN A0  // Joystick X-Achsen-Pin (analoger Pin A0 des Arduino Uno)
#define VRY_PIN A1  // Joystick Y-Achsen-Pin (analoger Pin A1 des Arduino Uno)
#define SW_PIN  7   // Joystick Taster-Pin (digitaler Pin 7 des Arduino Uno)

// Eigenschaften der Schlange
#define SNAKE_SIZE 4  // Größe jedes Segments der Schlange
#define MAX_SNAKE_LENGTH 50  // Maximale Länge der Schlange (reduziert für Arduino Uno, um Speicher zu sparen)
int snakeX[MAX_SNAKE_LENGTH], snakeY[MAX_SNAKE_LENGTH];  // Arrays zur Speicherung der Segmentpositionen der Schlange
int snakeLength = 5;  // Anfangslänge der Schlange
int directionX = 1, directionY = 0;  // Anfangsrichtung der Bewegung (nach rechts)

// Eigenschaften des Futters
int foodX = random(2, (SCREEN_WIDTH - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;  // X-Koordinate des Futters (nicht direkt am Rand)
int foodY = random(12 / SNAKE_SIZE, (SCREEN_HEIGHT - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;  // Y-Koordinate des Futters (Spielbereich unterhalb des Rahmens, nicht direkt am Rand)

// Score
int score = 0;

void setup() {
  Serial.begin(9600);  // Initialisiere serielle Kommunikation mit niedrigerer Baudrate für Arduino Uno

  // Initialisiere Display
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {  // Initialisiere das OLED-Display mit der I2C-Adresse 0x3C
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);  // Stoppe, falls die Display-Initialisierung fehlschlägt
  }
  display.clearDisplay();  // Leere den Display-Puffer
  display.display();  // Zeige den geleerten Puffer an

  // Initialisiere Joystick
  pinMode(VRX_PIN, INPUT);  // Setze Joystick X-Achsen-Pin als Eingang
  pinMode(VRY_PIN, INPUT);  // Setze Joystick Y-Achsen-Pin als Eingang
  pinMode(SW_PIN, INPUT_PULLUP);  // Setze Joystick-Taster-Pin als Eingang mit internem Pull-up-Widerstand

  // Initialisiere Position der Schlange
  for (int i = 0; i < snakeLength; i++) {
    snakeX[i] = SCREEN_WIDTH / 2 - (i * SNAKE_SIZE);  // Setze anfängliche X-Koordinaten der Schlangensegmente
    snakeY[i] = SCREEN_HEIGHT / 2;  // Setze anfängliche Y-Koordinate der Schlangensegmente
  }
  Serial.println("Setup abgeschlossen");
}

void loop() {
  // Lese Joystick-Werte
  int xValue = analogRead(VRX_PIN);  // Lese X-Achsen-Wert vom Joystick
  int yValue = analogRead(VRY_PIN);  // Lese Y-Achsen-Wert vom Joystick
  Serial.print("Joystick X: ");
  Serial.print(xValue);
  Serial.print(" Y: ");
  Serial.println(yValue);

  // Setze Richtung basierend auf Joystick-Eingaben, verhindere diagonale Bewegung
  if (xValue < 300 && directionX == 0) {  // Bewegung nach links, wenn derzeit nicht horizontal bewegt wird
    directionX = -1;
    directionY = 0;
    Serial.println("Richtung: Links");
  } else if (xValue > 700 && directionX == 0) {  // Bewegung nach rechts, wenn derzeit nicht horizontal bewegt wird
    directionX = 1;
    directionY = 0;
    Serial.println("Richtung: Rechts");
  } else if (yValue < 300 && directionY == 0) {  // Bewegung nach oben, wenn derzeit nicht vertikal bewegt wird
    directionX = 0;
    directionY = -1;
    Serial.println("Richtung: Oben");
  } else if (yValue > 700 && directionY == 0) {  // Bewegung nach unten, wenn derzeit nicht vertikal bewegt wird
    directionX = 0;
    directionY = 1;
    Serial.println("Richtung: Unten");
  }

  // Aktualisiere Position der Schlange
  for (int i = snakeLength - 1; i > 0; i--) {  // Bewege jedes Segment zur Position des vorherigen Segments
    snakeX[i] = snakeX[i - 1];
    snakeY[i] = snakeY[i - 1];
  }
  snakeX[0] += directionX * SNAKE_SIZE;  // Aktualisiere Kopfposition in X-Richtung
  snakeY[0] += directionY * SNAKE_SIZE;  // Aktualisiere Kopfposition in Y-Richtung
  Serial.print("Schlangenkopf X: ");
  Serial.print(snakeX[0]);
  Serial.print(" Y: ");
  Serial.println(snakeY[0]);

  // Prüfe auf Kollision mit dem Futter
  if (snakeX[0] == foodX && snakeY[0] == foodY) {  // Wenn der Schlangenkopf das Futter erreicht
    if (snakeLength < MAX_SNAKE_LENGTH) {
      snakeLength++;  // Erhöhe die Schlangenlänge
      score++;  // Erhöhe den Score
      Serial.println("Futter gegessen, Schlangenlänge: " + String(snakeLength));
    }
    // Generiere neue Futterposition (nicht direkt am Rand)
    foodX = random(2, (SCREEN_WIDTH - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
    foodY = random(12 / SNAKE_SIZE, (SCREEN_HEIGHT - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
    Serial.print("Neues Futter bei X: ");
    Serial.print(foodX);
    Serial.print(" Y: ");
    Serial.println(foodY);
  }

  // Prüfe auf Kollision mit den Wänden
  if (snakeX[0] < SNAKE_SIZE || snakeX[0] >= SCREEN_WIDTH - SNAKE_SIZE || snakeY[0] < 10 || snakeY[0] >= SCREEN_HEIGHT - SNAKE_SIZE) {
    Serial.println("Kollision mit der Wand, Spiel wird zurückgesetzt");
    resetGame();  // Setze das Spiel zurück, wenn die Schlange die Wand trifft
  }

  // Prüfe auf Kollision mit sich selbst
  for (int i = 1; i < snakeLength; i++) {
    if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {  // Wenn der Schlangenkopf mit ihrem eigenen Körper kollidiert
      Serial.println("Kollision mit sich selbst, Spiel wird zurückgesetzt");
      resetGame();  // Setze das Spiel zurück
    }
  }

  // Zeichne alles
  display.clearDisplay();  // Leere den Display-Puffer

  // Zeichne den Score
  display.setTextSize(1);  // Setze Textgröße
  display.setTextColor(SSD1306_WHITE);  // Setze Textfarbe
  display.setCursor(0, 0);  // Setze Cursor für Score
  display.print("Score: ");
  display.print(score);  // Zeige aktuellen Score an

  // Zeichne den Rand um das Spielfeld
  display.drawRect(0, 10, SCREEN_WIDTH, SCREEN_HEIGHT - 10, SSD1306_WHITE);  // Zeichne einen weißen Rahmen um das Spielfeld

  // Zeichne die Schlange
  for (int i = 0; i < snakeLength; i++) {
    display.fillRect(snakeX[i], snakeY[i], SNAKE_SIZE, SNAKE_SIZE, SSD1306_WHITE);  // Zeichne jedes Segment der Schlange
  }

  // Zeichne das Futter
  display.fillRect(foodX, foodY, SNAKE_SIZE, SNAKE_SIZE, SSD1306_WHITE);  // Zeichne das Futter

  display.display();  // Zeige den aktualisierten Puffer an

  delay(150);  // Verzögerung zur Steuerung der Geschwindigkeit der Schlange
}

void resetGame() {
  // Setze Eigenschaften der Schlange zurück
  snakeLength = 5;  // Setze die Schlangenlänge zurück
  directionX = 1;  // Setze die Richtung nach rechts zurück
  directionY = 0;
  score = 0;  // Setze den Score zurück
  for (int i = 0; i < snakeLength; i++) {
    snakeX[i] = SCREEN_WIDTH / 2 - (i * SNAKE_SIZE);  // Setze die Position der Schlange zurück auf die Mitte
    snakeY[i] = SCREEN_HEIGHT / 2;
  }
  // Generiere neue Futterposition (nicht direkt am Rand)
  foodX = random(2, (SCREEN_WIDTH - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
  foodY = random(12 / SNAKE_SIZE, (SCREEN_HEIGHT - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
  Serial.println("Spiel zurückgesetzt");
  Serial.print("Neues Futter bei X: ");
  Serial.print(foodX);
  Serial.print(" Y: ");
  Serial.println(foodY);
}

Nach dem Upload sollte es auf dem Display direkt losgehen: In der Mitte erscheint, die zunächst 5 Pixel lange Schlange und bewegt sich. Ein Stück Futter erscheint zufällig platziert auf dem Spielfeld. Wie du sicherlich weißt, ist dein Ziel, so viel zu fressen wie möglich – ohne mit dem Spielfeldrand oder dir selbst zu kollidieren.

Oben links findest du deine Punktzahl. Für jede Verlängerung deiner Schlange erhöht sich dein Score um 1.

So funktioniert der Sketch

Lass uns ein paar wichtige Stellen des Sketchs anschauen.

Setup

Die setup()-Funktion führt alle notwendigen Initialisierungen durch:

  • Serielle Kommunikation wird gestartet, um Debugging-Informationen auszugeben.
  • Das OLED-Display wird initialisiert. Falls die Initialisierung fehlschlägt, wird eine Fehlermeldung ausgegeben und das Programm angehalten.
void setup() {
  Serial.begin(9600);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);
  }
  display.clearDisplay();
  display.display();
}
  • Der Joystick wird initialisiert, indem seine Pins als Eingänge konfiguriert werden.
  • Die Position der Schlange wird initial in der Mitte des Displays gesetzt.

Loop

Die loop()-Funktion läuft kontinuierlich und behandelt alle Aspekte des Spiels, wie die Steuerung, Bewegung, Kollisionserkennung und die Ausgabe auf dem Display.

Joystick-Steuerung

Der Joystick wird verwendet, um die Bewegungsrichtung der Schlange zu ändern. Die analogen Eingänge des Joysticks liefern Werte, die bestimmen, in welche Richtung sich die Schlange bewegt:

  • Wenn der Joystick nach links bewegt wird, wird die Richtung auf links gesetzt, solange die Schlange sich nicht bereits nach rechts bewegt.
  • Gleiches gilt für die anderen Richtungen.
if (xValue < 300 && directionX == 0) {
  directionX = -1;
  directionY = 0;
  Serial.println("Richtung: Links");
}
Bewegung der Schlange

Die Position der Schlange wird durch eine Schleife aktualisiert, in der jedes Segment der Schlange zur Position des vorherigen Segments bewegt wird. Der Kopf der Schlange wird in die gewählte Richtung verschoben.

for (int i = snakeLength - 1; i > 0; i--) {
  snakeX[i] = snakeX[i - 1];
  snakeY[i] = snakeY[i - 1];
}
snakeX[0] += directionX * SNAKE_SIZE;
snakeY[0] += directionY * SNAKE_SIZE;
Fressen des Futters

Wenn die Schlange das Futter erreicht, wird ihre Länge erhöht, und ein neuer Punktestand wird berechnet. Danach wird das Futter an einer neuen Position generiert, die nicht direkt am Rand liegt, um das Spiel einfacher zu machen.

if (snakeX[0] == foodX && snakeY[0] == foodY) {
  if (snakeLength < MAX_SNAKE_LENGTH) {
    snakeLength++;
    score++;
  }
  foodX = random(2, (SCREEN_WIDTH - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
  foodY = random(12 / SNAKE_SIZE, (SCREEN_HEIGHT - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
}
Kollisionserkennung

Die loop()-Funktion prüft auch, ob die Schlange mit den Rändern des Spielfeldes oder mit sich selbst kollidiert:

  • Wenn die Schlange mit einer Wand kollidiert, wird das Spiel zurückgesetzt.
  • Wenn die Schlange ihren eigenen Körper berührt, wird ebenfalls das Spiel zurückgesetzt.
if (snakeX[0] < SNAKE_SIZE || snakeX[0] >= SCREEN_WIDTH - SNAKE_SIZE || snakeY[0] < 10 || snakeY[0] >= SCREEN_HEIGHT - SNAKE_SIZE) {
  resetGame();
}
Anzeige auf dem OLED-Display

Das OLED-Display wird in jeder Schleife aktualisiert:

  • Der Punktestand wird oben links angezeigt.
  • Ein weißer Rahmen wird um das Spielfeld gezeichnet, um die Grenzen des Spielfeldes anzuzeigen.
  • Die Schlange und das Futter werden auf dem Display gezeichnet.
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print("Score: ");
display.print(score);

display.drawRect(0, 10, SCREEN_WIDTH, SCREEN_HEIGHT - 10, SSD1306_WHITE);

for (int i = 0; i < snakeLength; i++) {
  display.fillRect(snakeX[i], snakeY[i], SNAKE_SIZE, SNAKE_SIZE, SSD1306_WHITE);
}
display.fillRect(foodX, foodY, SNAKE_SIZE, SNAKE_SIZE, SSD1306_WHITE);
display.display();

Das Spiel zurücksetzen

Die resetGame()-Funktion setzt die Schlange zurück auf ihre Ausgangsposition und -länge, wenn eine Kollision erkannt wird. Auch der Punktestand wird zurückgesetzt, und eine neue Position für das Futter wird generiert.

void resetGame() {
  snakeLength = 5;
  directionX = 1;
  directionY = 0;
  score = 0;
  for (int i = 0; i < snakeLength; i++) {
    snakeX[i] = SCREEN_WIDTH / 2 - (i * SNAKE_SIZE);
    snakeY[i] = SCREEN_HEIGHT / 2;
  }
}

Snake auf dem ESP32 spielen

Wenn du an dieser Stelle bereits Snake auf dem Arduino UNO gespielt hast, wirst du sicherlich bemerkt haben, dass das Spiel sehr gemächlich – um nicht zu sagen „ruckelig“ – abläuft. Das liegt an der eher begrenzten Leistung, die auch beim Modell R4 nicht ausreichend ist, alle Berechnungen in der Loop-Funktion schnell genug durchzuführen.

Falls du jedoch auch einen ESP32 dein Eigen nennst, kannst du das Spiel (fast) ganz einfach umziehen und ihm so mehr Geschwindigkeit einhauchen. Schließe das OLED-Display und den Joystick folgendermaßen am ESP32 an:

Anschluss von OLED-Display und Joystick am ESP32

Der Sketch

Damit Snake auf dem ESP32 läuft, sind nur ein paar kleinere Adaptionen nötig. So hinterlegst du im Sketch zunächst natürlich andere Pins. Aber auch die maximale Länge der Schlange kannst du hier nach oben setzen – von 50 für den Arduino auf 100 für den ESP32:

#define MAX_SNAKE_LENGTH 100  // Maximale Länge der Schlange

Auch die Schwellenwerte für den Joystick musst du beim ESP32 ändern, weil der dieser eine höhere Auflösung für die analogen Eingänge hat als der Arduino UNO. Der Arduino Uno verwendet eine 10-Bit-Auflösung, was bedeutet, dass die analogen Eingänge Werte von 0 bis 1023 zurückgeben. Der ESP32 hingegen verwendet eine 12-Bit-Auflösung, was einen Bereich von 0 bis 4095 ergibt.

Für eine Bewegung nach links ist das zum Beispiel ein Wert von 1000. Im Sketch für den Arduino UNO lag dieser Wert bei 300:

if (xValue < 1000 && directionX == 0) { //Schwellenwert für den ESP32

Mit einem Delay steuerst du die Geschwindigkeit der Schlange. Da dir auf dem ESP32 mehr Leistung zur Verfügung steht, setzt du diesen Wert hier in Zeile 147 des Sketchs auf 100 (statt 150 für den Arduino UNO).

Hier nun der gesamte Sketch für den ESP32:

// Snake spielen auf dem ESP32
// Pollux Labs

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>

// OLED Display Konfiguration
#define SCREEN_WIDTH 128  // Breite des OLED-Displays
#define SCREEN_HEIGHT 64  // Höhe des OLED-Displays
#define OLED_RESET    -1  // OLED-Reset-Pin (nicht verwendet)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Joystick Konfiguration
#define VRX_PIN 34  // Joystick X-Achsen-Pin
#define VRY_PIN 35  // Joystick Y-Achsen-Pin
#define SW_PIN  32  // Joystick Taster-Pin

// Eigenschaften der Schlange
#define SNAKE_SIZE 4  // Größe jedes Segments der Schlange
#define MAX_SNAKE_LENGTH 100  // Maximale Länge der Schlange
int snakeX[MAX_SNAKE_LENGTH], snakeY[MAX_SNAKE_LENGTH];  // Arrays zur Speicherung der Segmentpositionen der Schlange
int snakeLength = 5;  // Anfangslänge der Schlange
int directionX = 1, directionY = 0;  // Anfangsrichtung der Bewegung (nach rechts)

// Eigenschaften des Futters
int foodX = random(2, (SCREEN_WIDTH - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;  // X-Koordinate des Futters (nicht direkt am Rand)
int foodY = random(12 / SNAKE_SIZE, (SCREEN_HEIGHT - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;  // Y-Koordinate des Futters (Spielbereich unterhalb des Rahmens, nicht direkt am Rand)

// Score
int score = 0;

void setup() {
  Serial.begin(115200);  // Initialisiere serielle Kommunikation

  // Initialisiere Display
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {  // Initialisiere das OLED-Display mit der I2C-Adresse 0x3C
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);  // Stoppe, falls die Display-Initialisierung fehlschlägt
  }
  display.clearDisplay();  // Leere den Display-Puffer
  display.display();  // Zeige den geleerten Puffer an

  // Initialisiere Joystick
  pinMode(VRX_PIN, INPUT);  // Setze Joystick X-Achsen-Pin als Eingang
  pinMode(VRY_PIN, INPUT);  // Setze Joystick Y-Achsen-Pin als Eingang
  pinMode(SW_PIN, INPUT_PULLUP);  // Setze Joystick-Taster-Pin als Eingang mit internem Pull-up-Widerstand

  // Initialisiere Position der Schlange
  for (int i = 0; i < snakeLength; i++) {
    snakeX[i] = SCREEN_WIDTH / 2 - (i * SNAKE_SIZE);  // Setze anfängliche X-Koordinaten der Schlangensegmente
    snakeY[i] = SCREEN_HEIGHT / 2;  // Setze anfängliche Y-Koordinate der Schlangensegmente
  }
  Serial.println("Setup abgeschlossen");
}

void loop() {
  // Lese Joystick-Werte
  int xValue = analogRead(VRX_PIN);  // Lese X-Achsen-Wert vom Joystick
  int yValue = analogRead(VRY_PIN);  // Lese Y-Achsen-Wert vom Joystick
  Serial.print("Joystick X: ");
  Serial.print(xValue);
  Serial.print(" Y: ");
  Serial.println(yValue);

  // Setze Richtung basierend auf Joystick-Eingaben, verhindere diagonale Bewegung
  if (xValue < 1000 && directionX == 0) {  // Bewegung nach links, wenn derzeit nicht horizontal bewegt wird
    directionX = -1;
    directionY = 0;
    Serial.println("Richtung: Links");
  } else if (xValue > 3000 && directionX == 0) {  // Bewegung nach rechts, wenn derzeit nicht horizontal bewegt wird
    directionX = 1;
    directionY = 0;
    Serial.println("Richtung: Rechts");
  } else if (yValue < 1000 && directionY == 0) {  // Bewegung nach oben, wenn derzeit nicht vertikal bewegt wird
    directionX = 0;
    directionY = -1;
    Serial.println("Richtung: Oben");
  } else if (yValue > 3000 && directionY == 0) {  // Bewegung nach unten, wenn derzeit nicht vertikal bewegt wird
    directionX = 0;
    directionY = 1;
    Serial.println("Richtung: Unten");
  }

  // Aktualisiere Position der Schlange
  for (int i = snakeLength - 1; i > 0; i--) {  // Bewege jedes Segment zur Position des vorherigen Segments
    snakeX[i] = snakeX[i - 1];
    snakeY[i] = snakeY[i - 1];
  }
  snakeX[0] += directionX * SNAKE_SIZE;  // Aktualisiere Kopfposition in X-Richtung
  snakeY[0] += directionY * SNAKE_SIZE;  // Aktualisiere Kopfposition in Y-Richtung
  Serial.print("Schlangenkopf X: ");
  Serial.print(snakeX[0]);
  Serial.print(" Y: ");
  Serial.println(snakeY[0]);

  // Prüfe auf Kollision mit dem Futter
  if (snakeX[0] == foodX && snakeY[0] == foodY) {  // Wenn der Schlangenkopf das Futter erreicht
    if (snakeLength < MAX_SNAKE_LENGTH) {
      snakeLength++;  // Erhöhe die Schlangenlänge
      score++;  // Erhöhe den Score
      Serial.println("Futter gegessen, Schlangenlänge: " + String(snakeLength));
    }
    // Generiere neue Futterposition (nicht direkt am Rand)
    foodX = random(2, (SCREEN_WIDTH - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
    foodY = random(12 / SNAKE_SIZE, (SCREEN_HEIGHT - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
    Serial.print("Neues Futter bei X: ");
    Serial.print(foodX);
    Serial.print(" Y: ");
    Serial.println(foodY);
  }

  // Prüfe auf Kollision mit den Wänden
  if (snakeX[0] < SNAKE_SIZE || snakeX[0] >= SCREEN_WIDTH - SNAKE_SIZE || snakeY[0] < 10 || snakeY[0] >= SCREEN_HEIGHT - SNAKE_SIZE) {
    Serial.println("Kollision mit der Wand, Spiel wird zurückgesetzt");
    resetGame();  // Setze das Spiel zurück, wenn die Schlange die Wand trifft
  }

  // Prüfe auf Kollision mit sich selbst
  for (int i = 1; i < snakeLength; i++) {
    if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {  // Wenn der Schlangenkopf mit ihrem eigenen Körper kollidiert
      Serial.println("Kollision mit sich selbst, Spiel wird zurückgesetzt");
      resetGame();  // Setze das Spiel zurück
    }
  }

  // Zeichne alles
  display.clearDisplay();  // Leere den Display-Puffer

  // Zeichne den Score
  display.setTextSize(1);  // Setze Textgröße
  display.setTextColor(SSD1306_WHITE);  // Setze Textfarbe
  display.setCursor(0, 0);  // Setze Cursor für Score
  display.print("Score: ");
  display.print(score);  // Zeige aktuellen Score an

  // Zeichne den Rand um das Spielfeld
  display.drawRect(0, 10, SCREEN_WIDTH, SCREEN_HEIGHT - 10, SSD1306_WHITE);  // Zeichne einen weißen Rahmen um das Spielfeld

  // Zeichne die Schlange
  for (int i = 0; i < snakeLength; i++) {
    display.fillRect(snakeX[i], snakeY[i], SNAKE_SIZE, SNAKE_SIZE, SSD1306_WHITE);  // Zeichne jedes Segment der Schlange
  }

  // Zeichne das Futter
  display.fillRect(foodX, foodY, SNAKE_SIZE, SNAKE_SIZE, SSD1306_WHITE);  // Zeichne das Futter

  display.display();  // Zeige den aktualisierten Puffer an

  delay(100);  // Verzögerung zur Steuerung der Geschwindigkeit der Schlange
}

void resetGame() {
  // Setze Eigenschaften der Schlange zurück
  snakeLength = 5;  // Setze die Schlangenlänge zurück
  directionX = 1;  // Setze die Richtung nach rechts zurück
  directionY = 0;
  score = 0;  // Setze den Score zurück
  for (int i = 0; i < snakeLength; i++) {
    snakeX[i] = SCREEN_WIDTH / 2 - (i * SNAKE_SIZE);  // Setze die Position der Schlange zurück auf die Mitte
    snakeY[i] = SCREEN_HEIGHT / 2;
  }
  // Generiere neue Futterposition (nicht direkt am Rand)
  foodX = random(2, (SCREEN_WIDTH - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
  foodY = random(12 / SNAKE_SIZE, (SCREEN_HEIGHT - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
  Serial.println("Spiel zurückgesetzt");
  Serial.print("Neues Futter bei X: ");
  Serial.print(foodX);
  Serial.print(" Y: ");
  Serial.println(foodY);
}

Wie geht es weiter?

Du hast nun eine spielbare Version von Snake samt einer Anzeige deiner Punkte. Wie könntest du das Spiel noch erweitern oder verbessern? Eine Idee könnte zum Beispiel ein Highscore sein. Der aktuelle Halter dieses Highscores könnte mit dem Joystick seine Initialen eintragen, die neben der Punktezahl im Dateisystem des ESP32 gespeichert werden und auf dem Display angezeigt werden – ein guter Ansporn für eine weitere Partie Snake!

]]>
Cyclone – Neuauflage des Spieleklassikers https://polluxlabs.net/arduino-projekte/cyclone-neuauflage-des-spieleklassikers/ Mon, 24 Jun 2024 19:26:02 +0000 https://polluxlabs.net/?p=16712 Cyclone – Neuauflage des Spieleklassikers Weiterlesen »

]]>
Gastbeitrag – ein Projekt von Natascha Gebhardt, Simon Flamm, Marcel Gosch und Dr. Juliane König-Birk

Das Cyclone-Spiel ist ein beliebtes Automatenspiel aus Arcade-Spielhallen. Ziel des Spiels ist es, ein zirkulierendes Licht im richtigen Moment zu stoppen. Je nach Timing wird ein ganzer Treffer oder auch nur ein halber Treffer erreicht. Das Cyclone trackt dabei automatisch den aktuellen Highscore.

Spielablauf

Sobald das Gerät eingeschaltet wird, startet ein neues Spiel automatisch.  Zum Abbrechen eines laufenden Spiels kann das Gerät einfach ausgeschaltet werden.

Das Ziel des Spiels ist es, den Push-Button genau dann zu drücken, wenn die zirkulierende grüne LED sich mit der roten bzw. gelben LED überlagert. Das Gerät verfügt über eine integrierte Speicherung und zeichnet den aktuellen Highscore automatisch auf.

Erklärung der Anzeigemodi des LED-Ringes und LCD-Displays


Die Spielanzeige erfolgt hier sowohl über das LCD-Display als auch den LED-Ring. Beide verfügen über unterschiedliche Funktionen, die je nach Spielverlauf aktiviert werden.

Cyclone LED Ring
Ein Bild, das Text, Elektronik, Display, Elektronisches Gerät enthält.

Automatisch generierte Beschreibung Gelb: +1 Punkt – Half Hit
Das zirkulierende grüne Licht wird an einem der äußeren LEDs angehalten. Der gesamte Ring blinkt gelb auf.

Ein Bild, das Text, Elektronik, Display, Zahl enthält.

Automatisch generierte Beschreibung
Grün: + 2 Punkte – Full Hit
Das zirkulierende grüne Licht wird im richtigen Moment im zentralen LED angehalten. Der gesamte Ring blinkt grün auf.

Ein Bild, das Text, Elektronik, Messgerät enthält.

Automatisch generierte Beschreibung
ROT: -1 Leben (Flop)
Das zirkulierende grüne Licht wird im falschen Moment außerhalb der stationären LEDs angehalten. Der gesamte Ring blinkt rot auf.

Game Over

Bei einem Flop verliert der Spielende ein Leben. Verbleibende Leben werden auf dem Bildschirm angezeigt. Bei Verlust des letzten Lebens beginnt automatisch ein neues Spiel.

Benötigte Hardware für das Cyclone

Die Hardware ist erschwinglich und leicht zu finden. Sie umfasst ein Arduino Nano-Board, einen WS2812B-LED-Ring, ein 16×2-Zeichen-LCD mit I2C-Schnittstelle, einen Taster, einen Netzschalter und einen Summer. Diese Komponenten sind alle in einem 3D-gedruckten Gehäuse integriert, welches vom Design an einen Gameboy Color angelehnt wurde. Der Mikrocontroller wird mithilfe eines USB-Netzteils und einer 5000 mAh USB-Powerbank mit Spannung versorgt, sodass das Spiel problemlos mobil betrieben werden kann.

Löten des LED-Ringes

Der LED-Ring besteht aus vier einzelnen Viertelringen, diese weisen je 3 Anschlüsse auf:

  • 5V: Energieversorgung des LED-Ringes
  • Datenleitung: Übermittelt die Befehle des Microcontrollers an die LEDs
  • Ground: Schließen des Stromkreises

Die Verbindung der Leitungen erfordert einen Lötkolben und Lötzinn. Die korrekte Temperatureinstellung des Lötkolbens ist dabei zu beachten, um das Lötzinn verarbeiten zu können. Im Anschluss sollten die einzelnen Lötstellen auf ihre Qualität überprüft werden, da fehlerhafte Verbindungen später zu Problemen bei der Steuerung der LEDs führen können. Es sollte darauf geachtet werden, nicht zu viel Lötzinn zu verwenden, da sich dieses sonst auf die benachbarten Leitungen ausbreiten kann und zu Kurzschlüssen führt. Zusätzlich ist auf mögliche Lunker in den Lötstellen zu achten, da unsaubere Verbindungen die Spannungsversorgung der LEDs beeinträchtigen und die Datenübertragung stören können.

LED-Ring löten

Verkabelung der Bauteile

Die Verkabelung der einzelnen Komponenten kann gemäß der Abbildung vorgenommen werden. 

Schaltplan Cyclone

Stückliste

Nr.Bezeichnung
1LCD Modul Display
2LED-Ring
3Arduino NANO V3 
4Druckknopf-Mini-Rundschalter
5Kondensator 18μF
6Potentiometer
7Active Alarm Buzzer
8Wippe
9USB 2.0 Kabel

Gehäuse

Das Gehäuse kann anhand der im Anhang zur Verfügung gestellten STL-Dateien (Download weiter unten) ganz einfach aus dem 3D-Drucker gedruckt werden.

Tipp: 3D-Druckaufträge können lokal in Maker Space Standorten oder auch online günstig gedruckt werden

Zusätzlich besteht die Möglichkeit an Maker Space Standorten Kurse rund um das Thema 3D-Druck sowie der zugehörigen Software „Fusion 365“ zu absolvieren.

Software erklärt

Zuerst einmal ist es wichtig zu erwähnen, dass diverse Bibliotheken in die Arduino-IDE installiert werden müssen. Der Programmcode verwendet dabei die folgenden Bibliotheken:

#include <Adafruit_NeoPixel.h>
#include <util/atomic.h>
#include <LiquidCrystal_I2C.h>
#include <Arduino.h>
#include <EEPROM.h>

Der grundlegende Aufbau des Programms besteht aus mehreren CPP- und Header-Dateien. Dies ist aufgrund der Größe des Programmcodes ratsam, um die Übersicht zu behalten. 

Die Header-Datei enthält dabei die Deklarationen der einzelnen Elemente. Die CPP-Dateien enthalten den eigentlichen Programmcode der in der Header-Datei deklarierten Klassen und Funktionen.

Software 

Im Folgenden werden die wichtigsten Abschnitte und ihre Funktionen beschrieben. Dabei werden potenzielle Anpassungen des Codes aufgezeigt, sodass ein individuelles Spielerlebnis möglich ist. Zusätzlich werden generelle Grundlagen zur Umgebung Arduino-IDE angeschnitten. 

Highscore

Das Gerät speichert mithilfe der Funktion EEPROM (electrically erasable programmable Read-Only Memory) den Highscore und gibt diesen über das LCD-Display wieder. Die Verwendung der Funktion EEPROM in der Arduino EEPROM-Bibliothek ermöglicht es, Daten zu speichern und abzurufen, die auch nach dem Ausschalten des Geräts erhalten bleiben. Wird der Highscore gebrochen, wird dieser unmittelbar gespeichert. Ein Ausschalten des Gerätes während eines laufenden Spiels, führt zu einem Verlust des aktuellen Spielstandes, der Highscore bleibt aber weiterhin erhalten. 

Einstellungen 

Um die Schwierigkeit des Spiels individuell zu gestalten, können verschiedene Änderungen am Code vorgenommen werden:

Anzahl der LEDs 

Die Anzahl der LEDs, welche im Spiel getroffen werden sollen, kann in der CPP-Datei LED-Ring.CPP angepasst werden. Hierzu können beliebig viele LEDs an verschiedenen Positionen des Rings eingefügt werden. Zusätzlich lässt sich die Farbe der LEDs beliebig konfigurieren. Die erforderlichen Änderungen sind in folgendem Code-Abschnitt vorzunehmen:

m_strip.clear();
m_strip.setPixelColor((LEDRING_START_NUM - 1), 255, 165, 0);
m_strip.setPixelColor(LEDRING_START_NUM, 255, 0, 0);
m_strip.setPixelColor((LEDRING_START_NUM + 1), 255, 165, 0);
m_strip.setPixelColor(m_currentLed, 0, 255, 0);

Geschwindigkeit der zirkulierenden LED

Die Geschwindigkeit der zirkulierenden LED lässt sich in der Datei config.CP variieren:

Um die Geschwindigkeit des zirkulierenden LEDs zu ändern, muss der Wert für das Start-Delay angepasst werden. Ein Wert von 30ul entspricht einer Verzögerung von 30 Millisekunden. Durch eine entsprechende Änderung des Wertes kann das Spiel leichter oder auch schwieriger gestaltet werden. 

#define LEDRING_START_DELAY_MS 30ul

Wechseln der Position der zu treffenden LED

Die Anzahl der LEDs, welche im Spiel getroffen werden sollen, kann in der CPP-Datei LED-Ring.CPP verändert werden. Hierzu können beliebig viele LEDs an verschiedenen Positionen des Rings eingefügt werden. Zusätzlich lässt sich die Farbe der LEDs beliebig anpassen.

Eine beispielhafte Änderung der Position würde im Code so aussehen

 m_strip.clear();
 m_strip.setPixelColor((LEDRING_START_NUM - 10), 255, 165, 0);
 m_strip.setPixelColor(LEDRING_START_NUM, 255, 0, 0);
 m_strip.setPixelColor((LEDRING_START_NUM + 10), 255, 165, 0);
 m_strip.setPixelColor(m_currentLed, 0, 255, 0);
}

und zu folgender Positionierung auf dem LED-Ring führen: 

Achtung: Wird die Anzahl der LEDs geändert, muss auch der Treffer-Bereich angepasst werden. Diese Anpassung erfordert viele weitere Änderungen des Codes und sollte nur von Benutzern mit fortgeschritteneren Programmierkenntnissen vorgenommen werden.

Anzahl an Leben

Um die Anzahl an Leben zu ändern, kann der Wert in der CPP-Datei config.CPP beliebig festgelegt werden. Standardmäßig sind 3 Leben pro Durchgang eingestellt. 

#ifndef PLAYER_TOTLIVES
#define PLAYER_TOTLIVES 3  
#endif

Visuelle Einstellungen

Zusätzlich können auch visuelle Änderungen der LEDs vorgenommen werden. Hierbei kann sowohl die Farbe der LEDs als auch die Helligkeit verändert werden.

Helligkeit

Die Helligkeit kann in der CPP-Datei LEDRing.CPP angepasst werden.

Hierzu ist die folgende Code-Zeile anzupassen:

m_strip.setBrightness(15) 

Um die Einstellung der Helligkeit zu regulieren, kann die in den Klammern angegebene Zahl auf einen Wert zwischen 1 und 100 eingestellt werden. Hierbei steht der Zahlenwert für die prozentuale Helligkeit der LEDs.

Farbe

Die Farbe der LEDs wird ebenfalls in der CPP-Datei LEDRing.CPP festgelegt. Hierzu sind folgende Code-Zeilen entsprechend der gewünschten Farbe einzustellen.

m_strip.setPixelColor((LEDRING_START_NUM - 1), 255, 165, 0);
m_strip.setPixelColor(LEDRING_START_NUM, 255, 0, 0);
m_strip.setPixelColor((LEDRING_START_NUM + 1), 255, 165, 0);
m_strip.setPixelColor(m_currentLed, 0, 255, 0);

Die Farben sind als RGB-Werte definiert. Eine Anpassung der Farben erfolgt über das RGB-Spektrum (Rot, Grün, Blau). 

RGB Übersicht

Um die Einstellung der LED-Farbe anzupassen, werden die jeweiligen Grundfarbwerte für „Rot“, „Grün“ und „Blau“ mit den Zahlenwerten von 0 – 255 definiert. Der Zahlenwert 255 entspricht einem kräftigen Rot/Grün/Blau-Ton mit maximaler Intensität. Senkt man den Zahlenwert, sinkt die Intensität des Rot/Grün/Blau-Tones. Durch die Variation der Intensitäten der drei Grundfarben können nahezu unendlich viele Farben erzeugt werden.

Das Spiel bietet die Möglichkeit, frei nach Fantasie angepasst zu werden und um zusätzliche Funktionen sowie Schwierigkeitsgrade erweitert zu werden. Dabei haben Nachbauende die Freiheit, den Programmcode und die Spielmechaniken nach Belieben zu erweitern und anzupassen.

Wir wünschen viel Spaß bei der Umsetzung und dem Ausprobieren des Cyclones.

Informationen zu den Autor*innen:

Im sechsten Semester des Bachelor of Engineering Studiengangs Produktion- und Prozessmanagement an der Hochschule Heilbronn gilt es, eine Projektleistung eigenständig umzusetzen. Um unsere elektrotechnischen, CAD- sowie Programmierkenntnisse in der Praxis zu testen, entschieden wir uns im Dreierteam für die Umsetzung des Projektes Cyclone-Arcade Spiel bei Prof. Dr. Juliane König-Birk. 

Das Projekt bietet mit der Umsetzung einen umfangreichen Einstieg in die Elektrotechnik und die Programmierung des Mikrocontrollers zur Steuerung der einzelnen Bauteile mit der Arduino-IDE, welche auf der Programmiersprache C und C++ beruht. 

Der Aufbau beinhaltet viele grundlegende Bauteile. Dies hilft, ein besseres Verständnis zu erlangen, wie ein Produkt von der Idee bis hin zur Produktentstehung umgesetzt wird. 

Download

Den Programmcode, die STL-Datei für das Gehäuse sowie den Schaltplan kannst du hier herunterladen.

]]>
Die Arduino Cloud – erste Schritte https://polluxlabs.net/arduino-projekte/die-arduino-cloud-erste-schritte/ Fri, 17 May 2024 11:31:17 +0000 https://polluxlabs.net/?p=16500 Die Arduino Cloud – erste Schritte Weiterlesen »

]]>
Bei Pollux Labs findest du zahlreiche Projekte, die sich mit IoT auseinandersetzen – seien es ein ESP8266 Webserver, Nachrichten vom Microcontroller oder die smartphonegesteuerte Pflanzenbewässerung.

Hierbei kommen immer spezifische Entwicklungen zum Einsatz, mit deren Hilfe du dein jeweiliges Ziel umsetzt. Aber es geht natürlich auch komfortabler: Die Arduino Cloud ist ein Ökosystem, mit dem du deine Microcontroller vernetzen, auf Dashboards aktuelle Messdaten verfolgen und dich bei von dir festgelegten Ereignissen auf deinem Smartphone informieren lassen kannst. Das funktioniert nicht nur mit Arduinos – auch deinen ESP8266 oder ESP32 kannst du damit bequem ins Internet of Things bringen.

Außerdem bist du dabei nicht auf dein eigenes WLAN beschränkt und musst dich mit einer möglicherweisen riskanten Portweiterleitung auseinandersetzen: Du kannst von überall auf deine verbundenen Geräte zugreifen und sie steuern.

In diesem Tutorial erfährst du, wie du

  • einen kostenlosen Account in der Arduino Cloud erstellst,
  • deinen ESP8266 dort registrierst, ihn programmierst und
  • ihn von deinem Smartphone aus steuerst.

Einen Account in der Arduino Cloud erstellen

Bevor es losgehen kann, benötigst du ein kostenloses Benutzerkonto. Besuche hierfür die Webseite der Arduino Cloud und klicke oben rechts auf Get started. Wähle dann den Link Create one. Falls du bereits einen Account hast, kannst du dich natürlich umgehend einloggen.

Registrierung bei der Arduino Cloud

Den Arduino Create Agent installieren

Nachdem du deinen Account erstellt hast, benötigst du noch ein kleines Plugin. Der Arduino Create Agent sorgt für die Kommunikation deines Microcontrollers am PC mit der Arduino Cloud. Möglicherweise wurdest du schon nach deinem ersten Login mit einem Popup darauf aufmerksam gemacht – falls nicht, du kannst ihn auf dieser Webseite herunterladen und ihn anschließend installieren.

Installation des Arduino Create Agents

Sobald die Installation abgeschlossen ist, starte das Plugin – es muss übrigens immer laufen, wenn du Microcontroller mit der Arduino Cloud programmierst, also einen Sketch hochladen möchtest.

Dein erstes Board hinzufügen

Wenn du diese beiden grundlegenden Schritte geschafft hast, wird es Zeit, deinen ersten Microcontroller zur Cloud hinzuzufügen. Im Folgenden dient der ESP8266 als Beispiel.

Klicke dafür im Menü links auf Devices und anschließend oben rechts auf + Device. Auf der folgenden Seite kannst du auswählen, welches Gerät du verbinden möchtest – ein Arduino Board, einen Microcontroller eines „Drittanbieters“ oder etwas, um dessen Integration du dich selbst kümmerst (Manual). Der ESP8266 fällt in die zweite Kategorie, wähle als in diesem Fenster Third party device.

Im nächsten Schritt kannst du nun also den ESP8266 anhaken und das Modell auswählen:

ESP8266 in der Arduino Cloud installieren

Fehlt nur noch ein Name für das Board, den du gleich danach vergeben kannst. Hier empfiehlt es sich, auf den Verwendungszweck deines Controllers einzugehen, damit du ihn in einer längeren Liste leicht wiederfinden kannst.

Nun noch ein wichtiger Schritt: Du erhältst eine Device ID und einen Secret Key – zwei Zeichenfolgen, die du dir sicher abspeichern musst, da du sie später wieder benötigst. Praktischerweise kannst du dir sie auch als übersichtliches PDF herunterladen.

Sobald du eine Erfolgsmeldung erhältst, ist dein ESP8266 in der Arduino Cloud registriert und taucht in der entsprechenden Device-Liste auf.

Ein Thing erstellen

Klicke nun als nächstes auf den Listeneintrag des ESP8266. Es öffnet sich ein Fenster, in dem du auf der linken Seite einige Daten siehst und darunter den Button Create thing.

Ein Thing in der Arduino Cloud erstellen

Ein sogenanntes Thing ist so etwas wie das Abbild des ESP8266 in der Arduino Cloud. Hier kannst du seine Verbindung zu deinem WLAN einrichten, indem du die Zugangsdaten hinterlegst, die dann auf deinem ESP8266 abgespeichert werden, sobald du einen Sketch auf ihn hochlädst.

Außerdem kannst du Variablen erstellen, die sowohl im Sketch Verwendung finden als auch später in ein Dashboard eingebunden werden können. Ein Thing ist also so etwas wie eine Ebene zwischen Cloud und Hardware. Klicke nun als nächstes rechts im Bereich Netword auf Configure.

Konfiguration des Thing

Trage nun deine WLAN-Zugangsdaten sowie deinen Secret Key ein und speichere die Daten ab. Wenn du wieder zurück in der Übersicht bist, klicke oben im Bereich Cloud Variables auf Add. Hier hinterlegst du nun deine erste Variable.

Als ersten Test wirst du deinen ESP8266 so einrichten, dass du seine interne LED von einem Dashboard aus ein- und ausschalten kannst. Hierfür benötigst du eine entsprechende Variable, in der gespeichert ist, ob die LED leuchtet oder nicht. Hierfür reicht der Typ bool, der entweder den Wert true oder false annehmen kann. Stelle deine erste Variable also wie folgt ein:

Klicke abschließend auf den Button Add Variable.

Der erste Sketch für die Steuerung der LED

Jetzt folgt der Sketch, den du auf deinen ESP8266 hochladen wirst. Oben rechts findest du den Reiter Sketch. Nach einem Klick hierauf öffnet sich der Editor – ähnlich der Arduino IDE, die du sicherlich schon kennst:

Der erste Sketch für den ESP8266

Ein Großteil des Codes ist hier schon ausgefüllt, z.B. der Verweis auf die Zugangsdaten zu deinem WLAN oder der Start des Seriellen Monitors. Damit du die interne LED jedoch steuern kannst, fehlt noch ein bisschen. Zunächst der Befehl pinMode() für die Variable LED_BUILTIN (also die interne LED). Platziere diese Zeile Code in der Funktion void setup().

pinMode(LED_BUILTIN, OUTPUT);

Außerdem musst du noch die Funktion onInternalLEDChange() anpassen:

void onInternalLEDChange()  {
  if(internalLED){
    digitalWrite(LED_BUILTIN, LOW);
  }
  else{
    digitalWrite(LED_BUILTIN, HIGH);
  }
}

Hier legst du fest, dass wenn die Variable internalLED den Wert true hat, die interne LED ausgeschaltet wird. Das fühlt sich zwar „falsch herum“ an, ist aber das normale Verhalten des ESP8266… Wenn der Wert auf false springt, geht sie hingegen an.

Sobald du diese zwei Stellen ausgebessert hast, kannst du den Sketch auf deinen ESP8266 hochladen. Klicke hierfür links oben auf den Pfeil nach rechts. Hat alles geklappt? Dann wird es Zeit für den letzten Teil dieses Tutorials.

Die LED vom Dashboard (und vom Smartphone) aus steuern

Klicke im Menü links auf Dashboards und anschließend oben rechts auf + Dashboard. Anschließend landest du auf einer leeren Seite – nur oben findest du ein paar Buttons. Die leere Fläche ist dein frisches Dashboard, auf dem du Elemente wie Schalter, Diagramme, Buttons etc. platzieren kannst.

An dieser Stelle integrierst du einen Schalter, der die LED des ESP8266 an- und ausschalten kann. Klicke hierfür zunächst oben links auf den Bleistift:

Nun findest du rechts daneben den Button Add. Klicke hierauf, um die Übersicht der verfügbaren Widgets zu öffnen:

Arduino Cloud Dashboard Widgets

Wähle hier nun die Option Switch – das wird der Schalter für die LED deines ESP8266. Anschließend landet der Schalter auf die Mitte des Dashboards. Fehlt nur noch die Verbindung zur LED, sprich zur Variablen internalLED, die du mit diesem Schalter zwischen true und false hin- und herspringen lassen kannst.

Klicke dafür im rechten Bereich des Fenster unter Linked Variable auf den grünen Button und wähle die Variable aus:

Fehlt nur noch ein Klick auf den Button Link Variable – und anschließend unten rechts auf Done. Anschließend befindest du dich wieder auf deinem Dashboard, dessen Editor-Modus du oben links mit einem Klick auf das Auge verlassen kannst.

Nun ist dein Schalter betriebsbereit. Vorausgesetzt, dein ESP8266 läuft (entweder mit Strom vom USB-Kabel an deinem PC oder von einer anderen Quelle) und ist mit dem WLAN verbunden, kannst du nun mit dem Schalter die interne LED an- und ausschalten.

Den Schalter von unterwegs bedienen

Dein Dashboard betrachtest du gerade im Browser auf deinem Computer – aber du kannst den ESP8266 von unterwegs mit deinem Smartphone steuern! Hierfür benötigst du die IoT Remote App der Arduino Cloud, die es für Android und iOS gibt.

Nachdem du dich in der App angemeldet hast, findest du auch dort dein gerade erstelltes Dashboard mit dem Schalter. Und wie zu erwarten, kannst du auch damit deinen ESP8266 steuern – natürlich auch von unterwegs, ohne im heimischen WLAN zu sein.

Wie geht es nun weiter? Mit den Grundlagen, die du in diesem Tutorial gelernt hast, steht dir der Weg offen für zahlreiche IoT-Projekte – sei es mit einem Microcontroller oder auch mit mehreren, die du über die Arduino Cloud miteinander vernetzt. In Zukunft wirst du hier auf Pollux Labs noch viele weitere Ideen und Projekte finden. Bis dahin, viel Erfolg!

]]>
Automatische Pflanzenbewässerung https://polluxlabs.net/arduino-projekte/automatische-pflanzenbewaesserung/ Wed, 20 Dec 2023 14:13:03 +0000 https://polluxlabs.net/?p=15540 Automatische Pflanzenbewässerung Weiterlesen »

]]>
Wenn du deinem Arduino mehr vertraust als deinem grünen Daumen, ist dieses Projekt das richtige für dich. Ein Sensor kontrolliert die Feuchtigkeit im Boden – sobald ein bestimmter Wert unterschritten wird, springt eine Wasserpumpe an und gießt deine Pflanze. Ist der Boden wieder feucht genug, stoppt dein Arduino die Pumpe.

Für dieses Projekt benötigst du folgende Bauteile (jeweils eins):

Bei Amazon findest du auch vollständige Sets, wie z.B. dieses hier:

Aufbau des Projekts mit dem Arduino UNO

Für deine automatische Pflanzenbewässerung benötigst du neben einem Arduino einen Feuchtigkeitssensor, eine Wasserpumpe, ein 5V-Relais und eine Box für vier Batterien mit 1,5 V. Das können wie auf dem Bild unten AAA-Batterien sein, ich selbst habe den gängigen Typ AA verwendet. Die Extra-Batterien benötigst du, weil dein Arduino nicht genug Strom für die Wasserpumpe liefert.

Das Breadboard benötigst du nur, um die Erde und die 5V vom Arduino an den Sensor und das Relais weiterzuleiten – du kannst dir hierfür natürlich auch eine andere Lösung überlegen.

Arduino Automatische Pflanzenbewässerung

Orientiere dich beim Aufbau des Projekts an obiger Skizze. Ein Hinweis zum Relais: Die Belegung der Pins kann bei deinem Modell variieren – achte darauf den richtigen Pin für das Signal mit deinem Arduino zu verbinden.

So funktioniert das Projekt

Bevor wir uns dem Sketch zuwenden, zunächst ein paar Worte zur Funktionsweise: Der Feuchtigkeitssensor steckt natürlich in der Erde der Pflanze, die du automatisch gießen lassen möchtest. Am Analogpin (blaues Kabel) liest dein Arduino den aktuellen Feuchtigkeitswert aus. Sobald ein von dir festgelegter Wert überschritten wird, sendet der Arduino ein Signal an das Relais (grünes Kabel).

Nun öffnet das Relais die Verbindung zwischen den Pins NO (Normally Open) und COM (Common Pin). Somit kann also der Strom von den Batterien zur Wasserpumpe fließen. Noch ein Hinweis zur Sicherheit: Ich rate dringend davon ab, Haushaltsgeräte, die nicht für Experimentieren mit Arduino & Co gedacht sind, über ein Relais zu steuern.

Die Pumpe selbst liegt im Wasser und beginnt nun selbiges über einen Schlauch in die Pflanzenerde zu pumpen. Hierdurch verringert sich der Feuchtigkeitswert (obwohl die Feuchtigkeit natürlich steigt). Sobald ein bestimmter Wert unterschritten wurde, die Erde also feucht genug ist, sendet dein Arduino das Signal an das Relais, die Verbindung zwischen Wasserpumpe und Batterien zu schließen.

Der Sketch für die PflanzenBewässerung

Um dein Projekt in Betrieb zu nehmen, sind nur wenige Zeilen Code nötig. Kopiere den folgenden Sketch und lade ihn auf deinen Arduino:

int humidity = 0;
const int humidityPin = A0;
const int pumpPin = 7;

void setup() {
  Serial.begin(9600);
  pinMode(humidityPin, INPUT);
  pinMode(pumpPin, OUTPUT);
}

void loop() {
  humidity = analogRead(humidityPin);
  Serial.print("Feuchtigkeit: ");
  Serial.println(humidity);
  delay(200);

  if(humidity > 900) {
    digitalWrite(pumpPin, HIGH);
    delay(1000);
  }
  else {
    digitalWrite(pumpPin, LOW);
  }
}

Zunächst benötigst du eine Variable für den Feuchtigkeitswert, hier humidity. Anschließend legst du die beiden Pins für den Feuchtigkeitssensor und das Relais fest.

Im Setup startest du nur den Seriellen Monitor und legst du den jeweiligen pinMode fest: Am Pin A0 trifft ein Signal ein (also INPUT), über den Digitalpin 7 gibst du ein Signal aus (also OUTPUT).

Im Loop passiert folgendes: Über den Befehl analogRead() liest du den aktuellen Feuchtigkeitswert aus und zeigst ihn im Seriellen Monitor an – je trockener die Erde, desto höher der Wert. Hier könnte man sich überlegen, ob der Wert eher „Trockenheitswert“ heißen sollte. 😉

Falls ein von dir festgelegter Wert (im obigen Sketch ist das der Wert 900) überschritten wird, sendet dein Arduino das Signal HIGH an das Relais. Dort wird dann dafür gesorgt, dass die Wasserpumpe Strom von den Batterien erhält. Falls der Wert nicht überschritten wird, springt der Code wieder zum Anfang des Loops.

So ermittelst du den richtigen Wert: Warte bis die Pflanzenerde so trocken geworden ist, dass du sie eigentlich gießen würdest. Stecke nun den Feuchtigkeitssensor hinein und lies den Wert aus. Diese Zahl kannst du dann in deiner If-Anweisung verwenden.

Ein weiterer Pflanzenwächter

Ist dir dieses Projekt zu groß für die Fensterbank? Auf Pollux Labs findest du noch das Projekt Pflanzenwächter, das einen ATtiny85 verwendet und mit dem Strom einer 1,5V Knopfbatterie auskommt. Allerdings kann dich dieses Projekt nur mit einer LED über zu trockene Erde informieren und nicht selbst gießen.

]]>
Arduino Schnarchstopper mit Künstlicher Intelligenz https://polluxlabs.net/arduino-projekte/arduino-schnarchstopper-mit-kuenstlicher-intelligenz/ Fri, 13 Oct 2023 11:19:01 +0000 https://polluxlabs.net/?p=14697 Arduino Schnarchstopper mit Künstlicher Intelligenz Weiterlesen »

]]>
Schnarchen ist ärgerlich, klar. Dagegen gibt es Geräte, Kissen, Apps und viele andere Mittelchen – aber die meisten davon dürften kaum halten, was sie versprechen. Falls du schnarchst und versprochen hast, dich darum zu kümmern, kannst du mit diesem Tutorial dein Leiden mit deinem Hobby verbinden: Du baust einen Arduino Schnarchstopper mit künstlicher Intelligenz.

Zum Einsatz kommt ein Arduino Nano 33 BLE Sense und ein Vibrationsmotor*. Auf dem Microcontroller läuft ein KI-Modell, das über das Mikrofon des Arduinos erkennt, ob du schnarchst. Ist das der Fall, springt der Motor an und weckt dich (hoffentlich).

Aufbau des Schnarchstoppers

Neben dem Arduino Nano 33 BLE Sense benötigst du nur einen Vibrationsmotor und ein Verbindungskabel für eine 9V-Batterie. Der Motor sollte stark genug sein, um dich wecken zu können – wobei es natürlich auch darauf ankommt, wo du den Schnarchstopper platzierst. Wenn du eine Armbinde verwendest, kann der Vibrationsmotor beispielsweise direkt auf deinem Oberarm oder auch oberhalb des Handgelenks direkt auf deiner Haut aufliegen. Hierfür eignet sich zum Beispiel das Band, das dem Spiel Ring Fit für die Nintendo Switch* beiliegt.

Statt Vibration kannst du natürlich auch zu anderen Mitteln greifen. Ein Piezo-Summer dürfte dich wohl mit Sicherheit aus dem Schlaf reißen – leider aber auch deine Bettgenossin oder deinen Bettgenossen. Im Folgenden bleibst du beim Vibrationsmotor. Der Aufbau sieht dann so aus:

Schnarchstopper Hardware

Falls du einen Arduino mit Header-Pins hast, kannst du die Kabel der beiden Bauteile entsprechend anpassen. Löte hierfür jeweils zwei Kabel mit einer Buchse an, die du dann am Arduino aufstecken kannst. Hier der Aufbau als Skizze:

Skizze des Aufbaus der Hardware

Das passende KI-Modell und der Sketch

Damit der Schnarchstopper dein Schnarchen erkennt, benötigst du ein KI-Modell, das du in deinem Arduino Sketch verwendest.

Dieses Tutorial ist angelehnt an diesem Projekt auf GitHub – mit ein paar Anpassungen. Der Maker metanav hat dort schon viel Vorarbeit geleistet, die du weiterverwenden kannst. Lade dir auf GitHub oder direkt hier das Projekt als ZIP-Datei herunter:

Download KI-Modell

Entpacke anschließend die ZIP-Datei und öffne den Sketch tflite_micro_snoring_detection.ino, den du im Ordner Snoring-Guardian-main > snoring_detection_inferencing > examples >tflite_micro_snoring_detection findest.

Anschließend bindest du die mitgelieferte Bibliothek ein. Darin enthalten ist das KI-Modell, das du verwenden wirst. Öffne in der Arduino IDE den Menüpunkt Sketch > Bibliothek einbinden > ZIP-Bibliothek hinzufügen und wähle die Datei Snoring_detection_inferencing.zip

Den Arduino NANO und eine weitere Bibliothek installieren

Falls du du den Arduino Nano 33 BLE Sense in der Arduino IDE noch nicht verfügbar gemacht hast, hole das schnell nach. Öffne hierfür den Boardverwalter im Menü links, suche nach Arduino Nano BLE Sense und installiere die aktuelle Version von Arduino Mbed OS Nano Boards.

Installation des Arduino Nano 33 BLE Sense

Außerdem benötigst du noch die Bibliothek RingBuf, die du über den Bibliotheksverwalter installieren kannst. Aber Achtung: Die gleichnamige Bibliothek funktioniert nicht auf dem Arduino Nano. Installiere stattdessen die Bibliothek RingBuffer von Jean-Luc – Locoduino:

Installation der Bibliothek RingBuffer

Für einen Test wähle in der Arduino IDE dein Board aus und klicke oben links auf den Haken für die Überprüfung des Sketchs. Die Kompilierung nimmt einige Zeit in Anspruch, aber wenn die richtigen Bibliotheken installiert bzw. eingebunden wurden und die Verbindung zum Arduino Nano steht, sollte sie erfolgreich abgeschlossen werden:

Ausgabe nach Upload des Sketchs

Anpassungen im Sketch

Du kannst den Sketch für den Schnarchstopper direkt auf deinen Arduino hochladen und verwenden, aber je nachdem, welchen Vibrationsmotor (oder welches andere Bauteil) du verwendest, musst du ein paar Kleinigkeiten anpassen.

Im Sketch kümmert sich die Funktion void run_vibration() um den Start des Motors. Im Beispiel-Sketch sieht sie wie folgt aus:

void run_vibration()
{
  if (alert)
  {
    is_motor_running = true;

    for (int i = 0; i < 2; i++)
    {
      analogWrite(vibratorPin, 30);
      delay(1000);
      analogWrite(vibratorPin, 0);
      delay(1500);
    }
    
    is_motor_running = false;
  } else {
    if (is_motor_running)
    {
      analogWrite(vibratorPin, 0);
    }
  }
  yield();
}

Hier wird der Motor 3 Mal jeweils für eine Sekunde gestartet, mit einer Pause von 1,5 Sekunden dazwischen. Hierfür wird analogWrite() mit einem Wert von 30 verwendet. Der Vibrationsmotor, den ich verwende, versteht allerdings nur Ein und Aus. Falls das bei dir auch der Fall ist, ändere die betreffende Stelle folgendermaßen:

for (int i = 0; i < 2; i++) {
  digitalWrite(vibratorPin, HIGH);
  delay(5000);
  digitalWrite(vibratorPin, LOW);
  delay(1000);
}

Hier verwendest du digitalWrite() und sendest damit entweder ein HIGH oder LOW an den Motor. Ebenfalls sind dort die Lauf- und Pausenzeiten geändert – die fünf Sekunden zielen hier eher auf Schnarcher mit einem tiefen Schlaf.

Und noch eine Anpassung: Wenn du deinen Vibrationsmotor wie in der Skizze oben an den Pin D2 angecshlossen hast, ändere noch die entsprechende Zeile im Sketch:

int vibratorPin = 2;

Lade nun den Sketch auf deinen Arduino Nano hoch – du findest ihn ganz am Ende dieses Tutorials.

Den Schnarchstopper testen

Jetzt ist es so weit – sobald der Sketch erfolgreich auf deinem Arduino gelandet ist, öffne den Seriellen Monitor in der IDE. Dort siehst du die Vorhersagen, die das KI-Modell macht auf Basis der Geräusche, die es über das eingebaute Mikrofon des Arduinos erhält:

Ausgabe des Schnarchstoppers im seriellen Monitor

Im oben rot markierten Fall war das ein normales Hintergrundgeräusch (noise) mit einer Wahrscheinlichkeit von 99,219 %. Das hier jemand geschnarcht hat, war hingegen nur zu 0,781 % wahrscheinlich.

Es ist vermutlich etwas peinlich, aber imitiere nun mehrmals hintereinander typische Schnarchlaute. Du wirst sehen, dass die interne LED des Arduinos aufleuchtet und sich die Ausgabe im Seriellen Monitor entsprechend verändert. Sobald mehrmals ein Schnarchen erkannt wurde, springt auch der Vibrationsmotor an und vibriert im von dir in der Funktion run_vibration() definierten Rhythmus.

Als nächstes wird es Zeit für ein paar „echte“ Tests in der Nacht. Da du deinen Arduino Nano auch mit einer 9V-Batterie versorgen kannst, steht deinen Versuchen im Bett nichts im Wege. Vermutlich musst du mehrere Möglichkeiten der Positionierung des Motors ausprobieren, um von seinen Vibrationen aufzuwachen bzw. um dem Mikrofon des Arduinos zu ermöglichen, dich einwandfrei beim Schnarchen aufzunehmen. Sollte letzteres nicht der Fall sein, kann es zu Fehlalarmen kommen.

Und natürlich gibt es keine Garantie, dass dein neuer Schnarchstopper überhaupt zu ruhigen Nächten führt…

Entwickle dein eigenes KI-Modell

Da du bisher ein vorgefertigtes Modell verwendet hast, ist es möglich, dass es für dich nicht bestmöglich funktioniert. Jeder schnarcht schließlich anders – und die Schnarchgeräusche, die für das Training des Modells verwendet wurden, können stark von deinen eigenen abweichen.

Falls du also einen Schritt weitergehen möchtest, ist das kein Problem – nur etwas Arbeit. Auf Pollux Labs findest du Tutorials, wie du den Service Edge Impulse verwenden kannst, um ein persönliches KI-Modell zu entwickeln. Dort erfährst du, wie du deinen Arduino Nano 33 BLE Sense mit Edge Impulse verbindest, damit Daten sammelst und dein eigenes Modell trainierst. Im letztgenannten Tutorial geht es zwar um Bewegungsdaten, aber ähnlich funktioniert auch das Training mit Audio.

Apropos Audio, um ausreichend Schnarchtöne zu sammeln, eignet sich bereits dein Smartphone. Starte einfach eine Tonaufnahme und lass das Smartphone die Nacht über neben dir liegen. Die entsprechenden Passagen in der Audiodatei kannst du dann in Edge Impulse weiterverarbeiten.

Und nun viel Spaß und Erfolg beim Experimentieren mit deinem Schnarchstopper!

Der vollständige Sketch

Hier nun der gesamte Sketch mit den genannten Anpassungen:

// If your target is limited in memory remove this macro to save 10K RAM
#define EIDSP_QUANTIZE_FILTERBANK   0

/**
   Define the number of slices per model window. E.g. a model window of 1000 ms
   with slices per model window set to 4. Results in a slice size of 250 ms.
   For more info: https://docs.edgeimpulse.com/docs/continuous-audio-sampling
*/
#define EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW 3

/* Includes ---------------------------------------------------------------- */
#include <PDM.h>
#include <Scheduler.h>
#include <RingBuf.h>
#include <snore_detection_inferencing.h>

/** Audio buffers, pointers and selectors */
typedef struct {
  signed short *buffers[2];
  unsigned char buf_select;
  unsigned char buf_ready;
  unsigned int buf_count;
  unsigned int n_samples;
} inference_t;

static inference_t inference;
static bool record_ready = false;
static signed short *sampleBuffer;
static bool debug_nn = false; // Set this to true to see e.g. features generated from the raw signal
static int print_results = -(EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW);

bool alert = false;

RingBuf<uint8_t, 10> last_ten_predictions;
int greenLED = 23;
int vibratorPin = D2;   // Vibration motor connected to D2 PWM pin
bool is_motor_running = false;

void run_vibration() {
  if (alert) {
    is_motor_running = true;

    for (int i = 0; i < 2; i++) {
      digitalWrite(vibratorPin, HIGH);
      delay(5000);
      digitalWrite(vibratorPin, LOW);
      delay(1000);
    }

    is_motor_running = false;
  } else {
    if (is_motor_running) {
      analogWrite(vibratorPin, LOW);
    }
  }
  yield();
}



/**
   @brief      Printf function uses vsnprintf and output using Arduino Serial

   @param[in]  format     Variable argument list
*/
void ei_printf(const char *format, ...) {
  static char print_buf[1024] = { 0 };

  va_list args;
  va_start(args, format);
  int r = vsnprintf(print_buf, sizeof(print_buf), format, args);
  va_end(args);

  if (r > 0) {
    Serial.write(print_buf);
  }
}

/**
   @brief      PDM buffer full callback
               Get data and call audio thread callback
*/
static void pdm_data_ready_inference_callback(void)
{
  int bytesAvailable = PDM.available();

  // read into the sample buffer
  int bytesRead = PDM.read((char *)&sampleBuffer[0], bytesAvailable);

  if (record_ready == true) {
    for (int i = 0; i<bytesRead >> 1; i++) {
      inference.buffers[inference.buf_select][inference.buf_count++] = sampleBuffer[i];

      if (inference.buf_count >= inference.n_samples) {
        inference.buf_select ^= 1;
        inference.buf_count = 0;
        inference.buf_ready = 1;
      }
    }
  }
}

/**
   @brief      Init inferencing struct and setup/start PDM

   @param[in]  n_samples  The n samples

   @return     { description_of_the_return_value }
*/
static bool microphone_inference_start(uint32_t n_samples)
{
  inference.buffers[0] = (signed short *)malloc(n_samples * sizeof(signed short));

  if (inference.buffers[0] == NULL) {
    return false;
  }

  inference.buffers[1] = (signed short *)malloc(n_samples * sizeof(signed short));

  if (inference.buffers[0] == NULL) {
    free(inference.buffers[0]);
    return false;
  }

  sampleBuffer = (signed short *)malloc((n_samples >> 1) * sizeof(signed short));

  if (sampleBuffer == NULL) {
    free(inference.buffers[0]);
    free(inference.buffers[1]);
    return false;
  }

  inference.buf_select = 0;
  inference.buf_count = 0;
  inference.n_samples = n_samples;
  inference.buf_ready = 0;

  // configure the data receive callback
  PDM.onReceive(&pdm_data_ready_inference_callback);

  PDM.setBufferSize((n_samples >> 1) * sizeof(int16_t));

  // initialize PDM with:
  // - one channel (mono mode)
  // - a 16 kHz sample rate
  if (!PDM.begin(1, EI_CLASSIFIER_FREQUENCY)) {
    ei_printf("Failed to start PDM!");
  }

  // set the gain, defaults to 20
  PDM.setGain(127);

  record_ready = true;

  return true;
}

/**
   @brief      Wait on new data

   @return     True when finished
*/
static bool microphone_inference_record(void)
{
  bool ret = true;

  if (inference.buf_ready == 1) {
    ei_printf(
      "Error sample buffer overrun. Decrease the number of slices per model window "
      "(EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW)\n");
    ret = false;
  }

  while (inference.buf_ready == 0) {
    delay(1);
  }

  inference.buf_ready = 0;

  return ret;
}

/**
   Get raw audio signal data
*/
static int microphone_audio_signal_get_data(size_t offset, size_t length, float * out_ptr)
{
  numpy::int16_to_float(&inference.buffers[inference.buf_select ^ 1][offset], out_ptr, length);

  return 0;
}

/**
   @brief      Stop PDM and release buffers
*/
static void microphone_inference_end(void)
{
  PDM.end();
  free(inference.buffers[0]);
  free(inference.buffers[1]);
  free(sampleBuffer);
}


void setup()
{
  Serial.begin(115200);

  pinMode(greenLED, OUTPUT);
  pinMode(greenLED, LOW); 
  pinMode(vibratorPin, OUTPUT);  // sets the pin as output

  // summary of inferencing settings (from model_metadata.h)
  ei_printf("Inferencing settings:\n");
  ei_printf("\tInterval: %.2f ms.\n", (float)EI_CLASSIFIER_INTERVAL_MS);
  ei_printf("\tFrame size: %d\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
  ei_printf("\tSample length: %d ms.\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT / 16);
  ei_printf("\tNo. of classes: %d\n", sizeof(ei_classifier_inferencing_categories) /
            sizeof(ei_classifier_inferencing_categories[0]));

  run_classifier_init();
  if (microphone_inference_start(EI_CLASSIFIER_SLICE_SIZE) == false) {
    ei_printf("ERR: Failed to setup audio sampling\r\n");
    return;
  }

  Scheduler.startLoop(run_vibration);
}

void loop()
{

  bool m = microphone_inference_record();

  if (!m) {
    ei_printf("ERR: Failed to record audio...\n");
    return;
  }

  signal_t signal;
  signal.total_length = EI_CLASSIFIER_SLICE_SIZE;
  signal.get_data = &microphone_audio_signal_get_data;
  ei_impulse_result_t result = {0};

  EI_IMPULSE_ERROR r = run_classifier_continuous(&signal, &result, debug_nn);
  if (r != EI_IMPULSE_OK) {
    ei_printf("ERR: Failed to run classifier (%d)\n", r);
    return;
  }

  if (++print_results >= (EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW)) {
    // print the predictions
    ei_printf("Predictions ");
    ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)",
              result.timing.dsp, result.timing.classification, result.timing.anomaly);
    ei_printf(": \n");

    for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
      ei_printf("    %s: %.5f\n", result.classification[ix].label,
                result.classification[ix].value);

      if (ix == 1 && !is_motor_running && result.classification[ix].value > 0.9) {
        if (last_ten_predictions.isFull()) {
          uint8_t k;
          last_ten_predictions.pop(k);
        }

        last_ten_predictions.push(ix);

        uint8_t count = 0;

        for (uint8_t j = 0; j < last_ten_predictions.size(); j++) {
          count += last_ten_predictions[j];
          //ei_printf("%d, ", last_ten_predictions[j]);
        }
        //ei_printf("\n");
        ei_printf("Snoring\n");
        pinMode(greenLED, HIGH); 
        if (count >= 5) {
          ei_printf("Trigger vibration motor\n");
          alert = true;
        }
      }  else {
        ei_printf("Noise\n");
        pinMode(greenLED, LOW); 
        alert = false;
      }

      print_results = 0;
    }
  }
}


#if !defined(EI_CLASSIFIER_SENSOR) || EI_CLASSIFIER_SENSOR != EI_CLASSIFIER_SENSOR_MICROPHONE
#error "Invalid model for current sensor."
#endif
]]>
Code-Schloss mit Tastatur und Servo-Motor https://polluxlabs.net/arduino-projekte/code-schloss-mit-tastatur-und-servo-motor/ Mon, 09 Oct 2023 08:46:52 +0000 https://polluxlabs.net/?p=14658 Code-Schloss mit Tastatur und Servo-Motor Weiterlesen »

]]>
In diesem Projekt baust du dir ein Code-Schloss mit einer Folientastatur und einem Servo-Motor. Nur per Eingabe der richtigen Kombination wird sich der Servo-Motor in Bewegung setzen und damit eine mögliche Verriegelung lösen.

DIE FOLIENTASTATUR ANSCHLIESSEN

Für ein Code-Schloss braucht es natürlich ein Gerät, über das man den Code eingeben kann. Hierfür eignet sich die 4×4 Folientastatur perfekt. 

Zunächst installierst du die Verbindung zum Arduino. Die Tastatur besitzt 8 Buchsen; stecke hier ebenso viele Kabel hinein und verbinde diese der Reihe nach mit den Digitalpins 9 bis 2 an deinem Arduino:

Anschluss Folientastatur

DIE PASSENDE BIBLIOTHEK

Um dir die Steuerung der Tastatur so einfach wie möglich zu machen, gibt es eine Bibliothek. Diesmal nutzt du hierfür jedoch nicht den Bibliotheksverwalter, sondern bindest die Bibliothek manuell ein. Lade sie dir zunächst hier herunter.

Wähle nun im Menü der Arduino IDE den Punkt Sketch -> Bibliothek einbinden -> .ZIP Bibliothek hinzufügen

Für einen ersten Test der Tastatur verwendest du nun den Beispiel-Sketch, der in der Bibliothek mitgeliefert wird. Öffne diesen unter Datei -> Beispiele -> Keypad -> CustomKeypad

Bevor wir einen Blick auf den Code werfen, lade den Sketch zunächst auf deinen Arduino. Öffne nun den Seriellen Monitor und drücke eine der Tasten auf der Tastatur. Erscheint das entsprechende Zeichen in der Ausgabe? Wenn ja, perfekt.

DER BEISPIEL-SKETCH

Lass uns einen ganz kurzen Blick auf diesen Sketch werfen. Der meiste Code sind Funktionen der Bibliothek Keypad, aber zwei Dinge sind besonders interessant. Zunächst siehst du im Code die Anzahl der Tasten je Reihe und Spalte sowie eine Matrix, die die Werte bzw. Zeichen der Tastatur bestimmt:

const byte ROWS = 4;
const byte COLS = 4;

char hexaKeys[ROWS][COLS] = {
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'}
};

Das sind die Zeichen, die du auch auf der Tastatur selbst findest. Solltest du einmal andere Zeichen verarbeiten wollen, könntest du diese hier eintragen und damit einer Taste auf der Folientastatur zuweisen.

Der Anschluss der Tastatur am Arduino verbirgt sich in diesen beiden Variablen:

byte rowPins[ROWS] = {9, 8, 7, 6};
byte colPins[COLS] = {5, 4, 3, 2};

Das sind die Pins, die du auch im Anschlussplan oben findest. Wenn du einmal einen dieser Pins unbedingt für ein anderes Bauteil verwenden musst, kannst du hier der Tastatur einen abweichenden Digitalpin zuweisen.

Außerdem wird mit diesen Parametern das Objekt customKeypad erzeugt:

Keypad customKeypad = Keypad( makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

Und weiter geht’s – als nächstes installierst du zwei LEDs sowie den Servo-Motor und sorgst dafür, dass dieser sich nur nach der Eingabe des richtigen Codes bewegt.

DAS SCHLOSS AUFBAUEN UND PROGRAMMIEREN

Verbinde als erstes deinen Servo-Motor mit dem Arduino. Du kannst ihn entweder auf deinem Breadboard installieren und von dort aus verbinden – oder du steckst die Kabel des Servos direkt in die Pins GND, 5V und 11, so wie auf dieser Skizze.

Aufbau Code-Schloss

Die Reihenfolge der Anschlüsse GND, VCC und Signal können sich je nach Fabrikat des Motors unterscheiden. Bitte achte hierauf beim Anschluss.

Hinzu kommen noch zwei LEDs samt zwei Vorwiderständen mit je 220Ω. Schließe diese an die Digitalpins 12 und 13 an. Mit diesen zeigst du später an, ob der Code richtig oder falsch eingegeben wurde.

DER SKETCH FÜR DEIN CODE-SCHLOSS

Damit kommen wir schon zum Kern des Projekts. Der Servo soll sich nur bewegen, wenn jemand den richtigen Code per Tastatur eingibt. Dann dreht sich der Zeiger zur Seite und gibt z.B. den Deckel einer Box frei. Wie du das Gehäuse der Box gestaltest, ist natürlich dir überlassen.

Zu Beginn des Sketchs bindest du wie gewohnt zunächst die benötigten Bibliotheken ein:

#include <Keypad.h>
#include <Servo.h>

Anschließend folgen die Informationen für das Keypad, die du schon kennst:

const byte ROWS = 4;
const byte COLS = 4;

char hexaKeys[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};

byte rowPins[ROWS] = {9, 8, 7, 6};
byte colPins[COLS] = {5, 4, 3, 2};
Keypad customKeypad = Keypad( makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

Nun benötigst du drei Variablen bzw. Konstanten, die die gedrückte Taste key, den gesamten eigegebenen Code inputCode sowie das von dir hinterlegte Passwort code beinhalten. Gegen letzteres wirst du später die Eingabe prüfen. Wenn du später deinen eigenen Code hinterlegen möchtest, kannst du das hier tun.

char key;
String inputCode;
const String code = "1103A"; //Der von dir festgelegte Code

Zuletzt fehlen noch ein Objekt für den Servo-Motor und die beiden Variablen für die Anschlüsse der LEDs.

Servo servo;
int redLED = 12;
int greenLED = 13;

DIE SETUP-FUNKTION

Hier gibt es sicherlich nichts Neues für dich. Du startest den Seriellen Monitor, legst die pinMode für die LEDs fest und den Anschluss-Pin des Servo-Motors.

void setup() {
 
  Serial.begin(9600);
  pinMode(redLED, OUTPUT);
  pinMode(greenLED, OUTPUT);
  servo.attach(11);
}

DER LOOP

Als erstes benötigst du eine Variable, in der du den Wert der Taste speicherst, die gerade gedrückt wurde. Das ist hier die Variable key mit dem Typ char für Character. Den Wert speicherst du mithilfe der Funktion customKeypad.getKey().

char key = customKeypad.getKey();

Nun folgt eine Reihe von bedingten Anweisungen. Das Code-Schloss verwendet zwei Tasten, um das Schloss zu verriegeln (mit der Taste *) oder die Eingabe zu prüfen (mit #). Diese Tasten darfst du also nicht in deinem hinterlegten Code verwenden. Die erste If-Abfrage “horcht” auf die Taste * und verriegelt das Schloss, indem sie den Servo auf einen Winkel von 90° stellt. Natürlich kannst du diesen Winkel nach deinen Bedürfnissen ändern.

Zusätzlich schaltet sie die rote LED an (und die grüne aus) und leert die Variable inputCode, damit hier für die nächste Eingabe keine “alten” Zeichen mehr zu finden sind.

if (key == '*') {
  inputCode = "";
  Serial.println("Schloss verriegelt.");
  delay(1000);
  servo.write(90);
  digitalWrite(greenLED, LOW);
  digitalWrite(redLED, HIGH);

Die zweite Abfrage folgt gleich darauf mit einem else if und wird angesteuert, wenn die Taste # gedrückt wurde. In diesem Fall folgen wiederum zwei weitere Abfragen – eine, die ausgeführt wird, wenn der Code korrekt eingegeben wurde und die andere, falls dem nicht so ist.

} else if (key == '#') {
  if (inputCode == code) {
    Serial.println("Der Code ist korrekt. Öffne das Schloss...");
    digitalWrite(greenLED, HIGH);
    digitalWrite(redLED, LOW);
    servo.write(0);
  } else {
    Serial.println("Der Code ist falsch!");
    digitalWrite(greenLED, LOW);
    digitalWrite(redLED, HIGH);
  }

Falls der Code stimmt, also inputCode == code zutrifft, leuchtet die grüne LED und der Servo steuert auf 0°. Bei einer inkorrekten Eingabe, leuchtet entsprechend die rote LED – und nichts bewegt sich. In beiden Fällen wird die Variable für den eingegebenen Code wieder geleert:

inputCode = "";

Jetzt hast du alles, um das Schloss auf Tastendruck zu verriegeln. Außerdem um zu prüfen, ob der Code richtig oder falsch ist und entsprechend zu reagieren. Aber wie wird die Variable inputCode eigentlich befüllt?

Das geschieht in einem letzten else – die Anweisung, die bei allen Tasten außer * und # ausgeführt wird. Hier fügst du der Variablen inputCode einfach das zuletzt gedrückte Zeichen hinzu:

} else {
  inputCode += key;
  Serial.println(inputCode);
}

Hier nun der gesamte Sketch – viel Spaß mit deinem neuen Code-Schloss! :=

#include <Keypad.h>
#include <Servo.h>

const byte ROWS = 4;
const byte COLS = 4;

char hexaKeys[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};

byte rowPins[ROWS] = {9, 8, 7, 6};
byte colPins[COLS] = {5, 4, 3, 2};
Keypad customKeypad = Keypad( makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

char key;
String inputCode;
const String code = "1103A"; //Der von dir festgelegte Code

Servo servo;
int redLED = 12;
int greenLED = 13;


void setup() {

  Serial.begin(9600);
  pinMode(redLED, OUTPUT);
  pinMode(greenLED, OUTPUT);
  servo.attach(11);
}

void loop() {

  char key = customKeypad.getKey();

  if (key) {

    if (key == '*') {
      inputCode = "";
      Serial.println("Schloss verriegelt.");
      delay(1000);
      servo.write(90);
      digitalWrite(greenLED, LOW);
      digitalWrite(redLED, HIGH);

    } else if (key == '#') {
      if (inputCode == code) {
        Serial.println("Der Code ist korrekt. Öffne das Schloss...");
        digitalWrite(greenLED, HIGH);
        digitalWrite(redLED, LOW);
        servo.write(0);

      } else {
        Serial.println("Der Code ist falsch!");
        digitalWrite(greenLED, LOW);
        digitalWrite(redLED, HIGH);
      }

      inputCode = "";

    } else {
      inputCode += key;
      Serial.println(inputCode);
    }
  }
}
]]>
Eine Arduino Alarmanlage mit Geräuschsensor https://polluxlabs.net/arduino-projekte/eine-arduino-alarmanlage-mit-geraeuschsensor/ Fri, 01 Sep 2023 13:05:07 +0000 https://polluxlabs.net/?p=14348 Eine Arduino Alarmanlage mit Geräuschsensor Weiterlesen »

]]>
In diesem Projekt baust du dir deine eigene Arduino Alarmanlage. Diese besteht aus drei Komponenten: einem Geräuschsensor, einem aktiven Piezo-Summer und einer RGB-LED.

Mit dem Geräuschsensor misst du die Umgebungslautstärke. Sobald von dir bestimmte Schwellenwerte unter- bzw. überschritten werden, leuchtet die RGB-LED Grün oder Gelb. Sobald die Lautstärke ein festgelegtes Maß überschreitet, leuchtet die LED in Rot und aus dem Piezo ertönt ein schriller Alarmton.

In den folgenden Abschnitten lernst du, wie du den Geräuschsensor, den Piezo-Summer und die RGB-LED anschließt. Danach erfährst du, wie alle drei Bauteile zusammen die Alarmanlage bilden.

DER GERÄUSCHSENSOR

Zunächst erfährst du, wie du den Geräuschsensor (KY-037, auch Sound Sensor genannt) anschließt und damit Geräusche sowohl analog als auch digital in deinem Arduino UNO verarbeitest.

Die wichtigsten Bauteile des Geräuschsensors, den du hier kennenlernst, sind: das Mikrofon, ein Komparator (hier der LM393, der zwei Spannungen miteinander vergleicht) und ein Potentiometer (um den Schwellenwert einzustellen). Du schließt den Sensor über mindestens 3 der 4 Pins an – Anode, Kathode, Analog- und/oder Digitalausgang.

Sound Sensor on Arduino

ANSCHLUSS AM DIGITALEN AUSGANG

Du kannst den Geräuschsensor auf zwei Arten an deinem Arduino anschließen – analog oder digital. Der digitale Anschluss ist sinnvoll, wenn du etwas in Gang setzen möchtest, sobald ein lautes Geräusch erkannt wird. Das könnte zum Beispiel ein Klopfen an der Tür oder ein Knall sein. So schließt du deinen Sensor an:

Geräuschsensor am Arduino

Versorge deinen Sensor mit Strom über die Pins + und G (für Ground, also Minus) und schließe den digitalen Ausgang (DO) am Arduino am digitalen Eingang 3 an. Den analogen Ausgang (AO) verbindest du als nächstes am Arduino mit dem Pin A1. Und das war es schon.

Kopiere dir nun den folgenden Sketch und lade ihn auf deinen Arduino UNO.

const int sensor = 3;
const int led = LED_BUILTIN;
int noise  = 0;

void setup() {

pinMode(sensor, INPUT);
pinMode(led, OUTPUT);
Serial.begin(9600);

}

void loop() {

noise = digitalRead(sensor);
Serial.println(noise);

if (noise == 1){
  digitalWrite(led, HIGH);
}else{
  digitalWrite(led, LOW);
  }
}

Sollte die interne LED des Arduinos jetzt dauerhaft leuchten, bedeutet das, dass der Sensor durchgehend ein Geräusch erkennt. Nimm jetzt einen kleinen Schraubendreher zur Hand und drehe die Schraube am Potentiometer nach links – solange bis die LED ausgeht.

Jetzt erzeuge direkt neben dem Mikrofon ein lautes Geräusch – schnippe zum Beispiel mit den Fingern. Die LED sollte kurz aufleuchten. Falls nicht – drehe die Schraube wieder leicht nach rechts. Mit etwas Fingerspitzengefühl findest du die richtige Feinjustierung. Du kannst auch im Seriellen Monitor nachverfolgen, ob der Sensor ein Geräusch erkennt: Bei “Stille” siehst du dort eine Null, bei einem Geräusch eine 1.

UND JETZT DER ANALOGE AUSGANG

Wenn du den Geräuschsensor analog anschließt, erhältst du in Echtzeit ein Feedback zur Lautstärke. So kannst du zum Beispiel eine LED aufleuchten lassen, sobald eine von dir bestimmte Lautstärke überschritten wird.

Du hast den analogen Ausgang (AO) des Sensors ja bereits mit deinem Arduino verbunden. Lade nun folgenden Sketch hoch.

const int sensor = A1;
const int led = LED_BUILTIN;
int noise  = 0;

void setup() {

pinMode(sensor, INPUT);
pinMode(led, OUTPUT);
Serial.begin(9600);

}

void loop() {

noise = analogRead(sensor);
Serial.println(noise);

if (noise > 200){
  digitalWrite(led, HIGH);
}else{
  digitalWrite(led, LOW);
  }
}

Um die Lautstärke besser verfolgen zu können, starte den Seriellen Monitor und beobachte dort die Sensorwerte. Auch jetzt ist wieder etwas Feinjustierung nötig. Stelle das Potentiometer so ein, dass du im seriellen Monitor Werte siehst, du etwas unter 200 liegen. Wenn du jetzt ein Geräusch machst, das laut genug ist, dass der Wert über 200 schnellt, leuchtet die interne LED des Arduinos auf.

Du siehst also, es reicht hier nicht, dass ein Geräusch erkannt wird. Dieses Geräusch muss auch noch laut genug sein, um die LED einzuschalten.

Die Lautstärke wirst du mit einer RGB-LED darstellen und sobald eine bestimmte Lautstärke überschritten wird, ertönt ein Alarmsignal aus dem Piezo-Summer.

DEN AKTIVEN PIEZO UND DIE RGB-LED ANSCHLIESSEN

Den Geräuschsensor hast du ja bereits installiert. Allerdings benötigst du für die Arduino Alarmanlage nur den Analogausgang (AO) des Sensors. Das Kabel am Digitalausgang kannst du weglassen bzw. entfernen.

Mit der Spieluhr und dem Theremin hast du ja bereits den passiven Piezo-Summer kennengelernt. Wie du weißt, kannst du mit diesem unterschiedliche Töne erzeugen. Nicht so mit dem aktiven Piezo: Dieses Bauteil kann lediglich einen einzigen Ton erzeugen – und macht das, sobald es mit Strom versorgt wird. Für eine Alarmanlage ist das jedoch völlig ausreichend.

Wenn du dir nicht sicher bist, ob dein Piezo aktiv oder passiv ist, lege einfach 5V Spannung direkt von deinem Arduino an. Erklingt ein Ton? Dann ist es der aktive Piezo, den du für dieses Projekt brauchst.

Der Anschluss ist ganz einfach. Verbinde das kurze Bein des Piezo-Summers mit Minus und das lange mit dem Digitalpin 4 an deinem Arduino. Wenn später der Geräuschsensor einen Alarm auslöst, leitest du über diesen Pin Strom an den Piezo, der daraufhin zu piepsen anfängt.

Aufbau Alarmanlage Piezo RGB-LED

Etwas aufwändiger ist der Anschluss der RGB-LED. Insgesamt besitzt sie 4 Beinchen: eine Kathode, die du mit Minus verbindest, und drei Anoden – je eine für die Farben Rot, Grün und Blau. Wie du es von “regulären” LEDs gewohnt bist, musst du auch hier einen 220Ω Vorwiderstand einbauen – hier für jede Anode einen.

Die drei Anoden verbindest du mit den Digitalpins 9, 10 und 11. Diese drei Pins verfügen über die Pulsweitenmodulation (PWM), die du bereits kennengelernt hast, also du die Helligkeit einer einfachen LED gesteuert hast.

Möglicherweise passen die oben gezeigten Farbkanäle und Arduino-Pins je nach Bauart der LED nicht zusammen. Im Beispiel unten ist der rote Farbkanal rechts von der Kathode, was bei deiner LED jedoch anders sein kann. Das kannst du jedoch leicht herausfinden und beheben, wenn du später die Farbe des Lichts umschaltest: Passe einfach die Variablen im Sketch an schreibe hinter den Farbkanal den richtigen Pin:

int ledRed = 10;
int ledGreen = 9;
int ledBlue = 8;

Wenn du alles so wie auf der Skizze oben aufgebaut und verkabelt hast, kann es mit dem Code weitergehen.

DER SKETCH FÜR DIE Arduino ALARMANLAGE

Jetzt, wo du alles auf deinem Breadboard aufgebaut hast, wird es Zeit für das Programm. Im Folgenden erfährst du mehr über die einzelnen Teile des Programms und deren Funktion.

Gleich zu Beginn des Sketchs legst du fest, welche Hardware du an welchen Pins angeschlossen hast. Diese kannst du mit const natürlich auch als Konstanten festlegen. Außerdem benötigst du ein paar Variablen für die Helligkeit der einzelnen Farbkanäle der RGB-LED (z.B. brightnessRed = 150) und die Lautstärke, die der Geräuschsensor misst. Diese legst du zu Beginn auf Null fest.

int ledRed = 11;
int ledGreen = 10;
int ledBlue = 9;

int brightnessRed = 150;
int brightnessGreen = 150;
int brightnessBlue = 150;

int noise = 0;
int sensor = A1;

int piezo = 4;

DIE SETUP-FUNKTION

Hier gibt es vermutlich auch nichts, das du nicht bereits schon kennst. Du legst die Pins der LED und des Piezos als OUTPUT fest und startest den Seriellen Monitor.

void setup() {
 
  pinMode(ledRed, OUTPUT);
  pinMode(ledGreen, OUTPUT);
  pinMode(ledRed, OUTPUT);

  pinMode(piezo, OUTPUT);

  Serial.begin(9600);
}

DER LOOP

Hier wird es nun spannend. Als erstes misst du die Umgebungslautstärke, denn hierauf basieren später alle weiteren Aktionen im Sketch – also ob deine Arduino Alarmanlage anspringt oder nicht.

noise = analogRead(sensor);
Serial.println(noise);

Anschließend folgt die erste bedingte Anweisung. Wenn nämlich der Geräuschpegel unter einem bestimmten Wert liegt, soll die LED grün leuchten – was so viel bedeutet wie “Die Luft ist rein”. Den Wert von 200 (und die folgenden in den weiteren Anweisungen) kannst du natürlich anpassen. Achte auch darauf, dass du beim ersten Start der Alarmanlage den Geräuschsensor so kalibrierst, dass er bei Ruhe unter dem von dir festgelegten Wert liegt.

if(noise <= 200){
  analogWrite(ledGreen, brightnessGreen);
  analogWrite(ledRed, 0);
  analogWrite(ledBlue, 0);
  digitalWrite(piezo, LOW);
  }

Wie erwähnt, soll in diesem Zustand die LED grün leuchten. Das erreichst du, indem du nur den grünen Farbkanal mit der von dir festgelegten Helligkeit brightnessGreen aufleuchten lässt. Die beiden anderen Kanäle für Rot (Red) und Blau (Blue) erhalten die Helligkeit Null, sind also aus. Du kannst natürlich auch Farben mischen (auch wenn die LED diese nicht sehr differenziert darstellen kann) – hierfür eignet sich z.B. dieses RGB Color Wheel.

Auch der Piezo soll hier nicht ertönen, weswegen du keinen Strom an ihn leitest. Das erreichst du mit dem Parameter LOW in der Funktion digitalWrite().

EINE WEITERE ANWEISUNG

Wenn nun der Geräuschpegel etwas ansteigt, aber immer noch nicht hoch genug für einen Alarm ist, kommt eine zweite bedingte Anweisung ins Spiel – mit else if{}.

else if(noise > 200 && noise <= 350){
  analogWrite(ledRed, brightnessRed);
  analogWrite(ledGreen, brightnessGreen);
  analogWrite(ledBlue, 0);
  digitalWrite(piezo, LOW);
  }

Diese Befehle werden ausgeführt, wenn die Lautstärke zwischen 201 und 350 liegt. Wie gesagt, experimentiere mit diesen Werten, um sie an deine Gegebenheiten anzupassen.

Befindet sich also die Lautstärke in diesem Bereich, wechselt das Licht der LED von Grün zu Gelb. Für diese Farbe gibt es keinen eigenen Farbkanal, weswegen du sie kurzerhand mischst. Gelb ist eine Mischung aus Rot und Grün. Deshalb schaltest du diese Farbkanäle der LED und lässt den blauen Kanal erlöschen.

ALARM!

Wenn es nun noch lauter wird, soll der Alarm ertönen. Die LED strahlt in einem satten Rot und der Piezo-Summer fängt an zu piepsen.

else if(noise > 350){
  analogWrite(ledRed, brightnessRed);
  analogWrite(ledGreen, 0);
  analogWrite(ledBlue, 0);
  digitalWrite(piezo, HIGH);
  delay(10000);
  }

Diesmal schaltest du also nur den roten Kanal der LED ein. Außerdem leitest du nun Strom vom Arduino an den Piezo – mit der Funktion digitalWrite(piezo, HIGH);

Sicherlich sollte eine Alarmanlage so lange Krach machen, bis sie ausgeschaltet wird. Für ein erstes Experiment reicht jedoch vielleicht auch erst einmal eine Sekunde. Deshalb befindet sich am Ende noch ein delay() von 1.000 Millisekunden.

Und das war es! Lade den folgenden Sketch auf deinen Arduino, kalibriere deinen Geräuschsensor und probiere deine Arduino Alarmanlage aus.

int ledRed = 11;
int ledGreen = 10;
int ledBlue = 9;

int brightnessRed = 150;
int brightnessGreen = 150;
int brightnessBlue = 150;

int noise = 0;
int sensor = A1;

int piezo = 4;

void setup() {
 
  pinMode(ledRed, OUTPUT);
  pinMode(ledGreen, OUTPUT);
  pinMode(ledRed, OUTPUT);

  pinMode(piezo, OUTPUT);

  Serial.begin(9600);

}

void loop() {

  noise = analogRead(sensor);
  Serial.println(noise);

  if(noise <= 200){
    analogWrite(ledGreen, brightnessGreen);
    analogWrite(ledRed, 0);
    analogWrite(ledBlue, 0);
    digitalWrite(piezo, LOW);
    }
    else if(noise > 200 && noise <= 350){
      analogWrite(ledRed, brightnessRed);
      analogWrite(ledGreen, brightnessGreen);
      analogWrite(ledBlue, 0);
      digitalWrite(piezo, LOW);
      }
      else if(noise > 350){
        analogWrite(ledRed, brightnessRed);
        analogWrite(ledGreen, 0);
        analogWrite(ledBlue, 0);
        digitalWrite(piezo, HIGH);
        delay(10000);
        }
}

Apropos Piezo: Im verlinkten Tutorial erfährst du, wie du einen passiven Piezo-Summer verwenden kannst.

]]>
Ein Babyphone mit künstlicher Intelligenz und einem Arduino Nano 33 BLE Sense https://polluxlabs.net/arduino-projekte/ein-babyphone-mit-kuenstlicher-intelligenz-und-einem-arduino-nano-33-ble-sense/ Mon, 18 Jan 2021 22:36:23 +0000 https://polluxlabs.net/?p=4884 Ein Babyphone mit künstlicher Intelligenz und einem Arduino Nano 33 BLE Sense Weiterlesen »

]]>

Ein Babyphone ist eine praktische Sache, sollte das eigene Kleinkind mal außer Hörweite sein. Im Prinzip ist solch ein Gerät nichts anderes als ein Sender mit Mikrofon, der per Funk Geräusche an einen Empfänger überträgt. Weint oder schreit ein Baby aus dem Lautsprecher, erkennt jeder Mensch sofort, was hier los ist.

Aber kann eine künstliche Intelligenz auch lernen, Weinen und Schreien von anderen Geräuschen zu unterscheiden? Klar! Hier stellen wir ein Projekt vor, das mithilfe von Machine Learning und TinyML genau das macht. Alles was du dafür brauchst, ist ein Arduino Nano 33 BLE Sense und einen kostenlosen Account bei Edge Impulse.

Fortgeschrittene

3 – 4 Stunden

ca. 40€

Für dieses Projekt benötigst du:

Arduino® Board Nano 33 BLE Sense with headers
Arduino® Board Nano 33 BLE Sense with headers
Arduino Board Nano 33 BLE Sense with headers; Inhalt: 1 Stück

Das vorgestellte Projekt stammt vom Maker Ish Ot Jr. und wurde im Arduino Project Hub veröffentlicht. Du findest die Beschreibung (Englisch) hier. Im Folgenden beschäftigen wir uns mit den Voraussetzungen für das Babyphone mit KI, gehen auf wichtige Aspekte genauer ein und geben einen Ausblick auf weitere Anwendungen und Möglichkeiten.

Grundlagen

Das Babyphone mit dem Arduino Nano 33 BLE Sense funktioniert im Kern so: Zunächst sammelst du Geräusche von (d)einem Baby, die die Künstliche Intelligenz später erkennen soll. Damit sie diese von allen möglichen anderen Geräuschen unterscheiden kann, benötigst du auch Nebengeräusche, die im Kinderzimmer auftreten können – und zwar so viele Samples wie möglich, auch von der Stille eines träumenden Babys. 😉

Die Audiodaten kannst du direkt mit deinem Arduino sammeln. Wenn du noch nicht weißt, wie das geht, lese zunächst diese beiden Tutorials:

Wie du siehst, steht der Service von Edge Impulse im Mittelpunkt. Diesen kannst du kostenlos nutzen, um Daten zu sammeln, zu KI-Modellen zu verarbeiten und auf einem Microcontroller zu deployen.

Audio sammeln und ein Modell entwickeln

Wenn du nun weißt, wie du grundsätzlich Daten mit deinem Arduino Nano sammelst und in Edge Impulse speicherst, kannst du damit loslegen. Im Projekt im Arduino Project Hub legt der Autor zwei Labels an: Weinen (crying) und Geräusche (noise).

Wenn du für jedes deiner Labels mehrere Minuten Audio gesammelt hast, kann es weitergehen. In seinem Tutorial im Arduino Project Hub erläutert der Autor, welche Einstellungen er für sein Impulse Design verwendet. Damit erhält er eine Genauigkeit seines Modells von gut 86%.

Investiere in diesen Schritt des Projekts ruhig etwas Zeit und spiele mit den Einstellungen herum. Es ist wichtig, dass die einzelnen Labels (bzw. Features) gut von einander unterscheidbar sind, wie hier zu sehen. Diese Daten stammen aus einem anderem KI-Projekt, bei dem es um das Erkennen von Gesten geht.

KI-Modell in Edge Impulse
Beispiel für gut getrennte Features

Wenn du auf keinen grünen Zweig kommst, könnte eine Möglichkeit sein, einfach noch mehr Daten zu sammeln. Je mehr Material deine KI zum Lernen hat, desto besser. Achte auch darauf, dass du in Edge Impulse auch genügend Testdaten hast – diese dürfen sich nicht mit den Trainingsdaten überschneiden, um zuverlässige Ergebnisse zu erhalten.

So bringst du die KI auf deinen Arduino

Wenn du zufrieden bist mit deinem KI-Modell, wähle unter dem Menüpunkt Deployment die Kachel Arduino Library und lade dir die ZIP-Datei herunter. Hier enthalten sind eine Bibliothek und ein Sketch, den du im Folgenden etwas modifizierst.

Du könntest unter Deployment auch eine Firmware für deinen Arduino Nano 33 BLE Sense erstellen lassen. Dann könntest du die Auswertungen der KI jedoch nur im Seriellen Monitor verfolgen – etwas unpraktisch für ein Babyphone. Deshalb kommt der Sketch ins Spiel.

Im Tutorial im Project Hub geht der Autor sehr genau auf seine Anpassungen ein und erklärt, wie er die direkt am Arduino verbaute RGB LED verwendet, um darzustellen, ob ein Baby gerade weint oder nicht. Grün steht hier für „Alles in Ordnung“, also dafür, dass die Künstliche Intelligenz nur Noise erkennt. Rot hingegen für Weinen.

Wie geht es weiter?

Das vorgestellte Projekt zeigt sehr anschaulich, wie man mit einem einigermaßen günstigen Microcontroller und Edge Impulse relativ einfach ein KI-Projekt umsetzen kann. Du könntest das Modell noch verfeinern und nicht alles was nicht Weinen ist, einfach unter Noise zusammenfassen. So könnten weitere Labels die Ergebnisse noch verbessern.

Für diese detailliertere Auswertung ist die interne RGB LED vermutlich nicht ausreichend. Auf Pollux Labs findest du viele Tutorials zu Displays und auch zu einem NeoPixel LED Ring.

Wie sieht es mit anderen Geräuschen aus? Duscht gerade jemand? Oder schleudert deine Waschmaschine unruhig und könnte bald kaputtgehen? Das könnten alles Anwendungsfälle für dich sein. 🙂

]]>
Arduino Wettervorhersage https://polluxlabs.net/arduino-projekte/arduino-wettervorhersage/ Wed, 02 Dec 2020 10:34:52 +0000 https://polluxlabs.net/?p=4363 Arduino Wettervorhersage Weiterlesen »

]]>
Eine Wetterstation mit aktuellen Werten ist eine Sache – aber zu wissen, wie das Wetter in einigen Stunden sein wird, eine ganz andere. In diesem Projekt baust du dir so eine einfache Wettervorhersage mit dem Luftdrucksensor BMP180 und einem Servo.

Zugegeben, mit den Meteorologen in den Nachrichten kann dieses Projekt nicht mithalten, aber möglicherweise kann es dich vor den ganz großen Wetterumschwüngen warnen.

Anfänger

1 – 2 Stunden

ca. 10 € + Arduino

Für dieses Projekt benötigst du (Mengen s. Beschreibung):

So funktioniert die Wettervorhersage

Wie eingangs erwähnt, ist eine Wettervorhersage eine komplexe Angelegenheit, an der Spezialisten mit Großcomputern arbeiten. Es gibt jedoch einen Zusammenhang, den wir uns in diesem Projekt zunutze machen: Steigt der Luftdruck, bessert sich das Wetter – fällt er, wird das Wetter schlecht.

Auch das ist eine starke Vereinfachung der Realität sein, aber dieser Zusammenhang soll uns hier einmal genügen. Immerhin wird dieses Prinzip schon seit Jahrhunderten angewandt. Sicherlich kennst du alte Barometer wie dieses hier:

Dosen-Barometer, Quelle: Wikipedia/Langspeed

Mehr über Barometer und die möglichen Anwendungen erfährst du auf Wikipedia. In unserer Arduino Wettervorhersage messen wir in regelmäßigen Abständen den Luftdruck und vergleichen ihn mit dem zuletzt gemessenen Wert. Fällt der Luftdruck immer weiter, dreht ein Servo-Motor einen Zeiger nach links in Richtung schlechtes Wetter. Wenn der Luftdruck kontinuierlich steigt, bewegt sich der Zeig nach rechts – es wird also gutes Wetter geben. Übrigens: Mit unserer ESP8266 Wetterstation speicherst du Daten zum Luftdruck lokal in einer Datenbank.

Luftdruck messen mit dem BMP180

Für die Messung des Luftdrucks verwenden wir den Sensor BMP180. Du kannst auch den genaueren BMP280* oder einen BME280* verwenden. Letzterer misst auch die Luftfeuchte, die wir in diesem Projekt jedoch nicht brauchen.

Diese Sensoren lassen sich leicht per I²C anschließen und mit einer passenden Bibliothek komfortabel verwenden. In diesem Tutorial erfährst du mehr darüber, wie du den BMP180 anschließt und verwendest.

Das Wetter mit einem Servo anzeigen

Es gibt viele denkbare Möglichkeiten, um die Veränderungen des Luftdrucks anzuzeigen: Verschiedene Displays oder auch ein NeoPixel LED-Ring. Hier verwenden wir jedoch einen Servo-Motor und eine passende Schablone. Diese können z.B. Kinder farbig gestalten und sie hat ebenso den gewissen Retro-Charme. 🙂

Schablone für den Servo der Wettervorhersage
Schablone für den Servo

Was ein Servo-Motor ist, wie funktioniert und was es alles zu beachten gibt, erfährst in diesem Tutorial zu Servos.

Der Aufbau der Arduino Wettervorhersage

Es dauert nur wenige Minuten, die Wettervorhersage auf deinem Breadboard aufzubauen. Orientiere dich hierbei an diesem Schema:

Aufbau der Arduino Wettervorhersage

Achte darauf, dass du den Servo mit 5V und den BMP180 mit 3,3V versorgst. Die Beschriftung der Pins des BMP180 befindet sich auf seiner Unterseite – vergewissere dich, dass du sie richtig am Arduino angeschlossen hast, bevor du ihn mit Strom versorgst.

Der Sketch

Es sind nicht viele Zeilen Code nötig, um die Wettervorhersage zum Laufen zu bringen. Starte wie so oft, indem du die nötigen Bibliotheken einbindest. Wie du sie installierst, erfährst du in den oben genannten Tutorials.

#include <Wire.h>
#include <Adafruit_BMP085.h>
#include <Servo.h>

Anschließend erstellst du zwei Objekte – eines für den Luftdruck-Sensor und eines für den Servo:

Adafruit_BMP085 bmp;
Servo myServo;

Dazu benötigst du noch ein paar Variablen, um die Werte des Sensors und die gewünschte Position des Servos zu speichern:

int servoPosition;
long currentPressure;
long oldPressure;
int delta;

Die Setup-Funktion

Hier startest du den Seriellen Monitor und vergewisserst dich, dass der BMP180 richtig angeschlossen und funktionstüchtig ist. Anschließend weist du dem Servo den Anschlusspin 8 zu und drehst den Zeiger nach oben – auf 90°.

void setup() {
  Serial.begin(115200);
  if (!bmp.begin()) {
    Serial.println("Sensor not found!");
    while (1) {}
  }

  myServo.attach(8);
  myServo.write(90);
}

Übrigens: Wenn du nicht weißt, in welcher Position sich dein Servo gerade befindet und in welche Position der Zeiger bei der Montage schauen soll, dann bringe ihn erst nach dem Start an. Zu Beginn des Sketchs steht der Servo auf 90° – sodass der Zeiger nach oben gerichtet ist.

Der Loop der Arduino Wettervorhersage

Hier misst du als erstes den aktuellen Luftdruck und gibst ihn im Seriellen Monitor aus:

  currentPressure = bmp.readPressure();
  Serial.print("Current Pressure = ");
  Serial.print(currentPressure);
  Serial.println(" Pa");

Wie du siehst, geht das ganz einfach mit der Funktion bmp.readPressure(). Gleichzeitig speicherst du diesen Wert – der in Pascal ausgegeben wird – in der Variablen currentPressure.

Danach fragst du ab, ob sich der aktuelle Luftdruck gegenüber der Messung davor (gespeichert in der Variablen oldPressure) verändert hat. Falls ja, speicherst du diese Veränderung in der Variablen delta.

 if (oldPressure && currentPressure != oldPressure) {
   delta = currentPressure - oldPressure;

Im If-Statement siehst du die Bedingung oldPressure && – diese befindet sich hier, da es im ersten Messdurchgang noch keine alte Messung gibt. Erst wenn in dieser Variablen eine Zahl hinterlegt ist – sie also nicht mehr auf 0 bzw. false steht – wird diese Bedingung wahr. Alternativ kannst du auch folgendes schreiben:

 if (oldPressure == true && currentPressure != oldPressure) {

Den Servo steuern

Kommen wir zum Servo und der Anzeige der Wetterlage. Um die Veränderung des Luftdrucks in der Variablen delta anzuzeigen, musst du dem Servo mitteilen, wo er sich hindrehen soll. Ein Servo kann eine Position zwischen 0° und 180° einnehmen – zu Beginn des Sketchs steht er in der Mitte, also auf 90°.

Wir nehmen mal an, dass die maximale Veränderung in unserem Messzeitraum (mehr dazu gleich) bei +- 100 Pa liegt. Bei -100 Pa soll der Servo auf 0° fahren, bei +100 Pa entsprechend auf 180°. Hinweis: Möglicherweise liegen wir mit dieser Einschätzung falsch – hier sind also deine eigenen Experimente gefragt. Verfolge im Seriellen Monitor die Messwerte und kalibriere deine Wetterstation entsprechend.

Jedenfalls musst du die beiden Wertebereiche +-100 Pa und 0-180° unter einen Hut bringen. Hierfür bietet sich die Funktion map() an:

servoPosition = map(delta, -100, 100, 0, 180);

Hier nimmst du die aktuelle Veränderung delta, ihren möglichen Wertebereich +-100 und „mappst“ diesen Wert auf die möglichen Winkel des Servos: 0° bis 180°. Heraus kommt der Winkel, der der Veränderung des Luftdrucks entspricht. Diesen speicherst du in der Variablen servoPosition.

Anschließend steuerst du deinen Servo auf diese Position:

myServo.write(servoPosition);

Danach machst du die aktuelle Messung zur alten Messung oldPressure, mit der du die nächste vergleichst. Und als letztes wartest du eine gewisse Zeit bis zur Messung, in diesem Fall 5 Minuten bzw. 300.000 Millisekunden.

oldPressure = currentPressure;
delay(300000);

Hier nun der gesamte Sketch der Arduino Wettervorhersage zum Rauskopieren und Hochladen. Viel Spaß! 🙂

#include <Wire.h>
#include <Adafruit_BMP085.h>

#include <Servo.h>

Adafruit_BMP085 bmp;
Servo myServo;

int servoPosition;
long currentPressure;
long oldPressure;
int delta;

void setup() {
  Serial.begin(115200);
  if (!bmp.begin()) {
    Serial.println("Sensor not found!");
    while (1) {}
  }

  myServo.attach(8);
  myServo.write(90);
}

void loop() {
  currentPressure = bmp.readPressure();
  Serial.print("Current Pressure = ");
  Serial.print(currentPressure);
  Serial.println(" Pa");

  if (oldPressure && currentPressure != oldPressure) {
    delta = currentPressure - oldPressure;
    Serial.print("Change: ");
    Serial.print(delta);
    Serial.println();
  }

  servoPosition = map(delta, -100, 100, 0, 180);
  myServo.write(servoPosition);
  
  oldPressure = currentPressure;

  delay(300000);
}

Fehlen dir noch Bauteile? Dann wirf einen Blick in unsere Übersicht der besten Arduino Starter Kits.

]]>