Arduino Kurs Stufe II – #5 – Tastatur, eigene Alarmanlage

 

Während der Übungen in diesem Teil des Arduino-Kurses werden wir uns ansehen, wie wir in der Praxis am bequemsten eine Tastatur an unser Gerät anschließen können.

Unter Verwendung aller bisher besprochenen Komponenten werden wir eine Alarmzentrale mit dem Arduino als Hauptgehirn bauen.

Bestellen Sie ein Set mit Elementen und beginnen Sie mit dem Lernen in der Praxis! Hier gehts zum Shop >>

Zu Beginn möchte ich hinzufügen, dass dieser Artikel eine weitere Neuheit „einschmuggelt“. Bei der Programmierung einer Alarmzentrale werde ich zeigen, wie man mit der Programmierung eines Geräts umgeht, das eine ganze Reihe von Operationen ausführen muss. Durch die Verwendung eines Zustandsautomaten können wir den Code stark vereinfachen und vermeiden Verschachtelungen von Bedingungen. Außerdem wirst du feststellen, dass du mit dem richtigen Ansatz auch ohne Unterbrechungen ein gut funktionierendes Programm erstellen kannst.

Matrix-Tastatur für Arduino

Bisher hat uns der Anschluss jeder Taste an den Arduino einen Eingang gekostet. Mit einem, zwei oder drei Knöpfen war das kein Problem. Manchmal möchten wir jedoch mehr Daten in den Schaltkreis eingeben können. Zum Beispiel erfordert die Eingabe eines Pincodes für einen Alarm mindestens 10 Tasten (Ziffern 0 bis 9), und idealerweise würden wir gerne ein paar weitere Tasten zur Verfügung haben (z. B. zum Abbrechen oder Bestätigen von Vorgängen).

Einfach ausgedrückt – wir brauchen eine numerische Tastatur:

Beispiel einer numerischen Tastatur.

Natürlich wäre es umständlich und manchmal sogar unmöglich, jede Taste an einen eigenen Pin anzuschließen (vor allem bei größeren Tastaturen). Glücklicherweise gibt es eine clevere Lösung, um die Anzahl der benötigten Pins deutlich zu reduzieren.

Aufbau der Matrix-Tastatur

Interne Signalverbindung im Tastenfeld.

Dies ermöglicht uns die Kontrolle von
16 Tasten mit 8 Datenleitungen!

Wenn wir eine Taste drücken, schließen wir eine Spalte mit einer Zeile kurz. In der obigen Abbildung schließt zum Beispiel das Drücken der Taste „1“ die Zeile P1 mit der Spalte P5 kurz. Drückt man dagegen die Taste „4“, so wird die Zeile P2 mit der Spalte P5 kurzgeschlossen. Durch die Überprüfung der Verbindungen zwischen den Zeilen P1-P4 und den Spalten P5-P8 können wir feststellen, ob und welche Taste gedrückt wurde.

Die Matrix-Tastatur in der Praxis

Während der Übungen werden wir eine einfache Version der Tastatur (ohne Frontplatte) verwenden, bei der die internen Verbindungen sichtbar sind. So können diejenigen, für die das oben beschriebene Leseprinzip nicht klar ist, „aus der Nähe“ sehen, wie alles aussieht:

16 Tasten - 8 Leitungen
Rückseite der Matrix-Tastatur

Zeit, die Tastatur in die Praxis anzuwenden!

Fertige Sets für Forbot-Kurse
 Satz von Elementen   Garantierte Unterstützung   Versand in 24 Stunden

Die Komponenten für die Übungen aus dem Arduino-Kurs (Stufe 2) sind als fertige Sets erhältlich! Darin enthalten sind programmierbare Dioden, analoge und digitale Thermometer, 7-Segment-Anzeigen und ein Bewegungssensor (PIR).

KeyPad-Bibliothek

Im Falle des Arduino finden wir natürlich eine fertige Bibliothek, die es uns noch einfacher macht, solche Tastaturen zu verwenden. In diesem Fall wird es eine Bibliothek namens Keypad sein. Sie kann direkt im Bibliotheksmanager oder auf GitHub gefunden werden: https://github.com/Chris–A/Keypad

Informationen zur Installation der Bibliotheken findet man in diesem Artikel:
Arduino Kurs II – #2 – RGB LEDs (traditionell und WS2812)

Erste Verwendung der numerischen Tastatur

Zunächst ist es sinnvoll, ein Testprogramm zu schreiben, das prüft, ob eine Taste gedrückt wird. Wenn dies der Fall ist, wird das entsprechende Zeichen über den UART an den Computer gesendet.

Zunächst schließen wir die Tastatur mit den Pins 2 bis 9 an den Arduino an, wie in der Abbildung unten gezeigt. Die Reihenfolge der Pins ist wichtig, damit wir die gedrückten Tasten richtig lesen können.

Anschließen der Tastatur an den Arduino.

Die Tastatur kann in eine Kontaktplatte eingesteckt werden (die Tasten zeigen dann allerdings nach unten). Es lohnt sich also, Leitungen zu verwenden und sie direkt mit dem Arduino zu verbinden. Dazu musst du 8 männlich/weibliche Leitungen „erstellen“ (wie im vorherigen Artikel):

Verbindung der Leitungen, um einen Adapter für die Kontaktplatte zu erhalten.

In meinem Fall sah das Ganze nach dem Zusammenstecken und Verlegen so aus:

Erstes Testlayout mit der Matrixtastatur.

Jetzt können wir zur Programmierung übergehen. Zunächst binden wir die Bibliothek ein und geben alle notwendigen Informationen über unsere Tastatur und deren Anschluss an:

				
					#include <Keypad.h> //Bibliothek der Tastatur

const byte ROWS = 4; // wie viele Zeilen
const byte COLS = 4; //wie viele Spalten

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

Die Textstelle ist so klar, dass es wahrscheinlich nicht nötig ist, sie näher zu erläutern. Lediglich die von mir angegebenen Pin-Nummern (bzw. ihre Reihenfolge) könnten für Verwirrung sorgen. Dies wird etwas weiter unten deutlich werden.

Der nächste Schritt ist die Belegung der Tastatur, d.h. die Zuordnung bestimmter Zeichen zu den Tasten. Ich habe diese Reihenfolge übernommen:

Angenommenes Tasten-Mapping.

Diese Informationen geben wir wie folgt an:

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

Diese Notation mag unintuitiv erscheinen, aber sie hilft, das Programm zu organisieren. Diejenigen, die bereits Programmiererfahrung haben, werden natürlich erkennen, dass es sich um eine einfache zweidimensionale Tabelle handelt.

Im Arduino-Kurs werden wir noch auf die Tabellen zurückkommen – falls also jemand damit nicht vertraut ist,
dann gibt es jetzt nichts zu befürchten.

Der letzte Schritt bei der Konfiguration besteht darin, ein neues Objekt vom Typ Keypad zu erstellen, in unserem Fall wird es Tastatur genannt:

				
					Keypad Tastatur = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS ); //Initialisierung der Tastatur
				
			

Bei den Übungen im Artikel über programmierbare Dioden WS2812 mussten wir eine vergleichbare Operation durchführen, als wir einen neuen Lichtstreifen deklarierten. Sobald die Tastatur deklariert ist, kannst du weitermachen – das heißt, die Zeichen auslesen.

Es ist nicht erforderlich, die Pins
an denen die Tastatur angeschlossen ist, als Eingänge einzustellen.

Der gesamte Code sieht wie folgt aus:

				
					#include <Keypad.h> //Bibliothek der Tastatur

const byte ROWS = 4; // wie viele Zeilen
const byte COLS = 4; //wie viele Spalten

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

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

Keypad Tastatur = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS ); //Initialisierung der Tastatur

void setup(){
  Serial.begin(9600);
}
  
void loop(){
  char Taste = Tastatur.getKey();
  
  if (Taste){
    Serial.println(Taste);
  }
}
				
			

In der Hauptschleife des Programms erstellen wir eine Variable zum Speichern eines Zeichens (vom Typ char) und weisen ihr dann den von der Tastatur gelesenen Wert zu. Wir erhalten dieses Zeichen, wenn wir Tastatur.getKey() aufrufen. Außerdem prüfen wir, ob das Zeichen tatsächlich empfangen wurde, und wenn ja, senden wir es über den UART an den Computer zurück.

Die Bedingung if (Taste) ist erfüllt, wenn sich unter der Variable Taste ein beliebiges Zeichen befindet.
Jeder Wert größer als 0 in der Bedingung wird als WAHR behandelt.

Nach dem Hochladen des Programms zeigt der Monitor der seriellen Schnittstelle die Zeichen an, die den gerade gedrückten Tasten zugeordnet sind:

Der Effekt des ersten Programms.

Wenn der Computer andere Zeichen anzeigt als in der früheren Mapping-Zeichnung, lohnt es sich, die Anschlüsse erneut zu überprüfen. Eine Vertauschung der Reihenfolge der Leitungen kann dazu führen, dass die Funktion die gedrückten Tasten falsch interpretiert.

Eine andere Möglichkeit, einen solchen Fehler zu beheben, wäre, den Wert in der Mapping-Tabelle zu ändern. Wenn wir zum Beispiel die Tastatur umkehren wollten, müsste dort folgender Code eingefügt werden:

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

Natürlich sind auch weitaus „skurrilere“ Kombinationen möglich. Allerdings wird der Code dann weniger lesbar. Das ist genau der Grund, warum ich darauf bestanden habe, am Anfang bestimmte Pin-Nummern und Anschlussreihenfolgen zu verwenden.

Ein buchstäblicher Zwilling des obigen Programms findet sich in den Beispielen, die mit der Bibliothek geliefert werden. Darüber hinaus gibt es dort noch einige andere interessante Programme – ich möchte euch ermutigen, sie selbst zu testen. Im weiteren Verlauf dieses Artikels werden wir uns bereits mit der Programmierung einer einfachen Alarmzentrale beschäftigen.

Alarmzentrale auf dem Arduino

Unser Alarmsystem besteht aus einem numerischen Tastenfeld, einem Tongeber, einem Leuchtsignal, einem Bewegungsmelder (PIR) und einem Türöffnungssensor (Reedschalter).

Die verwendeten Alarmsensoren wurden im vorherigen Teil des Arduino-Kurses beschrieben.

Natürlich kann das Ganze auf viele verschiedene Arten funktionieren. Ich habe beschlossen, dass mein Alarm wie folgt funktioniert: Wenn der Strom eingeschaltet wird, gehen wir in den Standby-Modus. Die Alarmanlage tut nichts, sondern wartet darauf, aktiviert zu werden. Wenn man die Taste A (wie Alarm) drückt, wird der Scharfschaltvorgang ausgelöst.

Nach ein paar Sekunden (Zeit, um den Raum zu verlassen) wird die Alarmanlage aktiviert – von da an ist unser Raum bewacht. Sobald wir eine Bewegung im Raum feststellen, beginnt der Alarm sofort, eine Bedrohung zu signalisieren.

Wenn wir das Öffnen einer Tür erkennen, geben wir dem Benutzer einige Sekunden Zeit, um den Alarm zu deaktivieren, indem er einen vierstelligen Code eingibt. Wenn der Pincode falsch oder nicht eingegeben wird, wird der Alarm ebenfalls ausgelöst.

Darüber hinaus wird das Ganze so programmiert, dass der Alarm ohne Unterbrechungen, reibungslos und ohne Verzögerung abläuft. Und das alles durch den Aufbau eines einfachen Zustandsautomaten.

Was ist ein Zustandsautomat?

Im Zusammenhang mit dem Arduino bezeichnen wir einen Zustandsautomaten (oder endlichen Automaten) als eine bestimmte Methodik zum Schreiben von Programmen, die es uns ermöglicht, verschiedene Funktionen in dem Gerät einfach und klar zu implementieren, die in einer festen Reihenfolge ablaufen.

Dadurch vermeiden wir verschachtelte, lange bedingte Anweisungen. Das Thema ist von der theoretischen Seite her viel komplexer – ich verweise Interessierte auf Wikipedia. Für den Moment reicht uns ein lockerer Bezug auf diese Methode.

Hier werden wir uns auf die praktische Anwendung des Zustandsautomaten konzentrieren,
Die Theorie ist nicht notwendig. Die ganze Sache ist sehr intuitiv.

Anhand der obigen Beschreibung des Alarmbetriebs können wir 4 Zustände unterscheiden:

  1. Bereitschaft – die Alarmanlage wartet darauf, aktiviert zu werden.
  2. Überwachen – das System bewacht unsere Räumlichkeiten.
  3. Entschärfen – die Alarmanlage wartet auf die Eingabe des richtigen Pins.
  4. Alarmsignalisierung – die Alarmanlage gibt Töne und Lichtsignale ab.

Natürlich können diese Funktionen nur in der richtigen Reihenfolge aufeinander folgen. Es wird nicht vorkommen, dass wir die Alarmanlage entschärfen, wenn sie sich im Standby-Modus befindet, usw. Dies lässt sich am besten aus dem folgenden Diagramm ersehen (dies ist kein Zustandsdiagramm, sondern eine einfache grafische Darstellung der obigen Beschreibungen):

Wie man sieht, könnte das Programm leicht aus 4 unabhängigen Funktionen bestehen, die direkt zueinander führen – natürlich in einer festen Reihenfolge. Fangen wir also an, den Code zu schreiben.

Code für eine einfache Alarmzentrale auf einem Arduino

Ich hoffe, das ist offensichtlich, aber um sicherzugehen, möchte ich betonen: Die folgende Übung ist nur ein Beispiel, ein Spaß- und Hobbyprojekt. Es ist sicherlich keine professionelle und industrielle Lösung. Es geht rein um das Lernen!

Die Grundstruktur des Programms ist unten zu sehen. Zu Beginn habe ich bereits die Informationen über die Sensoren und den Summer eingegeben, die später verwendet werden sollen. Ich habe auch die Tastatur deklariert – die Anschlüsse sind die gleichen geblieben wie bei der ersten Verwendung.

Sehr wichtig ist die Variable ZustandAlarm, die uns sagt, in welchem Zustand sich unser Gerät gerade befindet. Ihr Wert bestimmt, welche Operationen gerade ausgeführt werden. Außerdem verwenden wir in der Hauptschleife die im Arduino-Kurs Stufe I, beschriebene switch-case Konstruktion.

Mit Hilfe dieses Switches bewegen wir uns zwischen dem Code,
der je nach dem aktuellen Zustand der Schaltung ausgeführt werden soll.

				
					#define SUMMER 11
#define REEDSCHALTER 10
#define PIR 1

#include <Keypad.h> //Bibliothek der Tastatur

const byte ROWS = 4; // wie viele Zeilen
const byte COLS = 4; //wie viele Spalten

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

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

Keypad Tastatur = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS ); //Initialisierung der Tastatur

volatile int ZustandAlarm = 1;

void setup() {
  pinMode(SUMMER, OUTPUT);
  pinMode(REEDSCHALTER, INPUT_PULLUP);
  pinMode(PIR, INPUT_PULLUP);
}

void loop() {
  
  switch(ZustandAlarm) { //Ausführung von zustandsspezifischen Aktionen
    case 1:
      //Bereitschaft

    break;
    
    case 2:
      //Überwachung

    break;
    
    case 3:
      //Entschärfen

    break;
    
    case 4:
      //Alarmsignalisierung

    break;
   }
  
}
				
			

Zustand 1: Alarmbereitschaft

Während dieses Zustands sollte das Gerät seine Bereitschaft signalisieren, indem z. B. eine LED aufleuchtet, und wenn die Taste „A“ gedrückt wird, sollte der Alarm in den Zustand 2 übergehen – nachdem er die Zeit zum Verlassen des Raums abgewartet hat. Als Diode verwenden wir die im ersten Artikel dieser Serie beschriebene RGB-Diodenstreifen. Wir schließen sie an Pin A0 an.

Anschluss des Diodenstreifens an den Arduino.

Natürlich erfordert das Programm auch das Hinzufügen einer neuen Bibliothek und die Initialisierung des Streifens. Während sich das Gerät im ersten Zustand befindet, leuchtet außerdem eine der LEDs grün.

				
					#define SUMMER 11
#define REEDSCHALTER 10
#define PIR 1

#include <Keypad.h> //Bibliothek der Tastatur
#include <Adafruit_NeoPixel.h> //Bibliothek des LED-Streifens

const byte ROWS = 4; //wie viele Zeilen
const byte COLS = 4; //wie viele Spalten

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

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

Keypad Tastatur = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS ); //Initialisierung der Tastatur
Adafruit_NeoPixel Streifen = Adafruit_NeoPixel(8, A0, NEO_GRB + NEO_KHZ800); //Konfiguration des LED-Streifens

volatile int ZustandAlarm = 1;

void setup() {
  pinMode(SUMMER, OUTPUT);
  pinMode(REEDSCHALTER, INPUT_PULLUP);
  pinMode(PIR, INPUT_PULLUP);

  Streifen.begin(); //Initialisierung des Streifens
  Streifen.show(); 
}

void loop() {
  
  switch(ZustandAlarm) { //Ausführen einer zustandsspezifischen Aktion
    case 1:
      //Bereitschaft
        Streifen.setPixelColor(0, Streifen.Color(0, 15, 0)); //Diode 1 leuchtet grün
        Streifen.show();
    break;
    
    case 2:
      //Überwachung

    break;
    
    case 3:
      //Entschärfen

    break;
    
    case 4:
      //Alarmsignalisierung

    break;
   }
  
}
				
			

Es ist nun an der Zeit, die Möglichkeit der Scharfschaltung hinzuzufügen. Zur Erinnerung: Wenn wir eine Taste drücken, die mit „A“ gekennzeichnet ist (wie Alarm), müssen wir zu Zustand 2 übergehen. Dazu fügen wir innerhalb von Zustand 1 die entsprechende Bedingung hinzu, das Fragment sieht wie folgt aus:

				
					  char Taste = 0;

  switch(ZustandAlarm) { //Ausführen einer zustandsspezifischen Aktion
    case 1:
      //Bereitschaft
      Streifen.setPixelColor(0, Streifen.Color(0, 15, 0)); //Diode nr 1 leuchtet grün
      Streifen.show();
      
      Taste = Tastatur.getKey();
      if (Taste == 'A') { //Alarm scharfschalten?
        ZustandAlarm = 2;
      }
        
    break;
    
    case 2:
      //Überwachung
      Streifen.setPixelColor(0, Streifen.Color(15, 0, 0)); //Diode nr 1 leuchtet rot
      Streifen.show();
    break;
				
			

Die Zuweisung der Variablen ZustandAlarm = 2; bewirkt, dass das Programm im nächsten Schleifenzyklus in den scharfgeschalteten Zustand übergeht. Natürlich ist es sinnvoll, wie beabsichtigt, hier die erwähnten paar Sekunden zum Verlassen des Raums hinzuzufügen. Um einen besseren Effekt zu erzielen, habe ich beschlossen, hier einen langen Lichteffekt zu erzeugen, der etwa 10 Sekunden dauert.

In diesem Fall wirkt sich die Unterbrechung des Programms mit der delay Funktion nicht negativ auf das Programm aus.

Außerdem habe ich dem Programm eine Funktion hinzugefügt, die alle LEDs ausschaltet. Es gab auch einen sehr kleinen delay im Zustand zwei (Standby). Dadurch können wir einerseits sicher sein, dass das Gerät funktioniert (Blinken der LED), und andererseits ist er klein genug, um den Rest des Programms nicht zu beeinträchtigen.

Im Moment sollte der Code wie folgt aussehen:

				
					#define SUMMER 11
#define REEDSCHALTER 10
#define PIR 1

#include <Keypad.h> //Bibliothek der Tastatur
#include <Adafruit_NeoPixel.h> //Bibliothek des LED Streifens

const byte ROWS = 4; // wie viele Zeilen
const byte COLS = 4; //wie viele Spalten

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

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

Keypad Tastatur = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS ); //Initialisierung der Tastatur
Adafruit_NeoPixel Streifen = Adafruit_NeoPixel(8, A0, NEO_GRB + NEO_KHZ800); //Konfiguration des LED-Streifens

volatile int ZustandAlarm = 1;

void setup() {
  pinMode(SUMMER, OUTPUT);
  pinMode(REEDSCHALTER, INPUT_PULLUP);
  pinMode(PIR, INPUT_PULLUP);

  Streifen.begin(); //Initialisierung des Streifens
  Streifen.show(); 
}

void loop() {
  char Taste = 0; //Variable zur Aufbewahrung von Tastaturzeichen
  int i = 0; //Hilfsvariable für Schleife

  switch(ZustandAlarm) { //Ausführung einer zustandsspezifischen Aktion
    case 1:
      //Bereitschaft
      Streifen.setPixelColor(0, Streifen.Color(0, 15, 0)); //Diode nr 1 leuchtet grün
      Streifen.show();
      
      Taste = Tastatur.getKey();
      if (Taste == 'A') { //Soll der Alarm scharfgestellt werden?
        for (i = 1; i < 8; i++) {
          Streifen.setPixelColor(i, Streifen.Color(0, 0, 15)); //Diode nr i leuchtet blau
          Streifen.show();
          delay(710);
        } // Ausführung der Schleife dauert ca. 5 Sekunden
        
         for (i = 1; i < 8; i++) {
          Streifen.setPixelColor(i, Streifen.Color(15, 0, 0)); //Diode nr i leuchtet rot
          Streifen.show();
          delay(710);
        } // Ausführung der Schleife dauert ca. 5 Sekunden 
        
        DiodenAusschalten();
        ZustandAlarm = 2;
      }
        
    break;
    
    case 2:
      //Überwachung
      Streifen.setPixelColor(7, Streifen.Color(15, 0, 0)); //Diode nr 8 leuchtet rot
      Streifen.show();
      delay(50);
      Streifen.setPixelColor(7, Streifen.Color(0, 0, 0)); //Diode nr 8 ausgeschaltet
      Streifen.show();
      delay(50);
      
    break;
    
    case 3:
      //Entschärfen

    break;
    
    case 4:
      //Alarmsignalisierung

    break;
   }
  
}

void DiodenAusschalten() {
  int i = 0;
  for (i = 0; i < 8; i++){
    Streifen.setPixelColor(i, Streifen.Color(0, 0, 0)); //Diode nr 1 ausgeschaltet  
  }

  Streifen.show();
}
				
			

Test der Alarmscharfschaltung in der Praxis (die rote LED blinkt tatsächlich stärker):

Zustand 2: Raumüberwachung

Wenn sich das Gerät im zweiten Zustand befindet, sollte es zusätzlich zum Blinken der LED ständig die Zustände der Sensoren überprüfen. Das Thema Unterbrechungen lassen wir an dieser Stelle außen vor – wir können hier darauf verzichten.

Bevor das Programm bearbeitet wird, müssen natürlich die benötigten Sensoren angeschlossen werden. Die Pins sind bereits im Programm mit der Direktive #define festgelegt. Für weitere Experimente genügt es, beide Sensoren (Reed-Schalter und PIR) an unsere Grundplatte anzuschließen:

Im nächsten Schritt fügen wir zwei Bedingungen hinzu. Die Erkennung einer Bewegung soll sofort den Alarm auslösen, während das Öffnen der Tür (Reedschalter) uns die Möglichkeit geben soll, das System zu deaktivieren. Mit Hilfe der Variable StatusAlarm kann dies sehr einfach geschehen

				
					    case 2:
      //Überwachung
      Streifen.setPixelColor(7, Streifen.Color(15, 0, 0)); //Diode 8 leuchtet rot
      Streifen.show();
      delay(50);
      Streifen.setPixelColor(7, Streifen.Color(0, 0, 0)); //Diode 8 ausgeschaltet
      Streifen.show();
      delay(50);

      if (digitalRead(PIR) == HIGH) {
        ZustandAlarm = 4; //Es wird sofort ein Alarm ausgelöst
      } else if (digitalRead(KONTAKTRON) == HIGH) {
        ZustandAlarm = 3; //Möglichkeit der Entschärfung
      }
      
    break;
				
			

Natürlich werden wir keine neuen Effekte sehen, wenn wir das Programm starten, da die Zustände 3 und 4 noch nichts bewirken. Daher werden wir zunächst den Zustand 3 mit der Entschärfung der Schaltung umgehen. Fügen wir zunächst ein paar Zeilen zum Zustand 4 hinzu, der einen Alarm auslösen wird. Vorerst nur einen Lichteffekt.

Zustand 4: Alarmsignalisierung

Im Moment sieht das Programm nicht vor, dass der Alarmmodus verlassen werden kann. In diesem Abschnitt des Codes kann man sich also „austoben“, es gibt keine Verzögerungen, die uns schaden könnten. Ich werde für den Anfang ein Blinken in zwei Farben, rot und blau, hinzufügen:

				
					    case 4:
      //Alarmsignalisierung
      for (i = 0; i < 8; i++) {  
        Streifen.setPixelColor(i, Streifen.Color(255, 0, 0)); //Diode Nr. i leuchtet rot 
      }
      Streifen.show();
      delay(100);
      
      for (i = 0; i < 8; i++) {  
        Streifen.setPixelColor(i, Streifen.Color(0, 0, 255)); //Diode Nr. i leuchtet blau
      }
      Streifen.show();
      delay(100);

    break;
   }
				
			

Der Code ist so einfach, dass es nichts zu erklären gibt. Falls jemand Zweifel hat, was hier vor sich geht, empfehle ich, zum Anfang des Arduino-Kurses (Stufe II) zurückzugehen. Von nun an sollte das Gerät bei Aktivierung des Alarms und wenn der PIR-Sensor eine Bewegung erkennt, einen Alarm auslösen:

Zustand 3: Eingabe des Pins

Es ist an der Zeit, das Programm um eine seiner wichtigsten Funktionen zu erweitern, nämlich die Alarmdeaktivierung. Wir werden dieses Problem in zwei Schritten lösen. Zunächst werden wir uns nur mit der Eingabe des Pincodes beschäftigen.

Später werden wir einen Zähler hinzufügen, der überprüft,
ob die Zeit für die Eingabe des Codes abgelaufen ist.

				
					int pinAlarmPosition = 1;
char pinZahl1 = '1';
char pinZahl2 = '2';
char pinZahl3 = '3';
char pinZahl4 = '4';
				
			

Wie ich bereits erwähnt habe, verwenden wir in diesem Stadium des Kurses noch keine Tabellen, daher habe ich den Code in 4 separate Variablen geschrieben. Wenn wir in einem der nächsten Artikel mit Tabellen arbeiten, können wir hierher zurückkehren und den Code eleganter schreiben.

Im Moment möchte ich jedoch möglichst einfach zeigen
wie dieser Codeprüfungsmechanismus funktioniert.

Sobald wir einen Code deklariert haben (in diesem Fall 1234), können wir im Zustand 3 eine Überprüfung vornehmen. Der Mechanismus funktioniert wie folgt – die Code-Kommentare sollten einiges aufklären:

				
					    case 3:
      //Entschärfung
      Taste = Tastatur.getKey();
      if (Taste) {
        //Ist die nächste angegebene Zahl richtig?
        if (pinAlarmPosition
 == 1 && Taste == pinZahl
1) { //Wenn wir die PIN-Position 1 prüfen
          pinAlarmPosition
++; //Zahl richtig, kann bei der nächsten überprüft werden
        } else if (pinAlarmPosition
 == 2 && Taste == pinZahl
2) { //Wenn wir die PIN-Position 2 prüfen
          pinAlarmPosition
++; //Zahl richtig, kann bei der nächsten überprüft werden         
        } else if (pinAlarmPosition
 == 3 && Taste == pinZahl
3) { //Wenn wir die PIN-Position 3 prüfen
          pinAlarmPosition
++; //Zahl richtig, kann bei der nächsten überprüft werden        
        } else if (pinAlarmPosition
 == 4 && Taste == pinZahl
4) { //Wenn wir die PIN-Position 4 prüfen
            ZustandAlarm = 1; //Alle 4 Zahlen des Codes sind richtig
            pinAlarmPosition
 = 1; //Zurücksetzen der eingegebenen Pin-Informationen    
        } else {
           ZustandAlarm = 4; //Fehler im PIN-Code - Alarm auslösen
           pinAlarmPosition
 = 1; //PIN-Eingabeinformationen zurücksetzen 
        }
      }
    break;
				
			

Zusammengefasst merkt sich dieser Mechanismus in der Variable pinAlarmPosition, wie viele richtige Zeichen des Codes bereits eingegeben wurden. Auf diese Weise wissen wir, mit welcher Variable wir das eingegebene Zeichen aktuell vergleichen müssen.

Wenn das vierte eingegebene Zeichen korrekt ist, bedeutet dies, dass der gesamte Code korrekt war – dann können wir zum Zustand 1 (Standby) zurückkehren. Wenn wir bei der Eingabe des Codes irgendwo einen Fehler machen, gehen wir zu Zustand 4 über und lösen den Alarm aus.

An diesem Punkt sollte der gesamte Code wie folgt aussehen:

				
					#define SUMMER 11
#define REEDSCHALTER 10
#define PIR 1

#include <Keypad.h> //Bibliothek der Tastatur
#include <Adafruit_NeoPixel.h> //Bibliothek des LED-Streifens

const byte ROWS = 4; // wie viele Zeilen
const byte COLS = 4; //wie viele Spalten

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

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

Keypad Tastatur = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS ); // Initialisierung der Tastatur
Adafruit_NeoPixel Streifen = Adafruit_NeoPixel(8, A0, NEO_GRB + NEO_KHZ800); //Konfiguration des LED-Streifens

volatile int ZustandAlarm  = 1;
int pinAlarmPosition = 1;
char pinZahl1 = '1';
char pinZahl2 = '2';
char pinZahl3 = '3';
char pinZahl4 = '4';

void setup() {
  pinMode(SUMMER, OUTPUT);
  pinMode(REEDSCHALTER, INPUT_PULLUP);
  pinMode(PIR, INPUT_PULLUP);

  Streifen.begin(); //Initialisierung des Streifens
  Streifen.show(); 
}

void loop() {
  char Taste = 0; //Variable zur Aufbewahrung von Tastaturzeichen
  int i = 0; //Hilfsvariable für Schleife

  switch(ZustandAlarm ) { //Ausführung einer zustandsspezifischen Aktion
    case 1:
      //Bereitschaft
      Streifen.setPixelColor(0, Streifen.Color(0, 15, 0)); //Diode nr 1 leuchtet grün
      Streifen.show();
      
      Taste = Tastatur.getKey();
      if (Taste == 'A') { //Alarm scharfschalten?
        for (i = 1; i < 8; i++) {
          Streifen.setPixelColor(i, Streifen.Color(0, 0, 15)); //Diode Nr i leuchtet blau
          Streifen.show();
          delay(710);
        } // Ausführung der Schleife dauert ca. 5 Sekunden
        
         for (i = 1; i < 8; i++) {
          Streifen.setPixelColor(i, Streifen.Color(15, 0, 0)); //Diode Nr i leuchtet rot
          Streifen.show();
          delay(710);
        } // Ausführung der Schleife dauert ca. 5 Sekunden      
          
        DiodeAusschalten();
        ZustandAlarm  = 2;
      }
        
    break;
    
    case 2:
      //Überwachung
      Streifen.setPixelColor(7, Streifen.Color(15, 0, 0)); //Diode Nr 8 leuchtet rot
      lišta.show();
      delay(50);
      Streifen.setPixelColor(7, Streifen.Color(0, 0, 0)); //Diode Nr 8 ausgeschaltet
      Streifen.show();
      delay(50);

      if (digitalRead(PIR) == HIGH) {
        ZustandAlarm  = 4; //Alarm wird sofort ausgelöst
      } else if (digitalRead(REEDSCHALTER) == HIGH) {
        ZustandAlarm  = 3; //Möglichkeit der Entschärfung
      }
      
    break;
    
    case 3:
      //Entschärfung
      Taste = Tastatur.getKey();
      if (Taste) {
        //Ist die nächste eingegebene Zahl richtig?
        if (pinAlarmPosition == 1 && Taste == pinZahl1) { //Wenn wir die PIN-Position 1 prüfen
          pinAlarmPosition++; //Zahl richtig, kann bei der nächsten überprüft werden
        } else if (pinAlarmPosition == 2 && Taste == pinZahl2) { //Wenn wir die PIN-Position 2 prüfen
          pinAlarmPosition++; //Zahl richtig, kann bei der nächsten überprüft werden        
        } else if (pinAlarmPosition == 3 && Taste == pinZahl3) { //Wenn wir die PIN-Position 3 prüfen
          pinAlarmPosition++; //Zahl richtig, kann bei der nächsten überprüft werden       
        } else if (pinAlarmPosition == 4 && Taste == pinZahl4) { //Wenn wir die PIN-Position 4 prüfen
            ZustandAlarm  = 1; //Alle 4 Zahlen des Codes sind richtig     
            pinAlarmPosition = 1; //Zurücksetzen der Informationen zur PIN-Eingabe  
        } else {
           ZustandAlarm  = 4; //Fehler im PIN-Code - Alarm auslösen
           pinAlarmPosition = 1; //Zurücksetzen der Informationen zur PIN-Eingabe  
        }
      }
    break;
    
    case 4:
      //Alarmsignalisierung
      for (i = 0; i < 8; i++) {  
        Streifen.setPixelColor(i, Streifen.Color(255, 0, 0)); //Diode Nr i leuchtet rot 
      }
      Streifen.show();
      delay(100);
      
      for (i = 0; i < 8; i++) {  
        Streifen.setPixelColor(i, Streifen.Color(0, 0, 255)); //Diode Nr i leuchtet blau
      }
      Streifen.show();
      delay(100);

    break;
   }
  
}

void DiodeAusschalten() {
  int i = 0;
  for (i = 0; i < 8; i++){
    Streifen.setPixelColor(i, Streifen.Color(0, 0, 0)); //Diode Nr 1 ausgeschaltet    
  }

  Streifen.show();
}
				
			

Die Funktionsweise dieses Codes in der Praxis:

Bei der zweiten Situation ist der Code korrekt:

Countdown bis zur Pin Eingabe

Nun muss noch ein Stück Code geschrieben werden, der den Alarm auslöst, wenn das Eintippen des Codes zu lange dauert. Natürlich kann man hier nicht z.B. delay(10000) schreiben, denn eine solche Anweisung würde das ganze Programm einfrieren und wir könnten den Code nicht überprüfen.

Aber … wir könnten eine kleine Verzögerung eingeben, z.B. 50 ms. Dieser Wert würde den Code nicht stören, wir könnten den Pin die ganze Zeit über eingeben. Wir müssen also nur eine Bedingung hinzufügen, die prüft, ob wir uns mehr als 100 Mal im Zustand 3 (Entschärfen) befunden haben.

Jedes betreten des Funktionsblocks dauert 50 ms, also ergeben 100 Eingaben 5 Sekunden.
Wird der Code in dieser Zeit nicht eingegeben, muss der Alarm aktiviert werden.

Dies in ein Programm zu übertragen, ist sehr einfach. Füge einfach eine globale Variable hinzu:

				
					int wievielZeitVergangen = 0;
				
			

Damit zählen wir, wie oft wir im Block zur Überprüfung des Pin-Codes gewesen sind. Dann fügen wir innerhalb des Funktionsblocks (ausgeführt während Zustand 3) hinzu:

				
					      delay(100);
      wieVielZeitVergangen++;

      if (wieVielZeitVergangen >= 50) {
        ZustandAlarm = 4;
      }
				
			

Jede 100 ms Wartezeit erhöht den Wert der Variable wievielZeitVergangen. Wenn wir 50 oder mehr verzeichnen, geht der nächste Zyklus der Hauptschleife zum Zustand 4 über und löst den Alarm aus. Es lohnt sich dennoch, dafür zu sorgen, dass die Variable wievielZeitVergangen von Null an zählt, wenn der Alarm uns die Möglichkeit gibt, den Pin einzugeben. Am besten fügt man diesen Code innerhalb von Zustand 2 ein, kurz bevor man zu Zustand 3 übergeht.

				
					      if (digitalRead(PIR) == HIGH) {
        ZustandAlarm = 4; //Alarm wird sofort ausgelöst
      } else if (digitalRead(REEDSCHALTER) == HIGH) {
        wievielZeitVergangen= 0; //Zurücksetzen der Variable
        ZustandAlarm = 3; //Möglichkeit der Entschärfung
      }
				
			

Die endgültige Version des Programms sieht wie folgt aus:

				
					#define SUMMER 11
#define REEDSCHALTER 10
#define PIR 1

#include <Keypad.h> //Bibliothek der Tastatur
#include <Adafruit_NeoPixel.h> //Bibliothek des LED-Streifens

const byte ROWS = 4; // wie viele Zeilen
const byte COLS = 4; //wie viele Spalten

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

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

Keypad Tastatur = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS ); //Initialisierung der Tastatur
Adafruit_NeoPixel Streifen = Adafruit_NeoPixel(8, A0, NEO_GRB + NEO_KHZ800); //Konfiguration des LED-Streifens

volatile int ZustandAlarm = 1;
int pinAlarmPosition = 1;
char pinZahl1 = '1';
char pinZahl2 = '2';
char pinZahl3 = '3';
char pinZahl4 = '4';

int wievielZeitVergangen = 0;

void setup() {
  pinMode(SUMMER, OUTPUT);
  pinMode(KONTAKTRON, INPUT_PULLUP);
  pinMode(PIR, INPUT_PULLUP);

  Streifen.begin(); //Initialisierung des Streifens
  Streifen.show(); 
}

void loop() {
  char Taste  = 0; //Variable zur Aufbewahrung von Tastaturzeichen
  int i = 0; //Hilfsvariable für Schleife

  switch(ZustandAlarm) { //Ausführung einer zustandsspezifischen Aktion
    case 1:
      //Bereitschaft
      Streifen.setPixelColor(0, Streifen.Color(0, 15, 0)); //Diode Nr 1 leuchtet grün
      Streifen.show();
      
      Taste  = Tastatur.getKey();
      if (Taste  == 'A') { //Alarm scharfschalten?
        for (i = 1; i < 8; i++) {
          Streifen.setPixelColor(i, Streifen.Color(0, 0, 15)); //Diode Nr i leuchtet blau
          Streifen.show();
          delay(710);
        } // Ausführung der Schleife dauert ca. 5 Sekunden
        
         for (i = 1; i < 8; i++) {
          Streifen.setPixelColor(i, Streifen.Color(15, 0, 0)); //Diode Nr i leuchtet rot
          Streifen.show();
          delay(710);
        } // Ausführung der Schleife dauert ca. 5 Sekunden     
          
        DiodenAusschalten();
        ZustandAlarm = 2;
      }
        
    break;
    
    case 2:
      //Überwachung
      Streifen.setPixelColor(7, Streifen.Color(15, 0, 0)); //Diode Nr 8 leuchtet rot
      Streifen.show();
      delay(50);
      Streifen.setPixelColor(7, Streifen.Color(0, 0, 0)); //Diode Nr 8 ausgeschaltet
      Streifen.show();
      delay(50);

      if (digitalRead(PIR) == HIGH) {
        ZustandAlarm = 4; //Alarm wird sofort ausgelöst
      } else if (digitalRead(KONTAKTRON) == HIGH) {
        wievielZeitVergangen= 0; //Zurücksetzen der Variable
        ZustandAlarm = 3; //Möglichkeit der Entschärfung
      }
      
    break;
    
    case 3:
      //Entschärfen
      Taste  = Tastatura.getKey();
      if (Taste ) {
        //Ist die nächste eingegebene Zahl richtig?
        if (pinAlarmPosition == 1 && Taste  == pinZahl1) { //Wenn wir die PIN-Position 1 prüfen
          pinAlarmPosition++; //Zahl richtig, kann bei der nächsten überprüft werden
        } else if (pinAlarmPosition == 2 && Taste  == pinZahl2) { //Wenn wir die PIN-Position 2 prüfen
          pinAlarmPosition++; //Zahl richtig, kann bei der nächsten überprüft werden        
        } else if (pinAlarmPosition == 3 && Taste  == pinZahl3) { //Wenn wir die PIN-Position 3 prüfen
          pinAlarmPosition++; //Zahl richtig, kann bei der nächsten überprüft werden       
        } else if (pinAlarmPosition == 4 && Taste  == pinZahl4) { //Wenn wir die PIN-Position 4 prüfen
            ZustandAlarm = 1; //Alle 4 Zahlen des Codes sind richtig
            pinAlarmPosition = 1; //Zurücksetzen der Informationen zur PIN-Eingabe      
        } else {
           ZustandAlarm = 4; //Fehler im PIN-Code - Alarm auslösen
           pinAlarmPosition = 1; //Zurücksetzen der Informationen zur PIN-Eingabe 
        }
      }

      delay(100);
      wievielZeitVergangen++;

      if (wievielZeitVergangen >= 50) {
        ZustandAlarm = 4;
      }
    break;
    
    case 4:
      //Alarmsignalisierung
      for (i = 0; i < 8; i++) {  
        Streifen.setPixelColor(i, Streifen.Color(255, 0, 0)); //Diode Nr i leuchtet rot 
      }
      Streifen.show();
      delay(100);
      
      for (i = 0; i < 8; i++) {  
        Streifen.setPixelColor(i, Streifen.Color(0, 0, 255)); //Diode Nr i leuchtet blau 
      }
      Streifen.show();
      delay(100);

    break;
   }
  
}

void DiodenAusschalten() {
  int i = 0;
  for (i = 0; i < 8; i++){
    Streifen.setPixelColor(i, Streifen.Color(0, 0, 0)); //Diode Nr 1 ausgeschaltet   
  }

  Streifen.show();
}
				
			

Die Funktionsweise dieses Teils des Programms in der Praxis:

Hinzufügen von Toneffekten

Damit der Alarm seine Aufgabe erfüllen kann, muss man einen Summer anschließen – es gibt eine Version mit oder ohne Generator. Ich habe mich für die Version ohne Generator entschieden und ihn an Pin 11 angeschlossen, wie zuvor deklariert:

Anschluss des Summers an die Alarmzentrale.

Dann habe ich zwei Zeilen zum 4. Zustand der Schaltung hinzugefügt, der für die Alarmsignalisierung zuständig ist. Weitere Informationen über die Funktion tone() findet man in Teil 3 des Kurses.

				
					    case 4:
      //Alarmsignalisierung
      for (i = 0; i < 8; i++) {  
        Streifen.setPixelColor(i, Streifen.Color(255, 0, 0)); //Diode Nr i leuchtet rot  
      }
      Streifen.show();
      tone(SUMMER, 4300);
      delay(100);
      
      for (i = 0; i < 8; i++) {  
        Streifen.setPixelColor(i, Streifen.Color(0, 0, 255)); //Diode Nr i leuchtet blau 
      }
      Streifen.show();
      tone(SUMMER, 3500);
      delay(100);

    break;
				
			

Die endgültige Funktionsweise des gesamten Alarms ist in dem unten gezeigten Video zu sehen:

Zusätzliche Aufgaben - für Freiwillige

Das Programm ist recht umfangreich geworden, daher werde ich es jetzt nicht weiter „strecken“. Wer möchte, kann das Programm jedoch um neue Funktionen erweitern. Ich gebe eine Liste von Dingen an, die man hinzufügen kann:

  • ein akustisches Signal beim Scharfschalten des Alarms,
  • ein akustisches Signal, wenn ein Pin eingegeben wird (Tastendruck – „Piep“),
  • eine separate Taste, die mit der Unterbrechung verbunden ist und den Alarm zurücksetzt,
  • umfangreichere Toneffekte.

Zusammenfassung

Im Laufe dieses Artikels habe ich zum ersten Mal versucht, mit euch zusammen einen langen Code zu erstellen. Ich hoffe, ihr findet diese Form des Artikels auch nützlich! Früher habe ich alle mit Unterbrechungen „erschreckt“, und hier habe ich sie nicht verwendet. Warum eigentlich? Weil man das nicht immer muss!

Mit diesem Programm ist es gelungen, Verzögerungen abzuschaffen. Seine Funktionsweise besteht praktisch darin, die Hauptschleife kontinuierlich zu durchlaufen und nur die (im Moment) notwendigen Operationen auszuführen. Daher muss man nicht befürchten, dass der Arduino ein Signal vom Sensor verpasst.

Es lohnt sich, die verschiedenen Lösungen zu kennen und sie für bestimmte Projekte auszuwählen. Wenn unser Gerät viele zeitaufwändigere Operationen durchführen soll, würden Unterbrechungen hier zweifellos ihre Anwendung finden.

Bestellen Sie ein Set mit Elementen und beginnen Sie mit dem Lernen in der Praxis! Hier gehts zum Shop >>

Nach oben scrollen