Sa. Jul 27th, 2024

Arduino ist eine offene Mikrocontroller-Plattform die aus einer Programmierumgebung und dem Mikrocontroller-Board. Aufgrund der einfachen Bedienbarkeit ist eine weltweite Community an Arduino-Freunden entstanden, die alle verschiedenste Projekte realisieren und die Ergebnisse mit allen Interessierten teilen.

Die Basis für Projekte ist meist der Arduino Uno für gut 20 Euro. Das Board verfügt über einen ATmega328P-Microcontroller, läuft mit den typischen 5 Volt, hat 14 Input/Output-Anschlüsse (I/O-Pins), 32 Kilobyte Flash-Speicher, USB-Anschluss und läuft auf moderaten 16 Megahertz.

Neben dem Uno gibt es aber noch viele weitere Boards in unterschiedlichen Größen und Leistungsklassen, die sich aber alle über die gleiche Entwicklungsumgebung (Arduino-IDE genannt) programmieren lassen. Als Programmiersprache wir C++ verwendet, die um einige spezifische Funktionen erweitert wurde.

Im folgenden sollen einige grundlegende Mechanismen vorgestellt werden, die das Fundament in verschiedenen Blog-Beiträgen bilden.

Ein- und Ausgabe von Daten

Wie oben erwähnt stellt jedes Arduino Board eine bestimmte Menge von Anschlüssen (PINs) bereit über die Daten eingelesen oder ausgelesen werden können. Dazu müssen die Anschlüsse-Pins über die Funktion pinMode entsprechen konfiguriert werden:

Nach dem die Konfiguration der Pins erfolgt ist, kann man Daten ein- bzw. ausgeben. Dazu stehen ebenfalls Funktionen mit den Namen digitalRead und digitalWrite zur Verfügung. Am sogenannten LED-Blinken-Beispiel lässt sich die Funktion sehr anschaulich darstellen:

void setup() {
  pinMode(13, OUTPUT); // Setzt den Digitalpin 13 als Outputpin
}

void loop() {
  digitalWrite(13, HIGH); // Setzt den Digitalpin 13 auf HIGH = "Ein"
  delay(1000);            // Wartet eine Sekunde
  digitalWrite(13, LOW);  // Setzt den Digitalpin 13 auf LOW = "Aus"
  delay(1000);            // Wartet eine Sekunde
}

Alle Funktionen sind in den Referenz-Seiten sehr gut beschrieben, so dass hier für weitere Details darauf referenziert werden soll.

Pull-up oder Pull-Down Widerstände

Ein Pullup- oder Pulldown-Widerstand wird dazu verwendet, einen Eingang auf einen definierten Wert zu „ziehen“. Normalerweise befindet sich der Eingang im Zustand „schwebend/hochohmig“, welcher sich irgendwo zwischen High und Low befindet.

Bei Arbeiten im Bereich der Mikrocontroller haben sich Werte von 4,7 kOhm für Pullup- bzw. 10 kOhm für Pulldown-Widerstände in den meisten Fällen bewährt.

Pull-Up und Pull-Down-Widerstands Schaltbilder

Es gibt zwei Möglichkeiten einen Schalter oder Taster mit einem logischen Eingangs-Pin zu verbinden. Will man dafür sorgen, dass der Eingangspin logisch LOW erhält, wenn die Taste gedrückt wird, so muss das Pull-Up-Prinzip verwendet werden. Der Pullup-Widerstand liegt zwischen dem Eingang und +Vcc. Beim Öffnen des Tasters zieht der Pullup-Widerstand die Spannung am Eingang hoch bis zum Betriebsspannungswert +Vcc, was logisch HIGH entspricht.

Will man dafür sorgen, dass der Eingang logisch HIGH erhält, wenn die Taste gedrückt wird, so muss das Pull-Down-Prinzip verwendet werden. Der Kontakt liegt zwischen dem Eingang und +Vcc. Der Pulldown-Widerstand liegt zwischen dem Eingang und GND. Beim Öffnen des Kontaktes zieht der Pulldown-Widerstand die Spannung am Eingang hinunter auf GND, was logisch LOW entspricht.

Interne Pull-x Widerstände

Um den zusätzlichen Bauteil- und Verdrahtungsaufwand beispielsweise beim ersten Prototypenaufbau zu vermeiden, sind im Microcontroller des Arduino-Boards bereits interne Pull-Up-Widerstände integriert. Sie lassen sich sehr einfach in der Pindeklaration hinzuschalten.

pinMode(8, INPUT_PULLUP);

Entstellen von Schaltern

Jeder mechanische Schalter schaltet nicht perfekt. Beim Schließen des Kontakts „hüpft“ dieser einige Male hin und her, d.h. er schließt und öffnet, bis er ganz geschlossen ist.

Prell-Verhalten eines Schalters

Dieses Schalter-Prellen kann sich je nach Schaltertyp sehr negativ auf das Verhalten des Programms auswirken. Daher sollte man Schlater entstellen. Dazu gibt es Hardware- und Software-technische Lösungsansätze.

Ein sehr sicheres Mittel zum Entprellen von Schaltern ist die Zeit nach dem schliessen quasi auszublenden in dem man ein RC-Glied einführt. Dieses sorgt dann für eine „Glättung“ der Spannungsspitzen und führt zu einem stabilen Eingangspegel.

Hardware-Tasten Entprellung

Weitere Möglichkeiten bieten Flipflops, was aber wiederum einen größeren Hardware-Aufwand zur Folge hat.

Software-technische Lösungen

Ohne Lib

gggg

const int buttonPin = 2;    
const int ledPin =  13;     
int ledState = LOW;
boolean buttonState = LOW; 

int pressed=0;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT);
}

void loop() {
  if(debounceButton(buttonState) == HIGH && buttonState == LOW){
    pressed++;
    buttonState = HIGH;
  }
  else if(debounceButton(buttonState) == LOW && buttonState == HIGH{
       buttonState = LOW;
  }
  if(pressed == 10){
    digitalWrite(ledPin,HIGH);
  }
}

boolean debounceButton(boolean state){
  boolean stateNow = digitalRead(buttonPin);
  if(state!=stateNow){
    delay(10);
    stateNow = digitalRead(buttonPin);
  }
  return stateNow; 
}

Dazu gibt es eine Bibliothek mit dem Namen „Bounce2“, die entsprechende Funktionen bereit stellt, um das mehrfache unkontrollierte senden von Signalen durch das Prellen zu verhindern.

Zunächst einmal muss für jeden Button eine Instanz von “Bounce” erzeugt werden. Im Konstruktor des Bounce Objektes wird der Pin an welchem der Taster angeschlossen ist und zusätzlich ein Wert für eine Pause (in Millisekunden)  übergeben.

#include <Bounce2.h>
#define BTN 2
int index = 0;

Bounce btnBouncer = Bounce(BTN, 50);

void setup() {
  Serial.begin(9600);
  pinMode(BTN, INPUT);
  digitalWrite(BTN, HIGH);  
}

void loop() {
  btnBouncer.update();

  if(btnBouncer.fell()){
    index = index+1;
    Serial.println(index);
  }
  
}

Port-Manipulation

Die Pins des Mikrocontroller sind in sogenannten Ports organisiert. Ein Port ist kein Pin sondern bezeichnet eine „Pin-Gruppe“. Die 14 Pins des Arduino Uno sind in 3 Ports gegliedert: Port B, C und D. Über die Port Manipulation schaltet man also die Pins eines Ports beziehungsweise einen ganzen Port auf einmal.

Port und Port-Register des Arduino UNO

Die Ports werden von jeweils 3 Registern gesteuert, die mit DDRx, PORTx und PINx bezeichnet werden. Wobei das x für die Ports Namen B,C und D stehen. Dh. für jeden Port gibt es 3 Steuer-Register Port, also zum Beispiel existiert für Port B ein DDRB-, ein PORTB- und ein PINB-Register. Analog gilt das auch für Port C und Port D.

Über das DDRx Register legt man fest, ob die einzelnen Pins eines Port OUTPUTs oder INPUTs sein sollen. Beispielsweise würde die Zeile DDRC = B00000111 die analog Eingänge A0 bis A2 als Input definieren und A3 bis A7 als Output definieren. 0 steht für Input und 1 für Output.

Über das PORTx Register legt man fest, ob die Pins auf LOW oder HIGH geschaltet werden sollen. Dabei steht das x wieder für den jeweiligen Port. Wenn wir also Pin 8, 10 und 12 auf HIGH und Pin 9, 11 und 13 auf LOW setzen wollen, schreiben wir PORTB = B101010.

Das PINx Register ist dazu da, um den aktuellen Status der Input Pins, also LOW oder HIGH, festzustellen. Dazu wählt man für x wieder den aktuellen Port und vergleicht dann das Register mit einer Abfolge. Beispielsweise überprüft if(PINB == 0b000000), ob alle Pins, des Ports B, auf LOW geschaltet sind. 

Ein etwas erweitertes Blink-Beispiel soll die praktische Anwendung illustrieren:

// Port-Manipulations Beispiele
char my_var =0;

void setup(){
  DDRB  = 0B11111111;   // alle Bits als Ausgang
  DDRC  = 0B11111111;   // Alle PINs im Port C sind Output
  DDRD  = 0B00000000;   // Alle PINs im Port D sind Input
  PORTD = 0B11111111;   // Alle PINs im Port D sind HIGH
  my_var = PIND     // Read Port D, put value in my_var
 }
void loop() {
  PORTB = B10101010; // Wechselblinker mit allen Ausgaengen
  delay(300);
  PORTB = B01010101;
  delay(300);
}

Damit stehen im Prinzip die identischen Mechanismen für die Konfiguration und die Ein- und Ausgabe von Daten wie Eingangs beschrieben bereit. Der Vorteil der Port-Manipulation liegt in der kompakten Formulierung, der Möglichkeit mehrere Pins gleichzeitig zu steuern und der sehr hohen Ausführungsgeschwindigkeit.  

Für jeden digitalWrite()-Befehl gibt es eine direkte Entsprechung: digitalWrite(16,HIGH) kann z.B. durch PORTC |= (1‹‹PC2) ersetzt werden, und digitalWrite(16,LOW) entsprechend durch PORTC &= ~(1‹‹PC2). Diese Schreibweise ist weniger einprägsam, dafür geht die Ausführung schneller. Die dafür zusätzlich erforderlichen Kenntnisse der Bit-Manipulation werden in einem separaten Beitrag erklärt. Der Nachteil gegenüber den leicht eingängigen Funktionsnamen (pinMode, digitalWrite, digitalRead) liegt sicher in der deutlich komplizierteren Handhabbarkeit.

Das gleiche Prinzip gibt es natürlich auch bei den leistungsfähigeren Boards wie dem Arduino Mega. Nur mit dem Unterschied, dass es hier natürlich wesentlich mehr Pins gibt. Die nachfolgende Graphik veranschaulicht die Pin-Zuordnung zu den Ports des Arduino Mega.

Arduino Mega Port-Belegung

Die einzelnen Register müssen nicht deklariert werden, sie sind in der Arduino-Entwicklungsumgebung bereits als Namen vordefiniert, und können direkt im Programmcode verwendet werden.