8Automatisches Gartenbewässerungssystem

In Kapitel 6 hast du das, was du über Arduino gelernt hast, mit einem Projekt, der Arduino-Netzwerk-Lampe, kombiniert. Teil des Vergnügens war, einige der einfachen Übungen mit einem praktischen Projekt zu verknüpfen. Du hast auch die Programmiersprache Processing kennengelernt und wie man sie zur Einrichtung eines Proxys auf deinem Computer verwenden kann, um Dinge zu tun, die mit deinem Arduino schwierig oder gar unmöglich wären.

In diesem Kapitel wirst du erneut einfache Beispiele mit einigen neuen Ideen verknüpfen, um ein praktisches Projekt durchzuführen. Unterwegs lernst du mehr zu Elektronik, Kommunikation und Programmieren, und wir werden einen Blick auf Konstruktionstechniken werfen.

Das Ziel dieses Projekts ist, automatisch jeden Tag das Wasser zur richtigen Zeit ein- und auszuschalten, außer wenn es regnet.

image

Auch wenn du keinen Garten besitzt, kannst du dennoch mit diesem Projekt Spaß haben. Wenn du nur eine kleine Hauspflanze hast, die du gießen möchtest, versuche dies mit nur einem Ventil zu bauen. Wenn du jeden Tag um 17 Uhr ein leckeres Getränk deiner Wahl zapfen möchtest, ziehe den Einsatz einer für Lebensmittel geeigneten Pumpe statt des Wasserventils in Erwägung. Adafruit verkauft zum Beispiel eine peristaltische Flüssigkeitspumpe mit Silikonschlauch (https://www.adafruit.com/product/1150).

Als Professor lehre ich viele Studenten, etwas zu bauen. Mit der Zeit wurde mir bewusst, dass Studenten manchmal denken, ich würde von Anfang an genau wissen, wie ein Projekt zu bauen sei. Tatsächlich ist die Entwicklung eines Projekts ein Vorgang, der extrem iterativ, also nur Schritt für Schritt vorangeht.

– Michael

Um ein Projekt neu anzulegen, beginne mit einer Idee und skizziere kleine Teile davon; dabei erfordert dies manchmal Änderungen an der ursprünglichen Idee. Wir müssen häufig einen Umweg machen, um zu lernen, wie ein neues Elektronikteil funktioniert, oder um ein Programmierkonzept zu verstehen, dem wir zuvor nie begegnet sind, oder um uns selbst daran zu erinnern, wie ein Merkmal des Arduino zu nutzen ist, das wir lange nicht genutzt haben oder das uns neu ist. Manchmal müssen wir in Fachbüchern nachschlagen, im Internet schauen oder jemanden um Rat bitten. Wir prüfen viele Beispiele, Anleitungen und Projekte, die etwas von dem enthalten, womit wir es zu tun haben. Wir entnehmen Teile von hier und da und kombinieren sie – vielleicht zunächst nur sehr grob, wie bei Frankensteins Monster, um zu sehen, wie Dinge zusammen funktionieren.

Während das Projekt vom Konzept über grobes Design bis zum Testen von Teilen der Hardware und Software immer weiter voranschreitet, müssen wir immer wieder zurückschauen und Änderungen an etwas vornehmen, das wir vorher getan haben, damit alles richtig zusammenarbeitet. Wir kennen keinen einzigen Ingenieur, der mit einem leeren Blatt Papier anfängt, ein ganzes Projekt von Anfang bis Ende entwirft, das dann genau nach Plan funktioniert, ohne jemals zurückzugehen und etwas ändern zu müssen.

Alles zuvor Genannte gilt sowohl für Hardware als auch für Software.

Was wir damit ausdrücken wollen: Auch du als Anfänger bist bereit, Projekte zu entwerfen. Beginne mit dem, was du weißt, und füge langsam Funktionen hinzu, eine neue Idee oder ein neues Teil nach dem anderen. Hab keine Angst, spannenden Ideen nachzugehen, die keinen unmittelbaren Nutzen haben.

Wann immer ich von einem Elektronikteil, einem Programmierkonzept oder einem interessant klingenden Trick höre, probiere ich es aus, auch wenn ich dafür keinen sofortigen Nutzen habe. Dieses Wissen wird dann ein weiteres Werkzeug in meinem Werkzeugkasten. Wenn du nicht mehr vorankommst oder etwas nicht weißt, denk dran, dass selbst professionelle Ingenieure ständig Neues lernen müssen.

– Michael

Dank der breiten und großzügigen Arduino-Community hast du über das Internet viele Ressourcen zur Verfügung, und solange du kein Eremit auf einer Bergspitze bist, kannst du wahrscheinlich in deiner Gegend ein Arduino Meetup, einen Club, Makerspace, Hackerspace oder sogar einen Menschen finden, der dir helfen kann.

Zu einigen Hinweisen, wie du das Beste aus Online-Ressourcen machst, schau unter »Online Hilfe bekommen« auf Seite 226 nach.

Ergänzend zum weiteren Lehren zu Elektronik, Programmieren und Konstruktion werde ich dir einen Einblick in den Entwurfsvorgang bieten. Du wirst sehen, dass manche der einfachsten Schaltungen oder Sketche immer wieder modifiziert werden, bis wir endlich beim angezielten Projekt ankommen. Dennoch habe ich einige Schritte ausgelassen, um zu vermeiden, dass dieses Kapitel ein ganzes Buch für sich wird. Iterationen brauchen Zeit!

Planung

Beginne wie in Kapitel 6 darüber nachzudenken, was du erreichen willst und welche Teile du benötigst.

Dieses Projekt verwendet gewöhnliche elektrische Wasserventile für den Garten, die es in jedem Baumarkt gibt. Wenn du schon im Laden bist, kannst du noch eine Stromquelle oder einen Transformator besorgen, der sich für diese Ventile eignet. In Kapitel 5 hast du den Einsatz eines MOSFET zum Steuern eines Motors erlernt. Das könnte auch für die Wasserventile funktionieren, allerdings könnten manche Wasserventile Wechselstrom (AC) benötigen, und MOSFETs können nur Gleichstrom (DC) steuern. Um Wechselstrom steuern zu können, benötigst du ein Relais, das sowohl Wechsel- als auch Gleichstrom steuern kann.

image

In »Antrieb größerer Lasten (Motoren, Lampen und dergleichen)« auf Seite 78 in Kapitel 5 hast du gelernt, dass ein MOSFET eine Art Transistor ist, in dem der Gate-Pin steuern kann, ob Strom zwischen den Drain- und Source-Pins fließt. In diesem Sinne ist ein MOSFET ein Schalter. Ein Relais ist auch ein Schalter. Im Relais befindet sich ein winziger mechanischer Schalter, der von einem Elektromagneten gesteuert wird: Durch Ein- und Ausschalten des Elektromagneten kannst du steuern, ob Strom durch den mechanischen Schalter fließt.

Um zu wissen, wann das Wasser ein- und ausgeschaltet werden soll, benötigen wir eine Art Uhr. Du könntest das in unserem Programm mit der im Arduino integrierten Zeitschaltuhr versuchen, aber das wäre kompliziert. Und noch schlimmer: Sie ist nicht besonders genau. Tatsächlich existiert ein solches Gerät, ist recht kostengünstig und lässt sich leicht mit Arduino einsetzen. Die RTC (Real Time Clock, zu Deutsch Echtzeituhr) ähnelt dem Gerät in deinem Computer, das Datum und Zeit im Auge behält, wenn du ihn längere Zeit ausgeschaltet lässt.

Wir benötigen außerdem einen Sensor, der uns sagt, ob es regnet. Wir verwenden einen Temperatur- und Feuchtigkeitssensor, da diese preisgünstig und leicht einsetzbar sind. Wir müssen die Temperatur nicht unbedingt wissen, aber du erhältst die Zusatzfunktion »frei Haus«, und sie könnte sich als nützlich erweisen.

Abschließend benötigen wir eine Möglichkeit, die Ein- und Ausschaltzeiten einzustellen, d. h. die Benutzerschnittstelle. Damit dieses Projekt nicht aus dem Ruder läuft, verwenden wir den seriellen Monitor als Benutzerschnittstelle. Wenn du mit der Zeit Arduino besser beherrschst, könntest du diesen durch ein LCD-Display und einen Drucktaster ersetzen.

Bevor du mit dem Programmieren beginnst, musst du darüber nachdenken, wie die Hardware angeschlossen werden soll. Ich benutze gern ein grobes Blockdiagramm, das mir hilft, alle benötigten Teile zu sehen und mir zu überlegen, wie sie verbunden werden sollten. Am Schluss musst du genau wissen, wie die Dinge anzuschließen sind, aber im Blockdiagramm (Abb. 8–1) verwenden wir nur einen Strich, um eine Art von Verbindung zu symbolisieren.

image

Abb. 8–1Blockdiagramm mit Echtzeituhr, Arduino, MOSFETs, Relais, Ventilen und Stromzufuhr

In diesem Diagramm gehen wir von drei separaten Wasserventilen aus, aber du siehst, wie davon abgeleitet werden kann, je nachdem, wie dein Bedarf aussieht.

Da dies ein fortgeschritteneres Projekt ist, werde ich Konstruktionstechniken vorstellen. Dieses Projekt soll monate- oder sogar jahrelang zuverlässig arbeiten – du hast hier also ein anderes Ziel als bei einem simplen Beispiel, das dir nur zeigen soll, wie etwas funktioniert. Die lötfreie Steckplatine, die du vorher genutzt hast, eignet sich hervorragend fürs Prototyping oder Experimentieren, aber aus Gründen der Zuverlässigkeit löten wir bei diesem Projekt die Komponenten auf ein Proto-Shield. Wir werden uns sowohl Gedanken über die Stromzufuhr machen als auch über alle Verbindungen zu den diversen externen Teilen wie die Wasserventile. Wir schauen uns sogar an, wie dieses Projekt mit einer Art von Gehäuse geschützt werden kann.

image

Bei Shields handelt es sich um Boards, die genau in die Pins des Arduino passen und zusätzliche Funktionen bereitstellen. Das Arduino Proto-Shield ist ein spezielles Shield, das dazu vorgesehen ist, dass du darauf deine eigene Schaltung aufbauen kannst.

Eine weitere Funktion, die für deine an Komplexität zunehmenden Projekte nützlich ist, ist eine Zustandsanzeige. Das ist beim Debugging hilfreich und insbesondere dann, wenn Teile deines Systems weit entfernt sind, wie die Wasserventile. Wir fügen LEDs hinzu, um anzuzeigen, dass die Wasserventile aktiviert sind. Vergiss nicht die Widerstände für die LEDs.

Nun, da wir einige weitere Details haben, lege ich gern eine vorläufige Einkaufsliste an. Bei komplexen Systemen gehe ich davon aus, dass ich Änderungen vornehmen muss: Wenn ich zum Beispiel am Sketch arbeite, stelle ich vielleicht fest, dass ich ein weiteres Teil benötige. (Die abschließende, vollständige Einkaufsliste findet sich als »Einkaufsliste für das Bewässerungsprojekt« auf Seite 188.)

Keine Sorge, wenn du nicht alle diese Teile kennst. Wir gehen im Weiteren detailliert auf sie ein:

Jetzt, wo du eine vorläufige Liste hast, lass uns jedes Teil ansehen und die Details ausarbeiten. Lass uns mit der RTC beginnen

Testen der Echtzeituhr (RTC)

Wenn ich den Einsatz eines mir neuen Gerätes plane, dann möchte ich zuerst sicherstellen, dass ich dessen Funktion verstehe, bevor ich das vollständige System entwerfe. Da die RTC für dich neu ist, schauen wir uns an, wie sie funktioniert.

Das wesentliche Teil einer RTC ist der Chip selbst. Der am häufigsten verwendete ist der DS1307, der für die korrekte Zeiteinhaltung einen Quarzkristall und eine Batterie benötigt, damit er weiterläuft, wenn der Rest des Systems abgeschaltet ist. Statt dieses selbst zu bauen, verwenden wir eins der vielen verfügbaren RTC-Module, was uns – für kleines Geld – Zeit einspart.

DS1307-RTC-Module sind von vielen verschiedenen Quellen erhältlich. Zum Glück funktionieren sie alle ähnlich und stellen auf ähnliche Weise Schnittstellen her. Ich entschied mich für das TinyRTC, erhältlich z. B. bei Reichelt Elektronik.

image

Abb. 8–2TinyRTC-Echtzeitmodul

Beachte, dass die Pins nicht immer enthalten sind; diese musst du gegebenenfalls separat bestellen und anlöten. Du kannst Pins und Stiftleisten aus vielen Quellen beziehen – Adafruit-Teil #392 schließt beispielsweise eine Menge Pins für dieses und zukünftige Projekte ein (https://www.adafruit.com/product/392), und Händler wie Reichelt, Segor und Watterott bieten vergleichbares für den deutschen Markt an.

image

Wenn Löten für dich neu ist, gibt es einen Link zu einer hervorragenden Anleitung in »Löten deines Projekts auf das Proto-Shield« auf Seite 167.

Dieses Gerät verwendet eine Schnittstelle namens I2C, die manchmal auch Two Wire Interface (Zweidraht-Schnittstelle; TWI) oder einfach Draht (Wire) genannt wird. Arduino stellt eine eingebaute Bibliothek für die I2C-Schnittstelle zur Verfügung (mit dem praktischen Namen Wire), und Adafruit bietet eine Bibliothek für das DS1307 an, RTClib genannt. Zum Installieren der RTClib führe in der Arduino-IDE Folgendes aus:

  1. Öffne den Library Manager durch Wahl von Sketch image Include Library image Manage Libraries.
  2. Gib im Kasten »Filter your search« RTClib ein.
  3. Klicke auf die Schaltfläche »Install« für die RTClib von Adafruit.
  4. Klicke auf die Schaltfläche »Close«.

Über die zu dieser Bibliothek gehörenden Beispiele kannst du prüfen, ob du die Bibliothek korrekt installiert hast. Dafür musst du keine Schaltung bauen, und genau genommen brauchst du noch nicht einmal deinen Arduino griffbereit zu haben. Öffne in der Arduino-IDE das File-Menü und wähle Examples image RTClib image ds1307 zum Öffnen eines Beispielprogramms. Statt die Upload-Schaltfläche anzuklicken, klicke auf die Verify-Schaltfläche (siehe Abb. 4–2). Wenn du die Mitteilung »Done compiling« erhältst, hast du die Bibliothek korrekt installiert.

image

Nach der Installation einer neuen Bibliothek ist es ratsam zu prüfen, ob die Bibliothek und auch alle Bibliotheken, von denen sie abhängig ist, korrekt installiert worden sind, bevor du versuchst, dein eigenes Programm zu schreiben.

Die meisten für Arduino geschriebenen Bibliotheken umfassen Beispiele, und da sie vermutlich von denselben Leuten kommen, die die Bibliothek verfasst haben, werden sie sehr wahrscheinlich korrekt sein.

Das TinyRTC-Modul wird mit zwei Pin-Sätzen geliefert: Einer besteht aus fünf, der andere aus sieben Positionen. Die meisten der Pins sind dupliziert, und andere Pins bieten zusätzliche Funktionen. Zum Testen der RTC musst du dir nur zu vier Pins Gedanken machen: die beiden Pins, die die I2C-Schnittstelle bilden, Strom und Masse. Der RTC müssen über ihren mit VCC gekennzeichneten Pin 5 V zugeführt werden, und ihre Masse (GND) muss an der Masse des Arduino angeschlossen werden.

image

Auf der Hardwareseite wird die I2C durch zwei spezifische Arduino-Pins (SDA und SCL) unterstützt, wie es in der Referenz der Arduino-Wire-Bibliothek (https://arduino.cc/en/reference/wire) beschrieben wird.

Für einen Schnelltest kannst du dich eines gängigen Tricks bedienen: Für Geräte, die sehr wenig Strom benötigen, wie die RTC, kannst du die digitalen Ausgänge für die Stromversorgung nutzen, indem du einen Pin auf HIGH und einen anderen auf LOW einstellst, wenn die Pins korrekt angeordnet sind. Ein I/O-Pin, der als digitaler Ausgang auf HIGH gesetzt ist, entspricht im Prinzip 5 V, und ein I/O-Pin, der als digitaler Ausgang auf LOW gesetzt ist, entspricht im Grunde der Masse.

Auf einem Uno ist SCL A5 und SDA A4. Zusätzlich zu diesen beiden Schnittstellen-Pins müssen wir VCC und GND an irgendwelchen anderen I/O-Pins anschließen, um für 5 V und GND bereitzustellen. Dies lässt sich durch Ausrichten der TinyRTC entsprechend Abb. 8–3 auf den analogen Eingangs-Pins des Arduino Uno bewerkstelligen. Du kannst auch eine Steckplatine und Schaltdraht verwenden, um die Verbindung herzustellen.

image

Abb. 8–3Direkt in die analogen Eingangs-Pins des Arduino Uno gesteckte TinyRTC. Der Versatz ist beabsichtigt und ermöglicht das Einstecken von SCL in A5 und von SDA in A4.

image

Auf manchen Arduinos mit anderen Mikrocontrollern können die I2C-Signale (SDA und SCL) auf anderen Pins sein. Aus diesem Grund erfüllen alle aktuellen Arduinos mit Uno-R3-Plattform ein neues Standard-Pin-Layout, bei dem die I2C-Pins nach AREF hinzugefügt werden. Sie stellen eine Ergänzung dar. Weitere Pins, die als 12C-Pins fungieren, werden dupliziert.

Die analogen Eingänge A4 und A5 kümmern sich um die I2C-Kommunikation, während A2 und A3 für Strom und Masse sorgen. A3 muss 5 V an den mit VCC gekennzeichneten RTC-Pin liefern, weshalb wir ihn auf HIGH einstellen, während A2 für Masse auf LOW gesetzt wird.

Jetzt sind wir für einen Test bereit! Öffne in der Arduino-IDE das File-Menü und wähle Examples image RTClib image ds1307 zum Öffnen des Beispielprogramms. Denk dran, dass du vor dem Kompilieren und Hochladen die Pins A2 und A3 für die Stromversorgung der TinyRTC einrichten musst. Füge die folgenden vier Zeilen gleich ganz oben zu setup() hinzu:

void setup() {

pinMode(A3, OUTPUT);

pinMode(A2, OUTPUT);

digitalWrite(A3, HIGH);

digitalWrite(A2, LOW);

image

Wenn du ein Board verwendest, das anders verdrahtet ist, musst du eine Steckplatine und Schaltdrähte für den Anschluss verwenden, sodass du diese zusätzlichen Codezeilen nicht hinzufügen solltest. Achte nur darauf, dass das Board wie vom Hersteller angewiesen verdrahtet wird.

Du kannst das Beispiel mit dieser Modifikation auch über den Link für die Beispielcodes von der Katalogseite der englischen Originalausgabe des Buches herunterladen (https://makezine.com/go/arduino-4e-github/).

Beachte hier, dass das Beispiel den seriellen Port bei 57.600 Baud öffnet.

Jetzt kannst du hochladen und nach Abschluss des Hochladens den seriellen Monitor öffnen. Prüfe den Auswahlkasten für die Baudrate unten rechts im seriellen Monitor und wähle »57600 Baud«. Die Ausgabe sollte in etwa so aussehen:

2013/10/20 15:6:22

since midnight 1/1/1970 = 1382281582s = 15998d

now + 7d + 30s: 2013/10/27 15:6:52

2013/10/20 15:6:25

since midnight 1/1/1970 = 1382281585s = 15998d

now + 7d + 30s: 2013/10/27 15:6:55

Beachte, dass Datum und Uhrzeit falsch sein könnten, aber die Sekunden sollten hochzählen. Wenn du eine Fehlermeldung erhältst, prüfe, ob die RTC in der richtigen Position ist, das heißt, ob SCL an A5 angeschlossen ist, usw. Kontrolliere außerdem, ob du die Pins A2 und A3 in setup() korrekt eingerichtet hast.

Zum Einstellen der korrekten Uhrzeit schau dir die setup()-Funktion an. An deren Ende siehst du diese Zeile:

rtc.adjust(DateTime(__DATE__,__TIME__));

Diese Zeile nimmt das Datum und die Uhrzeit, zu der der Sketch kompiliert wurde (__DATE__ bzw. __TIME__), und verwendet diese zum Einrichten der RTC. Natürlich kann sie um ein oder zwei Sekunden abweichen, aber das ist für unsere Zwecke ausreichend genau.

Kopiere die Zeile, damit sie außerhalb der if()-Bedingung ist, z. B. gleich unter rtc.begin():

rtc.begin();

rtc.adjust(DateTime(__DATE__,__TIME__));

Kompiliere den Sketch und lade ihn hoch – nun sollte der serielle Monitor das korrekte Datum und die korrekte Uhrzeit anzeigen:

014/5/28 16:12:35

since midnight 1/1/1970 = 1401293555s = 16218d

now + 7d + 30s: 2014/6/4 16:13:5

Wenn natürlich die Einstellung von Datum und Uhrzeit deines Computers falsch ist, spiegelt sich das hier wider.

Nachdem du die Uhrzeit der RTC eingestellt hast, solltest du rtc.adjust auskommentieren (und den Code hochladen), denn sonst stellst du immer wieder die Zeit zu dem Zeitpunkt zurück, an dem der Sketch kompiliert wurde. Die RTC wird nun auf Jahre hinaus diese Zeit beibehalten.

Zu weiteren Informationen zur Bibliothek und zu Beispielen lies Arduino Library und Understanding the Code in der Adafruit-Anleitung zu ihrem DS1307 Breakout Board Kit (https://learn.adafruit.com/ds1307-real-time-clock-breakout-board-kit?view=all).

Beachte, dass trotz der Unterschiede des Adafruit-Boards der Code derselbe ist.

Jetzt, wo du mit der RTC vertraut bist, wenden wir uns den Relais zu.

Testen der Relais

Welche Art von Relais brauchen wir? Das hängt davon ab, wie viel Strom die Wasserventile benötigen. Die meisten Gartenventile scheinen 300 mA zu verwenden. Das ist eine geringe Strommenge, und daher reicht ein kleines Relais aus. Es gibt Relais, die mit unterschiedlichen Spannungen betrieben werden können; wir werden eins verwenden, das mit 5 V arbeitet, damit wir keine weitere Stromversorgung benötigen. Abb. 8–4 zeigt ein beliebtes kleines 5-V-Relais.

image

Abb. 8–45-V-Relais

Fast jedes elektronische Gerät hat etwas, das sich Datenblatt nennt und auf dem alle technischen Details zum Gerät dokumentiert sind. Anfänger können von den vielen Informationen ein wenig erschlagen sein – normalerweise benötigst du aber nur einen winzigen Teil davon. Während du an Erfahrung zulegst, wirst du lernen, was wichtig ist und wie du es schnell findest. Wenn du im Datenblatt für das von uns gewählte Relais nachschaust, siehst du, dass es bis zu 2 Ampere und 30 Volt Gleichstrom (DC) oder 1 Ampere und 125 V Wechselstrom (AC) verarbeiten kann, was für uns mehr als genug ist. Dieses Relais hat auch den Vorteil, dass es mit unserer lötfreien Steckplatine sowie mit dem später von dir verwendeten Proto-Shield kompatibel ist.

Wann immer du etwas mit einem Arduino-Ausgang steuern möchtest, musst du daran denken, dass ein Arduino-Pin nur Geräte mit Strom versorgen sollte, die bis zu 20 mA benötigen (siehe »Antrieb größerer Lasten (Motoren, Lampen und dergleichen)« auf Seite 78). Wenn du im Datenblatt nach dem von diesem Relais benötigten Strom suchst, wirst du nichts finden. Du findest aber den Widerstand. Jetzt musst du ein bisschen rechnen, denn aufgrund der Kenntnis des Widerstands des Relais (125 Ω) und der Spannung, die der Arduino über die I/O-Pins abgibt (5 V), kannst du den Strom mithilfe des Ohm‘schen Gesetzes berechnen, das du gegen Ende von »Was ist Elektrizität?« auf Seite 42 kennengelernt hast. Durch Teilen der Spannung (5 V) durch den Widerstand (125 Ω) erhalten wir den Strom: 40 mA.

Da das über unserem Limit ist, benötigen wir MOSFETs. Zur Abwechslung verwenden wir einen anderen MOSFET als jenen, den wir in »Antrieb größerer Lasten (Motoren, Lampen und dergleichen)« auf Seite 78 eingesetzt haben. Wir verwenden den 2N7000, dessen Datenblatt du auf der Website von Onsemi Semiconductor findest.

Genau wie in »Antrieb größerer Lasten (Motoren, Lampen und dergleichen)« auf Seite 78 wird das Gate vom Arduino-I/O-Pin gesteuert, und Drain und Source bilden den Schalter, der das Relais steuert. Du musst drei 2N7000-MOSFETs zur Einkaufsliste hinzufügen, einen für jedes Relais.

Um zu verhindern, dass die MOSFET-Gates massefrei sind, füge drei 10-kΩ-Widerstände zu deiner Einkaufsliste hinzu, einen für jedes Relais.

image

Wenn du einen Arduino einschaltest oder zurücksetzt, beginnen alle digitalen Pins als Eingänge, bis dein Programm anläuft und dein pin-Mode() etwaige Pins in Ausgänge umwandelt. Das ist wichtig, denn in der kurzen Zeitspanne, bevor die pinMode()-Funktion deinen Pin in einen Ausgang umwandelt, ist das Gate weder HIGH noch LOW: Es ist massefrei, was bedeutet, dass der MOSFET sich einfach einschalten könnte, wodurch das Wasser kurz eingeschaltet werden würde. Auch wenn das bei den meisten Projekten nicht gleich den Weltuntergang bedeutet, ist es eine gute Angewohnheit, dies zu berücksichtigen. Wie in »Antrieb größerer Lasten (Motoren, Lampen und dergleichen)« auf Seite 78 in Kapitel 5 angedeutet, verhindert ein 10-kΩ-Widerstand zwischen I/O-Pin und Masse dies. 10 kΩ ist ein ausreichend kleiner Widerstand, der sicherstellt, dass das Gate nicht »massefrei« ist, aber es ist dennoch groß genug, dass es uns nicht behindert, wenn wir das Wasser aufdrehen wollen.

Ein auf diese Weise verwendeter Widerstand nennt sich Pulldown-Widerstand, weil er das Gate auf Masse »hinabzieht«. Manchmal muss eine Verbindung auf 5 V »hochgezogen« werden; in diesem Fall wird er Pullup-Widerstand genannt.

Wann immer wir ein Relais oder einen Motor steuern, sollten wir eine Diode hinzufügen, um die MOSFETs vor der beim Abschalten des Relais vom kollabierenden Magnetfeld erzeugten Rückspannung zu schützen. Unser MOSFET besitzt zwar eine eingebaute Diode, diese ist jedoch relativ klein. Für die Zuverlässigkeit ist es daher ratsam, eine zusätzliche, externe Diode hinzuzufügen – ein weiterer Posten also für deinen Einkaufszettel, diesmal drei 1N4148-Dioden (oder entsprechende). Wenn wir schon dabei sind, sollten wir die Teilenummer des Relais ergänzen, und weil wir die Art des Relais kennen, können wir auch den korrekten Sockel dafür angeben. Hier sind die Ergänzungen, die die Einkaufsliste in das von uns so genannte Stadium Revision 0.1 bringen:

Klingt fast so, als würde diese Schaltung komplex werden, oder? Es ist schwierig, sich vorzustellen, wie all diese Komponenten miteinander verbunden werden sollen.

Zum Glück gibt es zum Erfassen dieser Information ein schlaues System. Es nennt sich Schaltplan.

Elektronische Schaltpläne

Die meisten elektronischen Schaltungen werden vollständig durch zwei Dinge definiert, nämlich 1. dadurch, welche Komponenten verwendet werden und 2., wie sie verbunden werden. Durch möglichst genaues Erfassen allein dieser Informationen ist ein Schaltplan die beste Methode zur Darstellung und Kommunikation einer elektronischen Schaltung.

Ein Schaltplan gibt bewusst nicht die Größe, Form oder Farbe der Komponenten wieder oder wie sie physisch nebeneinander angeordnet sind, denn diese Informationen sind für die Definition des Schaltplans nicht relevant – sie sind Konstruktionsdetails für eine bestimmte Implementierung dieser Schaltung.

Jede Komponente wird durch ein Schaltplansymbol dargestellt, das eindeutig die Komponente identifiziert, aber nichts hinsichtlich ihrer Größe, Farbe usw. aussagt.

In manchen Fällen sehen Schaltplansymbole den durch sie dargestellten Komponenten sehr ähnlich, manchmal wiederum sehen sie völlig anders aus. Insbesondere das Schaltplansymbol für einen Arduino ähnelt überhaupt nicht einem Arduino. Aus Sicht eines Schaltplans spielt hinsichtlich eines Arduino nur eine Rolle, dass er bestimmte Pins hat (Strom, Eingänge, Ausgänge usw.). Daher wird er als ganz einfacher Kasten gezeichnet, an dem nur die Pins angezeigt werden.

Schaltplansymbole und Schaltpläne sind dafür vorgesehen, ihre Funktionen so schnell und deutlich wie möglich zu vermitteln. Dafür hat sich eine Reihe von Konventionen eingebürgert – zwei der wichtigsten hiervon sind die folgenden:

Das Schaltplansymbol für einen Arduino spiegelt diese Konventionen ebenfalls wider: VIN, 5 V und 3V3 sind oben, GND ist unten und die verschiedenen Regler (RESET, AREF usw.) sind links, weil sie auf dem Arduino Eingänge sind. Obwohl der Arduino drei GND-Pins hat, wird nur einer als Schaltplansymbol dargestellt, da sie elektrisch identisch sind. Da die digitalen und analogen Pins entweder Ein- oder Ausgänge sein können, ist ihre Anordnung etwas willkürlich.

Im Anhang D erfährst du mehr zu Schaltplänen.

Zurück zum aktuellen Projekt: Abb. 8–5 zeigt den Schaltplan für die von uns besprochene Schaltung. Denk dran, dass das Ziel die Bestätigung ist, mittels eines MOSFET ein Relais steuern zu können (obwohl das endgültige System drei Wasserventile haben wird, brauchen wir zur Bestätigung, dass der Plan funktioniert, nur eines zu prüfen).

image

Beachte die Pin-Nummern neben dem Schaltplansymbol für das Relais. Diese sind wichtig, da sie dir mitteilen, welcher Pin drinnen woran angeschlossen ist. Es ist wichtig, dies zu verstehen, um deine Schaltung richtig zu verdrahten. Beachte, dass der Pin-Abstand nicht gleich ist: Pins 1 und 4 sind weiter auseinander als Pins 4 und 8. Beachte auch, dass oben zwischen den Pins 8 und 9 ein schwarzer Streifen ist. Beachte abschließend, dass die Pin-Nummern von unten gesehen werden.

image

Abb. 8–5Schematische Darstellung eines Schaltplans zum Testen der Arduino-Steuerung eines Relais

Als Referenz ist Abb. 8–6 eine Darstellung derselben Schaltung auf einer lötfreien Steckplatine in dem bisher von uns verwendeten Stil: die bildhafte Darstellung eines Schaltplans im Gegensatz zur schematischen Darstellung in Abb. 8–5.

image

Abb. 8–6Bildhafte Darstellung eines Schaltplans zum Testen der Arduino-Steuerung eines Relais

image

Genau wie das Relais muss der MOSFET richtig identifiziert und korrekt verdrahtet werden. Der MOSFET hat eine abgerundete und eine flache Seite, wie in Abb. 8–6 dargestellt. Das ist notwendig, um die richtige Anordnung der Pins anzuzeigen. Diese Anordnung ist eine Besonderheit des 2N7000, die aber nicht unbedingt allgemeingültig ist. Andere MOSFETs könnten eine andere Anordnung der Pins aufweisen. Du musst immer das Datenblatt prüfen, um die Anordnung der Pins für deinen bestimmten MOSFET herauszufinden.

Achte auf die Dioden, den MOSFET und das Relais: Die Dioden sind polarisiert, beim MOSFET muss die abgeflachte Seite in die richtige Richtung weisen, und beim Relais muss der Streifen am richtigen Ende sein, damit alles funktioniert.

Wenn du Abb. 8–5 und Anhang D anschaust, wirst du feststellen, dass einige Komponenten, wie Widerstände, Lichtsensoren und (manche) Kondensatoren, Schaltplansymbole besitzen, die symmetrisch sind – sie sehen also, wenn man sie umdreht, genau gleich aus –, während andere Komponenten, wie LEDs, Dioden und MOSFETs, nicht symmetrisch sind. Widerstände, Lichtsensoren und (manche) Kondensatoren sind unpolarisiert, was bedeutet, dass sie, egal in welche Richtung der Strom durch sie hindurchfließt, gleich arbeiten, während Komponenten wie LEDs und Dioden polarisiert sind, was bedeutet, dass sie abhängig von der Fließrichtung des Stroms unterschiedlich arbeiten. Genauso haben MOSFET-Pins ganz spezielle Funktionen und können nicht synonym genutzt werden. Obwohl das nicht immer gilt, so sind aber im Allgemeinen Komponenten mit einem symmetrischen Schaltplansymbol unpolarisiert, und Komponenten mit einem asymmetrischen Schaltplansymbol sind polarisiert.

Sobald du die Schaltung gebaut hast, besteht der nächste Schritt im Schreiben des Sketches. Zum Testen bevorzuge ich wenn möglich den Einsatz eines der Arduino-eigenen Beispiele, da ich weiß, dass der Sketch korrekt ist. Da das Relais beim Aktivieren ein leises Klickgeräusch von sich gibt, solltest du beim Durchführen des Blink-Beispiels das Relais ein Mal pro Sekunde klicken hören – dazu musst du keine Zeile Code schreiben.

Bestätige vor dem Hochladen des Sketches, dass der Sketch den Pin steuert, der am MOSFET angeschlossen ist. Wenn du dem Plan gefolgt bist, hast du ihn am Pin 13 angeschlossen: genau der Pin, der im Blink-Beispielsketch gesteuert wird. Außerdem wird die LED genau zur selben Zeit blinken, zu der das Relais klicken sollte.

image

Es ist stets ratsam, bevor du den Sketch hochlädst, zu bestätigen, dass die im Sketch verwendeten Pins tatsächlich die von dir verdrahteten Pins sind. Du magst einen absolut korrekten Sketch haben und eine perfekt zusammengebaute Schaltung, aber wenn dein Sketch andere Pins als deine Schaltung verwendet, dann funktioniert es nicht und du könntest auf der Suche nach dem Problem eine Menge Zeit verschwenden.

Wenn du das Relais nicht klicken hörst, prüfe die Tipps zur Fehlerbehebung in Kapitel 11. Denk dran: Das Klicken ist sehr schwach – du musst dein Ohr ganz nah ans Relais halten und solltest dich in einem wirklich ruhigen Raum befinden.

Jetzt können wir das Wasserventil hinzufügen. Das Wasserventil wird am Relais und an seiner eigenen Stromversorgung angeschlossen. Wasserventil und Stromversorgung sind wahrscheinlich mit Litzendraht versehen, der sich so gut wie gar nicht auf einer lötfreien Steckplatine verwenden lässt. Es hat sich als praktisch erwiesen, ein kurzes Stück Massivdraht am Litzendraht zu befestigen, wenn du mit einer lötfreien Steckplatine arbeitest, wie in Abb. 8–7 dargestellt.

image

Abb. 8–7An Litzendraht angelötetes kurzes Stück Massivdraht für Arbeiten auf lötfreier Steckplatine

Du musst die Lötstelle mit Isolierband oder einem Stück Schrumpfschlauch isolieren, um zu verhindern, dass sie versehentlich einen anderen Draht berührt.

image

Wann immer du ein ungeschütztes Metallstück hast, so wie die gerade verbundenen Drähte oder die langen blanken Leiter eines Fotowiderstands oder gar etwas, das nicht Teil der Schaltung ist, wie beispielsweise eine Schraube, musst du dafür sorgen, dass es keine anderen Teile einer Schaltung berühren und eine Verbindung herstellen kann, die du nicht möchtest. Das nennt sich Kurzschluss, und dieser kann deine Schaltung daran hindern, richtig zu funktionieren. Um Kurzschlüsse zu verhindern, isoliere immer alle freiliegenden Drähte oder sichere Dinge so, dass sie sich nicht bewegen und etwas berühren können, das sie nicht berühren dürfen.

Du bist wahrscheinlich schon mit Isolierband vertraut, einer allseits bekannten, preisgünstigen und einfachen Methode. Eine etwas professionellere Technik ist der Einsatz eines Schrumpfschlauchs. Der Schlauch wird zugeschnitten, über die freiliegenden Drähte oder Verbindungen geschoben und dann mit einer Heißluftpistole erwärmt, wodurch der Schlauch schrumpft und sich eng um die Verbindung schmiegt.

An dieser Stelle merkst du vielleicht, dass du eine Möglichkeit finden musst, um diese Verbindung beim Bau des endgültigen Systems herzustellen. Hierfür gibt es viele Wege, aber hochwertige Schraubklemmen sind eine gute Wahl, wie in Abb. 8–8 dargestellt.

image

Abb. 8–8Hochwertige Schraubklemme mit zwei Positionen

Nun hast du einen weiteren Punkt für deinen Einkaufszettel. Hier sind die Ergänzungen, die uns zu Revision 0.2 der Einkaufsliste führen:

Abb. 8–9 zeigt die schematische Darstellung eines Schaltplans mit hinzugefügtem Wasserventil und dessen Stromversorgung.

image

Abb. 8–9Schematische Darstellung eines Schaltplans zum Testen der Arduino-Steuerung eines Wasserventils

Und Abb. 8–10 zeigt eine bildhafte Darstellung des Schaltplans genau der gleichen Schaltung.

image

Abb. 8–10Bildhafte Darstellung eines Schaltplans zum Testen der Arduino-Steuerung eines Wasserventils

image

Die Anordnung des Wasserventils und der Stromversorgung für das Wasserventil sind in diesen beiden Ansichten vertauscht. Ich tat dies der Übersichtlichkeit halber, um zu vermeiden, dass sich die Drähte im Schaltplan kreuzen. Beim Anschließen einer Komponente und einer Stromversorgung über einen Schalter spielt die Anordnung keine Rolle, solange der Schalter steuert, ob die Schaltung geschlossen oder offen ist.

Du kannst denselben Blink-Sketch verwenden – du solltest immer noch das Relais klicken hören. Du hörst möglicherweise nicht das Wasserventil klicken, da manche Wasserventile nur funktionieren, wenn in ihnen ein Wasserdruck herrscht. Ich hatte Glück: Meine Wasserventile machten sich beim Einschalten mit einem sehr lauten Klicken bemerkbar.

Was ist mit den LEDs? Diese können an einer Reihe verschiedener Stellen installiert werden: an den digitalen Ausgängen, am MOSFET-Ausgang oder am Relais-Ausgang. Wenn möglich, platziere ich LEDs gern am weitesten entfernten Punkt, um so viel wie möglich zu überprüfen – lass uns diese daher an den Relais-Ausgang setzen. (Wenn du möchtest, kannst du LEDs an allen erwähnten Etappen anordnen, was es sehr erleichtert, genau zu erkennen, wo das Signal stoppt.)

Welchen Widerstand sollten wir verwenden? Diese LED bezieht ihren Strom aus der Stromversorgung des Wasserventils. Die meisten Wasserventile scheinen entweder für 12 oder 24 Volt zu sein. Lass uns, um sicherzugehen, ein 24-V-System entwerfen – wenn deins ein 12-V-System ist, kannst du entweder den Wert reduzieren oder deine LED wird etwas gedämpfter leuchten. Sie sollte dennoch hell genug sein.

LED-Widerstand = (Spannung der Stromversorgung - Spannung der LED)/ (gewünschter LED-Strom)

image

Wenn du unsicher bist, welchen Widerstandswert du zum Reduzieren des Stroms verwenden sollst, nimmst du am besten einen Widerstand mit einem größeren Wert. Die LED wird über einen recht breiten Wertebereich leuchten – wenn sie zu dunkel ist, kannst du immer noch den Widerstand reduzieren.

Die meisten LEDs verwenden rund 2 Volt und sind unter 30 mA sicher, sodass wir einen Widerstand R = (24-2) V/30 mA = 733 Ω haben. Du kannst diesen guten Gewissens auf bis zu 1 kΩ aufrunden; das Ergebnis ist ein etwas geringerer Strom und eine leicht gedimmte LED.

Aber warte – unter »Planung« auf Seite 84 sagte ich dir, dass manche Wasserventile Wechselstrom verwenden, und dann später unter »Elektronische Schaltpläne« auf Seite 119, dass LEDs polarisiert sind. Polarisiert bedeutet, dass es für die LEDs eine Rolle spielt, in welche Richtung der Strom fließt, und Wechselstrom, dass der Strom ständig seine Richtung ändert. Wird das nicht die LEDs beschädigen? Wie sich herausstellt, können LEDs einer gewissen Spannung in der falschen Richtung widerstehen, aber wenn die Spannung zu hoch ist, könnte die LED Schaden nehmen. Zum Glück können andere Dioden viel höhere Spannungen sicher vertragen; lass uns daher weitere drei 1N4148-Dioden zum Schutz der LEDs verwenden, was uns einen weiteren Punkt zur Einkaufsliste – nun Revision 0.3 – beschert:

Abb. 8–11 zeigt den um LD und Diode erweiterten Schaltplan. Wir haben auf die Polarität der Stromversorgung für das Wasserventil sowie des Wasserventils hingewiesen, aber das ist nur relevant, wenn du ein Gleichstromsystem hast. Wenn du ein Wechselstromsystem hast (eher üblich), dann haben diese keine Polarität.

image

Abb. 8–11Schematische Darstellung eines Schaltplans zum Testen des Wasserventils mit hinzugefügter LED

Und Abb. 8–12 zeigt die bildhafte Darstellung des Schaltplans. Ich habe die Widerstandswerte weggelassen; achte daher darauf, sie mit dem Plan zu vergleichen und den richtigen Widerstand am richtigen Ort anzuordnen. Achte auch auf die Polarität der LED und Diode – die LED-Anode ist am Wasserventil und die Anode der Diode an der LED-Kathode anzuschließen.

Bevor du die Stromversorgung für das Wasserventil herstellst, überprüfe erneut deine Verdrahtung, insbesondere die Relaisanschlüsse. Du willst nicht die Stromspannung für das Wasserventil in den Arduino leiten, da das mit ziemlicher Sicherheit einen Schaden verursacht. Führe ein weiteres Mal den Blink-Sketch aus. Du solltest das Relais sowie möglicherweise das Klicken des Ventils hören, und die LED sollte aufleuchten.

image

Abb. 8–12Bildhafte Darstellung eines Schaltplans zum Testen des Wasserventils mit hinzugefügter LED

Nachdem wir das Relais und das Wasserventil erfolgreich überprüft haben, lass uns den Temperatur- und Feuchtigkeitssensor testen.

Testen des Temperatur- und Feuchtigkeitssensors

Der DHT11 ist ein beliebter Temperatur- und Feuchtigkeitssensor. Wie die RTC ist er preisgünstig und lässt sich leicht mit Arduino verwenden. Dem Datenblatt zufolge wird der DHT11 wie in Abb. 8–13 dargestellt angeschlossen. Beachte den Pullup-Widerstand am Data-Pin.6

image

Abb. 8–13Schematische Darstellung eines Schaltplans zum Testen des Temperatur- und Feuchtigkeitssensors DHT11

Da wir eine Komponente hinzufügen, die einen benötigt, lass uns einen weiteren 10-kΩ-Widerstand auf unseren Einkaufszettel schreiben. Wir sind jetzt bei Version 0.4:

Aufgrund des Pullup-Widerstands können wir den Trick, den wir bei der RTC angewandt haben (direktes Aufsetzen auf den Arduino), nicht verwenden, sondern müssen dies auf einer Steckplatine vornehmen (Abb. 8–14).

image

Der Schaltplan einer Schaltung ist der gleiche, egal ob du die Schaltung auf einer Steckplatine oder auf eine andere Weise aufbaust.

image

Abb. 8–14Bildhafte Darstellung eines Schaltplans zum Testen des Temperatur- und Feuchtigkeitssensors DHT11

Du kannst die Adafruit-DHT11-Bibliothek genauso installieren wie zuvor die RTClib-Bibliothek.

Prüfe, ob du die Bibliothek richtig installiert hast, indem du das Beispiel DHTtester aus der DHT-Kategorie der Beispiele öffnest, und klicke dann auf die Verify-Schaltfläche (siehe Abb. 4–2). Wenn du die Mitteilung »Done compiling« erhältst, dann hast du die Bibliothek korrekt installiert.

Bevor du den Sketch auf deinen Arduino lädst, beachte, dass das Beispiel drei verschiedene Modelle von DHT-Sensoren unterstützt: DHT11, DHT21 und DHT22. Zur korrekten Auswahl des richtigen Modells wird eine Konstante namens DHTTYPE mit entweder DHT11, DHT21 oder DHT22 definiert:

// Uncomment whatever type you're using!

//#define DHTTYPE DHT11 // DHT 11

#define DHTTYPE DHT22 // DHT 22 (AM2302)

//#define DHTTYPE DHT21 // DHT 21 (AM2301)

Beachte, dass DHT11 und DHT21 ignoriert werden, da diese beiden Zeilen Kommentare sind, oder, wie Programmierer zu sagen pflegen: Jene Zeilen werden auskommentiert. Da du den Sensor DHT11 verwendest, musst du die Zeile mit dem DHT22 auskommentieren, dann kommentiere die Zeile mit DHT11 ein:

// Uncomment whatever type you're using!

#define DHTTYPE DHT11 // DHT 11

//#define DHTTYPE DHT22 // DHT 22 (AM2302)

//#define DHTTYPE DHT21 // DHT 21 (AM2301)

Die Zeilen mit dem DHT22 und dem DHT21 tun nichts, aber sie dienen uns als Gedächtnisstütze, dass die Bibliothek mit diesen drei Sensoren arbeitet. Auf diese Weise legst du fest, welche du verwendest.

image

Du bist möglicherweise einem weiteren Typ einer Konstante begegnet: die konstante Variable. Abgesehen vom etwas plumpen Namen ist sie ebenfalls wichtig und nützlich.

Die Unterschiede zwischen einer Variablen (wie eine Ganzzahl), einer konstanten Variablen und einem genannten konstanten Wert sind subtil und etwas kompliziert. Im weitesten Sinne verwendet eine konstante Variable einen winzigen Teil des Speichers deines Arduino und hält sich an Bereichsregeln. Im Gegensatz dazu verwendet ein genannter konstanter Wert keinen Speicher und hat stets einen globalen Bereich.

Als allgemeine Regel solltest du die Verwendung genannter konstanter Werte vermeiden und sie nur verwenden, wenn eine Bibliothek nach ihnen verlangt.

Auf der Arduino-Website (arduino.cc) kannst du mehr zu genannten konstanten Werten, zum Stichwort const und zur Variable scope erfahren.

Sobald du den korrekten Typ des DHT-Sensors definiert hast, bestätige, dass der Sketch denselben Pin verwendet, an dem du den Sensor angeschlossen hast, lade das Beispiel DHTtester auf deinen Arduino und öffne den seriellen Monitor. Du solltest etwas in dieser Art sehen:

DHTxx test!

Humidity: 47.00 % Temperature: 24.00 *C 75.20 *F Heat index: 77.70 *F

Humidity: 48.00 % Temperature: 24.00 *C 75.20 *F Heat index: 77.71 *F

Du kannst den Feuchtigkeitssensor durch leichtes Anhauchen überprüfen. Die Feuchtigkeit in deinem Atem sollte zu einem Anstieg der Feuchtigkeit führen. Du kannst versuchen, die Temperatur zu steigern, indem du deine Finger um den Sensor legst, aber da du nur das Kunststoffgehäuse berührst und nicht den Sensor selbst, wirst du die Temperatur wahrscheinlich nicht wesentlich erhöhen.

Da du nun ein sicheres Gefühl bei deinen Komponenten hast, beginne nun mit dem Software-Design.

Programmieren

Stell dir vor! Das Schreiben von Code (Programmieren) erfordert ebenfalls Planung. Bevor du mit dem Tippen beginnst, musst du dir ein paar Gedanken dazu machen, was du vorhast. So ähnlich, wie du die neue Elektronik getestet hast, bevor du den gesamten Entwurf gestaltet hast, wirst du jeden Teil des Codes testen, bevor es weitergeht. Je weniger Code, desto leichter ist es, Probleme aufzuspüren.

Einstellen der Ein- und Ausschaltzeiten

Wir wollen die Wasserventile zu unterschiedlichen Zeiten im Tagesverlauf ein- und ausschalten. Wir benötigen eine Möglichkeit, diese Werte aufzuzeichnen. Da wir drei Ventile haben, könnten wir ein Array verwenden, mit einem Eintrag je Ventil. Das wird es zudem später leichter machen, wenn wir weitere Wasserventile hinzufügen möchten. Du erinnerst dich vielleicht, dass wir ein Array namens Puffer verwendet haben (siehe Beispiel 6–2 in Kapitel 6), um diese Zeichen zu speichern, als sie vom Processing-Sketch versandt wurden. Arrays werden kurz in »Variablen« auf Seite 241 beschrieben.

Hier ist eine Möglichkeit dafür:

const int NUMBEROFVALVES = 3;

const int NUMBEROFTIMES = 2;

int onOffTimes [NUMBEROFVALVES][NUMBEROFTIMES];

image

Der Einfachheit halber nehmen wir an, dass du das Wasser an jedem Tag der Woche zur selben Zeit ein- und ausschaltest. Mit zunehmenden Programmierkenntnissen kannst du das ändern, um für verschiedene Tage der Woche unterschiedliche Zeiten einzustellen bzw. auch mehrmals im Verlauf eines Tages. Wenn du ein Projekt beginnst, fang am besten mit dem einfachsten System an und füge erst Funktionen hinzu, wenn du sicher bist, dass alles ordnungsgemäß funktioniert.

Beachte, dass ich, anstatt eine feste Anzahl von Array-Dimensionen zu verwenden, zuerst zwei konstante Variablen erstellt habe. Das soll mich daran erinnern, was diese Zahlen bedeuten, und eine spätere Änderung erleichtern. Die Namen der konstanten Variablen sind in Großbuchstaben gehalten, um zu unterstreichen, dass sie Konstanten sind.

Keine Angst, wenn du noch nie ein zweidimensionales Array gesehen hast. Stell es dir als Tabelle vor. Die Zahl in den ersten [] ist die Anzahl der Zeilen, und die Zahl in den zweiten [] ist die Anzahl der Spalten. Eine Zeile repräsentiert ein Ventil, und wir verwenden die erste Spalte zum Speichern der Uhrzeit, zu der das Ventil eingeschaltet wird, und die zweite Spalte zum Speichern der Uhrzeit zum Ausschalten des Ventils.

Lass uns auch für die Spaltenzahlen konstante Variablen erstellen. Denk dran, dass der Index von Elementen innerhalb eines Arrays immer bei Null beginnt:

const int ONTIME = 0;

const int OFFTIME = 1;

Als Nächstes brauchen wir eine Möglichkeit zur Eingabe dieser Informationen – eine Art von Benutzerschnittstelle. Normalerweise ist eine Benutzerschnittstelle in Form eines Menüs, aber wir werden es uns extrem einfach machen, indem wir den seriellen Monitor verwenden.

Erinnerst du dich, wie wir in Kapitel 6 dem Arduino sagen mussten, in welcher Farbe er das Licht leuchten lassen sollte? Wie ich dort schon erwähnte: Der Arduino ist ein einfaches Gerät – also wählen wir eine einfache Methode zum Kodieren der Farbe.

Wir werden hier etwas Ähnliches machen: Kodieren der Uhrzeiten auf die denkbar einfachste Art und Weise.

Wir müssen die ON- und die OFF-Zeit für jedes Ventil einstellen können. Wir könnten eine Zahl zur Angabe des gewünschten Ventils verwenden, gefolgt vom Buchstaben N für »ein« und F für »aus«, gefolgt von der Uhrzeit. Wir könnten die Uhrzeit im 24-Stunden-Format eingeben, z. B. 0135 für 1:35 Uhr. Somit würden wir

2N1345 2F1415

eingeben, um Ventil 2 um 13:45 Uhr ein- und um 14:15 Uhr auszuschalten.

Um uns das Leben einfacher zu machen, lass uns stets die Großbuchstaben N und F verwenden.

In unserem Code müssten wir die Zeichenfolge parsen oder separieren, die wir in die korrekten Gruppen eingeben.

image

Eine Gruppe aufeinanderfolgender Zeichen wird als Zeichenfolge bezeichnet.

Wenn du dir den Arduino-Sketch anschaust (Beispiel 6–2), siehst du, dass wir Serial.available() und Serial.read() nutzen, die Funktionen des seriellen Objekts sind. Es stellt sich heraus, dass das serielle Objekt mehr Funktionen hat, wie bei Arduino beschrieben.

Wir verwenden die Funktion Serial.parseInt(), die digitale Zeichen liest und diese in Ganzzahlen umwandelt. Sie stoppt, wenn sie ein Zeichen erkennt, das keine Zahl ist. Wir lesen die Buchstaben (N oder F) direkt mit Serial.read().

Für Testzwecke drucken wir einfach nach dem Lesen jeder Zeile das gesamte Array aus, wie in Beispiel 8–1 dargestellt.

Beispiel 8–1Parsen der an das Bewässerungssystem geschickten Befehle

/*

Example 8-1. Parsing the commands sent to the irrigation system

*/

const int NUMBEROFVALVES = 3;

const int NUMBEROFTIMES = 2;

int onOffTimes [NUMBEROFVALVES][NUMBEROFTIMES];

const int ONTIME = 0;

const int OFFTIME = 1;

void setup(){ Serial.begin(9600);

};

void loop() {

// Read a string of the form "2N1345" and separate it

// into the first digit, the letter, and the second number

// read only if there is something to read

while (Serial.available() > 0) {

// The first integer should be the valve number

int valveNumber = Serial.parseInt();

// the next character should be either N or F

// do it again:

char onOff = Serial.read();

// next should come the time

int desiredTime = Serial.parseInt();

//Serial.print("time = ");

//Serial.println(desiredTime);

// finally expect a newline which is the end of

// the sentence:

if (Serial.read() == '\n') {

if ( onOff == 'N') { // it's an ON time

onOffTimes[valveNumber][ONTIME] = desiredTime;

}

else if ( onOff == 'F') { // it's an OFF time

onOffTimes[valveNumber][OFFTIME] = desiredTime;

}

else { // something's wrong

Serial.println ("You must use upper case N or F only");

}

} // end of sentence

else {

// Sanity check

Serial.println("no Newline character found");

}

// now print the entire array so we can see if it works

for (int valve = 0; valve < NUMBEROFVALVES; valve++) {

Serial.print("valve # ");

Serial.print(valve);

Serial.print(" will turn ON at "); Serial.

print(onOffTimes[valve][ONTIME]); Serial.

print(" and will turn OFF at "); Serial.

print(onOffTimes[valve][OFFTIME]); Serial.

println();

}

} // end of Serial.available()

}

Du kannst diesen Sketch von der Katalogseite der englischen Originalausgabe dieses Buches herunterladen (https://makezine.com/go/arduino-4e-github/).

Öffne nach dem Laden des Sketches in Arduino den seriellen Monitor und prüfe die Baudrate und die Auswahlkästchen in der unteren rechten Ecke des seriellen Monitors. Wähle Newline und 9600 Baud. Das Zeilenendezeichen sorgt dafür, dass jedes Mal, wenn du eine Zeile durch Drücken der Eingabetaste auf deinem Computer beendest, dein Computer ein Newline-Zeichen an deinen Arduino schickt.

Wenn du zum Beispiel Ventil #1 um 13:30 Uhr einschalten möchtest, gib 1N1330 ein und drücke die Eingabetaste. Du solltest Folgendes sehen:

valve # 0 will turn ON at 0 and will turn OFF at 0

valve # 1 will turn ON at 1330 and will turn OFF at 0

valve # 2 will turn ON at 0 and will turn OFF at 0

Beachte, dass ich im Sketch überprüfe, ob das Zeichen zwischen den Zahlen entweder N oder F ist und ob nach der zweiten Zahl ein Newline-Zeichen steht. Diese Art der »Plausibilitätsprüfung« ist praktisch, um Fehler aufzudecken, die du bei der Eingabe gemacht haben könntest und die dein Programm verwirren könnten. Des Weiteren ist sie gut dazu geeignet, Fehler in deinem Programm aufzuspüren. Vielleicht fallen dir noch andere Einsatzmöglichkeiten für Plausibilitätsprüfungen ein – beispielsweise könntest du prüfen, ob die Uhrzeit gültig ist, z. B. weniger als 2359, und ob die Ventilnummer geringer ist als NUMBEROFVALVES.

image

Ein Programm, das zur Handhabung allein der korrekten Daten vorgesehen ist, ist sehr empfindlich, da es jeden Fehler erkennt, egal ob durch eine Benutzereingabe oder durch einen Fehler an anderer Stelle hervorgerufen. Durch eine Prüfung der Daten vor Programmstart kannst du Fehler identifizieren, statt zu versuchen, mit fehlerhaften Daten zu arbeiten, was zu unerwartetem oder falschem Verhalten führen könnte. Das macht dein Programm stabil – eine höchst erwünschte Eigenschaft, insbesondere weil Menschen nicht immer zuverlässig sind.

Bevor wir weitergehen, möchte ich dir einen neuen Trick zeigen. Der gerade von uns entwickelte Brocken Code ist sehr lang, und wir müssen noch eine ganze Menge hinzufügen. Beim Lesen und Steuern des Programms wird es langsam unübersichtlich.

Zum Glück können wir uns einer sehr schlauen und häufig genutzten Programmiermethode bedienen. In »Reich mir den Parmesan« auf Seite 35 erläuterten wir, was eine Funktion ist und dass setup() und loop() zwei Funktionen sind, die Arduino voraussetzt. Bisher hast du das gesamte Programm innerhalb dieser beiden Funktionen geschaffen.

Was ich noch nicht herausgestellt habe, ist, dass du auf dieselbe Weise, in der du setup() und loop() erzeugst, auch andere Funktionen schaffen kannst.

Warum ist das wichtig? Weil es eine sehr praktische Methode ist, ein langes und kompliziertes Programm in kleine Funktionen aufzubrechen, die jeweils eine bestimmte Aufgabe haben. Außerdem kannst du diese Funktionen nach Belieben benennen – verwendest du also Namen, die die Funktion beschreiben, wird das Programm viel einfacher.

image

Immer wenn Code, der eine bestimmte Aufgabe erfüllt, recht umfangreich wird, ist er ein geeigneter Kandidat, um aus ihm eine Funktion zu machen. Wann ist er groß genug? Das bleibt dir überlassen. Meine Faustregel: Wenn ein Brocken Code mehr als zwei Bildschirme beansprucht, dann ist er reif dafür. Ich kann zwei Bildschirmseiten Code im Kopf behalten, aber mehr nicht.

Eine wichtige Überlegung ist, ob sich der Brocken Code leicht extrahieren lässt. Ist er von vielen Variablen abhängig, die nur innerhalb einer anderen Funktion sichtbar sind? Sobald du mehr über den Wertebereich von Variablen erfährst, wirst du sehen, dass auch dies ein wichtiger Aspekt ist.

Der gerade von uns entwickelte Code liest zum Beispiel eine Anweisung vom seriellen Monitor, parst sie und speichert dann die Uhrzeiten im Array ab. Wenn wir das zu einer Funktion namens expectValveSetting() machen, dann lautet unser Loop einfach:

void loop() {

expectValveSettings();

}

Das lässt sich viel leichter lesen und, noch wichtiger, es ist leichter zu verstehen, während wir den Rest des Programms entwickeln.

Natürlich müssen wir diese Funktion erzeugen, und das tun wir wie folgt:

void expectValveSettings() {

// Read a string of the form "2N1345" and separate it

// into the first digit, the letter, and the second number

// read only if there is something to read

while (Serial.available() > 0) {

// ... rest of the code not repeated here

}

Der Rest ist genau derselbe wie im Beispiel 8–1. Ich habe den Rest der Funktion ausgelassen, weil ich nicht weitere zwei Seiten verschwenden wollte.

Jetzt können wir uns den anderen Dingen zuwenden, die wir tun müssen, und diese ebenfalls zu Funktionen machen.

Prüfung, ob es Zeit zum Ein- oder Ausschalten eines Ventils ist

Als Nächstes schauen wir uns die Daten von der RTC an und überlegen, wie wir diese verwenden, um zu entscheiden, wann es Zeit ist, etwas ein- oder auszuschalten. Wenn du zum RTC-Beispiel ds1307 zurückgehst, siehst du, wie die Uhrzeit dargestellt wird:

Serial.print(now.hour(), DEC);

Praktischerweise ist das bereits eine Zahl, sodass der Vergleich mit den gespeicherten Stunden und Minuten leicht sein wird.

Um auf die RTC zugreifen zu können, musst du deinem Programm Teile des ds1307-Beispiels hinzufügen. Oben, vor setup(), füge dies hinzu:

#include <Wire.h>

#include «RTClib.h"

RTC_DS1307 rtc;

Dieses Mal werden wir nicht die analogen Eingangs-Pins für 5 V und GND verwenden. Warum? Weil analoge Eingänge rar sind – es gibt nur sechs von ihnen, und wir haben bereits zwei für die I2C-Schnittstelle zur RTC verloren. Im Moment braucht unser Projekt keine analogen Eingänge, aber vielleicht fällt uns später noch etwas ein.

In setup() benötigst du Folgendes:

#ifdef AVR

Wire.begin();

#else

// I2C pins connect to alt I2C bus on Arduino Due

Wire1.begin();

#endif rtc.begin();

Jetzt überlege, was du tun musst: Solange die aktuelle Uhrzeit größer ist als die Uhrzeit, zu der du ein Ventil EIN-schalten möchtest, und geringer ist als die Uhrzeit, zu der du das Ventil AUS-schalten möchtest, solltest du es einschalten. Zu allen anderen Zeiten soll es AUS sein.

Aus der RTC-Bibliothek kannst du die aktuelle Zeit wie folgt bekommen:

dateTimeNow = rtc.now();

Und dann kannst du Teile der Uhrzeit wie folgt angehen:

dateTimeNow.hour()

dateTimeNow.minute()

Erkennst du das Problem? Wir haben die Uhrzeit in einem Format mit vier Ziffern gespeichert, bei der die ersten beiden Ziffern die Stunde und die letzten beiden Ziffern die Minuten sind. Wir können keinen mathematischen Vergleich anstellen, ohne diese Zahl in Stunden und Minuten aufzuteilen – und dann wird der Vergleich kompliziert.

Es wäre schön, wenn wir nur eine Zahl hätten. Das können wir erreichen, indem wir die Stunden in Minuten konvertieren und die Anzahl der Minuten seit Mitternacht speichern. Auf diese Weise müssen wir uns nur mit einer Zahl beschäftigen, und der mathematische Vergleich ist einfach. (Wir müssen daran denken, das Wasser nie vor Mitternacht aufzudrehen, um es nach Mitternacht wieder abzudrehen. Das wäre eine weitere Gelegenheit für eine Plausibilitätsprüfung, um unser Programm robuster zu gestalten.)

Der folgende Code veranschaulicht dies:

int nowMinutesSinceMidnight = (dateTimeNow.hour() * 60) + dateTimeNow.minute();

Und dann sieht der Vergleich wie folgt aus:

if ( ( nowMinutesSinceMidnight >= onOffTimes[valve][ONTIME]) &&

( nowMinutesSinceMidnight < onOffTimes[valve][OFFTIME]) )

{

digitalWrite(??, HIGH);

}

else

{

digitalWrite(??, LOW);

}

Moment, was hat das mit den Fragezeichen auf sich? Wir müssen für jedes Ventil die Pin-Nummer kennen. Unsere for()-Schleife zählt die Ventile lediglich ab: 0, 1 und 2. Wir müssen einen Weg für die Angabe finden, welche Pin-Nummer zu welchem Ventil gehört. Wir können ein Array verwenden:

int valvePinNumbers[NUMBEROFVALVES];

Indem wir dieselbe Variable verwenden, die wir zuvor erstellt haben, wird dieses Array immer genau dieselbe Anzahl von Zeilen haben wie das andere Array, auch wenn du später die Anzahl der Ventile änderst.

In setup() fügen wir die korrekten Pin-Nummern in das Array ein:

valvePinNumbers[0] = 6; // Ventil 0 ist auf Pin 6

valvePinNumbers[1] = 8; // Ventil 1 ist auf Pin 8

valvePinNumbers[2] = 3; // Ventil 2 ist auf Pin 3

image

Wann immer du auf einem Index basierende Informationen nachschlagen musst, ist ein Array eine gute Methode dafür. Stell es dir wie eine Nachschlagetabelle vor.

Jetzt können wir unsere Fragezeichen behandeln:

if ( ( now.hour() > onOffTimes[valve][onTime]) &&

( now.hour() < onOffTimes[valve][offTime]) ) {

Serial.println("Turning valve ON");

digitalWrite(valvePinNumbers[valve], HIGH);

}

else {

Serial.println("Turning valve OFF");

digitalWrite([valve], LOW);

}

Ein letzter Punkt: Wir müssen die vierstelligen Zahlen im Array in Stunden und Minuten trennen. Dies könnte leichter sein, wenn der Benutzer die Informationen eintippt. Wir werden den Benutzer bitten, zwischen den Stunden und Minuten einen Doppelpunkt : einzufügen, sodass wir sie als separate Zahlen lesen, wandeln sie direkt in Minuten seit Mitternacht um und speichern diese im Array. Wenn wir die Uhrzeiten ausgeben, müssen wir sie in das übliche Zeitformat zurück umwandeln. Beispiel 8–2 zeigt jetzt unsere modifizierte Funktion expectValveSetting() sowie die neue Funktion printSetting().

image

Du hast wahrscheinlich an zwei oder drei verschiedene Lösungsmöglichkeiten gedacht. Die meisten Programmierprobleme, wenn nicht sogar überhaupt die meisten technischen Probleme, lassen sich auf vielerlei Weise lösen. Ein professioneller Programmierer könnte Effizienz, Tempo, Speichereinsatz, vielleicht sogar Kosten berücksichtigen, aber als Anfänger solltest du dich nach dem richten, was für dich am einfachsten zu verstehen ist.

Beispiel 8–2Die Funktion expectValveSetting()

/*

* Example 8-2. expectValveSetting() and printSettings() functions

* Read a string of the form "2N13:45" and separate it into the

* valve number, the letter indicating ON or OFF, and the time

*/

void expectValveSetting() {

// The first integer should be the valve number

int valveNumber = Serial.parseInt();

// the next character should be either N or F

char onOff = Serial.read();

int desiredHour = Serial.parseInt(); // get the hour

// the next character should be ':'

if (Serial.read() != ':') {

Serial.println("no : found"); // Sanity check

Serial.flush();

return;

}

int desiredMinutes = Serial.parseInt(); // get the minutes

// finally expect a newline which is the end of the sentence:

if (Serial.read() != '\n') { // Sanity check

Serial.println("You must end your request with a Newline");

Serial.flush();

return;

}

// Convert desired time to # of minutes since midnight

int desiredMinutesSinceMidnight

= (desiredHour*60 + desiredMinutes);

// Put time into the array in the correct row/column

if ( onOff == 'N') { // it's an ON time

onOffTimes[valveNumber][ONTIME]

= desiredMinutesSinceMidnight;

}

else if ( onOff == 'F') { // it's an OFF time

onOffTimes[valveNumber][OFFTIME]

= desiredMinutesSinceMidnight;

}

else { // user didn't use N or F

Serial.print("You must use upper case N or F ");

Serial.println("to indicate ON time or OFF time");

Serial.flush();

return;

}

printSettings(); // print the array so user can confirm settings

} // end of expectValveSetting()

void printSettings(){

// Print current on/off settings, converting # of minutes since

// midnight back to the time in hours and minutes

Serial.println();

for (int valve = 0; valve < NUMBEROFVALVES; valve++) {

Serial.print("Valve ");

Serial.print(valve);

Serial.print(" will turn ON at ");

// integer division drops remainder: divide by 60 to get hours

Serial.print((onOffTimes[valve][ONTIME])/60);

Serial.print(":");

// minutes % 60 are the remainder (% is the modulo operator)

Serial.print((onOffTimes[valve][ONTIME])%(60));

Serial.print(" and will turn OFF at ");

Serial.print((onOffTimes[valve][OFFTIME])/60); // hours

Serial.print(":"); Serial.print((onOffTimes[valve]

[OFFTIME])%(60)); // minutes Serial.println();

}

}