Interrupts

Inhalte

  • Interrupts als Trigger um Funktionen sofort aufzurufen

Einführung

Viele Anwendungen sind so konzipiert, dass sie die meiste Zeit eine bestimmte Funktion erfüllen und nur beim Eintritt eines definierten Ereignisses eine bestimmte andere Funktion ausführen müssen (Ein Beispiel hierfür wäre ein Kaffeeautomat, der die meiste Zeit im Leerlauf ist, aber bei einem Münzeinwurf in einen anderen Modus wechselt).

Für manche Anwendungen ist es wichtig, dass sie – egal was sie gerade ausführen – unterbrochen werden können (etwa um einen Not-Stopp einer Anlage ausführen zu können).

Da diese Art von Unterbrechungen oder Wechsel sehr gerne zum Einsatz kommen, haben viele Mikrocontroller eine spezielle Routine hierfür vorgesehen: Interrupts.

Interrupts an Pins

Interrupts sind Funktionen, beim Eintreffen eines bestimmten Ereignisses sofort ausgeführt werden (egal, wo man sich in der Ausführung eines Programms gerade befindet). Das Arduino hat die Eigenschaft, dass die Auslösung eines Interrupts an bestimmte digitale Pins gekoppelt werden können (am Arduino UNO sind dies Pins 2 und 3). Diese Pins werden überwacht und können den Prozessor unterbrechen um eine definierte Methode, die Interrupt Service Routine (ISR), auszuführen. Diese ISRs müssen sehr kurz und schnell ausführbar gehalten werden um den Prozessor nicht zu lange zu blockieren. Dafür sind sie aber auch für Anwendungen mit sehr schnellen Zustandswechsel wie Drehencoder geeignet.

Beispiel 1: Interrupts an digitalen Pins anwenden

Folgender Code demonstriert die Einbindung einer ISR und die Verbindung der Routine mit dem digitalen Pin. Beim Anhängen an der Routine an den Pin wird außerdem definiert, welches Ereignis die ISR auslösen soll. Dieses Ereignis bezieht sich auf die Flanke des Pins. Zur Wahl stehen

  • RISING (steigende Flanke)
  • FALLIN (fallende Flanke)
  • CHANGE (also bei steigender oder fallender Flanke) und
  • LOW (ISR wird immer ausgeführt, solange der Pin LOW ist).

Das Programm an sich macht nichts anderes, als einen Pin zu überwachen und bei einem Wechsel des Zustands, die ISR aufzurufen. Diese wiederrum invertiert nur den Zustand einer Variable, die auf den ledPin 13 geschrieben wird.

Code-Analyse

Die Variable int state  wird als volatile  definiert. Dies sollte für alle Variablen gemacht werden, die in einer ISR verändert werden (sie werden hierfür in einem speziellen Speicherbereich abgelegt und können somit auf jeden Fall korrekt upgedatet werden).

Die Methode attachInterrupt(digitalPinToInterrupt(interruptPin), myISR, CHANGE) definiert den Interrupt-Pin, die ISR für diesen Pin und auf welchen Zustand geachtet werden soll:

  • Interrupt-Pin: Hierbei wird empfohlen, dass die Pin-Nummer nicht direkt angegeben wird, sondern stattdessen die Pin-ID über die Methode digitalPinToInterrupt(interruptPin)  herausgefunden wird. Grund hierfür ist, dass bei verschiedenen Arduino Plattformen (neben dem UNO existieren noch weitere) die Prozessoren möglicherweise andere  Bezeichnungen für diese Pins haben. Mit dem Aufruf dieser Methode ist garantiert, dass der Code auch auf anderen Boards wie erwartet läuft.
  • ISR: Der Name der Methode (hier myISR ) wird als Parameter angegeben.
  • Zustand: Durch die Angabe von CHANGE  wird die vorhin angegebene Methode immer dann aufgerufen, wenn der Zustand wechselt (egal ob von LOW zu HIGH oder umgekehrt).

In der loop  Methode geschieht nichts, außer dass der Zustand der Variable state  auf einen Pin geschrieben wird.

Die Methode myISR  ist eine sehr kurz gehalten und erfüllt hier nur die Funktion, dass der Zustand der Variable state  gewechselt wird. Da myISR  durch den Interrupt immer dann aufgerufen wird, wenn sich der Zustand des Interrupt-Pin ändert, ist es nicht notwendig die Funktion irgendwo anders im Code aufzurufen.

PinChangeInterrupts (PCI)

Am Arduino können für die alle Pins (nicht nur 2 und 3 wie beim UNO) mit einem PinChangeInterrupt belegt werden. Diese lösen sowohl bei RISING als auch FALLING aus, allerdings werden die ISRs immer in Gruppen von Pins definiert (d.h. mehrere Pins haben dieselbe Interrupt-Routine). Für manche Anwendungen wie z.B. Drehencoder ist dies egal, für andere muss eventuell weitere Funktionalität eingefügt werden um zwischen den Pins unterscheiden zu können.

Einen guten Überblick über PCIs gibt dieser Artikel.

Zusammenfassung

Interrupts am Arduino bieten die Möglichkeit eine ISR sofort und sehr schnell auszuführen. Mittels der Funktion attachInterrupt  wird die ISR Funktion, der Pin und der Zustandswechsel definiert. Nick Gammon empfiehlt für den Code in der ISR folgendes:

 

  • Code kurz halten
  • delay -Funktion nicht verwenden
  • Keine Serial prints
  • Variablen, die im Hauptcode und der ISR verwendet werden müssen volatile  sein
  • Interrupts sollen nicht ein- und ausgeschalten werden

 

Weiterführende Links

  • https://www.arduino.cc/en/Reference/Volatile
  • https://www.arduino.cc/en/Reference/AttachInterrupt
  • http://gammon.com.au/interrupts
  • http://playground.arduino.cc/Main/PinChangeInterrupt

 

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

Arduino Datentypen (1)

Inhalte

  • Datentypen in Arduino (Auswahl)
    • integer ( int)
    • floating point ( float)
    • boolean ( boolean)
    • character ( char), byte ( byte)
  • Das Schlüsselwort unsigned
  • Type Casts

Einführung

Die Arduino-Entwicklungsumgebung verwendet die Programmiersprache C um seinen Prozessor programmieren zu können. C ist eine sogenannte statisch typisierte Sprache, d.h. eine Variable bekommt bei seiner Initialisierung einen Typ, der nicht später mehr geändert werden kann.

Der Wertenbereich einer Variablen ist begrenzt, d.h. Variablen können nur Werte bis zu einer bestimmten Größe aufnehmen.

Die wichtigsten Datentypen für Variablen sowie deren Verwendung sind im Folgenden angeführt. Darüber hinaus existieren noch eine Reihe von weiteren Typen, die später in Arduino Datentypen 2 behandelt werden.

Integer ( int)

Eine Integervariable kann nur ganze Zahlen abspeichern. Der Wertebereich am Arduino UNO beträgt – 2^15 bis 2^15 – 1 , also -32 768 bis 32 767. (Dieser Wertebereich entspricht 16 Bit).

Integervariablen können keine Zahlen mit Nachkommastellen aufnehmen. Eine Berechnung wie folgende führt also zu einem falschen Ergebnis:

Die Division von 5 / 2 sollte eigentlich 2.5 ergeben. Die Integervariable nimmt aber nur ganze Zahlen auf, weshalb in der Variable (wahrscheinlich) 2 abgespeichert wird.

Arduino Referenz: https://www.arduino.cc/en/Reference/Int

Floating Point ( float)

Fließkommazahlen (engl. floating point) werden im Speicher anders abgelegt als Integer-Zahlen. Sie benötigen mehr Speicher und sind bei Berechnungen für den Prozessor aufwendiger zu behandeln (man sollte sie daher nur dann einsetzen, wenn es nicht anders möglich ist). Allerdings haben Sie einen wesentlich größeren Wertebereich und können Zahlen mit Nachkommastellen abspeichern.

Um ganze Zahlen als Fließkommazahlen zu behandeln, versieht man sie mit einem Komma-Punkt. Will man Integer-Variablen als Fließkommazahlen behandeln, benötigt man einen Typecast (siehe weiter unten), mit dem ein Variablentyp in einen anderen umgewandelt wird.

Arduino Referenz: https://www.arduino.cc/en/Reference/Float

Boolean ( boolean)

Boolsche Variablen können nur den Wert 0 oder 1 aufnehmen. Sie werden gerne verwendet um binäre Zustände wie true oder false, HIGH oder LOW o.ä. zu kennzeichen.

boolean booleanVariable = 0;

Arduino Referenz: https://www.arduino.cc/en/Reference/BooleanVariables

Character ( char)

Characters sind ganze Zahlen mit einem sehr kleinem Wertebereich von -128 bis 127. Sie belegen intern nicht viel Speicher (8 Bits), werden aber gerne verwendet um einzelne Buchstaben abzuspeichern. Dazu wird jeder Buchstabe einem Zahlenwert zugeordnet. Die ASCII-Tabelle enthält einen Überblick über diese Zuordnungen:

Der Buchstabe A hat also den Dezimalwert 64 zugeordnet.

Initialisiert kann ein Char daher auf mehrere Weisen:

Beide Zuweisungen sind gleichwertig. Bei der Initialisierung mit Buchstaben muss dieses in Anführungszeichen gestellt werden.

Arduino Referenz: https://www.arduino.cc/en/Reference/Char

Das Schlüsselwort unsigned

Ganzzahlige Variablentypen können auch in einer Variante ohne Vorzeichen verwendet werden. (Negative Zahlen können daher nicht in diesen Variablen abgelegt werden). Vorteil dieser Variablen ist aber, dass sie im positiven dann einen größeren Wertebereich abdecken können. Bei einer Integer verschiebt sich etwa der Wertebereich von -32 768 bis 32767 zu 0 bis 65535.

Das Schlüsselwort unsigned  dient dazu, eine Variable zu einer vorzeichenlosen Variablen zu machen.

Eine Ausnahme existiert für den Datentyp Character (siehe folgender Abschnitt).

Byte ( byte)

Der Datentyp Byte entspricht von der Größe her (8 Bit) dem Character. Er ist allerdings per Definition vorzeichenlos, sodass sein Wertebereich zwischen 0 und 255 liegt.

Type Casts

Manchmal ist es notwendig eine Variable von einem Typ in einen anderen umzuwandeln. Hierzu verwendet man einen Type Cast. Dieser wird durchgeführt, indem man vor den Variablennamen in runden Klammern den neuen Typ anführt

Achtung: Bei der Umwandlung von Werten, die nicht in einen anderen Wertetyp passen, kommt es möglicherweise zu einer Stutzung des Werts:

 

Aufgaben

[TBD]