Arduino: Multi-IO und EEPROM - [Teil 3]

Teil 3 

In den ersten beiden Teilen (Teil 1, Teil 2) haben wir mehrere Taster an einen analogen Pin angeschlossen und mit dem internen EEPROM die Eingangswerte der Taster gespeichert. Wir öffnen in diesem Beitrag erneut die Trickkiste und schließen mehrere LEDs an, ohne die gleiche Anzahl Pins belegen zu müssen. Los geht's.

Benötigte Hardware 

Anzahl Bauteil Anmerkung
Verbindungskabel
1 Breadboard
1 Taster
1 Arduino Nano oder Uno
1 Widerstand 20 KOhm
Mehrere Widerstände 110 Ohm (abhängig von der Farbe der LEDs)
mehrere Widerstände 10 KOhm
mehrere Leuchtdioden (in diesem Fall sechs)
1 PC mit Arduino IDE

Vorbereitung

Wir benötigen die Schaltung mit mehreren Tastern aus Teil 1:


...und fügen einen weiteren Taster hinzu:


Hinweis:
Ich habe hier den vierten Taster mit 20 KOhm verbunden, da sonst die Wertebereiche zwischen dem dritten und vierten Taster zu nah beieinander liegen. Als Alternative kann man auch den Wert der Konstanten RANGE verringern. 

Wir verbinden nun den Jumper an Pin drei und starten den Arduino neu. Der Lernmodus für die Taster wird gestartet. Wir betätigen alle Taster nacheinander und entfernen den Jumper wieder. Das Programm aus Teil 2 sollte genauso funktionieren wie vorher. Der vierte Taster ist noch ohne Funktion.

Aus wenig viel machen

Der Plan ist, mehrere Leuchtdioden an den Arduino anzuschließen, ohne für jede LED einen digitalen Ausgangspin zu verwenden. Eine Möglichkeit wäre das Multiplexen (auch Muxen genannt). Das Gegenstück dazu ist das Demultiplexen (bzw. Demuxen). Dabei werden mit einer kombinatorischen Logik die Dateneingänge nacheinander verschaltet, damit sie seriell am Ausgang anliegen. Oder im Fall des Demuxers werden die Daten am Eingang auf mehrere Ausgänge verteilt. Erreicht wird das mit Selektionssignalen.

Im
AZ-Delivery Shop ist eine LED-Matrix erhältlich, die mit einem MAX7219 Multiplex IC gesteuert wird. Es sind neben den beiden Leitungen für die Spannungsversorgung nur drei Datenleitungen notwendig, um 8x8 (also insgesamt 64) LEDs anzusteuern. Außerdem sind diese Module kaskadierbar. Es gibt die Möglichkeit, unter diesem Link in einem Online-Simulator solch eine Schaltung nachzuvollziehen. Unter Schaltungen -> Sonstige Schaltungen -> LED Array kann man sehen, wie der Strom fließt und welche LED eingeschaltet wird.

Eine weitere Möglichkeit, die Ausgänge zu erweitern, wäre ein Schieberegister wie der SN74HC595N IC. Damit lassen sich ebenfalls mit nur drei Datenleitungen acht LEDs ansteuern. Der Chip funktioniert so, dass die Zustände von acht Bit nacheinander in ein Register geschoben und dann auf den Ausgang geschaltet werden. Jede einzelne LED wird dann durch ein Bit angesteuert. Für 8x8 LEDs bräuchte man dann aber schon acht von diesen Schieberegistern, die man übrigens auch hintereinander schalten kann.

Man kann LEDs auch in einer Matrix ohne IC verschalten. Dabei werden die Leuchtdioden in Zeilen und Spalten angeordnet und verbunden. Es sind dann so viele Leitungen notwendig, wie Zeilen und Spalten vorhanden sind. Eine 3x3 LED-Matrix benötigt dann sechs Leitungen für neun LEDs. Das ist ebenfalls eine Ersparnis. Details dazu kann man hier nachlesen.

Kommen wir nun aber zu der Methode, die ich in diesem Projekt verwenden möchte. Wir brauchen dafür keinen zusätzlichen IC. 1995 hat Charlie Allen das Charlieplexing in Verbindung mit Mikrocontrollern entwickelt. Dabei wird das Tri-State-Verfahren verwendet. Das heißt, dass es nicht nur zwei, sondern drei Zustände gibt.

Die Tri-State-Multiplex-Methode ist allerdings schon seit Ende der 1970er bekannt und patentiert. Wie können wir nun mit einem Arduino-Pin drei Zustände abbilden? Die digitalen Pins können die Zustände HIGH oder LOW einnehmen. Allerdings haben wir auch noch die Möglichkeit, jeden Pin als Ein- oder als Ausgang zu definieren. Somit ergibt sich mit INPUT der dritte Zustand.

Wenn man z.B. zwei LEDs in gegensätzlicher Stromflussrichtung und parallel an zwei Pins anschließt, fließt entweder Strom durch eine LED oder die andere. Dabei wird ein Pin als Eingang definiert und damit hochohmig.

Was die Anzahl der Pins angeht, hat man mit zwei Pins und zwei LEDs noch nicht viel erreicht. Nutzt man aber drei Pins, kann man von jedem zu jedem Pin zwei LEDs in jeweils gegensätzlicher Richtung parallel anschließen. Das sind dann schon sechs LEDs mit nur drei Pins. Erweitert man die Schaltung um einen weiteren digitalen Pin, hat man schon die Möglichkeit, 12 LEDs anzusteuern. Es gibt eine kleine Formel, mit der man berechnen kann, wie viele Ausgänge mit
n Eingängen angesteuert werden können:

n*(n-1)

Nehmen wir an, wir wollen alle 13 digitalen Pins eines Arduino verwenden. Dafür müssen wir nur die 13 für n einsetzen:

13*(13-1)=156

Es ist zu beachten, dass beim Charlieplexing nicht zwei oder mehr LEDs gleichzeitig leuchten können. Lässt man allerdings jede LED einzeln schnell hintereinander aufleuchten, scheint es für uns so, als ob alle LEDs gleichzeitig eingeschaltet sind.

Jede LED benötigt einen Vorwiderstand. Man schließt an jedem Pin einen Widerstand vor die LEDs. Da nun der Strom für jede LED durch zwei Widerstände fließt, teilt man deren Widerstandswert durch zwei. Nehmen wir an, wir nutzen einen 220 Ohm Widerstand für eine LED. Dann brauchen wir nun ca. 100 Ohm an jedem Pin. Auf den Webseiten von elektronik-labor.de und pcbheaven.com ist das Verfahren ausführlich und sehr gut beschrieben. Wir wollen das Rad nicht neu erfinden, wir wollen es nur drehen lassen.

Ich beziehe mich auf die Infos von pcbheaven.com, da dort eine Zustandstabelle aufgeführt ist, die man so für die Programmierung verwenden kann. Die Richtung der LEDs unterscheidet sich hier im Gegensatz zu anderen Quellen. Das ist aber nicht weiter von Belang. Man müsste nur die Zustandstabelle anpassen.

Der Aufbau

Im folgenden Schaltplan ist zu sehen, wie die Widerstände und LEDs an den Arduino angeschlossen werden:


Durch die Kombination HIGH, LOW und INPUT an den drei Pins D5, D6 und D7 kann jede der sechs LEDs einzeln zum Leuchten gebracht werden. Im folgenden Bild schalten wir D7 auf HIGH, D6 auf LOW und D5 auf INPUT (sowie auf LOW). Der Strom fließt durch LED 2, die damit leuchtet:


Im folgenden Bild ergänzen wir unsere Schaltung mit den vier Tastern durch die sechs LEDs:

Die anschließende Tabelle zeigt die Zustände der drei Pins für alle LEDs:

PIN D5 PIN D6 PIN D7
LED 1 INPUT HIGH LOW
LED 2 INPUT LOW HIGH
LED 3 HIGH LOW INPUT
LED 4 LOW HIGH INPUT
LED 5 HIGH INPUT LOW
LED 6 LOW INPUT HIGH

Die Software 

Wie bereits weiter oben erwähnt, können wir die vorangegangene Zustandstabelle sehr gut in unseren Code integrieren. Das machen wir in Form eines mehrdimensionalen Arrays. Wir können dort aber nicht LOW, HIGH oder INPUT eintragen. LOW wird als 0, HIGH als 1 und INPUT als -1 dargestellt:

// MAXLED Zeilen fuer LEDs, MAXPORTS Spalten fuer Pins 
int charlieMatrix[MAXLEDS][MAXPINS] =  {{-1,  1,  0}, 
                                        {-1,  0,  1}, 
                                        { 1,  0, -1}, 
                                        { 0,  1, -1}, 
                                        { 1, -1,  0}, 
                                        { 0, -1,  1}};

Die Anzahl der Zeilen ist gleichzeitig die Anzahl der LEDs. Die Anzahl der digitalen Pins entspricht der Anzahl der Spalten. Dafür deklarieren wir uns zuvor Konstanten, damit die Werte einfacher anzupassen sind, sollten wir die Schaltung verändern.

#define MAXLEDS 6 
#define MAXPINS 3

Wir können nun mit Schleifen über das Array iterieren, dabei die Zustände auslesen und auf die jeweiligen Pins geben. ich habe eine Variable hinzugefügt, die später als Zeitabstand zwischen dem Aufleuchten der einzelnen LEDs dienen wird. Zur Erinnerung: ist diese Pause sehr kurz, sind alle LEDs scheinbar gleichzeitig eingeschaltet:

const int pause = 1;

Die nächsten Variablen, die wir benötigen, sind folgende:

 

int ledcounter = 0; 
unsigned long ledMillis = 0; 
unsigned long ledMillis_old = 0; 
 
unsigned int ledSwitch = 0; 
byte ledSwitch_HIGH = B00000000; 
byte ledSwitch_LOW = B00111111;

Ein Zähler läuft durch unsere LEDs. Dann messen wir wieder die Zeit, um sie mit unserer Pause zu vergleichen. Das sorgt für nichtblockierende Wechsel zwischen den LEDs. Der ledswitch ist eine Variable, die bestimmt, welche von den LEDs ein- und welche ausgeschaltet werden. Dafür lesen wir die einzelnen Bits aus, die wie Schalter fungieren. Um die Schaltung später einfacher zu erweitern, habe ich hier gleich die Möglichkeit implementiert, mehr als 8 Bit schalten zu können.

Die Binärschreibweise funktioniert in der Arduino IDE allerdings nur mit einem Byte. Ein Integer ist zwei Byte (16 Bit) groß. Also teilen wir es zuerst in zwei einzelne Bytes auf und setzen sie später zu einem Wert zusammen. Natürlich kann man die Variable auch direkt in dezimal oder hexadezimal angeben. Dann ist es nicht nötig, aus zwei Bytes eine 16-Bit-Variable zusammenzusetzen. Man kann in der Bitschreibweise jedoch leichter erkennen, welche LEDs aktiviert sein sollen und man muss nicht lange umrechnen.

Als Nächstes habe ich eine Funktion implementiert, mit der wir in einem Rutsch alle LEDs ausschalten. Sie bekommt den Namen resetLEDs() und wird immer ausgeführt. Sie wird auch ausgeführt, bevor eine LED eingeschaltet wird. Man muss dann nicht erst prüfen, wie die Zustände aktuell geschaltet sind. Man schaltet einfach alles auf LOW und setzt dann die Zustände neu:

void resetLEDs(){ 
  DDRD = DDRD & B00011111;   
  PORTD = PORTD & B00011111; 
}

Ich habe mir hier die Pin- und Portmanipulation zunutze gemacht. Damit ist es möglich, die Pins eines Ports gleichzeitig anzusprechen. Die Pins 0 bis 7 gehören zum Port D. DDRD bestimmt, welcher Pin von Port D als Ein- (Wert auf 0) und welcher als Ausgang (Wert auf 1) initialisiert wird. PORTD setzt die Pins dann entweder auf HIGH oder LOW und damit auf AN oder AUS. Durch den logischen &-Operator maskieren wir nur unsere verwendeten drei Pins (von rechts gezählt, beginnend bei 0) D5, D6, und D7.

Hinweis: Initialisiert man einen Pin als EINGANG, kann man mit PORTD den internen Pull-Up-Widerstand aktivieren. Mit PIND kann man den Zustand abfragen, wenn die Pins als Eingang initialisiert wurden. 

Die nächste Funktion, die wir brauchen, implementiert das Charlieplexing:

void LED(byte row) { 
  resetLEDs(); 
  if (row >= 0 && row < MAXLEDS) { 
    for (int i = 0; i < MAXLEDS; i++) { 
      if (row == i) { 
        for (int j = 0; j < MAXPORTS; j++) { 
          if (charlieMatrix[i][j] == -1) { 
            bitClear(DDRD, j + 5); 
            bitClear(PORTD, j + 5); 
          } 
          else if (charlieMatrix[i][j] == 0) { 
            bitSet(DDRD, j + 5); 
            bitClear(PORTD, j + 5);         
          } 
          else if (charlieMatrix[i][j] == 1) { 
            bitSet(DDRD, j + 5); 
            bitSet(PORTD, j + 5);         
          } 
        }         
        return; 
      }  
    } 
  } 
}

Wie bereits erwähnt, führen wir zuerst resetLEDs() aus. Dann iterieren wir über die Zeilen des Arrays mit den Zuständen der LED-Pins, die gleichzeitig die Nummer der LED darstellt. Den Wert für die Zeile bekommen wir übergeben. Wir suchen also in dem Array nach der angegebenen Zeilennummer. Haben wir die Zeile erreicht, iterieren wir über alle Spalten und damit über alle Pins.

Die drei if-Anweisungen (bzw. else if) führen dann die Aktionen passend zu den drei verschiedenen Zuständen aus. Auch hier nutzen wir die Portmanipulation. Einzelne Bits kann man in der Arduino IDE mit
bitClear() und bitSet() auf 0 bzw. 1 setzen.

Wir können nun alle LEDs einzeln ein- und ausschalten. Wir müssen der Funktion nur noch sagen, welche LED wir gern leuchten sehen würden und wie lang die Pause zwischen den Wechseln sein soll. Das erledigt die nächste Funktion:

void lightOn(unsigned int leds) { 
  ledMillis = millis(); 
  if (ledMillis - ledMillis_old >= pause) { 
    ledMillis_old = ledMillis; 
    if (bitRead(leds, ledcounter)) { 
      LED(ledcounter); 
    } 
    else { 
      resetLEDs(); 
    } 
     
    ledcounter++; 
    if (ledcounter >= MAXLEDS) { 
      ledcounter = 0; 
    } 
  } 
}

Dieser Funktion übergeben wir die Variable ledSwitch. Diese enthält wie gesagt die Schalter für jede einzelne LED. In der Funktion messen wir die Zeit und vergleichen sie mit unserer vorgegebenen Pause-Variablen. Ist dieser Wert überschritten, springen wir mit dem ledcounter  in unserer LED-Schalter-Variablen an die entsprechende Stelle und prüfen, ob das Bit auf 1 gesetzt ist.

Dann wird die dazugehörige LED eingeschaltet, indem wir unsere zuvor geschriebene Funktion
LED() aufrufen und ihr die Nummer der LED übergeben. Der Zähler wird dann noch begrenzt auf die Anzahl unserer verwendeten LEDs. Alles läuft, ohne zu blockieren.

Nun müssen wir noch im setup() folgende Zeilen einfügen:

resetLEDs();
  ledSwitch = ledSwitch_HIGH;
  ledSwitch = ledSwitch << 8;
  ledSwitch |= ledSwitch_LOW;  


Damit schalten wir zuerst alle LEDs aus und setzen noch unsere Variable
ledSwitch aus zwei einzelnen Bytes zusammen. Wie bereits erwähnt hätte in unserem Fall eine Variable vom Typ Byte ausgereicht. Erweitern wir aber das Projekt und wollen trotzdem die Bit-Schreibweise nutzen, sind wir damit auf mehr als acht LEDs vorbereitet.

In der loop() fügen wir nun noch in die switch-case-Anweisung den case 3 ein und ergänzen bzw. ändern einige Zeilen in den anderen Cases:

switch (state) { 
  case 0: { 
    // alles ausschalten 
    ledState = LOW; 
    digitalWrite(ledPin, ledState); 
    resetLEDs(); 
    state = -1; 
  } break; 
  case 1: { 
    // Onboard-LED einschalten 
    // Charlieplexing-LEDs ausschalten 
    ledState = HIGH; 
    digitalWrite(ledPin, ledState); 
    resetLEDs(); 
    state = -1; 
  } break; 
  case 2: { 
    // Charlieplexing-LEDs ausschalten 
    // Onboard-LED blinken lassen 
    resetLEDs(); 
    currentMillis = millis(); 
    if (currentMillis - previousMillis >= interval) { 
      previousMillis = currentMillis; 
      ledState = !ledState; 
      digitalWrite(ledPin, ledState); 
    } 
  } break; 
  case 3: { 
    //Onboard-LED ausschalten 
    //Charlieplexing-LEDs einschalten 
    ledState = LOW; 
    digitalWrite(ledPin, ledState); 
    lightOn(ledSwitch); 
  } break; 
  default: break; 
}

In case 0 wird alles ausgeschaltet. Die Variable state setzen wir auf -1, damit die CPU nach der ersten Ausführung nur den leeren default case ausführt. In case 1 schalten wir die Onboard-LED ein und die Charlieplexing-LEDs aus.

In case 2 werden die Charlieplexing-LEDs ausgeschaltet und die Onboard-LED blinkt. Hier darf die
state-Variable nicht auf -1 gesetzt werden, da die Zustandsmaschine weiter diesen case ausführen muss. In case 3 ebenso. Dort wird die Onboard-LED ausgeschaltet und die Charlieplexing-LEDs werden eingeschaltet.

Aktivieren wir mit dem vierten Taster den case 0, scheint es so, als wären die Charlieplexing-LEDs alle gleichzeitig angeschaltet. Ändern wir die Variable pause auf den Wert 200, sehen wir, wie sie nacheinander eingeschaltet werden. Ändern wir die ersten (von rechts beginnend) sechs Bit der Variable ledSwitch, können wir bestimmen, welche LEDs aktiviert und welche deaktiviert sein sollen.

Ergebnis

Wir können nun mehrere Taster an einen einzigen analogen Pin anschließen. Ändern wir die Anzahl der Taster, brauchen wir uns keine Werte vom Analog-Digital-Wandler notieren, sondern einfach mit dem Jumper an Pin D3 den Lernmodus aktivieren (Neustart vorausgesetzt). Wir nutzen dafür dynamische Speicherreservierung und das interne EEPROM.

Die Onboard LED blinkt ohne das Programm zu blockieren. Auch die verschiedenen Modi können ohne Warten mithilfe der Taster umgeschaltet werden. Wir haben sechs LEDs an nur drei Pins angeschlossen, indem wir das Charlieplexing-Verfahren verwenden. Die LEDs können zwar nicht gleichzeitig eingeschaltet werden. Durch sehr kurze zeitliche Abstände simulieren wir jedoch das gleichzeitige Leuchten.

Nachtrag 

Um die Charlieplexing-LEDs wie ein pendelndes Lauflicht zu programmieren, müssen wir einige Dinge anpassen. Es müssen alle LEDs mithilfe der Variable ledSwitch aktiviert sein.

Die Variable
pause sollte auf einen Wert von 150 eingestellt werden (abhängig davon, wie schnell das Licht wandern soll). Dann fügen wir eine neue Variable dir (für direction, engl. Richtung) hinzu und setzen sie auf den Wert 1.

Für die andere Richtung ändern wir das im späteren Programmverlauf in -1 und wieder zurück. In der Funktion
lightOn() setzen wir nun den ledcounter nicht mehr zurück auf 0, wenn er den Wert der Konstanten MAXLEDS erreicht hat. Wir prüfen, ob die Richtung größer oder kleiner als 0 ist (enspricht dann 1 oder -1). Dann prüfen wir, ob MAXLEDS oder 0 erreicht wurden und ändern jedes Mal die Richtung, wenn das der Fall ist. Dadurch zählt der ledcounter nicht mehr nur aufwärts, sondern er pendelt zwischen 0 und MAXLEDS - 1. 

void lightOn(unsigned int leds) { 
  ledMillis = millis(); 
  if (ledMillis - ledMillis_old >= pause) { 
    ledMillis_old = ledMillis; 
    if (bitRead(leds, ledcounter)) { 
      LED(ledcounter); 
    } 
    else { 
      resetLEDs(); 
    } 
 
    if (dir > 0) { 
      ledcounter++; 
      if (ledcounter >= MAXLEDS - 1) { 
        dir = -1; 
      } 
    } 
    else if (dir < 0) { 
      ledcounter--; 
      if (ledcounter < 1) { 
        dir = 1; 
      } 
    } 
  } 
}

Möchten wir, dass das Lauflicht immer wieder bei der ersten LED beginnt, wenn wir es einschalten, müssen wir in der switch-case-Anweisung in den cases 0 bis 2 die Variablen ledcounter auf 0 und dir auf 1 zurücksetzen. 

Laden wir das Programm auf den Arduino und betätigen den vierten Taster, sehen wir ein LED-Lauflicht. Dabei ist die Anzahl der LEDs leicht zu erweitern, da nur die Konstanten geändert werden müssen. 

Hier noch jeweils der komplette Quellcode des Programms ohne pendelndes Lauflicht ... :

/*  Arduino: Multi-IO und EEPROM
 *  von Andreas Wolter
 *  fuer AZ-Delivery.de
 *  
 *  Funktion:
 *    - Einen analogen Eingang für mehrere Taster verwenden.
 *    - Die Taster aktivieren verschiedene Funktionen.
 *    - Eingangswerte der Taster im EEPROM speichern
 *    - Jumper fuer Lernmodus der Tasterwerte
 *    - Charlieplexing für mehrere LEDs an wenigen Pins
 *  
 *  Verwendete Hardware:
 *    - Arduino Nano R3.0 oder Uno R3.0
 *    - mehrere Widerstände 10 KOhm
 *    - Widerstand 20 KOhm
 *    - mehrere Widerstände 110 Ohm (abhängig von der Farbe der LEDs)
 *    - mehrere Leuchtdioden (in diesem Fall sechs)
 *  
 *  
 *  Verwendete Bibliotheken:
 *    - EEPROM.h
 *  
 *  Quelle Schaltplan fuer Charlieplexing:
 *    http://www.pcbheaven.com/wikipages/Charlieplexing/
 *  
 *  Quelle Beispiel fuer EEPROM: 
 ***
    eeprom_put example.

    This shows how to use the EEPROM.put() method.
    Also, this sketch will pre-set the EEPROM data for the
    example sketch eeprom_get.

    Note, unlike the single byte version EEPROM.write(),
    the put method will use update semantics. As in a byte
    will only be written to the EEPROM if the data is actually
    different.

    Written by Christopher Andrews 2015
    Released under MIT licence.
 ***
 *  
 *  Pinout: siehe Dokumentation
 */
#include <EEPROM.h>

#define BUTTON_PIN A1
#define RANGE 50
#define JUMPER 3
#define MAXLEDS 6
#define MAXPINS 3

// Charlieplexing
int pause = 1;

/*
|           | PIN D5 | PIN D6 | PIN D7 |
| --------- | ------ | ------ | ------ |
|   LED 1   | INPUT  |  HIGH  |  LOW   |
|   LED 2   | INPUT  |  LOW   |  HIGH  |
|   LED 3   |  HIGH  |  LOW   | INPUT  |
|   LED 4   |  LOW   |  HIGH  | INPUT  |
|   LED 5   |  HIGH  | INPUT  |  LOW   |
|   LED 6   |  LOW   | INPUT  |  HIGH  |
*/

// MAXLED Zeilen fuer LEDs, MAXPINS Spalten fuer Pins
int charlieMatrix[MAXLEDS][MAXPINS] =  {{-1,  1,  0},
                                        {-1,  0,  1},
                                        { 1,  0, -1},
                                        { 0,  1, -1},
                                        { 1, -1,  0},
                                        { 0, -1,  1}};

// 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;
int ledcounter = 0;
unsigned long ledMillis = 0;
unsigned long ledMillis_old = 0;

unsigned int ledSwitch = 0;
byte ledSwitch_HIGH = B00000000;
byte ledSwitch_LOW = B00111111;

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

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

// Button
int buttonCounter = 0;
int eepromCounter = 1;
int *pButtonValues = NULL;
int *pTemp = NULL;

void resetLEDs(){
  DDRD = DDRD & B00011111;  
  PORTD = PORTD & B00011111;
}

void LED(byte row) {
  resetLEDs();
  if (row >= 0 && row < MAXLEDS) {
    for (int i = 0; i < MAXLEDS; i++) {
      if (row == i) {
        for (int j = 0; j < MAXPINS; j++) {
          if (charlieMatrix[i][j] == -1) {
            bitClear(DDRD, j + 5);
            bitClear(PORTD, j + 5);
          }
          else if (charlieMatrix[i][j] == 0) {
            bitSet(DDRD, j + 5);
            bitClear(PORTD, j + 5);        
          }
          else if (charlieMatrix[i][j] == 1) {
            bitSet(DDRD, j + 5);
            bitSet(PORTD, j + 5);        
          }
        }        
        return;
      } 
    }
  }
}

void lightOn(unsigned int leds) {
  ledMillis = millis();
  if (ledMillis - ledMillis_old >= pause) {
    ledMillis_old = ledMillis;
    if (bitRead(leds, ledcounter)) {
      LED(ledcounter);
    }
    else {
      resetLEDs();
    }
    
    ledcounter++;
    if (ledcounter >= MAXLEDS) {
      ledcounter = 0;
    }
  }
}

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

void buttonInit() {
  bool wasPressed = false;
  int input = 0;
  bool isThere = false;
  int i = 0;
  
  Serial.print("Alle Buttons nacheinander druecken und anschliessend den Jumper an Pin D");
  Serial.print(JUMPER);
  Serial.println(" entfernen");
  
  while(digitalRead(JUMPER) == LOW) {
    input = analogRead(BUTTON_PIN);
    if ((input < (1023 - RANGE)) && !wasPressed) {
      wasPressed = true;
      if (pTemp != NULL) {
        for (i = 0; i <= buttonCounter - 1; i++) {
          if (input < pButtonValues[i] + RANGE && input > pButtonValues[i] - RANGE) {
            isThere = true;
            break;
          }
        }
      }
      
      if (!isThere) {
        pTemp = (int*) realloc (pButtonValues, buttonCounter + 1 * sizeof(int));
        pButtonValues = pTemp;
        pButtonValues[buttonCounter] = input;
        buttonCounter++;
        digitalWrite(LED_BUILTIN, HIGH);
        delay(1000);
        digitalWrite(LED_BUILTIN, LOW);
      }
      else {
        isThere = false;
      }
    }
    
    else if (((analogRead(BUTTON_PIN)) > (1023 - RANGE)) && wasPressed) {
      wasPressed = false;
    }
    delay(100);
  }

  // Werte ins EEPROM uebertragen
  EEPROM.write(0, buttonCounter);
  for (i = 0; i <= buttonCounter - 1; i++) {
    Serial.print(i);
    Serial.print(" ");
    Serial.print(pButtonValues[i]);
    Serial.print(" ");
    Serial.println(eepromCounter);

    EEPROM.put(eepromCounter, pButtonValues[i]);
    eepromCounter += sizeof(int);
  }  
}

void setup() {
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  pinMode(JUMPER, INPUT_PULLUP);
  if (digitalRead(JUMPER) == LOW) {
    buttonInit();
  }
  else {
    buttonCounter = EEPROM.read(0);
    Serial.print("Buttons: ");
    Serial.println(buttonCounter);
    for (int i = 0; i <= buttonCounter - 1; i++) {
      pTemp = (int*) realloc (pButtonValues, i + 1 * sizeof(int));
      pButtonValues = pTemp;
      EEPROM.get(eepromCounter, pButtonValues[i]);
      
      Serial.print(i);
      Serial.print(" ");
      Serial.print(pButtonValues[i]);
      Serial.print(" ");
      Serial.println(eepromCounter);

      eepromCounter += sizeof(int);
    }    
  }
  resetLEDs();
  ledSwitch = ledSwitch_HIGH;
  ledSwitch = ledSwitch << 8;
  ledSwitch |= ledSwitch_LOW;  
}

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: {
      // alles ausschalten
      ledState = LOW;
      digitalWrite(ledPin, ledState);
      resetLEDs();
      state = -1;
    } break;
    case 1: {
      // Onboard-LED einschalten
      // Charlieplexing-LEDs ausschalten
      ledState = HIGH;
      digitalWrite(ledPin, ledState);
      resetLEDs();
      state = -1;
    } break;
    case 2: {
      // Charlieplexing-LEDs ausschalten
      // Onboard-LED blinken lassen
      resetLEDs();
      currentMillis = millis();
      if (currentMillis - previousMillis >= interval) {
        previousMillis = currentMillis;
        ledState = !ledState;
        digitalWrite(ledPin, ledState);
      }
    } break;
    case 3: {
      //Onboard-LED ausschalten
      //Charlieplexing-LEDs einschalten
      ledState = LOW;
      digitalWrite(ledPin, ledState);
      lightOn(ledSwitch);
    } break;
    default: break;
  }
}

 

... und mit pendelndem Lauflicht:

/*  Arduino: Multi-IO und EEPROM
 *  von Andreas Wolter
 *  fuer AZ-Delivery.de
 *  
 *  Funktion:
 *    - Einen analogen Eingang für mehrere Taster verwenden.
 *    - Die Taster aktivieren verschiedene Funktionen.
 *    - Eingangswerte der Taster im EEPROM speichern
 *    - Jumper fuer Lernmodus der Tasterwerte
 *    - Charlieplexing für mehrere LEDs an wenigen Pins
 *    - LEDs als Lauflicht
 *  
 *  Verwendete Hardware:
 *    - Arduino Nano R3.0 oder Uno R3.0
 *    - mehrere Widerstände 10 KOhm
 *    - Widerstand 20 KOhm
 *    - mehrere Widerstände 110 Ohm (abhängig von der Farbe der LEDs)
 *    - mehrere Leuchtdioden (in diesem Fall sechs)
 *  
 *  
 *  Verwendete Bibliotheken:
 *    - EEPROM.h
 *  
 *  Quelle Schaltplan fuer Charlieplexing:
 *    http://www.pcbheaven.com/wikipages/Charlieplexing/
 *  
 *  Quelle Beispiel fuer EEPROM:
 ***
    eeprom_put example.

    This shows how to use the EEPROM.put() method.
    Also, this sketch will pre-set the EEPROM data for the
    example sketch eeprom_get.

    Note, unlike the single byte version EEPROM.write(),
    the put method will use update semantics. As in a byte
    will only be written to the EEPROM if the data is actually
    different.

    Written by Christopher Andrews 2015
    Released under MIT licence.
 ***
 *  
 *  Pinout: siehe Dokumentation
 */
#include <EEPROM.h>

#define BUTTON_PIN A1
#define RANGE 50
#define JUMPER 3
#define MAXLEDS 6
#define MAXPINS 3

// Charlieplexing
int pause = 150;
int dir = 1;

/*
|           | PIN D5 | PIN D6 | PIN D7 |
| --------- | ------ | ------ | ------ |
|   LED 1   | INPUT  |  HIGH  |  LOW   |
|   LED 2   | INPUT  |  LOW   |  HIGH  |
|   LED 3   |  HIGH  |  LOW   | INPUT  |
|   LED 4   |  LOW   |  HIGH  | INPUT  |
|   LED 5   |  HIGH  | INPUT  |  LOW   |
|   LED 6   |  LOW   | INPUT  |  HIGH  |
*/

// MAXLED Zeilen fuer LEDs, MAXPORTS Spalten fuer Pins
int charlieMatrix[MAXLEDS][MAXPINS] =  {{-1,  1,  0},
                                        {-1,  0,  1},
                                        { 1,  0, -1},
                                        { 0,  1, -1},
                                        { 1, -1,  0},
                                        { 0, -1,  1}};

// 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;
int ledcounter = 0;
unsigned long ledMillis = 0;
unsigned long ledMillis_old = 0;

unsigned int ledSwitch = 0;
byte ledSwitch_HIGH = B00000000;
byte ledSwitch_LOW = B00111111;

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

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

// Button
int buttonCounter = 0;
int eepromCounter = 1;
int *pButtonValues = NULL;
int *pTemp = NULL;

void resetLEDs(){
  DDRD = DDRD & B00011111;  
  PORTD = PORTD & B00011111;
}

void LED(byte row) {
  resetLEDs();
  if (row >= 0 && row < MAXLEDS) {
    for (int i = 0; i < MAXLEDS; i++) {
      if (row == i) {
        for (int j = 0; j < MAXPINS; j++) {
          if (charlieMatrix[i][j] == -1) {
            bitClear(DDRD, j + 5);
            bitClear(PORTD, j + 5);
          }
          else if (charlieMatrix[i][j] == 0) {
            bitSet(DDRD, j + 5);
            bitClear(PORTD, j + 5);        
          }
          else if (charlieMatrix[i][j] == 1) {
            bitSet(DDRD, j + 5);
            bitSet(PORTD, j + 5);        
          }
        }     
        return;
      } 
    }
  }
}

void lightOn(unsigned int leds) {
  ledMillis = millis();
  if (ledMillis - ledMillis_old >= pause) {
    ledMillis_old = ledMillis;
    if (bitRead(leds, ledcounter)) {
      LED(ledcounter);
    }
    else {
      resetLEDs();
    }

    if (dir > 0) {
      ledcounter++;
      if (ledcounter >= MAXLEDS - 1) {
        dir = -1;
      }
    }
    else if (dir < 0) {
      ledcounter--;
      if (ledcounter < 1) {
        dir = 1;
      }
    }
  }
}

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

void buttonInit() {
  bool wasPressed = false;
  int input = 0;
  bool isThere = false;
  int i = 0;
  
  Serial.print("Alle Buttons nacheinander druecken und anschliessend den Jumper an Pin D");
  Serial.print(JUMPER);
  Serial.println(" entfernen");
  
  while(digitalRead(JUMPER) == LOW) {
    input = analogRead(BUTTON_PIN);
    if ((input < (1023 - RANGE)) && !wasPressed) {
      wasPressed = true;
      if (pTemp != NULL) {
        for (i = 0; i <= buttonCounter - 1; i++) {
          if (input < pButtonValues[i] + RANGE && input > pButtonValues[i] - RANGE) {
            isThere = true;
            break;
          }
        }
      }
      
      if (!isThere) {
        pTemp = (int*) realloc (pButtonValues, buttonCounter + 1 * sizeof(int));
        pButtonValues = pTemp;
        pButtonValues[buttonCounter] = input;
        buttonCounter++;
        digitalWrite(LED_BUILTIN, HIGH);
        delay(1000);
        digitalWrite(LED_BUILTIN, LOW);
      }
      else {
        isThere = false;
      }
    }
    
    else if (((analogRead(BUTTON_PIN)) > (1023 - RANGE)) && wasPressed) {
      wasPressed = false;
    }
    delay(100);
  }

  // Werte ins EEPROM uebertragen
  EEPROM.write(0, buttonCounter);
  for (i = 0; i <= buttonCounter - 1; i++) {
    Serial.print(i);
    Serial.print(" ");
    Serial.print(pButtonValues[i]);
    Serial.print(" ");
    Serial.println(eepromCounter);

    EEPROM.put(eepromCounter, pButtonValues[i]);
    eepromCounter += sizeof(int);
  }  
}

void setup() {
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  pinMode(JUMPER, INPUT_PULLUP);
  if (digitalRead(JUMPER) == LOW) {
    buttonInit();
  }
  else {
    buttonCounter = EEPROM.read(0);
    Serial.print("Buttons: ");
    Serial.println(buttonCounter);
    for (int i = 0; i <= buttonCounter - 1; i++) {
      pTemp = (int*) realloc (pButtonValues, i + 1 * sizeof(int));
      pButtonValues = pTemp;
      EEPROM.get(eepromCounter, pButtonValues[i]);
      
      Serial.print(i);
      Serial.print(" ");
      Serial.print(pButtonValues[i]);
      Serial.print(" ");
      Serial.println(eepromCounter);

      eepromCounter += sizeof(int);
    }    
  }
  resetLEDs();
  ledSwitch = ledSwitch_HIGH;
  ledSwitch = ledSwitch << 8;
  ledSwitch |= ledSwitch_LOW;  
}

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.print("state: ");
    Serial.println(state);
    alte_zeit = millis();
  }
  input_alt = input;

  switch (state) {
    case 0: {
      // alles ausschalten
      ledState = LOW;
      digitalWrite(ledPin, ledState);
      resetLEDs();
      ledcounter = 0;
      dir = 1;
      state = -1;
    } break;
    case 1: {
      // Onboard-LED einschalten
      // Charlieplexing-LEDs ausschalten
      ledState = HIGH;
      digitalWrite(ledPin, ledState);
      resetLEDs();
      state = -1;
      ledcounter = 0;
      dir = 1;
    } break;
    case 2: {
      // Charlieplexing-LEDs ausschalten
      // Onboard-LED blinken lassen
      resetLEDs();
      currentMillis = millis();
      if (currentMillis - previousMillis >= interval) {
        previousMillis = currentMillis;
        ledState = !ledState;
        digitalWrite(ledPin, ledState);
      }
      ledcounter = 0;
      dir = 1;
    } break;
    case 3: {
      //Onboard-LED ausschalten
      //Charlieplexing-LEDs einschalten
      ledState = LOW;
      digitalWrite(ledPin, ledState);
      lightOn(ledSwitch);
    } break;
    default: break;
  }
}

Andreas Wolter

für AZ-Delivery Blog

3 Kommentare

Lucien FERRAND

Lucien FERRAND

Hallo,
Das ist eine großartige Möglichkeit, Stecknadeln zu retten.
Dieses Moor ist großartig und ich nutze den Vorteil, dass AZ.Delivery es geschaffen hat.
Ich habe diese 3 Teile vom Online-Übersetzer www.deepl.com ins Französische übersetzt.
(es kann einige Übersetzungsfehler geben; aber wenn es helfen kann…
Es ist schade, dass wir die Skizze zu Github nicht finden können.
Mit freundlichen Grüßen.
L.FERRAND
Übersetzt mit www.DeepL.com/Translator (kostenlose Version)

Herr Drews

Herr Drews

Hallo,
sehr guter Blog-Beitrag. Schön wäre ein Link zu den vorlaufenden teilen.
Bei mir ist per Mail nur Teil 1 angekommen.
L. Drews

Veit B.

Veit B.

Tolle Sache. Als Rentner hab ich viel Zeit und finde die Dinge die ich hier vorfinde Klasse. Auch der Preis für die Bauteile ist erträglich. Für jung und alt. Weiter so.

Einen Kommentar hinterlassen

Alle Kommentare werden vor der Veröffentlichung moderiert