Relais zur Schaltung großer Ströme

Inhalt

  • Relais (engl. Relay)

    Einführung

Mikrocontroller können mit ihren zahlreichen digitalen Ausgängen potentiell sehr viele Dinge steuern. Die Ströme, die über diese Ausgänge fließen können, sind aber sehr gering. Sie eignen sich daher nicht um größere Leistungen zu erbringen und Lasten wie elektrodynamische Elemente (Relais, Hubmagnete, Motoren, …) zu schalten.

Benötigt werden also Schaltungen, die mit kleiner Leistung große Lasten steuern können. In diesem Abschnitt wird das Relais vorgestellt, der ein dynamischer Schalter für getrennte Stromkreise ist. In einem weiteren Abschnitt werden Transistoren als Schalter eingeführt.

Relais als Schalter

Ein Relais ist ein elektromagnetischer Schalter, der mit dem Anlegen einer Spannung gesteuert werden kann. Es besteht üblicherweise aus einer Spule und einem beweglichen Leiter in örtlicher Nähe dieser Spule. Der Leiter agiert als Schalter und ist Teil eines separaten Stromkreises, der gesteuert werden soll. Wird die Spule mit Strom durchflossen, so erzeugt sie ein elektromagnetisches Feld. Dieses Feld zieht den Leiter des Schalters an und bewegt ihn in eine öffnende (bzw. schließende) Stellung. Der zweite Stromkreis wird also mit der Spannung aus dem ersten Stromkreis gesteuert.

Relais werden oft verwendet um mit kleinen Spannungen auch sehr große Spannungen zu schalten. Je nach Bauform sind sie unterschiedlich beschaffen. Die Ausgänge sind meistens wiefolgt benannt.

  • NO (normally open): Liegt keine Spannung an sind die Schalter offen
  • NC (normally closed): Liegt keine Spannung an sind die Schalter geschlossen
  • CO (change over): Dieser Ausgang wechselt zwischen den beiden anderen.
Das Relais dient hier als Schalter, um mit einer kleinen Spannung (Steuerstromkreis) eine größere Spannung (Laststromkreis) zu schalten.
Das Relais dient hier als Schalter, um mit einer kleinen Spannung (Steuerstromkreis) eine größere Spannung (Laststromkreis) zu schalten.

Selbstinduktion in der Spule

Kernelement des Relais ist eine Spule, die das elektromagnetische(EM) Feld zur Betätigung des Schalters erzeugt. Eine Spule hat die Eigenschaft der Selbstinduktion: Bewegte Ladungen erzeugen um den Leiter herum ein EM-Feld, das widerrum andere Ladungen in Bewegung versetzen kann. In einer Spule liegen aufgrund der Wicklung des Leiters die Windungen eng nebeneinander. Daher können bewegte Ladungen auf andere Ladungen im selben Leiter eine elektromagnetische Wirkung ausüben.

Wird also eine Spule mit Strom durchflossen, dann treibt die Selbstinduktion die Ladungen in anderen Windungen auch weiter. Wenn nun der Stromfluss unterbrochen wird (z.B. weil keine Spannung mehr anliegt), dann bleibt die Selbstinduktion zunächst noch bestehen. Sie treibt die Ladungen im Leiter weiter und erzeugt daher am Ende einen Ladungsüberschuss, während am anderen Ende ein Ladungsmangel entsteht.

Die Selbstinduktion schwächt sich mit der Zeit ab und er Stromfluss kommt schließlich zum erliegen. Bis es aber soweit ist kann der Ladungsüberschuss teilweise sehr groß werden, sodass sich am Ausgang der Spule eine hohe Spannung aufbauen kann, die sogar Bauteile beschädigen oder zerstören können.

Intuitiv kann man sich diesen Vorgang wie ein Wasserrad mit Trägheit (Spule) vorstellen. Beim Anlegen einer Wasserleitung mit Druck (Spannung) benötigt das Rad aufgrund seiner Trägheit eine Weile, bis es sich vom Wasserdruck (Ladungen) in Bewegung setzt und das Wasser weitertransportiert.  Dreht man am Eingang das Wasser ab, so dreht sich das Rad zunächst aufgrund seiner Trägheit weiter. Da es weiterhin versucht Wasser zu transportieren, entsteht am Ausgang des Wasserrads ein Überdruck, während am Eingang ein Unterdruck aufgrund von Wassermangel entsteht.

Freilaufdiode zum Schutz

Um den Aufbau einer Hochspannung zu verhindern wird parallel zur Spule eine Diode in Sperrichtung geschalten. Dioden haben als Halbleiterelemente die Eigenschaft Strom nur in eine Richtung passieren zu lassen. In Sperrichtung (antiparallel) geschalten verhindert sie den Strom über sich selbst, sodass der ganze Strom über die Spule fließen muss.

Wenn sich beim Ausschaltvorgang die Spannung am Ausgang der Spule erhöht, so herrscht dort auf einmal eine größere Spannung als am Eingang (die Spannung hat sich umgekehrt).  Für die Diode gilt dies ebenso, da sie parallel zur Spule ist. Da sich die Spannung aber nun umgekehrt hat ist sie nun für die überschüssigen Ladungen in Durchlassrichtung. Sie können zum Eingang der Spule zurückfließen, weshalb sich am Ausgang keine hohe Spannung entwickeln kann, die zerstörend wirken könnte. Die Diode wirkt also wie eine Art Rücklaufventil.

[FILM Freilaufdiode]

Nachteile von Relais

Zusammenfassung

Relais werden als Schalter für separate Stromkreise verwendet. Gerne werden sie eingesetzt um mit kleinen Spannungen größere Spannungen zu schalten. Aufgrund der Induktivität der im Relais vorhandenen Spule sollte man eine Freilaufdiode antiparallel zum Relais verwenden, da sonst schädliche Hochspannungen entstehen könnten.

 

State Machines

Inhalt

  • State-Machines (deutsch: Automaten) als Abstraktion von Abläufen und Zuständen

Einführung

Viele Prozesse sind nichts anderes als ein definierter Ablauf von Vorgängen. State-Machines (deutsch: Automaten) sind eine Abstraktion dieses Modells und dienen etwa dazu komplexe Abläufe abzubilden, um sie leichter planbar zu machen.

Eine sehr einfache Art von State-Machine haben sie mit dem Arduino bereits kennengelernt. Das Zustandsdiagram der Arduino Umgebung könnte man etwa wiefolgt aufzeichnen:

sm_arduino

Der Eingang ganz links (rst_n) entspricht dem RESET, also dem allerersten Start des Systems. Nach dem Start befinden wir uns zuerst im setup-Zustand (im Arduino entspricht dies dem Aufruf der setup -Funktion). Ist dies abgeschlossen geht das System über in den loop-Zustand. Von diesem gibt es keine Verbindung mehr in einen anderen Zustand, außer sich selbst. In diesem Zustand verharrt die State-Machine nun für immer (außer es wird ein RESET durchgeführt).

Das folgende Beispiel zeigt abstrahiert in einem Zustandsdiagramm den Vorgang Kaffee kochen. Dieses Diagramm hat einen Start (rst_n) und zwei Endzustände (ERROR und FERTIG).

sm_kaffee_beschrieben

Der Ablauf des Kochens erfolgt in mehreren Zuständen.

  1. Check Zutaten: Überprüft ob alles (Wasser, Kaffee, Tasse, …) vorhanden ist.
    • Alles vorhanden ? -> weiter zu Zustand Wasser kochen
    • Etwas fehlt? -> weiter zu ERROR
  2. Wasser kochen: Wasser wird aufgekocht
    • Erfolgreich? -> Weiter zu Wasser pressen
    • Wasser kocht nicht (z.B. wegen Defekt des Heizelements) – > weiter zu ERROR
  3. Wasser pressen: Wasser wird durch Kaffee gepresst
    • Erfolgreich? -> Je nach gewünschtem Kaffee weiter zu
      • FERTIG
      • Milch zugeben
      • Zucker zugeben. Achtung! Der Ablauf ist so definiert, dass, wenn Milch und Zucker verlangt werden, zunächst der Zucker zugegeben wird.
    • Pressung fehlerhaft (z.B. weil die Pumpe nicht funktioniert) -> ERROR
  4. Milch zugeben
    • Erfolgreich -> FERTIG
    • Milch kann nicht zugegeben werden (z.B. weil Schlauch verstopft) -> ERROR
  5. Zucker zugeben
    • Erfolgreich -> je nach Kaffee weiter zu
      • Milch zugeben
      • FERTIG
    • Zucker kann nicht zugegeben werden (z.B. weil verstopft) -> ERROR
  6. FERTIG (Endzustand): Alles wurde erfolgreich abgewickelt, Kaffee kann entnommen werden
  7. ERROR (Endzustand): Irgendetwas ging beim Ablauf schief. Der Fehler muss behoben werden

Gerade bei komplexen Abläufen hilft eine Abstraktion in ein Zustandsdiagram ungemein. Definiert werden müssen Zustände und Übergänge zwischen den Zuständen.

State Machines für das Arduino

Simulieren lässt sich eine State-Machine am Arduino beispielsweise, indem man den Code in klar strukturierte Blöcke zusammenfasst, diesen mit einer ID versieht und in einer Variable die ID des aktuellen Zustands abspeichert.

In einer einfachen Form eignet sich für die Strukturierung des Codes die switch -Bedingung. Im folgenden Code wird über die Variable state  einer von drei Zuständen definiert. Mit jedem Aufruf der loop -Funktion wird über das switch -Statement geprüft, in welchem Zustand man sich befindet und der entsprechende Code dieses Zustands ausgeführt.

Codebeispiel 1

simple_sm

In den einzelnen Zuständen existiert außerdem immer eine if-Bedingung die prüft, ob eine gewisse Bedingung ( CONDITION_TO_CHANGE_TO_STATE_X )erfüllt ist. Wenn dies der Fall ist, wird die state -Variable neu gesetzt, sodass beim folgenden Aufruf der loop -Funktion das switch -Statement den Code des entsprechenden Zustands ausführt.

Beispiel 1: Ein vollwertiges Beispiel

[BEISPIEL einfügen]

StateMachine Bibliotheken

Das Konzept der State-Machines wurde in mehreren Bibliotheken abgebildet. Beispiele hierfür sind etwa

Zusammenfassung

State-machines sind eine Abstraktion eines Ablaufs. Sie besteht aus Zuständen und Übergängen zwischen den Zuständen. In Programmen umgesetzt dienen Sie zur besseren Strukturierung von Abläufen. Eine Möglichkeit, eine simple SM umzusetzen bietet die switch -Bedingung. Mehr Möglichkeiten bieten diverse Bibliotheken, die das Konzept stärker formalisieren.

Weiterführende Links

  • http://www.thebox.myzen.co.uk/Tutorial/State_Machine.html
  • http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=11425&page=1

 

Zahlensysteme

Inhalte

  • Definition von
    • Dezimal
    • Binär
    • Hexadezimal
  • Umrechnungen
    • Binär -> Dezimal
    • Hexadezimal -> Dezimal
    • Hexadezimal <-> Binär
  • Anschreiben in Arduino

Dezimal- und Binärsystem

Hexadezimalsystem

Zahlen in Arduino

Im Programmcode können ganze Zahlen in unterschiedlichen Notationen angegeben werden. Nicht immer ist die Angabe von Dezimalzahlen vorteilhaft, gerade weil die Variablen intern in Byte-Größen abgelegt werden (d.h. im Speicher werden immer Vielfache von Bytes abgelegt. Beispielsweise hat eine int -Variable am Arduino UNO die Größe 2 Bytes).

Folgende Präfixe definieren die Art der Notation

Dezimal

Binär

Hexadezimal

 

Aufgabe

  • Lernen Sie die Umrechnung von Dezimal in Binär und umgekehrt.
  • Lernen Sie die Konvertiertung von Hexadezimal in Binär und umgekehrt.

Weiterführende Links

  • https://www.arduino.cc/en/Reference/IntegerConstants
  • http://www.binaryhexconverter.com/

 

LCDs

Inhalt

  • Benutzung eines 16×2 LC-Displays zur Textausgabe

Einführung

Auch wenn blinkende LEDs für die Signalisierung von Daten oder Zuständen oft ausreichen, so sind sie für die Vermittlung von komplexen Daten an einen User ungeeignet. Liquid Crystal Displays (LCDs) zur Darstellung von Text können Daten für den/die Benutzer/in in einer lesbaren Form vermitteln.

Dieser Artikel demonstriert die Anwendung eines 16×2 LCDs, die von vielen Herstellern günstig hergestellt werden. Dank eines standardisierten Interfaces (Hitachi HD44780) können es am Arduino mit der LiquidCrystal Library relativ leicht betrieben werden.

Das 16×2 LCD

16x2-Character-LCD

16×2 LC-Displays bestehen aus zwei Zeilen von jeweils 16 Pixel-Matrizen mit 5×7 Pixeln. Diese Auflösung erlaubt die Darstellung von allen Zeichen der ASCII-Tabelle und Sonderzeichen, die auch selber definiert werden können.

Anschluss an das Arduino

LCDs nach dem HD44780 Standard haben 16 Pins zur Spannungsversorgung und Kommunikation. Am Arduino werden sie wiefolgt angeschlossen:

lcd_16x2_Steckplatine

Die Leitungen an Pin 2 bis 5 des Arduinos dienen zur Datenübertragung, die Verbindungen an Pin 11 und 12 sind Steuerleitungen (vereinfacht gesagt teilen sie dem Controller des Displays mit wann welche Daten wohin geschrieben werden).

Die Stromversorgung erfolgt über den Displayanschluss 1 und 2 (ganz links).

Das Potentiometer an Displayansschluss 3 dient zur Anpassung des Kontrasts der LCD-Pixel. Manchmal kann man sich auch das Poti ersparen und den Pin direkt mit dem GND verbinden.

Displayanschluss 5 sollte ebenfalls mit GND verbunden werden. (Liegt dieser Anschluss auf LOW, dann kann schreibend auf das Display zugegriffen werden).

LED-Hintergrundlicht

Manche Displays haben auch eine LED Hintergrundbeleuchtung. Die interne LED wird dann mit einem Vorwiderstand über die Anschlüsse 15 (Spannung) und 16 (GND) angeschlossen.

Beispiel 1: Ansteuerung vom Arduino

Die LiquidCrystal-Bibliothek macht die Ansteuerung des Displays unkompliziert.

Code-Analyse

Das LCD-Objekt muss zunächst als Bibliothek eingebunden ( #include <LiquidCrystal.h> ) und initialisiert werden ( LiquidCrystal lcd(12, 11, 5, 4, 3, 2);), wobei die Parameter die Anschluss-Pins des Displays am Arduino entsprechen.

Über einen Cursor wird die aktuell zu beschreibende Position am Display festgelegt ( lcd.setCursor(0, 1) ), wobei Spalten und Zeilen mit 0 zu zählen begonnen werden.

lcd.print()  gibt dann ein oder mehrere Zeichen aus, wobei der Cursor automatisch um eine Spalte weiter geschoben wird.

Beispiel 2: Ausgabe von eigenen Zeichen

Neben den Zeichen der ASCII-Tabelle können auch eigene Zeichen entworfen werden. Zeichen können hierbei in der 5×7 Pixelmatrix designed und müssen dann als Array von Bytes abgelegt werden. Eine große Hilfe hierfür ist die Online-Applikation Custom Character Creator, bei der die Pixel per Mausklick gesetzt werden können.

smiley

Anmerkung: In diesem Array von Bytes werden die einzelnen Bytes in binär-Notation angelegt. Diese erkennt man daran, dass den Zahlen immer ein 0b  vorangestellt wird. Das Array besteht aus 7 Elementen, wobei die binären 1 immer für ein gesetztes Pixel, 0 für ein nicht gesetztes Pixel stehen.

Um ein solches Zeichen anwenden zu können ist es notwendig, dieses im lcd -Objekt anzulegen (bevor die Kommunikation mit lcd.begin()  begonnen wird). Dieser Befehl speichert das neue Zeichen an den internen Speicherplatz mit Index 0:

Mit dem Aufruf von folgendem Befehl kann dieses Zeichen nun am LCD geschrieben werden:

Anmerkung: Vor dem Index muss ein Typecast auf den Type uint8_t  durchgeführt werden (Dieser Variablentyp entspricht einer unsigned int  mit einer Größe von einem Byte). Der Grund hierfür ist hier nachlesbar.

Der folgende Beispielcode ist von der Seite des Custom Character Creator kopiert.

 

Aufgabe

  • Erweitern Sie das Beispiel aus Sensoren-1 um ein LCD-Display. Auf diesem sollen die aktuell gemessenen Spannungswerte des LDRs mit einem Besschreibungstext ausgegeben werden.
  • Ab einem gewissen Schwellwert soll das LCD zu blinken anfangen (um z.B. zu signalisieren, dass ein kritischer Wert überschritten wurde). Hierfür eigenen sich die Funktionen lcd.noDisplay()  und lcd.display()

Die Referenz der Bibliothek enthält möglicherweise weitere interessante Informationen.

Weiterführende Links

  • https://www.arduino.cc/en/Tutorial/LiquidCrystalDisplay
  • Aufbau des 16×2 LCDs: http://www.engineersgarage.com/insight/how-lcd-works
  • https://omerk.github.io/lcdchargen/

Arduino Multitasking

Inhalt

  • Multitasking, um mehrere Dinge parallel auszuführen

Einführung

Applikationen mit Mikrocontrollern führen oft mehr als eine Tätigkeit gleichzeitig aus. Auf PCs mit modernen Betriebssystemen ist dies eine völlige Selbstverständlichkeit, bei Mikrocontrollern muss man sich selber um die parallele Abarbeitung der Tasks kümmern.

Die bislang oft eingesetzte delay -Funktion ist in den meisten Anwendungen, die mehr als eine Aufgabe durchführen, sehr kontraproduktiv. Sie blockiert den ganzen Prozessor, sodass nichts anderes gleichzeitig passieren kann. Will man etwa zwei LEDs mit unterschiedlichen Zeitintervallen blinken lassen, so ist dies mit der delay-Funktion fast nicht möglich.

Im Folgenden wird eine andere Strategie vorgestellt um eine quasi-parallele Ausführung zu ermöglichen. Sie basiert darauf Zeitmessungen vorzunehmen.

delay  vs. Zeitmessung

Das Blink-Beispiel verwendet die delay -Funktion, um den Blink-Zyklus für eine LED abzuarbeiten.

Während delay  ausgeführt wird, können keine anderen Befehle ausgeführt werden. Folgendes Beispiel ist eine Alternative, bei der das Programm nicht blockiert wird, sondern im loop mit jeder neuen Ausführung überprüft wird, ob ein entsprechendes Zeitintervall vergangen ist.

Beispiel 1: Blink ohne delay

Basierend auf dem Beispiel File -> Examples – > 02.Digital -> BlinkWithoutDelay

Code-Analyse

Die Variablen previousTime  und currentTime  sind vom Typ unsigned long . long s speichern wie int s nur ganze Zahlen, allerding können diese einen wesentlich größeren Bereich umfassen. Die Funktion millis()  gibt die aktuelle Zeit in Millisekunden seit dem Start des Programms aus.

Im loop wird zunächst die aktuelle Zeit abgefragt und dann mit der previousTime  verglichen. Ist die Differenz größergleich dem definierten Interval, dann wird eine Aktion ausgeführt (Umschalten der LED).

Das Prinzip dieser Methode basiert also darauf, dass der loop nicht blockiert, sondern immer wieder schnell hintereinander aufgerufen wird (bei einem Code wie oben beschrieben geschieht dies mehrere tausend mal pro Sekunde). Bei jedem Aufruf wird verglichen, ob ein gewisses Zeitintervall vergangen ist. Falls das Intervall vorbei ist, wird eine Aktion durchgeführt.

Multitasking am Arduino

Die Methode der Zeitmessung ist fast immer gegenüber dem delay zu bevorzugen. Folgendes Beispiel demonstriert die separate Behandlung von drei blinkenden LEDs. Es handelt sich hierbei um keine echte parallele Ausführung, da die Ausführung der aktionen tatsächlich nur sehr schnell hintereinander passieren.

Beispiel 2: Triple-Blink ohne delay

Beispiel 1 wird hier auf drei LED (an Pin 11, 12, 13) ausgeweitet. Für jede LED muss jeweils ihr Zustand (state), Zeit (previous) und Intervall (interval) gespeichert werden:

Code-Anmerkungen

In C sind Initialisierungen von mehreren Variablen auf einmal erlaubt. Der Ausdruck int led1, led2, led3; legt also drei Variablen vom Typ int  an. Dasselbe gilt für Zuweisungen. previous1 = previous2 = previous3 = currentTime; bedeutet, dass alle 3 previous -Variablen den Wert von currentTime  bekommen.

Zusammenfassung

Die delay -Funktion sollte nach Möglichkeit im Programmcode nicht eingesetzt werden. Um Zeitintervalle zu überprüfen empfiehlt sich die Verwendung der millis() -Funktion, die die aktuelle Zeit in Millisekunden seit Start des Programms zurückgibt. Mittels Differenzberechnung der aktuellen Zeit und einer zuletzt gespeicherten Zeit kann man Zeitintervalle überprüfen, ohne das die Ausführung des restlichen Programms blockiert wird.

Weiterführende Links

  • Vergleich von Zeitpunkten und Intervallen: http://arduino.stackexchange.com/questions/12587/how-can-i-handle-the-millis-rollover

Servomotoren

Inhalte

  • Servomotoren als vielseitig verwendbare Beweger
  • Anwendung von Code-Libraries in Arduino

Einführung

Servomotoren (Servos) sind Aktoren, mit denen kontrollierte Drehbewegungen durchgeführt werden können (d.h. der Winkel der Drehung ist einstellbar). Meistens ist der Drehbereich auf 180° beschränkt, es existieren aber auch Modelle, die eine kontinuierliche Drehung ohne Beschränkung durchführen können. Intern bestehen sie üblicherweise aus einem Gleichstrommotor für den Antrieb, einem Getriebe zur Erhöhung der Stellkraft und einer Steuerschaltung um den Drehwinkel zu kontrollieren. Die Steuerschaltung basiert auf einem Potentiometer, mithilfe dessen der aktuelle Drehwinkel ablesbar ist. Electronicsplanet hat eine gute schematische Darstellung zu dem Thema.

Anwendung finden Servos in in ferngesteuerten Fahrzeugen, als Öffner/Schließer in Haussteuerungsystemen (z.B. bei Lüftungen), u.v.m.

Anwendung von Servomotoren

Servos sind leicht in eine elektronische Schaltung einzubinden. Üblicherweise haben sie drei Anschlusskabel:

  • GND (schwarz oder braun)
  • +5V (rot)
  • Steuerleitung (gelb oder orange)

Um Servomotoren anzusteuern wird auf der Steuerleitung ein Signalpuls mit einer definierten Länge ausgegeben, wobei die Länge des Pulses den Winkel definiert, den die Motorwelle einnehmen soll.

  • 0°: Puls mit 1ms HIGH, 20ms LOW
  • 180°: Puls mit 2ms HIGH, 20ms LOW
  • Werte zwischen 1 und 2 ms entsprechen Werten dazwischen.

Die Ansteuerung des Servos erfolgt also mit einer Art von Pulsweitenmodulation, wobei die Pulse immer nur sehr kurz bleiben

Von Racso – Eigenes Werk, GFDL, https://commons.wikimedia.org/w/index.php?curid=5525798

Beispiel 1: Servomotoren manuell angesteuert

Servo_Steckplatine

Code-Anmerkungen

delayMicroseconds  ist eine Funktion, die das Programm eine entsprechende Anzahl von Mikrosekunden aufhält und daher wesentlich kürzere Zeitintervalle kontrollieren kann als delay .

Beispiel 2: Die Servo-Library

Für die Ansteuerung von Servos existiert auch eine Bibliothek, die einfacher anzuwenden ist als manuelle Ansteuerung aus Beispiel 1. Folgender Code basiert auf dem Sweep-Beispiel von der Arduino Webseite.

Dieser Programmcode verwendet die in der Arduino-IDE integrierte Servo-Bibliothek.

Code-Analyse

Durch die Einbindung der Servo-Bibliothek können Servo -Objekte angelegt werden. Ein Servo -Objekt mit Namen myservo  wird in Zeile 3 angelegt. Dieses Objekt hat diverse Eigenschaften und Methoden, die mit einem Punkt aufgerufen werden können:

myservo.attach(9)  etwa hängt definiert Pin 9 als Steuerpin für die Servokontrolle. Mit myservo.write(pos)  wird der Servo nun angewiesen auf pos  zu fahren.

Alle Methoden der Servo-Bibliothek können in der entsprechenden Dokumentation auf der Arduino Webseite nachgelesen werden.

Weiterführende Links

  • http://www.electronicsplanet.ch/Roboter/Servo/intern/intern.htm
  • https://www.arduino.cc/en/Tutorial/Sweep
  • https://www.arduino.cc/en/Guide/Libraries

 

DSP (1) Glättung von Signalen

Inhalt

  • Digitale Signalverarbeitung (Mittelwertfilter) zur Glättung von Sensorwerten
  • Ringbuffer zur Implementierung eines Mittelwertfilters

Vorraussetzung

Einführung

Bei Messungen mit analogen Sensoren sind die Ergebnisse oft verrauscht, d.h. sie weichen sie weichen zueinander leicht ab.

Beispiel: eine saubere Wertefolge mit langsam ansteigenden Werten

15,15,15,15,15,15,16,16,16,16,16,16,17,17,17,17,17,17,17

Beispiel: eine verrauschte Wertefolge mit langsam ansteigenden Werten

15,14,17,14,15,16,17,18,14,16,17,15,16,17,18,16,19,17,18

Im Durchschnitt ist das saubere und das verrauschte Signal fast gleich, allerdings ist dies beim verrauschten Signal viel schlechter zu erkennen. Leider hat man in der Realität fast immer mit rauschhaften Signalen zu tun. Es werden daher Methoden benötigt, wie das Rauschen aus dem Signal gefiltert werden kann.

Im Gebiet der Digitalen Signalverarbeitung (Digital Signal Processing – DSP), bei der Wertefolgen auf digitalen Prozessoren verarbeitet werden, gibt es eine Reihe von Methoden um rauschhafte Signale zu verbessern.

Mittelwertfilterung (Running Average)

Eine einfache Methode, um ein verrauschtes Signal zu glätten ist eine Mittelwertfilterung. Dabei wird aus den letzten Messergebnissen der Durchschnitt gebildet und dieser Durchschnitt als der aktuelle Messwert angesehen.

Um eine bestimmte Anzahl von Messwerten abzuspeichern werden wir zunächst einen Ringbuffer mit einem Array erstellen. In einem Ringbuffer wird ein neuer Messwert immer an den nächsten Arrayindex geschrieben (der Wert an dieser Stelle ist der älteste im Array und wird überschrieben). Erreicht man das Ende des Arrays wird der Index wieder auf Null gesetzt und man fängt wieder am Anfang zu zählen an.

Für die Durchschnittsberechnung werden alle Werte des Arrays aufsummiert und anschließend durch die Anzahl der Werte dividiert.

Der berechnete Durchschnittswert über den zeitlichen Verlauf hin wesentlich ruhiger. Ein Nachteil dieses Filters ist, dass er relativ träge ist. Das bedeutet, schnelle Änderungen wirken sich aufgrund der Durchschnittberechnung nur relativ langsam auf das Endergebnis aus.

Beispiel 1: Running Average in Arduino

Für dieses Beispiel ist ein Aufbau mit einem LDR, wie im Abschnitt Sensoren (1) gezeigt, notwendig. Das Beispiel zeigt, wie mit Hilfe eines Ringbuffers ein Running-Average einer Messung gebildet werden kann. Die Ausgabe ist der aktuelle, tatsächliche Messwert sowie der Durchschnittswert über die letzten Messungen.

Code-Analyse

Anfangs wird das Array sensorValues mit einer vorher festgelegten Größe ( arraySize ) erstellt.

In setup wird das Array zunächst einmal mit einem Messwert voll beschrieben, damit die erste Durchschnittsberechnung den gemessenen Werten sehr ähnlich ist.

In loop wird zunächst die Messung vorgenommen und an den aktuellen Index des Array sensorValues  geschrieben. Über den Aufruf der Funktion getAverage(), die weiter unten definiert ist, wird der Mittelwert der im Array vorhandenen Werte berechnet.

Anschließend wird der gemessene Wert und der Mittelwert über die serielle Konsole ausgegeben.

Nun wird noch die Index-Variable erhöht, damit bei der nächsten Messung die folgende Stelle im Array beschrieben wird. Für den Fall, dass wir das Ende des Arrays erreicht haben, fangen wir wieder bei Null an.

Die Ausgabe

Klar erkennbar ist, dass die Messwerte im Durchschnitt ungefähr gleich bleiben, während die tatsächlich gemessenen Werte teilweise erheblich voneinander abweichen.

Zusammenfassung

Analoge Messwerte sind sehr oft von Rauschen betroffen. Mittels digitaler Signalverarbeitung kann das Rauschen vermindert werden. Eine Methode hierfür ist die Mittelwertfilterung (Running Average). Diese speichert eine Reihe von Messwerten und bildet anschließend daraus den Durchschnitt. Eine Möglichkeit einen Mitterlwertfilter zu implementieren ist der Ringbuffer.

Arduino Datentypen (2) – Arrays

Inhalte

  • Arrays, um mehrere Werte desselben Typs zu speichern

Einführung

Arrays dienen dazu mehrere Werte eines Datentyps in einer Variable in einer „Sammlung“ zu speichern. Beispielsweise ist es oft nötig, mehrere Messwerte eines Sensors zu speichern, um etwa Messinformationen aus der Vergangenheit zu mit den aktuellen Werten zu vergleichen. Über einen Index lässt sich auf die aktuelle Stelle des Arrays zugreifen.

Arrays

Initialisierung

Alle bisher kennengelernten Datentypen lassen sich auch als Array mit mehreren Stellen initialisieren. Die bei der Initialisierung angegebene Größe lässt sich im nachhinein nicht mehr ändern. Im folgenden Beispiel sind mehrere Varianten, wie ein Array initialisiert werden kann angeführt:

Für den Datentyp char ist auch eine Initialisierung wiefolgt möglich:

Zugriff auf die einzelnen Stellen

Ist ein Array initialisiert, kann man auf die einzelnen Stellen lesend oder schreibend mit einem Index in eckigen Klammern []  zugreifen. Achtung: Die Indizes werden bei 0 zu zählen begonnen.

Wird versucht auf Stellen außerhalb des Arrays zuzugreifen, erhält man einen Fehler!

Beispiel

In diesem Beispiel wird ein Array mit 10 Werten initialisiert. Über eine for-Schleife werden die Werte anschließend ausgegeben. Die Laufvariable i wird für die Indizierung des Arrays verwendet.

 

 

 

 

Sensoren (1)

Inhalte

  • Einfache Sensoren: LDR, Thermistor, …
  • Benz-Formel
  • Die map -Funktion

    Einführung

Viele Anwendungen erfordern die Messung physikalischer Größen wie Licht, Temperatur, Druck, Distanz, … Viele elektronische Bauteile verhalten sich variabel unter Einwirkung von einer oder mehrerer dieser Größen (z.B. verändert sich der Widerstand unter Einfluss der Temperatur). Diese Bauteile können daher als Sensoren für diese Größen verwendet werden.
Im Folgenden wird eine Übersicht über die Anwendung dieser Sensoren gegeben.

Analoge Sensoren mit variablen Widerstand

  • Photowiderstände (Light Dependend Resistor – LDR) sind Bauteile mit einem variablen ohmschen Widerstand. Je nach einfallender Lichtmenge ändert sich der Widerstandswert.
  • Thermistoren haben einen variablen Widerstand abhängig von der Temperatur, der sie ausgesetzt sind.
  • Drucksensoren (Force Sensing Resistor – FSR) ändern ihren Widerstand in Abhängigkeit mit dem auf sie ausgeübten Druck.
  • u.v.m

Schaltung für Sensoren mit variablen Widerstand

Die Bauteile mit variablen Widerstand werden oft mit einem zweiten Widerstand in Serie geschalten (Spannungsteiler). Die Spannung zwischen diesen beiden Widerständen ist proportional zum Verhalten des variablen Widerstands.

analog_sensor_schaltung_Schaltplan

Der Widerstand des Sensors ist zwar variabel, bewegt sich aber zwischen einem Minimum und einem Maximum. Diese Werte können entweder aus dem Datenblatt abgelesen werden oder mit einem Multimeter gemessen werden.

Abhängig von diesen Werten muss der zweite, feste Widerstand (Referenzwiderstand) dimensioniert werden. Ziel ist es den Messbereich des analogen Inputs des Arduinos möglichst gut auszunutzen. (Erläuterung: Die analog-Digital-Wandler des Arduinos arbeiten mit einer Auflösung von 1024 Werten (10 Bit) zwischen Minimum (0V) und Maximum (5V)). Wenn die gemessenen Werte beispielsweise zwischen 200 und 300 liegen, dann wird der Gesamtbereich des ADCs nicht gut ausgenutzt. Über einen passenden Referenzwiderstand am Spannungsteiler kann dieser Bereich vergrößert werden, wobei auch mit der Wahl eines idealen Widerstands nie der gesamte Bereich zw. 0 und 1023 ausgenutzt werden wird.)

Gerne wird zur Findung des Referenzwiderstandswerts die Benz-Formel (nach Axel Benz) verwendet:

benz_formel

Beispiel 1: Lichtmessung mit einem LDRs

Folgender Aufbau demonstriert die Funktionsweise eines LDRs. Er variiert seinen Widerstand mit dem einfallenden Licht. Um die Größe des Referenzwiderstands zu ermitteln messen wir zunächst den Widerstand des LDRs bei Dunkelheit (max) und bei Lichteinfall (min). Für den hier verwendeten LDR werden folgende Werte gemessen:

  • Minimum = 570 Ω
  • Maximum = 4,7 kΩ

Mit der Benz-Formel wird der Referenzwiderstand berechnet

benz_formel_werte

Ein solcher Widerstand existiert in keinem zur Verfügung stehenden Sortiment, daher wird der nächstliegende gewählt – in diesem Beispiel ist das 1.5 kΩ.

analog_sensor_schaltung_Steckplatine

Folgender Code basiert auf dem Beispiel File -> Examples -> 03.Analog -> AnalogInOutSerial (das Beispiel wurde leicht modifiziert). Nach dem Upload auf das Board kann man im Serial Monitor die gemessenen Werte beobachten.

Hält man nun den LDR mit der Hand zu, dann steigen die gemessenen Werte aufgrund der Widerstandsänderung an. Wir haben unseren ersten Sensor implementiert.

Code-Analyse:

In diesem Beispiel wird in Zeile XX die Spannungsmessung mit analogRead  vorgenommen. Anschließend wird der gemessene Wert noch mit der map-Funktion (unten beschrieben) skaliert und in die Variable outputValue  gespeichert. Anschließend erfolgt eine Ausgabe der beiden Variablen am Serial Monitor.

Die map-Funktion

Die Widerstände der Bauteile sind zwar variabel, bewegen sich aber immer innerhalb eines bestimmten Bereichs. Dementsprechend sind auch die gemessenen Spannungen immer in einem bestimmten Bereich, der aber nicht die ganze Größe des Bereichs (0-1023) ausnützt. Eine Möglichkeit die gemessenen Werte auf einen anderen Bereich zu skalieren (d.h. vergrößern oder verkleinern) ist die map -Funktion.

Für die map -Funktion werden drei Dinge definiert:

  • Ein zu konvertierender Wert
  • Der Eingangs-Wertebereich (definiert über ein Minimum und ein Maximum)
  • Der Ausgangs-Wertebereich (definiert über ein Minimum und ein Maximum)Der zu konvertiertende Wert ist Verhältnis zum Eingangswertebereich zu sehen. Der Wert wird nun so verändert, dass er im selben Verhältnis im Ausgangswertebereich zu sehen ist.

map(Wert, InputMin, InputMax, OutputMin, OutputMax)

Beispiel 1

InputMin: 200, InputMax: 600
OutputMin: 0, OutputMax1023

Wert: 200 -> gemappter Wert: 0
Wert: 600 -> gemappter Wert 1023
Wert: 400 -> gemappter Wert: 511
Wert: 500 -> gemappter Wert: 767

Beispiel 2

InputMin: 200, InputMax: 600
OutputMin: 255, OutputMax 0

Wert: 200 -> gemappter Wert: 255
Wert: 600 -> gemappter Wert 0
Wert: 400 -> gemappter Wert: 128
Wert: 500 -> gemappter Wert: 64

Hier der zugehörige Test-Code:

 

Aufgabe

[VIDEO]

  • Passen Sie Beispiel 1 so an, dass die gemessenen Eingangswerte den Ausgangsbereich 0 bis 255 möglichst gut abdecken.
  • Ersetzen Sie den LDR durch einen Thermistor, passen Sie den Widerstandswert entsprechend an und modifizieren die Werte der Map-Funktion wie in der vorigen Aufgabe.

Serielle Kommunikation am Arduino

Inhalte

  • Serielle Kommunikation am Arduino in der Praxis

Einführung

Das Arduino verfügt über eine Kommunikationsschnittstelle, die als serieller Port oder UART (Unviversal Asynchronous Reciever Transmitter) bezeichnet wird. Dieser erlaubt es dem Mikrocontroller mit anderen Geräten über ein simples, digitales Protokoll zu kommunizieren.
Dieser Artikel befasst sich mit der praktischen Anwendung dieser Schnittstelle. Die Theorie zur Funktionsweise von seriellen Protokollen wird in [ARTIKEL] behandelt.

Asynchrone Serielle Protokolle sind in der Welt der Mikrocontroller sehr weit verbreitet und werden gerne verwendet um Daten auszutauschen oder Steuerbefehle zu versenden.

Anwendung der seriellen Kommunikation

Bei der seriellen Kommunikation am Arduino werden Daten in Byte-Größe übertragen. Um eine funktionierende Kommunikation zwischen zwei Geräten herzustellen müssen beide manuell mit denselben Parametern konfiguriert werden. Der wichtigste Parameter ist die Geschwindigkeit der Datenübertragung, die in Bits pro Sekunde (bps) angegeben wird.

Zeichen vom Arduino senden

Beispiel 1.1: Senden vom Arduino an einen PC

Eine Verbindung zwischem den Arduino und dem PC kann über die USB-Schnittstelle des Arduinos hergestellt werden. Hierbei gibt sich das Arduino am PC als virtuelle serielle Schnittstelle zu erkennen.

Laden Sie zunächst folgenden Code auf das Arduino.

Öffnen Sie anschließend den Serial Monitor in der Arduino IDE und stellen Sie sicher, dass dieser ebenfalls auf 9600 bps konfiguriert ist.

serial_monitor
Durch Klick auf „Serial Monitor“ öffnet sich dieses als Fenster
serial_monitor_window
Der „Serial Monitor“. Unten rechts befindet sich das Drop-Down Menu, mit dem die Kommunikationsgeschwindigkeit des PCs angepasst werden kann.

Code-Analyse:

Um den seriellen Port verwenden zu können muss er zunächst initialisiert werden. In der Setup-Funktion geschieht dies mit dem Befehl Serial.begin(9600), wobei 9600 die Übertragungsgeschwindigkeit in bps ist, die für die folgende Kommunikation des Arduinos definiert wird.

In der Funktion loop wird anschließend regelmäßig durch den Aufruf der Funktion Serial.println()  ein Datenwert von der seriellen Schnittstelle versandt. Dieser Wert wird in der Arduino IDE im seriellen Monitor ausgegeben.

Serial.println()  und Serial.print()

Zum Senden von Zeichen können zwei Kommandos verwendet werden:

  • Serial.print()  versendet die angegebenen Zeichen oder Werte
  • Serial.println()  (lies: print line) versendet die Zeichen und schließt die Zeichen mit einem Zeilenumbruch ab. D.h. alle folgenden Zeichen beginnen erst in der nächsten Zeile.

Beispiel 1.2: print und println

Im folgenden Beispiel wird der Befehl Serial.print()  mehrmals hintereinander mehrfach aufgerufen, um Zeichen nebeneinander in einer Zeile auszugeben. Mit dem aufruf von Serial.println()  wird die Zeile abgeschlossen und eine neue Zeile beginnt.

Code-Analyse

Im der Funktion loop  wird zunächst mit Serial.print("ANFANG: ");  Die Zeichenkette Anfang:  an den seriellen Port geschickt. Die Anführungszeichen "  am Anfang und Ende der Zeichenkette sind notwendig, damit das System ANFANG: nicht für einen Variablennamen hält sondern als Zeichenkette interpretiert.

In der folgenden for -Schleife wird der Serial.print()  Befehl mehrmals hintereinander ausgeführt. Beachten Sie, dass bei der Ausgabe alle Befehle in der selben Zeile erscheinen.

Die Zeile Serial.println("ENDE");  schließt den loop ab. Da println  automatisch einen Zeilenumbruch einfügt, werden die folgenden Zeichen in der nächsten Zeile begonnen.

Zeichen und Zahlen

Beide Funktionen können als Argument verschiedene Typen von Variablen annehmen. Je nach Variablentyp ist die Ausgabe eventuell unterschiedlich. Während die Ausgabe von byte , int , float , … den Zahlenwert ausgibt, wird bei char das ASCII-Zeichen des Werts ausgegeben.

Befehle am Arduino empfangen

[VIDEO Zeichen empfangen]

Das Arduino kann auch Befehle von einem anderen Gerät, beispielsweise von einem PC oder einem anderem Arduino empfangen. Der Mikrocontroller besitzt hierfür einen kleinen Pufferspeicher, in dem die empfangenen Zeichen zwischengespeichert werden, bis sie von aus dem Speicher geholt werden (d.h. als ProgrammiererIn muss man sich selber darum kümmern).

Die wichtigsten Befehle hierfür lauten

Serial.available()  : Überprüft, ob ein oder mehrere neue Zeichen angekommen sind. Rückgabewert ist die Anzahl der Zeichen bzw. -1, wenn kein Zeichen verfügbar ist.

Serial.read()  : Liest das erste Byte (Zeichen) im Pufferspeicher aus. Dabei wird es aus dem Puffer entfernt.

Serial.peek()  : Liest das erste Byte im Puffer aus, belässt es aber im Speicher. (Wird daher peek mehrmals hintereinander aufgerufen, dann kann bekommt man immer dasselbe Zeichen zu sehen).

Beispiel 2.1: Senden von einem PC an das Arduino

Im folgenden Beispiel kann man über den Serial Monitor eine Ziffer an das Arduino schicken. Die LED blinkt dann so oft, wie der Wert des Zeichens ist. Zu beachten ist, dass die Übertragung als ASCII Zeichen geschieht. Der Buchstabe „1“ hat in der ASCII-Tabelle den Wert 49. Diese Differenz muss durch eine Subtraktion ausgeglichen werden.

Code-Analyse

Die Variable receivedValue  vom Typ Byte wird verwendet um die Zahlenwerte des empfangenen Zeichens zu speichern.

Die erste if -Abfrage prüft, ob im Pufferspeicher ein Zeichen empfangen wurde. Wenn Serial.available() > 0  ergibt, dann geht es im if -Block weiter. Zunächst wird das erste vorhandene Zeichen ausgelesen, in der Byte-Variable receivedValue  gespeichert und gleich wieder mittels println()  ausgegeben (in der Ausgabe erkennt man gleich, dass das ASCII-Zeichen einer Ziffer nicht gleich dem Wert der Ziffer ist, z.B. 2 -> 50).

Da nur Ziffern als gültig erachtet werden, prüft eine weitere if-Abfrage, ob das Zeichen tatsächlich eine Ziffer ist, also im Bereich 48 (0) bis 58 (9) ist. Ist dies der Fall, wird mittels Subtraktion der Wert der Variable dem tatsächlichen Ziffernwert angepasst. Anschließend wird die LED dementsprechend oft geblinkt.

Zusammenfassung

Die serielle Schnittstelle wird zum Senden von Binärdaten vom und zum Arduino verwendet. Sender und Empfänger müssen auf dieselben Parameter (z.B. Übertragungsgeschwindigkeit) konfiguriert werden. Die gesendeten Zeichen verwenden die Werte des ASCII-Codes.

Weiterführende Links