Arduino: Multi-IO und EEPROM - [Teil 1] - AZ-Delivery

Teil 1 - Eingänge

In diesem Projekt möchte ich zwei Themen aufgreifen, auf die wahrscheinlich jeder Arduinonutzer irgendwann stoßen wird. Zum einen kann es passieren, dass die Anzahl der Ein- und Ausgangspins nicht ausreicht. Schließt man ein LCDisplay für die Ausgabe, mehrere Sensoren und ein SD-Karten-Breakoutboard für Datenspeicherung per SPI an einen Arduino Nano oder Uno an, bleiben nicht mehr viele Pins für Taster und LEDs übrig. Wer als MCU einen ATTiny verwendet, ist noch stärker eingegrenzt. Ich werde dafür die Trickkiste öffnen und zeigen, welche Möglichkeiten zur Verfügung stehen, um dieses Problem zu lösen.

Das zweite Thema ist die permanente Datenspeicherung. Dank eines integrierten Speichers können im kleinen Rahmen Daten erhalten bleiben, sollte die Stromzufuhr vom Arduino getrennt werden. Die Umsetzung dafür ist denkbar einfach. Los geht's.

Benötigte Hardware 

Anzahl Bauteil Anmerkung
1 Verbindungskabel
1 Breadboard
1 Taster
1 Arduino Nano oder Uno
Mehrere Widerstände 10 KOhm
PC mit Arduino IDE

Vorbereitung

Normalerweise würden wir die digitalen Pins der Arduino Boards verwenden und an jeden einzelnen einen Taster anschließen. Die Zahl der Taster ist dann begrenzt durch die Anzahl der vorhandenen Pins. Es handelt sich dabei um Signalleitungen. Das Signal ist dann aber binär, also 0 oder 1.

Neben den digitalen Pins sind allerdings auch analoge Pins vorhanden. Dort sind Analog-Digital-Wandler angeschlossen. Sie ermöglichen (umgewandelte) Digitalwerte im Bereich 0 bis 1023. Das ist schon etwas mehr als nur 0 und 1. Oft wird dort ein veränderbarer Widerstand angeschlossen, z.B. ein Potentiometer.

Damit wird ein Spannungsteiler aufgebaut, mit dem man händisch die gewünschten Digitalwerte einstellen kann. Nutzen wir statt eines veränderbaren mehrere feste Widerstände, kann man den Wertebereich in verschiedene Stufen einteilen. Wir bauen dafür einen veränderbaren Spannungsteiler. Alle Taster sind dabei nur an einem analogen Pin angeschlossen.

Widerstände am analogen Eingang

Schließt man einen veränderbaren Widerstand (Potentiometer oder auch Poti) an einen Arduino an, geschieht das über die drei Pins GND, Schleifer und 5V. Der Schleifer liefert den Widerstandswert am analogen Eingang des Arduino. Nutzen wir einen 10 KOhm Potentiometer und drehen es, erhalten wir am Arduino Werte zwischen 0 und 1023. Das wird durch den Analog-Digital-Wandler (auch ADC, Analog-Digital-Converter) umgesetzt. 0 entspricht dabei dem höchsten Widerstand von 10 KOhm, 1023 bedeutet so gut wie kein Widerstand. Wir können diese Werte manuell verändern, indem wir am Potentiometer drehen. 

Ersetzen wir nun den veränderbaren durch einen festen Widerstand von ebenfalls 10 KOhm, bleibt der Wert am analogen Eingang natürlich dauerhaft bei 0. Setzen wir nun noch einen Taster dazwischen, können wir zwischen 0 und 1023 umschalten, ohne die Werte dazwischen zu berücksichtigen. Zur besseren Veranschaulichung habe ich in den folgenden zwei Bildern diese Schaltkreise dargestellt. Es soll zeigen, dass wir hier das gleiche Prinzip verwenden.

Prüfen der Eingangswerte

Um nun zu testen, ob es funktioniert, schließen wir alles wie im linken Bild an. Sie können als Gegentest natürlich auch den rechten Aufbau nutzen.

Anschließend öffnen wir in der Arduino IDE einen neuen Sketch und schreiben dort folgendes hinein:

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

void loop() {
  Serial.println(analogRead(A1));
}

Laden wir das Programm auf den Arduino und öffnen den seriellen Monitor (Strg + Umschalt + M), sehen wir den Wert 1023 als durchgehende Ausgabe. Betätigen wir nun den Taster, ändert sich der Wert auf 0. 

Hinweis: An dieser Stelle wird man bereits feststellen, dass der AD-Wandler des Arduinos nicht sehr genau ist. Die Werte schwanken etwas. Das müssen wir später berücksichtigen. Das bedeutet auch, dass man nicht einfach 1024 Taster anschließen kann, da wir immer etwas Puffer brauchen.

Wir haben nun wieder zwei Zustände. Allerdings 0 und 1023. Wir werden das Programm so erweitern, dass wir damit die Onboard-LED des Arduinos ein- und ausschalten.

Wir werden gleich zu Beginn das einfache Entprellen ohne Unterbrechung hinzufügen, in dem wir eine gewisse Zeitspanne prüfen, während derer der Taster ignoriert wird. Außerdem soll auch das Gedrückthalten des Tasters nicht dazu führen, dass die LED immer wieder an- und ausgeschaltet wird.

#define BUTTON_PIN A1 
#define RANGE 50 
 
// LED 
const int ledPin = LED_BUILTIN; 
bool ledState = LOW; 
 
// Entprell Timer 
unsigned long prell_delay = 200; 
unsigned long alte_zeit = 0; 
 
// Input 
int input = 0; 
int input_alt = 0; 
 
void setup() { 
  pinMode(ledPin, OUTPUT); 
} 
 
void loop() { 
  input = analogRead(BUTTON_PIN); 
 
  // Entprellen 
  // wenn Taster jetzt AN und vorher AUS und aktuelle Zeit - alte Zeit groesser als vorgegebenes Delay 
  if (input < (1023 - RANGE) && input_alt > (0 + RANGE) && millis() - alte_zeit > prell_delay) { 
    ledState = !ledState; 
    digitalWrite(ledPin, ledState); 
    alte_zeit = millis(); 
  } 
  input_alt = input; 
}

Ich möchte den Quellcode kurz beschreiben:

Zuerst legen wir den Pin des Tasters A1 als Konstante fest. Genauso ist der Wertebereich, in dem der AD-Wandler schwanken kann, ein konstanter Wert. In dem Fall 50. Somit beachten wir im nicht gedrückten Zustand des Tasters die Werte 973 bis 1023. Im gedrückten Zustande 0 bis 50. Als Pin für die LED nutzen wir die Konstante für die integrierte LED.

Für das Umschalten nutzen wir eine Variable vom Typ bool, da sie damit an- bzw. ausgeschaltet wird. Damit wir erkennen können, ob der Taster gedrückt bleibt, brauchen wir zwei Variablen für den Eingang. Nach jedem Schleifendurchlauf wird der vorherige Wert mit dem aktuellen Wert verglichen. Im setup() setzen wir den LED-Pin als Ausgang. In der Hauptschleife lesen wir den analogen Eingang an A1, unserem Tasterpin. Wir testen dann drei Bedingungen. 

Die erste ist, ob der Eingangswert kleiner ist, als 973 (1023 minus RANGE). Also muss der Wert zwischen 0 und 973 liegen, damit ein Tastendruck erkannt wurde. Die zweite Bedingung ist, ob sich der aktuelle Einganswert vom vorherigen unterscheidet. Der vorherige Wert muss größer als 50 (0 plus RANGE) sein. Liegt der Wert also zwischen 50 und 1023, wurde der Taster losgelassen.

Die dritte Bedingung wird für das Entprellen verwendet. Hier wird die Zeit seit dem ersten Tastendruck gemessen. Danach wird für die Dauer, die wir als Entprellzeit eingetragen haben, der Taster ignoriert. Erst danach ist diese Bedingung wieder erfüllt.

Durch das kaufmännische UND werden die drei Bedingungen miteinander verknüpft, sodass alle drei erfüllt sein müssen. Ist das der Fall, wird der Wert der booleschen Variable umgekehrt und auf den LED-Pin ausgegeben.

Als Nächstes muss noch die aktuelle Zeit gespeichert werden, damit sie für den nächsten Durchlauf beim Entprellen verwendet werden kann. Außerdem wird dann noch der aktuelle Input des Tasters gespeichert, damit auch er beim nächsten Durchlauf verglichen werden kann.

Mehr Taster

Damit haben wir nun einen Taster am analogen statt am digitalen Eingang angeschlossen. Uns geht es aber nun darum, die Zahl der Taster zu erhöhen. Wir teilen den Wertebereich dann nicht mehr in 0 und 1023 (plus sicheren Bereich natürlich), sondern abhängig von den gewählten Widerständen nutzen wir weitere Werte.

Erweitern wir unsere Schaltung um zwei weitere Taster:

 

Laden wir nun das kurze Programm vom Anfang dieses Beitrages auf den Arduino und beobachten die Ausgabe des seriellen Monitors.

Wird der erste Taster betätigt, sind 5V und GND über R1 miteinander verbunden. Dieser Widerstand zieht das Potenzial auf einen definierten Wert. Am analogen Eingang erhalten wir ungefähr 0 (die möglichen Schwankungen ignorieren wir an dieser Stelle). Betätigen wir den Taster 2, sind GND und 5V über R1 und R2 verbunden. Das ist dann der Spannungsteiler mit jeweils 10 KOhm.

In meinem Fall wird mir der Wert 509 angezeigt. Betätigt man den dritten Taster, sind GND und 5V über alle drei Widerstände verbunden. Der zusätzliche Widerstand beträgt nun 20 KOhm. Mir wird der Wert 680 angezeigt.

Hinweis: Es ist auf diese Weise nicht möglich, zwei oder mehr Taster gleichzeitig abzufragen.

Um diese drei Taster nun sinnvoll zu nutzen, müssen wir in unserem Programm auf die Werte 0, 509 und 680 prüfen. Anhand dieser Werte erkennen wir dann, ob und welcher Taster betätigt wurde. Dabei müssen wir wieder die Schwankungen abfangen.

Wir ändern unser Programm nun so, dass den Werten vom analogen Eingang Nummern zugeordnet werden. Sie entsprechen den Nummern unserer Taster. Anhand der Nummern wird die Onboard-LED entweder ausgeschaltet, eingeschaltet oder sie blinkt. Natürlich alles ohne zu blockieren, damit das Programm sofort auf jeden Tastendruck reagiert.

Wir schreiben zuerst die Werte für die Taster in ein Array:

int buttonValues[3] = {0, 509, 680};
Jetzt benötigen wir eine Funktion, die den analogen Eingang ausliest und für unsere drei verschiedenen Werte die Nummern 0, 1 oder 2 zurückgibt:
int buttonRead(){
  int i = 0;
  int input = analogRead(BUTTON_PIN);
  for (i = 0; i <= BUTTONS - 1; i++) {    
    if (input < buttonValues[i] + RANGE && input > buttonValues[i] - RANGE) {
      return i;
    }
  }
  return -1; 
}

Wir nennen die Funktion buttonRead(). Sie bekommt keine Werte übergeben. Sie gibt unsere Ganzzahlen in Form der Tasternummer zurück. Es wird zuerst der Wert am analogen Eingang eingelesen. Dann iterieren wir über das Array, das wir zuvor eingerichtet haben.

Als Grenze für den Zähler richten wir eine Konstante
BUTTONS ein, in die wir die Anzahl unserer Taster eintragen. Wir vergleichen in jedem Schritt, ob der aktuelle Wert am analogen Eingang dem Wert im Array an der Stelle i entspricht. Ist das der Fall, geben wir den Wert von i zurück und verlassen damit die Funktion.

Wir achten darauf, dass die Werte am analogen Eingang schwanken können. Wir nutzen dafür die Konstante
RANGE. Sollte es keine Übereinstimmung geben, geben wir den Wert -1 zurück. Diesen habe ich gewählt, um klar unterscheiden zu können.

Im setup() aktivieren wir nun zum Testen noch einmal den seriellen Monitor:

Serial.begin(115200);

In der loop() rufen wir nun nicht mehr analogRead(BUTTON_PIN) auf, sondern unsere selbst definierte Funktion:

input = buttonRead();

Die gespeicherten Werte in der Variablen input liegen nun nicht mehr zwischen 0 und 1023, sondern sollten unseren Tasternummern 0, 1 und 2 entsprechen. Folglich müssen wir die if-Abfrage für den Taster und die dazugehörige Aktion ändern:

 if (input > -1 && input_alt != input && millis() - alte_zeit > prell_delay) {   
    state = input;
    Serial.println(state);
    alte_zeit = millis();
  }

Wir prüfen wieder auf drei Bedingungen. Der Wert in der Variablen input muss größer sein, als 0. Dann haben wir einen Taster betätigt. Er muss sich vom alten Zustand input_alt unterscheiden. Damit erkennen wir, ob wir den Taster schon einmal losgelassen haben. Erst dann dürfen wir ihn wieder berücksichtigen. Außerdem muss das geforderte Zeitintervall eingehalten werden, das für das Entprellen des Tasters benötigt wird. Wir deklarieren vorher eine Variable state, die wir später für unsere verschiedenen Zustände der LED nutzen werden:

int state = 0;

In diese Variable schreiben wir den Wert des Eingangs, wenn die drei Bedingungen erfüllt sind. Warum machen wir das? Nun, die Variable input hat im normalen Zustand den Wert -1, den wir später nicht gebrauchen können. Somit wird state nur die Werte 0, 1 oder 2 enthalten (oder höher, wenn noch mehr Taster hinzukommen).

Das Programm sollte nun ungefähr so aussehen:

#define BUTTON_PIN A1
#define RANGE 50
#define BUTTONS 3

// State Machine
int state = 0;

// LED
const int ledPin = LED_BUILTIN;
bool ledState = LOW;

// Entprell Timer
unsigned long prell_delay = 200;
unsigned long alte_zeit = 0;

// Input
int input = 0;
int input_alt = 0;

// Button
int buttonValues[BUTTONS] = {0, 509, 680};

int buttonRead(){
  int i = 0;
  int input = analogRead(BUTTON_PIN);
  for (i = 0; i <= BUTTONS - 1; i++) {    
    if (input < buttonValues[i] + RANGE && input > buttonValues[i] - RANGE) {
      return i;
    }
  }
  return -1; 
}

void setup() {
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  input = buttonRead();

  // Entprellen
  // wenn Taster jetzt AN und vorher AUS und aktuelle Zeit - alte Zeit groesser, als vorgegebenes Delay
  if (input > -1 && input_alt != input && millis() - alte_zeit > prell_delay) {   
    state = input;
    Serial.println(state);
    alte_zeit = millis();
  }
  input_alt = input;
}

Wir geben zu Testzwecken den Inhalt von state auf dem Bildschirm aus. Übertragen wir das Programm auf den Arduino und öffnen den seriellen Monitor, werden uns die Werte für die Nummern der Taster ausgegeben, wenn wir sie betätigen.

Ein, Aus und Blinken

Um nun zwischen verschiedenen Zuständen der LED umzuschalten, schreiben wir uns eine Zustandsmaschine. Dafür nutzen wir die switch-case-Anweisung. Diese prüft die Variable state. Hat sie den Wert 0, soll die LED ausgeschaltet werden. Der Wert 1 schaltet sie ein. Der Wert 2 lässt sie blinken. 

switch (state) {
    case 0: {
      if (ledState != LOW) {
        ledState = LOW;
        digitalWrite(ledPin, ledState);
      }
    } break;
    case 1: {
      if (ledState != HIGH) {
        ledState = HIGH;
        digitalWrite(ledPin, ledState);
      }
    } break;
    case 2: {
      currentMillis = millis();
      if (currentMillis - previousMillis >= interval) {
        previousMillis = currentMillis;
        ledState = !ledState;
        digitalWrite(ledPin, ledState);
      }
    } break;
    default: break;
  }

Die switch-case-Anweisung wird in jedem Durchlauf der Hauptschleife loop() ausgeführt. Der normale Zustand der Variable state ist 0. Also wird in jedem Schleifendurchlauf case: 0 ausgeführt. Dort wird geprüft, ob die LED eingeschaltet (= HIGH) ist. Somit verhindern wir, dass der Arduino ständig die LED ausschaltet, obwohl sie bereits ausgeschaltet ist. Ist sie an und state ist nicht LOW, dann schreiben wir mit digitalWrite() den Wert LOW auf den Ausgangspin der LED. Danach sollte der Arduino "leerlaufen".

Betätigen wir nun den zweiten Taster, ändert sich die Variable state von 0 auf 1. Nun wird in jedem Durchlauf der Hauptschleife case: 1 ausgeführt. Ist ledState nicht HIGH, wird die auf HIGH gesetzt und der Wert auf den Ausgangspin der LED gegeben. Damit leuchtet dann die Onboard-LED. 

Hinweis: Natürlich kann man auch die Variable state anders herum abfragen mit:

if (ledState == HIGH) {}
für case: 0 bzw.:
if (ledState == HIGH) {}

für case: 1. Ich habe das für mich so gewählt.

Mit dem ersten Taster wird die LED aus- und mit dem zweiten Taster eingeschaltet. Fehlt noch das Blinken. Dafür nutzen wir case: 2. Wir deklarieren uns Variablen, die wir für die Zeitmessung nutzen:

unsigned long previousMillis = 0;
const unsigned long interval = 500;
unsigned long currentMillis = 0;

Wir speichern uns die aktuelle Zeit. Dann subtrahieren wir die aktuelle Zeit von der zuvor gespeicherten Zeit und vergleichen sie mit unserem Blinkintervall. Ist die Zeit überschritten, speichern wir die aktuelle Zeit als alte Zeit, ändern den Wert für die LED und geben ihn auf den Ausgangspin. Somit blinkt die LED in diesem Fall im 500 ms-Rhythmus.

Wir haben hier bewusst auf
delay() verzichtet, denn wenn wir wieder die anderen Taster betätigen, wird sofort der Zustand der LED umgeschaltet, ohne zu warten.

Der Quellcode müsste nun folgendermaßen aussehen:

#define BUTTON_PIN A1
#define RANGE 50
#define BUTTONS 3

// State Machine
int state = 0;

// LED
const int ledPin = LED_BUILTIN;
bool ledState = LOW;
unsigned long previousMillis = 0;
const unsigned long interval = 500;
unsigned long currentMillis = 0;

// Entprell Timer
unsigned long prell_delay = 200;
unsigned long alte_zeit = 0;

// Input
int input = 0;
int input_alt = 0;

// Button
int buttonValues[BUTTONS] = {0, 509, 680};

int buttonRead(){
  int i = 0;
  int input = analogRead(BUTTON_PIN);
  for (i = 0; i <= BUTTONS - 1; i++) {    
    if (input < buttonValues[i] + RANGE && input > buttonValues[i] - RANGE) {
      return i;
    }
  }
  return -1; 
}

void setup() {
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  input = buttonRead();

  // Entprellen
  // wenn Taster jetzt AN und vorher AUS und aktuelle Zeit - alte Zeit groesser, als vorgegebenes Delay
  if (input > -1 && input_alt != input && millis() - alte_zeit > prell_delay) {   
    state = input;
    Serial.println(state);
    alte_zeit = millis();
  }
  input_alt = input;

  switch (state) {
    case 0: {
      if (ledState != LOW) {
        ledState = LOW;
        digitalWrite(ledPin, ledState);
      }
    } break;
    case 1: {
      if (ledState != HIGH) {
        ledState = HIGH;
        digitalWrite(ledPin, ledState);
      }
    } break;
    case 2: {
      currentMillis = millis();
      if (currentMillis - previousMillis >= interval) {
        previousMillis = currentMillis;
        ledState = !ledState;
        digitalWrite(ledPin, ledState);
      }
    } break;
    default: break;
  }
}


Wir haben nun mehrere Taster an nur einem Eingang angeschlossen und jeder löst eine andere Aktion aus. Mit den Widerständen kann man nun noch weitere Taster hinzufügen. Probieren Sie es aus.

Vorschau

Im nächsten Teil werden wir das Testen der Eingangswerte automatisieren. Das heißt, dass wir Widerstände und Taster anschließen und deren Eingangswerte dauerhaft speichern. Dafür müssen wir nicht den seriellen Monitor verwenden und auch keine Werte notieren. Wir programmieren uns einen Lernmodus, in dem der Arduino seine Taster kennenlernt. Außerdem wollen wir dann noch mehrere LEDs anschließen, wobei wir auch weniger Pins verwenden, als wir LEDs nutzen werden. Bis dahin.

Andreas Wolter

für AZ-Delivery Blog
 

Für arduinoProjekte für fortgeschrittene

5 comments

jowo

jowo

Hallo, gut Beitrag. Ist der Beitrag auch als pdf zum Download zu bekommen, wie bei Elektrizitätslehre für Hobby-Elektroniker Teil 1 bis 3? Vielen Dank

Rainer Unverhau

Rainer Unverhau

Super erklärt und kompakter und gut kommentierter Co de. Vielen Dank :-)

Wolferl

Wolferl

Schöner, kompakter Code für ein häufiges Problem- und Anwendungsfeld.

Jacky Burget

Jacky Burget

Sehr gute Idee und Tipp zur Verwendung der analog eingange vom Arduino

Evandro Franceschini

Evandro Franceschini

⭐⭐⭐⭐⭐
Grazie!

Leave a comment

All comments are moderated before being published

Recommended blog posts

  1. ESP32 jetzt über den Boardverwalter installieren - AZ-Delivery
  2. Internet-Radio mit dem ESP32 - UPDATE - AZ-Delivery
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1 - AZ-Delivery
  4. ESP32 - das Multitalent - AZ-Delivery