Arduino

Aus m-wiki
Version vom 28. Oktober 2021, 09:00 Uhr von Snoopy78 (Diskussion | Beiträge) (→‎Taster einlesen und LED einschalten)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Zur Navigation springen Zur Suche springen

Einführung

Arduino Uno

Der Arduino ist ein Mikrokontroller, welcher über eine Entwicklungsumgebung programmiert und geladen wird. Anschliessend läuft das Programm (Sketch genannt) selbständig auf dem Kontroller. Im Gegensatz zu einem Minicomputer wie dem Raspberry Pi kann aber immer nur ein Programm gleichzeitig auf dem System laufen.

Der Arduino sollte auf gewisse Maximalströme beschränkt werden: Siehe dazu die Antwort im Diskussionsforum.

Im Gegensatz zum Raspi hat der Arduino mehrere PWM-Pins. Diese sind mit einer Tilde (~) gekennzeichnet. Es handelt sich um die Pins 3, 5, 6, 9, 10, 11. Die Frequenz auf den Pins beträgt 490 Hz, wobei die Pins 5 and 6 wegen internen Timern den doppelten Takt von 980 Hz haben.

Installation und Programmierung

Programme für den Arduino werden in der Entwicklungsumgebung (Download) geschrieben, dann auf den Arduino übertragen und dort ausgeführt. Für die Installation des USB-Treibers wird der Arduino nach der Installation der IDE mit dem PC verbunden. Installiert man das Ganze in eine virtuelle Maschine, so muss man das Gerät anschliessend darauf mappen. Danach fragt das Betriebsystem nach einem Treiber, der sich im Unterverzeichnis der IDE befindet, falls WIndows den Treiber nicht selbständig installiert.
Für die Verbindung zum Gerät findet man anschliessend den virtuellen Comport in der Computerverwaltung.

Die Sprache ist an c++ angelehnt, doch man braucht sich nicht um Header-Dateien zu kümmern, sondern schreibt einfach das gewünschte Programm. Die Datei ist reiner Text und wird mit der Endung .ino gespeichert. Die Programmierung ist ähnlich wie Python und die Initialisierung der Hardware ähnlich wie beim Raspberry Pi.

Bibliotheken verwenden

Die IDE hat schon diverse Bibliotheken drin (Sketch -> Include Library). Möchte man aber für Sensoren externe Bibliotheken einbinden, so kann man das Tutorial von Heise verwenden.

Variablen

Variablen, die ausserhalb der Funktionen definiert werden, sind global und daher in allen Funktionen sichtbar. Im Gegensatz zu Python kann man diese dann in einer Funktion ohne Schlüsselwort verändern.

Verwendet man vor der Definition const, so ist es eine Konstante, deren Werte nicht mehr geändert werden darf.

Für die Variablen gibt es folgende Bereiche:

  • int -> ganze Zahlen von ??
  • float ?

Befehle

Auflistung der wichtigsten Befehle. Die Referenz der Funktionen befindet sich auf der Referenz-Homepage.

Name Beschrieb Beispiel
digitalRead(Pin) Liest den aktuellen Zustand eines digitalen Pins, dessen Nummer man im Parameter mitgibt. Als Rückgabe erhält man HIGH oder LOW. zustand = digitalRead(4);
analogRead(Pin) Liest den Wert des analogen Pins im Parameter. Der Rückgabewert liegt zwischen 0..1023. zuckergehalt = analogRead(A2);
analogWrite(Pin, Wert) Schreibt auf einen digitalen PWM-Pin den gewünschten Wert (0..255). Mehr Details zu PWM auf dem Arduino in der Einführung. analogWrite(11, 214);
digitalWrite(Pin, Zustand) Setzt den übergebenen Pin in den gewünschten Zustand (HIGH / LOW). digitalWrite(7, HIGH);
pinMode(Pin, Modus) Digitale Pins als Ein- oder Ausgänge festlegen. pinMode(3, OUTPUT);
if(1.Wert Bedingung 2.Wert) {} else {} Vergleicht, ob die Bedingung zutrifft. In diesem Fall wird der if-Teil ausgeführt. Optional kann im nichtzutreffenden Fall in den else-Teil verzweigt werden. if(alpha != beta) { print("Ungleich"); } else { print("Gleich"); }
delay(Zeit) Stoppt die Programmausführung für die angegebene Zeitdauer in Milisekunden. Während dieser Zeit werden auch keine Schalter eingelesen. Entsprechend sollte stattdessen mit Timer und/oder Interrupts gearbeitet werden. delay(500);
Serial.begin(Baudrate) Starten der seriellen Schnittstelle mit der angegebenen Baudrate (Standard 9600). Der Start wird im setup() durchgeführt. Serial.begin(9600)
Serial.print(Inhalt) Ausgabe von Text oder Variablen auf die serielle Schnittstelle. Bei Text muss dieser in Anführungszeichen stehen. Zeilenumbruch mit println. In Kombination mit F kann die Ausgabe formatiert werden. Serial.print("Text: ");Serial.println(Variable)
for(int Start; Endbedingung; Zähler) {} Zählt die angegebene Anzahl Schleifendurchgänge. for(int i = 0; i >= 5; i++) {}
setup() Standardmässige Funktion, welche beim Start des Controllers 1 Mal durchlaufen wird. void setup() { }
loop() Standardmässige Funktion, welche nach dem Verlassen der setup()-Funktion dauerhaft durchlaufen wird, bis man den Kontroller abschaltet. void loop() { }
tone(Pin, Frequenz, [Zeitdauer]) Im Gegensatz zu analogWrite() wird hier keine fixe Frequenz verwendet, sondern man gibt die Frequenz für den Pin vor, wobei aber das Tastverhältnis immer 50% beträgt. Der generierte Ton ist entsprechend immer gleich laut. Ohne Zeitdauer bleibt der Ton aktiv bis ein neuer Ton gesendet oder mit noTone() beendet wird. tone(9, 456);
map(Wert, Eingang tief, Eingang hoch, Ausgang tief, Ausgang hoch) Rechnet einen gegebenen Eingangsbereich auf einen gegebenen AUsgangsbereich um.. Auf diese Weise kann man einen Bereich ausweiten oder eingrenzen, wenn etwa die Sensorwerte nur einen Teilbereich abdecken, doch man ein komplettes Ausgagsspektrum abdecken möchte. map(sensor, eingangTief, eingangHoch, 0, 255);

Taster einlesen und LED einschalten

// button.ino - light a led by pressing button
// (c) BotBook.com - Karvinen, Karvinen, Valtokari

// globale Variablen definieren, die in loop() und setup() bekannt sein müssen
int buttonPin = 2;                       // Die Pin-Nummern sind auf dem Arduino direkt ersichtlich.
int ledPin = 13;                         // Die eingebaute LED ist mit dem Pin 13 gekoppelt.
int buttonStatus = LOW;                  // Initialisierung des Status

void setup() {                           // Diese Routine wird beim Start einmalig durchlaufen und dient zur Initialisierung der Hardware.
  pinMode(ledPin, OUTPUT);               // Pin als Output definieren
  pinMode(buttonPin, INPUT);             // Pin als Input definieren
  digitalWrite(buttonPin, HIGH);         // Den internen Pull-Up-Widerstand aktivieren.
                                         // Die vorherigen zwei Befehle können folgendermassen zusammengefasst werden: pinMode(buttonPin, INPUT-PULLUP);
                                         // Wenn der interne Pull-WIderstand verwendet wird, so wird der Taster am Pin und an GND angeschlossen, da er über den Pin gespiesen wird.
} // end setup()

void loop() {                            // Diese Schleife wird bis zum Abschalten des Arduino unendlich abgearbeitet
  buttonStatus = digitalRead(buttonPin); // Den Status des Tasters einlesen
  if (LOW == buttonStatus) {             // Wenn der Widerstand auf GND gezogen wird, ist der Taster gedrückt. 
                                         // Dadurch dass man die Konstante nach vorne nimmt, verhindert man Fehler, falls man ein = vergisst.
    digitalWrite(ledPin, HIGH);          // LED einschalten
  } // end if
  else {
    digitalWrite(ledPin, LOW);           // sonst die LED ausschalten
  } // end else
} // end loop()

Das Programm kann von der Webseite vom Sensorbuch heruntergeladen werden.

Serielle Schnittstelle

Der Arduino kann über die digitalen Pins 0 und 1 seriell mit einem Gerät kommunizieren. Möchte man aber nur Werte vom Arduino auslesen oder debuggen, so kann man dies direkt über das USB-Verbindungskabel erledigen.
Hier ein Beispiel für die Ausgabe eines analogen Wertes über die serielle Schnittstelle. Ausführliche Kommentare stehen im Abschnitt oben.

// squeeze_serial.ino - flexiforce squeeze level to serial
// (c) BotBook.com - Karvinen, Karvinen, Valtokari

int squeezePin = A0;
int force = -1; // Da die analoge Schnittstelle Werte von 0-1023 liefert sieht man, ob Werte ankommen

void setup() {
  Serial.begin(9600); // Verbindung mit 9600 bit/s herstellen. Im Arduino Monitor ist dies die Standard-Geschwindigkeit.
} // end setup()

void loop() {
  force = analogRead(squeezePin);
  Serial.println(force); // Den Wert auf die serielle Schnittstelle ausgeben.
  delay(500);
} // end loop()

Interrupts

Wie beim Raspi kann auch der Arduino Interrups verarbeiten. Meist wird dies eingesetzt, um bei Inputs ein Programm aufzurufen oder etwas zu erledigen, wenn eine bestimmte Zeit abgelaufen ist.

Dienstroutinen (ISR)

Nur die Inputs 2 und 3 können beim Arduino Uno Dienstroutinen auslösen. Je nach Kontrollertyp sind auch andere Pins möglich. Welche dies sind, kann in der Arduino-Hilfe zur Funktion attachInterrupt() nachgeschaut werden. Pin 2 wird beim Aufruf als 0 und Pin 3 als 1 übergeben. Die Definition des Interrupts geschieht im setup().

Die Dienstroutine sollte möglichst kurz sein, damit der Prozessor sich wieder um die anderen Sachen kümmern kann. Siehe dazu die Anmerkungen in der Arduino-Hilfe. Folgende Anpassungen am Programmcode sind notwendig:

...
volatile int tasterStatus = 0; // Hiermit wird sichergestellt, dass der Wert beim Interrupt-Durchlauf keine falschen Werte enthält
...
setup() {
  ...
  attachInterrupt(digitalPinToInterrupt(Pin-Nummer), pin_ISR, CHANGE); // 0 -> Pin 2 überwachen, pin_ISR -> Name des Interrupts, der aufgerufen wird, CHANGE -> Der Interrupt  wird bei jedem Wechsel (HIGH / LOW) aufgerufen.
  ...
} // end setup

void pin_ISR() { // Die Routine kann keine Werte übernehmen und keine zurückliefern. Es wird nur mit globalen Variablen gearbeitet.
 tasterStatus = digitalRead(buttonPin);
 digitalWrite(ledPin, tasterStatus);
} // die Routine sollte möglichst kurz gehalten werden

Der dritte Parameter, kann folgende Werte annehmen:

  • LOW -> Interrupt wird getriggert, wenn der Pin LOW ist
  • CHANGE -> Interrupt wird getriggert, wenn der Pin den Wert ändert
  • RISING -> Interrupt wird getriggert, wenn der Pin von LOW auf HIGH wechselt
  • FALLING Interrupt wird getriggert, wenn der Pin von HIGH auf LOW wechselt
  • HIGH Interrupt wird getriggert, wenn der Pin HIGH ist (Funktioniert nur bei Due-, Zero- und MKR1000-Boards)

Hier ist es wichtig, dass man nicht einfach 2 verschiedene Routinen für den gleichen Pin definieren kann und so bei RISING und FALLING etwas anderes ausführen kann. Möchte man dies, so muss die Unterscheidung mit einem Flag kontrolliert werden und mit CHANGE aufgerufen werden. Die obigen Werte sind gut für die Flankenerkennung. Prellt der Taster, so kann man eine mehrfach Triggerung damit aber nicht vermeiden, sondern muss diese zusätzlich abfangen.

Sensoren

Genau wie beim Raspi gibt es analoge und digitale Sensoren für den Arduino. Dieser hat aber direkt fünf analoge Eingänge, so dass diese Sensoren direkt verarbeitet werden können. Beim 5V-Arduino werden alle Pegel > 3V als HIGH und alle < 1.5V als LOW angesehen. Zustände dazwischen sind undefiniert und sollten nicht für digitale Pins verwendet werden. Damit nicht alle Sensoren und Aktoren doppelt unter dem Raspi und dem Arduino geführt werden, auch wenn das Auslesen schlussendlich ähnlich ist, verweise ich jeweils auf den anderen Abschnitt und erfasse nur die jeweiligen Unterschiede. Für weitere Infos also unter dem Raspi nachschauen.

Druck (Kraft)

Der Anschluss wird beim Raspi beschrieben und das Einlesen findet sich im Abschnitt über den Potentiometer.

Infrarot (Entfernung)

Sensor / Schranke

Hier sendet eine eine Infrarot-Diode Licht aus, das reflektiert und von der lichtempfindlichen Empfängerdiode wieder aufgefangen wird. Starkes Sonnenlicht oder sehr dunkle Oberflächen können aber dazu führen, dass der Empfänger den ausgesendeten Strahl nicht detektiert. Je nach Gerät kann man die Empfindlichkeit mit einem kleinen Trimm-Poti einstellen.

Fürs verarbeiten des Signals kann man das gleiche Programm wie für den Taster einlesen verwenden, da der Sensor nur ein digitales Signal schaltet, kann man nicht erkennen, wie weit man von etwas entfernt ist, sondern nur, dass sich etwas im eingestellten Bereich des Senders befindet. Um den (genauen) Abstand zu detektieren braucht es einen analogen Abstandsmesser.

Abstandsmesser

Für analoge Abstandswerte kann man den [GP2Y0A] verwenden, der im Bereich von 10-80cm arbeitet. Da hier das Sonnenlicht oder extrem dunkle oder gestreute Fläche Falschmessungen liefern können, gibt es als Alternative Ultraschallsensoren.

Potentiometer

Dieser variable WIderstand fungiert als Spannungsteiler. Entsprechend wird eine Seite mit 5V gespiesen und die andere Seite an GND angeschlossen. Der mittlere Pin hingegen an A0. Entsprechend der Stellung ist die Differenzspannung nun grösser oder kleiner. Diese Spannung wird dann umgerechnet und man erhält einen Wert zwischen 0 und 1023. Je nach Auflösung des Analogbausteines kann der Wert noch verfeinert werden. Es ist egal, wie man das Poti anschliesst. Je nach Anschluss sind einfach die ma. und min. Werte vertauscht. In einem Programm muss man dies dann halt kalibrieren oder das Poti umgekehrt anschliessen.

Dieses Verfahren wird bei allen analogen Sensoren verwendet. Man muss dabei nur wissen, welche Stellung welchen Wert liefert, um die Sensoren korrekt auszuwerten. Wenn der Sensor feine Werte liefert, muss man evtl. einen zusätzlichen Analogbaustein verwenden, welcher diese Werte besser auflöst als der interne Baustein des Arduino, der 10 Bit liefert.

Um den Arduino nicht zu überlasten, sollte das Potentiometer einen Mindestwert von 50 nicht unterschreiten, damit nicht mehr als 100mA fliessen. Allgemein sollte mit möglichst grossen Widerständen gearbeitet werden, um den thermischen Verlust möglichst klein zu halten. Entsprechend kann man gut ein 10k-Poti einsetzen.

// pot.ino - control LED blinking speed with potentiometer
// (c) BotBook.com - Karvinen, Karvinen, Valtokari
// Es wird nur "neues" kommentiert. Grundlegende Kommentare im Abschnitt Programmierung

int potPin = A0;             // Analoge Eingänge werden mit Ax gekennzeichnet
int ledPin = 13;
int poti = 0;                // Mögliche Werte von 0 bis 1023

void setup() {
  pinMode(ledPin, OUTPUT);
} // end setup()

void loop() {
  poti = analogRead(potPin); // Die Funktion liest den Wert am gegebenen Pin ein
  digitalWrite(ledPin, HIGH);// LED einschalten
  delay(poti/10);            // Warten um den Wert / 10 Millisekunden
  digitalWrite(ledPin, LOW);
  delay(poti/10);
} // end loop()

Schall

Sensoren und ein Link mit Anwendungen befinden sich beim Raspi. Hier noch ein möglicher Besipielcode, bei dem man die Berechnungen sieht.

// hc-sr04.ino - distance using ultrasonic ping sensor
// (c) BotBook.com - Karvinen, Karvinen, Valtokari

int trigPin = 2;	     // löst die Messung aus
int echoPin = 3;	     // beendet die Messung
float v = 331.5 + 0.6 * 20; // Schallgeschwindigkeit bei 20°C in m/s. Mit einem Temperaturfühler könnte man hier auch die Variable anpassen.

void setup() {
  Serial.begin(115200);
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
} // end function setup()

float distanceCm(){	
  digitalWrite(trigPin, LOW);	// Trigger-Pin-Flanke generieren
  delayMicroseconds(3);	        // Warten, damit die Flanken sauber generiert werden
  digitalWrite(trigPin, HIGH);	
  delayMicroseconds(5);	
  digitalWrite(trigPin, LOW);	

  float tUs = pulseIn(echoPin, HIGH);    // Die Funktion liefert die gemessene Zeit in Mikrosekunden
  float t = tUs / 1000.0 / 1000.0 / 2.0; // Die Zeit in Sekunden umrechnen
  return t * v * 100;                    // Distanz in cm zurückliefern
} // end function distanceCm()

void loop() {
  int d = distanceCm();    // Rückgabewert in ganze cm 
  Serial.println(d, DEC);  // Wert als Dezimalzahl auf der ser. Konsole ausgeben
  delay(200);
} // end function loop()

Temperatursensor

Damit können Temperaturen direkt eingelesen werden. Ein Beschrieb findet sich beim Raspi. Vollständige Kommentare zum Programm im Abschnitt zum Potentiometer. Infos zur ser. Schnittstelle im entsprechenden Abschnitt.

// temperature_lm35.ino - LM35 temperature in Celcius to serial
// (c) BotBook.com - Karvinen, Karvinen, Valtokari

int lmPin = A0;

void setup() {
   Serial.begin(9600);
} // end setup()

float tempC() {
  float raw = analogRead(lmPin);   // Dieser Wert könnte auch direkt im loop ausgelesen und als Parameter der Funktion übergeben werden.
  float percent = raw / 1023.0;    // Prozentwert des Rohwertes als Floatzahl berechnen
  float volts = percent * 5.0;     // Beim LM35 ergibt sich damit die absolute Spannung in Millivolt. Diese hängt von der Reverenzspannung (5V) ab.
  return 100.0 * volts;            // Jetzt hat man die Temeperatur in °C
} // end tempC()
 
void loop() {
  Serial.println(tempC());
  delay(200);
} // end loop()

1-wire Temperatursensor

Auch der Arduino unterstützt die 1-Wire-Schnittstelle. Es gibt dazu auf der Arduino-Homepage eine Anleitung. Um die entsprechenden Geräte einzubinden, gibt es allgemeine oder spezifische Bibliotheken.

// Example testing sketch for various DHT humidity/temperature sensors (https://github.com/adafruit/DHT-sensor-library/blob/master/examples/DHTtester/DHTtester.ino)
// Written by ladyada, public domain 
// REQUIRES the following Arduino libraries: 
// - DHT Sensor Library: https://github.com/adafruit/DHT-sensor-library 
// - Adafruit Unified Sensor Lib: https://github.com/adafruit/Adafruit_Sensor 

#include "DHT.h" 
#define DHTPIN 8     // Digital pin connected to the DHT sensor 

//#define DHTTYPE DHT11   // DHT 11 
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321 
//#define DHTTYPE DHT21   // DHT 21 (AM2301) 

// Connect pin 1 (on the left) of the sensor to +5V 
// Connect pin 2 of the sensor to whatever your DHTPIN is 
// Connect pin 3 (on the right) of the sensor to GROUND (if your sensor has 3 pins) 
// Connect pin 4 (on the right) of the sensor to GROUND and leave the pin 3 EMPTY (if your sensor has 4 pins) 
// Connect a 10K resistor from pin 2 (data) to pin 1 (power) of the sensor 

DHT dht(DHTPIN, DHTTYPE); // Initialize DHT sensor. 

void setup() { 
 Serial.begin(9600); 
 Serial.println(F("DHTxx test!")); 
 dht.begin(); 
} // end  setup()

void loop() { 
 delay(2000); // Wait a few seconds between measurements.

 // Reading temperature or humidity takes about 250 milliseconds! 
 // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor) 
 float h = dht.readHumidity(); 

 float t = dht.readTemperature(); // Read temperature as Celsius (the default)
 float f = dht.readTemperature(true); // Read temperature as Fahrenheit (isFahrenheit = true) 

 // Check if any reads failed and exit early (to try again). 
 if (isnan(h) || isnan(t) || isnan(f)) { 
   Serial.println(F("Failed to read from DHT sensor!")); 
   return; 
 } // end if 
 
 float hif = dht.computeHeatIndex(f, h); // Compute heat index in Fahrenheit (the default) 
 float hic = dht.computeHeatIndex(t, h, false); // Compute heat index in Celsius (isFahreheit = false)
 
 Serial.print(F("Luftfeuchte: ")); 
 Serial.print(h); 
 Serial.print(F("%  Temperatur: ")); 
 Serial.print(t); 
 Serial.print(F("°C ")); 
 Serial.print(f); 
 Serial.print(F("°F  Gefühlte Temperatur: ")); 
 Serial.print(hic); 
 Serial.print(F("°C ")); 
 Serial.print(hif); 
 Serial.println(F("°F")); 
} // end loop()

Aktoren

Servo-Motor

Durch die Servo-Bibliothek können Motoren einfach mit dem anzusteuernden PWM-Pin angeschlossen werden:

#include <Servo.h> // Bibliothek, um die Ansteuerung zu abstrahieren.
Servo myServo;

const int motor = 9;
const int poti = A0;

int potiWert = 0;
int winkel = 0;

void setup() {
 myServo.attach(motor);
} // end setup()

void loop() {
 potiWert = analogRead(poti);
 winkel = map(potiWert, 0, 1023, 0, 255); // Den Eingangswert von 0..1023 auf 0..255 mappen, da der PWM-Ausgang nur diesen Bereich hat
 myServo.write(winkel); // Ausgabe der neuen Position
 delay(15); // Wartezeit, damit sich der Motor bewegen kann
} // end loop()