In dieser Veröffentlichung werde ich meine Erfahrungen mit der Erstellung eines IoT-Geräts von Grund auf teilen: von der Entstehung einer Idee und ihrer Implementierung in Hardware bis zur Erstellung einer Firmware für einen Controller und einer Webschnittstelle für die Verwaltung eines erstellten Geräts über das Internet.
Vor dem Erstellen dieses Geräts habe ich:
- Schaltkreise fast nicht verstanden. Nur auf der Ebene der Arbeitsprinzipien
Widerstand / Transistor ... Ich hatte keine Erfahrung mit der Erstellung komplizierter Schaltungen. - Nie entworfene Leiterplatten.
- Nie gelötete SMD-Komponente. Das Niveau des Lötkolbens lag auf dem Niveau der Lötdrähte und einer Art Relais.
- Ich habe noch nie so komplexe Programme für einen Mikrocontroller geschrieben. Die ganze Erfahrung war auf der Ebene „LED in Arduino anzünden“, und ich traf zum ersten Mal den ESP8266-Controller.
- Ich habe ziemlich viel C ++ für den "großen Bruder" geschrieben, aber das war vor mehr als einem Dutzend Jahren und alles wurde vor langer Zeit vergessen.
Die Erfahrung als Programmierer (hauptsächlich Microsoft .NET) und systemisches Denken haben mir natürlich geholfen, das Thema zu verstehen. Ich denke, der Leser wird dazu in der Lage sein. Nützliche Links und Artikel im Internet Meer. Am interessantesten und hilfreichsten, um das Thema zu verstehen, bringe ich den Artikel mit.
Erklärung des Problems
Ich wohne in einem Privathaus in der Nähe von Minsk, und mein eigener Pool, wenn auch der einfachste, ist ein wesentlicher Bestandteil der „Vorteile“, die viele Menschen in einem Landhaus erhalten. In unserem instabilen Klima stellte sich heraus, dass das Schwimmen im Pool im Freien unangenehm ist: Das Wasser kühlt sich nachts ab und windiges Wetter am Tag macht das Schwimmen nicht angenehm. Letztes Jahr habe ich mit meinen eigenen Händen eine geodätische Kuppel eines volleren über dem Pool gebaut, einen Hügel gesetzt und einen Bungee aufgehängt - die Kinder sind glücklich.

Fotobericht über den Bau der Kuppel auf Flickr.
Dieses Jahr ging ich noch weiter und beschloss, eine Poolheizung aus einem Gaskessel zu organisieren,
Dies dient zum Heizen des Hauses im Winter und zum Heizen des Warmwassers im Sommer.
Im Sommer schaltet der "Heizkreis" des Kessels mit Hilfe von Ventilen auf Heizung um
Pool. Das Poolwasser wird mit Hilfe eines Titan-Wärmetauschers erwärmt, dessen Primärkreislauf das Kühlmittel (heißes Wasser ohne Verunreinigungen) aus dem Heizkreislauf und das Sekundärwasser aus dem Pool durch eine Umwälzpumpe des Filtersystems pumpt. Da ich den Pool mit einem Chlorator benutze (viele interessante Themen werden im ForumHouse beschrieben ), enthält das Wasser etwas Salz und ein Titan-Wärmetauscher wird benötigt. Sie können das Wasser nicht einfach direkt durch den Kessel nehmen und lassen - sonst korrodieren Sie alle Rohre mit Salz.

Beim Durchgang durch den Wärmetauscher gibt der vom Kessel mit einer Temperatur von etwa 70 bis 90 ° C erhitzte Wärmeträger dem Wasser aus dem Pool Wärme ab und erwärmt es um einige Grad. Das Kühlmittel selbst kühlt sich um einige zehn Grad ab und kehrt zum Kessel zurück, um wieder zu sein
aufgewärmt. Das Verhältnis der Wasserkühlung vom Kessel zur Warmwasserbereitung des Pools hängt von vielen Faktoren ab: der Kapazität des Wärmetauschers und der Geschwindigkeit der Wasserzirkulation im Primär- und Sekundärkreislauf.
Die vom Pool mit dem Wärmetauscher verbundenen Rohre sind gewöhnliche Polyethylenrohre
Wird derzeit zur Versorgung von Privathaushalten mit kaltem Wasser verwendet. Billigkeit, Druckfestigkeit, Korrosionsfreiheit - das sind die Hauptvorteile solcher Rohre. Bei allen Rohren aus Polyethylen ist die Betriebstemperatur ausnahmslos auf 40 Grad Celsius begrenzt. Im Prinzip ist dies mehr als genug für den Pool.
Es besteht jedoch eine hohe Wahrscheinlichkeit eines Notfalls bei der Pumpe
Die Wasserumwälzung des Poolwassers stoppt aus irgendeinem Grund und der Kessel heizt den Wärmetauscher weiter auf. In diesem Fall steigt das Wasser im Sekundärkreis des Wärmetauschers schnell genug auf die Temperatur des Primärkreises an, was bedeutet, dass die Abschnitte der Polyethylenrohre neben dem Wärmetauscher schmelzen und das Wasser aus dem Pool flutet den ganzen Raum herum.
Es muss möglich sein, die Überhitzung des Wärmetauschers zu schützen.
Schnelle Lösung
Um dieses Problem zu lösen, wurde ein Durchflusssensor, der nach dem Prinzip des Hall-Effekts arbeitet, in den Kreislauf des Poolwasser-Umwälzkreislaufs aufgenommen. Zusätzlich befinden sich Temperatursensoren im Sekundärkreis
Wärmetauscher bieten eine zweite Verteidigungsstufe und verfolgen mögliche Überhitzung.
Es ist unmöglich, die Überhitzung nur durch Temperatursensoren zu kontrollieren: Das System hat eine große Trägheit: nach einem plötzlichen Wasserstopp im Poolkreislauf bei
Beim Abschalten des Kessels steigt die Temperatur noch einige Zeit an Der Kessel treibt das erwärmte Wasser immer noch durch Trägheit entlang des Kreislaufs und verhindert so eine Überhitzung von „mir selbst, meinem Geliebten“.
Daher ist es wichtig, so schnell wie möglich zu reagieren: nämlich den Wasserfluss im Kreislauf zu stoppen
Pool.
Ein Durchflusssensor wurde z . Das Kunststoffgehäuse und der fehlende Kontakt des Sensors mit Wasser ermöglichen die Verwendung in Salzwasser.
Bei Temperatursensoren wurde beschlossen, den Dallas DS18B20 zu verwenden. Sie können problemlos mehrere Teile gleichzeitig an einem 1-Draht- Bus anschließen.

Es wurde beschlossen, ein Paar Sensoren an den Ein- und Ausgang des Sekundär- und Primärteils zu hängen
Schaltung: insgesamt 4 Sensoren. Ein zusätzlicher Vorteil dieses Ansatzes ist
Die Möglichkeit, Systemparameter zu überwachen: Sie können überwachen, wie stark das Kühlmittel im Primärkreislauf gekühlt und wie viel Wasser aus dem Pool im Sekundärkreislauf erwärmt wird. Also - um die Heizoptimalität zu überwachen und den Zeitpunkt des Aufheizens vorherzusagen.
Sensorpositionen an Wärmetauscher- und Einlassrohren Geräteparameter
Der erste Prototyp des Geräts wurde auf Basis von Arduino Uno gebaut und erfolgreich gestartet.

Aber dann wurde klar, dass ich mehr möchte. 16 Kubikmeter Wasser erhitzt, auch nur knapp
ein paar grad sind nicht schnell. Und ich möchte die Heizparameter direkt von der Arbeit aus überwachen, ein- und ausschalten. Gleichzeitig wäre es aber interessant, beispielsweise Heizpläne pro Tag zu erstellen.
Nun, da wir bereits ein IoT-Gerät erhalten, warum steuern wir dann nicht gleichzeitig die Fernaktivierung des Poolchlorators und der Pumpe dafür?
Leistungsbeschreibung
Daher wurde beschlossen, ein Gerät zu entwickeln - einen multifunktionalen Pool-Controller. Er muss in der Lage sein:
- Um die Poolheizung über den Wärmetauscher zu steuern, schalten Sie den Gaskessel zum Erhitzen von Wasser ein / aus.
- Verhindern Sie eine Überhitzung des Wärmetauschers, indem Sie das Vorhandensein eines Poolwasserflusses im Sekundärkreislauf und die Übertemperatur des Sekundärkreislaufs überwachen.
- Anzeige der Heizungsstatistik in Echtzeit (Temperatur am Einlass und Auslass beider Kreisläufe).
- Zeichnen Sie die Temperaturwerte (Protokoll) im Flash-Speicher auf. Daten anzeigen für
ein bestimmter Zeitraum in Form eines Diagramms. - Schalten Sie die Poolpumpen und den Chlorator mit einem Relais ein / aus.
- Verwalten Sie alle Geräteparameter remote über den integrierten Micro-Web-Server.
Es gab auch die Versuchung, Blink, MQTT zu schrauben. Aber von diesen "Schnickschnack" in der ersten Stufe
Es wurde beschlossen, abzulehnen. Und noch mehr, ich würde die Möglichkeit der Kontrolle nicht irgendwo nach außen nutzen wollen. Der eingebaute Webserver für meine Zwecke ist völlig ausreichend. Die Sicherheit wird dadurch gewährleistet, dass Sie nur über ein VPN von außen in das Heimnetzwerk gelangen können.
Hardware
Als Controller wurde beschlossen, den billigen und beliebten ESP8266 zu verwenden. Es war perfekt für meine Zwecke, abgesehen von einer Sache: Anpassung der Signalpegel von 5-Volt-Sensoren an die 3,3-Volt-Reglerlogik. Im Prinzip scheinen Dallas-Sensoren bei 3 Volt zu arbeiten, aber ich habe eine ziemlich lange Leitung vom Controller zu den Sensoren, ungefähr 7 Meter. Daher ist es besser, die Spannung zu erhöhen.
Es wurde festgestellt, dass die Hardware erforderlich ist:
- ESP8266-Controller oder sein älterer Bruder ESP32 (als DevKit- Modul).
- Ausrichtung der Signalpegel für Sensoren.
- Der Leistungsregler ist ein 5-Volt-Teil der Schaltung.
- Relaissteuermodul.
- RTC-Uhr + Flash-Speicher für die Protokollierung.
- Das einfachste 2-zeilige LCD-Display zur Anzeige der aktuellen Werte der Sensoren und des Status des Geräts und des Relais.
- Mehrere physische Tasten zur Steuerung des Status des Geräts ohne Zugriff über das Internet.
Viele Komponenten aus der Liste werden als Module für Arduino verkauft und viele Module sind mit 3.3V-Logik kompatibel. Ich wollte dies jedoch nicht mit Drahtbündeln auf dem Steckbrett „jammen“, weil ich ein ordentlich schönes „Gerät“ haben möchte. Ja, und für das Geld, das den Chinesen für die Module gegeben wird, können Sie Ihre individuelle Leiterplatte vollständig zeichnen und bestellen, und die Erwartung ihrer Ankunft wird durch eine relativ schnelle und zuverlässige Installation kompensiert.
Ich stelle noch einmal fest, dass dies meine erste Erfahrung in der Schaltung und beim Entwerfen der Hardware solcher Dinge ist. Ich musste viel lernen. In meiner Spezialität bin ich ein bisschen distanziert von Mikrocontrollern. Aber alles "auf meinen Knien" zu tun, erlaubte nicht den Geist des Perfektionismus, der in mir lebt.
Schaltplan
Es gibt eine große Anzahl von Programmen auf dem Markt, mit denen Sie eine Schaltung und eine Leiterplatte zeichnen können. Ohne Erfahrung in diesem Bereich mochte ich sofort EasyEDA - einen kostenlosen Online-Editor, mit dem Sie einen Schaltplan wunderschön malen, überprüfen können, ob nichts vergessen wurde und alle Komponenten Verbindungen haben, eine Leiterplatte zeichnen und dann sofort die Produktion bestellen.
Die erste Schwierigkeit, auf die ich gestoßen bin: Es gibt viele Optionen für den DevKit ESP8266- oder ESP32-Controller, einige unterscheiden sich in der Position der Stifte und ihrem Zweck, andere sogar in der Breite. Es wurde beschlossen, die Schaltung so zu zeichnen, dass DevKit in beliebiger Breite und mit beliebiger Position der Klemmen und an den Seiten davon angebracht werden kann - 2 Reihen Paar Überbrückungslöcher und anschließende Verkabelung, um die erforderlichen Klemmen in Bezug auf den speziell gekauften Controller zu verbinden.
Platzieren Sie unter dem Controller und 2 Reihen gepaarter Jumper: JH1 und JH2 im Diagramm:

Die Position der Pins von Eingang 5 V und Ausgang 3,3 V der Stromversorgung des eingebauten Stabilisators sowie von GND schien mir für verschiedene DevKit gleich zu sein, aber ich entschied mich trotzdem, auf Nummer sicher zu gehen und sie auch zu Jumpern zu machen: JP1, JP2, JP3 im Diagramm.
Ich beschloss, die Jumper zu signieren, indem ich sie mit Funktionen an die Komponenten der Schaltung anschloss, die sie wahrscheinlich ausführen werden.
Und so sieht es mit dem DevKit ESP8266 aus, das ich schließlich gekauft und installiert habe Hier sind D1 (GPIO5) und D2 (GPIO4) für den I2C-Bus verantwortlich, D5 (GPIO14) für 1-Draht, D6 (GPIO12) - für den Empfang von Impulsen vom Durchflusssensor.
Schaltplan:

(Bild anklickbar)
Trotz des Vorhandenseins eines eingebauten Leistungsreglers für 3,3 V an Bord des ESP8266 benötigen wir immer noch 5 Volt für die Stromversorgung der Sensoren und des LCD und 12 Volt für die Stromversorgung des Relais. Es wurde beschlossen, die Platine mit 12 Volt zu versorgen und den Spannungsregler AMS1117-5.0 am Eingang anzubringen, um die gewünschten 5 Volt am Ausgang zu erhalten.
Um die Signalpegel auf dem 1-Draht-Bus anzupassen, habe ich einen BSS138 c-Feldeffekttransistor mit beidseitiger Spannung verwendet.

Sehr gut über die Pegelanpassung wird im Artikel Anpassen der logischen Pegel von 5-V- und 3,3-V-Geräten beschrieben .
Um die Signalpegel des Durchflusssensors anzupassen, habe ich nur einen Spannungsteiler über den Widerständen verwendet. Der Durchflusssensor ist einfach ein offenes Kollektorgerät . Einige Sensoren verfügen möglicherweise bereits über einen eingebauten Pull-up-Widerstand. Dies sollte berücksichtigt werden:

Blau im Diagramm ist eine schematische Bezeichnung der Durchflusssensoranordnung. Rechts vom Stecker befinden sich Spannungsteiler, die von mir ausgewählt wurden, um einen maximalen Pegel von 3,3 Volt am Ausgang zu haben.
Am I2C-Bus habe ich eine DS3231SN-Echtzeituhr und einen AT24C256C-Flash-Speicher zum Speichern von Protokollen aufgehängt. Der im ESP8266 integrierte Flash-Speicher ist nicht geeignet, da er nur wenige Umschreibzyklen aufweist (10 Tausend gegenüber 1 Million für AT24Cxxx laut Datenblättern).
Die Relaissteuerung ist auf einer Reihe von PCF8574AT- und ULN2803A-Chips organisiert.

Der erste Chip ist ein I2C-Mikrocontroller-Port-Expander. Der Status des aktiven Ausgangs oder Eingangs PCF8574AT wird durch Auswahl einer Adresse auf dem I2C-Bus ausgewählt.
Der Chip verfügt über einige interessante Funktionen, die im Artikel I2C-Port-Expander PCF8574 ausführlich beschrieben sind .
Der Chip kann die Last (Relais) nicht direkt steuern. Hierzu wird eine Transistormatrix ULN2803A verwendet. Es gibt eine Funktion: Die Matrix kann ihre Ausgänge leicht mit einer Last gegen Masse ziehen. Wenn also eine Versorgungsspannung an den zweiten Pol des Relais angelegt wird, fließt Strom durch die Relaiswicklung und die Relaiskontakte schließen. Leider erhalten wir mit dieser Einbeziehung einen Nebeneffekt: Der Signalwert von der Steuerung wird invertiert und alle Relais "klicken", wenn die Schaltung eingeschaltet wird. Ich habe noch nicht herausgefunden, wie diese Funktion entfernt werden kann.
Weitere Informationen zum Chip finden Sie hier .
Der Port-Expander PCF8574AT kann auch als Eingang verwendet werden: An einigen Eingängen können Hardwaretasten aufgehängt werden, um deren Werte auf dem I2C-Bus zu lesen. In der Abbildung können die Pins 4-7 verwendet werden, um den Status der Tasten zu lesen. Die Hauptsache ist nicht zu vergessen, programmgesteuert die eingebaute Straffung der entsprechenden Beine zur Ernährung zu ermöglichen.
Gleichzeitig habe ich die Verkabelung der Transistormatrix überlassen, falls Sie plötzlich zusätzliche Relais anschließen möchten. Für mögliche Verbindungen habe ich alle Kabel zu den Steckern gebracht (genauer gesagt zu den Löchern darunter, in denen die Drähte gelötet oder der Standard-2,54-mm-DIP-Stecker gelötet werden kann).
Mit dem Pin des INT-Port-Expanders kann schnell auf Knopfdruck reagiert werden. Es kann an einen freien Port des Controllers angeschlossen werden und den Interrupt-Trigger so einstellen, dass der Status dieses Pins geändert wird.
Das zweizeilige LCD-Display wird ebenfalls über den PCF8574AT-Expander gesteuert. Der Hauptpunkt: Das Display wird mit 5 Volt betrieben, während das Display selbst mit 3-Volt-Logik gesteuert wird. Standard-Arduino-Adapter für I2C sind übrigens nicht für doppelte Spannung ausgelegt. Ich habe die Idee einer solchen Verbindung irgendwo im Internet gefunden, leider habe ich den Link verloren, daher zitiere ich die Quelle nicht.
Leiterplatte
Bei der Entwicklung der Platine stellte sich heraus, dass normale Teile mit Beinen zu viel Platz beanspruchen und viele Chips im DIP-Design nicht leicht zu finden sind. Nachdem ich im Internet gelesen hatte, dass die SMD-Installation nicht so kompliziert und mit den richtigen Fähigkeiten noch weniger zeitaufwändig ist, entschied ich mich, die Platine für SMD-Teile zu entwerfen. Und ich habe mich nicht geirrt. Es stellte sich heraus, dass es sich um ein kompaktes, wunderschönes Motherboard handelte, auf dem ich problemlos alles platzieren konnte, was ich brauchte. SMD-Teile mit einem guten Lötkolben, Flussmittel und Lötmittel erwiesen sich als sehr einfach zu montieren.
Auf der Platine habe ich ein paar quadratische Ränder von Löchern für das Prototyping hinzugefügt, wenn ich plötzlich etwas anderes löten möchte.
Ich habe eine Leiterplatte mit den Maßen 97x97 mm hergestellt. Es passt problemlos in einen Standard-Elektrokasten. Darüber hinaus sind Platten mit einer Größe von weniger als 100 x 100 billig herzustellen. Die Herstellung einer Mindestcharge von 5 Platten gemäß dem entwickelten Layout kostete 5 USD, die Lieferung nach Weißrussland weitere 9 USD.

Das Design des Boards befindet sich auf der EasyEDA-Website und steht jedem zur Verfügung.
Ich stelle fest, dass auf dem Foto des Controllers unten das erste Beispiel der Platine zu sehen ist, auf dem ich viele unnötige und unnötige Dinge "verdreht" habe (in der Hoffnung, diese minimale Menge von 5 Platinen in anderen Projekten zu verwenden). Hier und auf EasyEDA habe ich eine "gereinigte" Version all dieser unnötigen Dinge gepostet.

Fotos von beiden Seiten der TafelVorderseite:

Rückseite:

Software-Teil
Um den Mikrocontroller zu programmieren, wurde angesichts des Rückstands in Form eines Prototyps auf Arduino Uno beschlossen, die Arduino-Umgebung mit dem installierten
Arduino Core ESP8266 zu verwenden. Ja, Sie können
Lua auf dem ESP8266 verwenden, aber sie sagen, dass es Hänge gibt. Ich würde angesichts der kritischen Funktion überhaupt nicht wollen.
Die Arduino-Umgebung selbst scheint mir etwas veraltet zu sein, aber zum Glück gibt es eine
Erweiterung für Visual Studio von Visual Micro. In der Umgebung können Sie IntelliSence-Codehinweise verwenden, schnell zu Funktionsdeklarationen und Refactor-Code springen: Im Allgemeinen alles, was die Umgebung für "erwachsene" Computer zulässt. Mit der kostenpflichtigen Version von Visual Micro können Sie auch bequem Code debuggen, aber ich war mit der kostenlosen Option zufrieden.
Projektstruktur
Das Projekt besteht aus folgenden Dateien:
Projektstruktur in Visual Studio Datei | Termin |
---|
WaterpoolManager.ino
| Deklaration grundlegender Variablen und Konstanten. Initialisierung. Hauptschleife.
|
HeaterMainLogic.ino
| Die Grundlogik zur Steuerung des Kesselrelais (je nach Temperatur) und der Hilfsrelais.
|
Sensors.ino
| Sensordaten lesen
|
Settings.ino
| Geräteeinstellungen, die im Flash-Speicher des Controllers gespeichert werden
|
LCD.ino
| Informationsausgabe auf dem LCD
|
ClockTimer.ino
| RTC Clock Reading oder Clock Simulation
|
Relays.ino
| Relais Ein / Aus-Steuerung
|
ButtonLogic.ino
| Logik der Reaktion auf den Status der Hardwaretasten
|
ReadButtonStates.ino
| Hardware-Schaltflächenzustände lesen
|
EEPROM_Logging.ino
| Sensordatenerfassung im EEPROM
|
WebServer.ino
| Eingebauter Webserver für Geräteverwaltung und Statusanzeige
|
Webseiten
| Webserverseiten werden in diesem Ordner gespeichert.
|
index.h
| Die Hauptseite zur Anzeige des Gerätestatus. Lesen des aktuellen Status mit Ajax-Aufruf. Alle 5 Sekunden aktualisieren.
|
loggraph.h
| Zeigt ein Protokoll der Sensordaten und Relaiszustände in einem Diagramm an. Die jqPlot-Bibliothek wird verwendet - die gesamte Konstruktion erfolgt auf der Clientseite. Die Anfrage an die Steuerung geht nur an eine Binärdatei - Kopien von Daten aus dem EEPROM.
|
logtable.h
| auch, aber in Form einer Tabelle
|
settings.h
| Verwalten der Geräteeinstellungen: Festlegen von Grenzwerten für Temperatur, Wasserdurchfluss und Häufigkeit der Datenerfassung
|
time.h
| Aktuelle Zeiteinstellung
|
| Bibliotheken
|
EepromLogger.cpp
| Flash-Protokollbibliothek
|
EepromLogger.h
|
crc8.cpp
| 8-Bit-CRC für eine Bibliothek zählen
|
crc8.h
|
TimeSpan.cpp
|
|
TimeSpan.h
|
OneWire tempSensAddr. . ( 4 ):
while (ds.search(tempSensAddr[lastSensorIndex]) && lastSensorIndex < 4) { Serial.print("ROM ="); for (byte i = 0; i < 8; i++) { Serial.print(' '); Serial.print(tempSensAddr[lastSensorIndex][i], HEX); } if (OneWire::crc8(tempSensAddr[lastSensorIndex], 7) != tempSensAddr[lastSensorIndex][7]) { Serial.print(" CRC is not valid!"); } else lastSensorIndex++; Serial.println(); } ds.reset_search(); lastSensorIndex--; Serial.print("\r\nTemperature sensor count: "); Serial.print(lastSensorIndex + 1, DEC); , (). Serial LCD : // Read sensor values and print temperatures ds.reset(); ds.write(0xCC, TEMP_SENSOR_POWER_MODE); // Request all sensors at the one time ds.write(0x44, TEMP_SENSOR_POWER_MODE); // Acquire temperatures delay(1000); // Delay is required by temp. sensors char tempString[10]; for (byte addr = 0; addr <= lastSensorIndex; addr++) { ds.reset(); ds.select(tempSensAddr[addr]); ds.write(0xBE, TEMP_SENSOR_POWER_MODE); // Read Scratchpad tempData[addr] = ds.read() | (ds.read() << 8); // Read first 2 bytes which carry temperature data int tempInCelsius = (tempData[addr] + 8) >> 4; // In celsius, with math rounding Serial.print(tempInCelsius, DEC); // Print temperature Serial.println(" C"); }
Laut Datenblatt benötigen Sensoren zwischen der Anforderung eines Temperaturwerts und dem Empfang einer Antwort vom Sensor eine Verzögerung von mindestens 750 ms. Daher führte der Code eine Verzögerung mit einem kleinen Spielraum ein.
Diese Verzögerung, wenn das gesamte Gerät nur auf eine Antwort wartet, ist zu Beginn akzeptabel, es ist jedoch absolut unangemessen, jedes Mal mit einer regelmäßigen Abfrage der Sensoren zu warten. Daher wurde der folgende knifflige Code geschrieben, der alle 50 ms vom Timer aufgerufen wird:
#define TEMP_MEASURE_PERIOD 20 // Time of measuring, * TEMP_TIMER_PERIODICITY ms #define TEMP_TIMER_PERIODICITY 50 // Periodicity of timer calling, ms timer.attach_ms(TEMP_TIMER_PERIODICITY, tempReadTimer); int tempMeasureCycleCount = 0; void tempReadTimer() // Called many times in second, perform only one small operation per call { tempMeasureCycleCount++; if (tempMeasureCycleCount >= TEMP_MEASURE_PERIOD) { tempMeasureCycleCount = 0; // Start cycle again } if (tempMeasureCycleCount == 0) { ds.reset(); ds.write(0xCC, TEMP_SENSOR_POWER_MODE); // Request all sensors at the one time ds.write(0x44, TEMP_SENSOR_POWER_MODE); // Acquire temperatures } // Between phases above and below should be > 750 ms int addr = TEMP_MEASURE_PERIOD - tempMeasureCycleCount - 1; if (addr >= 0 && addr <= lastSensorIndex) { ds.reset(); ds.select(tempSensAddr[addr]); ds.write(0xBE, TEMP_SENSOR_POWER_MODE); // Read Scratchpad tempData[addr] = ds.read() | (ds.read() << 8); // Read first 2 bytes which carry temperature data } }
Zu Beginn jedes tempMeasureCycleCount-Zyklus werden Sensoren aufgefordert, ihre Werte zu lesen. Nach ungefähr 50 solcher Zyklen (und insgesamt sind es 50 · 20 = 1000 ms = 1 s) wird der Wert jedes Sensors einzeln gelesen. Alle Arbeiten sind in Teile zerlegt, sodass der Code, der im Timer-Interrupt ausgeführt wird, vom Controller nicht viel Zeit in Anspruch nimmt.
Der Wert des Durchflusssensors wird wie folgt berechnet. Durch Unterbrechung des Stifts, an dem der Sensor hängt, erhöhen wir den Wert des Zählers der Zecken, die vom Durchflusssensor kamen:
pinMode(FLOW_SENSOR_PIN, INPUT); attachInterrupt(digitalPinToInterrupt(FLOW_SENSOR_PIN), flow, RISING); // Setup Interrupt volatile int flow_frequency; // Flow sensor pulses int flowMeasureCycleCount = 0; void flow() // Flow sensor interrupt function { flow_frequency++; }
In demselben Timer, in dem Temperatursensoren abgefragt werden, nehmen wir diesen Tick-Wert einmal pro Sekunde und übersetzen ihn mit der Konstante FLOW_SENSOR_CONST in Liter, deren Wert in den Eigenschaften des Sensors angegeben ist:
flowMeasureCycleCount++; if (flowMeasureCycleCount >= 1000 / TEMP_TIMER_PERIODICITY) { flowMeasureCycleCount = 0; litersInMinute = (flow_frequency / FLOW_SENSOR_CONST); // Pulse frequency (Hz) = FLOW_SENSOR_CONST*Q, Q is flow rate in L/min. flow_frequency = 0; // Reset Counter }
Protokollierung von Daten von Sensoren und Gerätestatus
Bei der Entwicklung des Protokollierungsmechanismus kann die Tatsache, dass das Gerät plötzlich ausgeschaltet werden kann, d.h. in fast jedem Moment. Wenn Sie die Aufnahme beenden, müssen wir in der Lage sein, alles, was aufgenommen wurde, bis zum letzten Moment wiederherzustellen. Gleichzeitig können wir nicht ständig denselben Bereich des Flash-Speichers neu schreiben (z. B. einen bestimmten Titel an einem bestimmten Ort, wobei wir uns an die Adresse erinnern, an die die Aufnahme zuletzt gesendet wurde), um ein beschleunigtes „Löschen“ des Flash-Laufwerks an diesem Ort zu vermeiden.
Nach einiger „Kumulierung“ wurde das folgende Aufzeichnungsmodell erfunden und implementiert:

Jeder Datensatz ist ein Datensatz, der Informationen über den aktuellen Wert des Wasserflusses, die Sensortemperaturen sowie den Status des im Byte codierten Geräts enthält (einzelne Bits geben an, ob das Relais eingeschaltet ist oder nicht, ob die Heizung aktiviert ist oder nicht):
struct LogEvent { unsigned char litersInMinute = 0; unsigned char tempCelsius[4]{ 0, 0, 0, 0 }; unsigned char deviceStatus = 0; }
Nach jedem Datensatz gibt es ein CRC- Prüfsummenbyte, das angibt, ob der Datensatz korrekt geschrieben wurde und ob im Allgemeinen mindestens etwas an diesem Speicherort geschrieben wurde.
Da es zu teuer wäre, Daten zur aktuellen Zeit ( Zeitstempel ) für jeden Datensatz in Bezug auf das Volumen aufzuzeichnen, sind die Daten in großen Blöcken mit jeweils N Datensätzen organisiert. Der Zeitstempel für jeden Block wird im Übrigen nur einmal aufgezeichnet - er wird anhand von Informationen über die Häufigkeit der Protokollierung berechnet.
unsigned int logRecordsInBlock = 60 * 60 / loggingPeriodSeconds; // 1 block for hour unsigned int block_size = sizeof(Block_Header) + logRecordsInBlock * (record_size + crcSize); unsigned int block_count = total_storage_size / block_size;
Bei einer Protokollierungshäufigkeit von einmal alle 30 Sekunden haben wir beispielsweise 120 Einträge in einem Block, und die Blockgröße beträgt ungefähr 840 Byte. Insgesamt können wir 39 Blöcke in den Speicher eines Flash-Laufwerks mit einer Größe von 32 Kilobyte einfügen. Bei einer solchen Organisation stellt sich heraus, dass jeder Block an einer genau definierten Adresse im Speicher beginnt und das „Durchlaufen“ aller Blöcke kein Problem darstellt.
Dementsprechend haben wir mit einer plötzlichen Unterbrechung des Datensatzes während des letzten Herunterfahrens des Geräts einen unvollendeten Block (dh, in dem einige der Datensätze fehlen). Wenn das Gerät eingeschaltet ist, sucht der Algorithmus nach dem letzten gültigen Blockheader (Zeitstempel + crc). Und setzt die Aufnahme fort, beginnend mit dem nächsten Block. Die Aufzeichnung erfolgt zyklisch: Der letzte Block überschreibt die Daten des ältesten Blocks.
Beim Lesen werden alle Blöcke nacheinander gelesen. Ungültige Blöcke (solche, die CRC für den Zeitstempel nicht bestehen) werden vollständig ignoriert. Datensätze in jedem Block werden bis zum Treffen des ersten ungültigen Datensatzes gelesen (d. H. Desjenigen, bei dem die Aufzeichnung das letzte Mal unterbrochen wurde, wenn der Block nicht vollständig aufgezeichnet wurde). Der Rest wird ignoriert.
Für jeden Datensatz wird die aktuelle Zeit basierend auf dem Zeitstempel des Blocks und der Seriennummer des Datensatzes im Block berechnet.
LCD
Das Gerät verwendet eine Anzeige QC1602A, die 2 Zeilen mit 16 Zeichen anzeigen kann. In der ersten Zeile werden die aktuellen Informationen zu den aktuellen Werten der Sensoren angezeigt: Durchfluss und Temperaturen. Wenn der angegebene Grenzwert überschritten wird, wird neben dem Wert ein Ausrufezeichen angezeigt. Die zweite Zeile zeigt den Status des Heizungsrelais und der Pumpe sowie die Zeit, die seit dem Ein- und Ausschalten der Heizung vergangen ist. Alle 5 Sekunden zeigt das Display in der zweiten Zeile kurz die aktuellen Grenzwerte an. Fotos der Anzeige in verschiedenen Modi werden am Ende der Veröffentlichung angezeigt.
Grafiken
Auf Anforderung über den integrierten Webserver werden die Protokolldaten in binärer Form mit JavaScript gelesen:
var xhttp = new XMLHttpRequest(); xhttp.open("GET", "logs.bin", true); xhttp.responseType = "arraybuffer"; xhttp.onprogress = updateProgress; xhttp.onload = function (oEvent) { var arrayBuffer = xhttp.response; if (arrayBuffer) { var byteArray = new Uint8Array(arrayBuffer); … }}; xhttp.send(null);
Das Lesen in einem gängigen nicht-binären Format wie Ajax wäre ein unzulässiger Luxus für den Controller, vor allem wegen der großen Menge, die der eingebaute http-Server zurückgeben müsste.
Aus dem gleichen Grund wird die jqPlot- JavaScript-Bibliothek zum Erstellen von Diagrammen verwendet, und die JS-Bibliotheksdateien selbst werden von gängigen CDNs geladen.
Ein Beispiel für den Zeitplan des Geräts:

Es ist deutlich zu sehen, dass gegen 9:35 Uhr das Gerät zum Heizen eingeschaltet wurde und der Kessel begann, den Heizkreislauf allmählich zu erwärmen (Sensoren T3, T4), wonach die Temperatur des Poolkreislaufs zu steigen begann (Sensoren T1, T2). Irgendwann gegen 10:20 Uhr schaltete der Kessel auf Warmwasser im Haus um, die Temperatur des Heizkreislaufs sank. Nach weiteren 10 Minuten kehrte der Kessel zum Erhitzen des Poolwassers zurück. Um 10:50 Uhr ereignete sich ein Unfall: Die Pumpe für das zirkulierende Wasser im Pool wurde plötzlich ausgeschaltet. Der Wasserdurchfluss fiel stark auf Null ab, das Heizungsrelais wurde ausgeschaltet (rot gepunktete Linie in der 2. Tabelle), um eine Überhitzung zu vermeiden. Das Gerät blieb jedoch in einem Heizzustand (rote Linie in der 2. Tabelle). Das heißt, Wenn die Pumpe wieder eingeschaltet würde und die Temperaturen normal wären, würde das Gerät wieder heizen. Ich stelle fest, dass nach einer Notabschaltung der Pumpe die Temperaturen im Poolwasserkreislauf (T1, T2) aufgrund der Überhitzung des Wärmetauschers stark anstiegen. Und wenn der Kessel nicht scharf abgeschaltet würde, gäbe es Probleme.
Eingebetteter Webserver
Für die Kommunikation mit der Außenwelt wird die Standardklasse ESP8266WebServer verwendet . Wenn das Gerät gestartet wird, wird es als Zugriffspunkt mit dem in #define AP_PASS angegebenen Standardkennwort initialisiert. Eine Webseite wird automatisch geöffnet, um ein verfügbares WLAN-Netzwerk auszuwählen und ein Passwort einzugeben. Nach Eingabe des Kennworts wird das Gerät neu gestartet und stellt eine Verbindung zum angegebenen Zugriffspunkt her.
Fertiges Gerät
Das fertige Gerät wurde zur Verkabelung in eine Standardschneidbox gelegt. Darin wurde ein Loch für das LCD und Löcher für die Anschlüsse ausgeschnitten.

Fotos der Fassade des Geräts in verschiedenen ModiMit der Anzeige der nach dem Einschalten verstrichenen Zeit:

Mit angezeigten Grenzwerten:

Fazit
Abschließend möchte ich sagen, dass ich bei der Entwicklung eines solchen Geräts große Erfahrungen mit Schaltkreisen, Leiterplattendesign, Installationsfähigkeiten für SMD-Komponenten, der Architektur und Programmierung von Mikrocontrollern gesammelt habe. Ich erinnerte mich an fast vergessenes C ++ und den sorgfältigen Umgang mit Speicher und anderen begrenzten Controller-Ressourcen. In gewissem Umfang waren auch Kenntnisse in HTML5, JavaScript und Debugging-Fähigkeiten von Skripten im Browser hilfreich.
Diese Fähigkeiten und die Freude an der Entwicklung des Geräts sind die Hauptvorteile. Und die Quellcodes des Geräts, Schaltplan, Leiterplatten - bitte verwenden, ändern. Alle Projektquellcodes befinden sich auf GitHab. Hardware in einem öffentlichen Projekt auf EasyEDA. Ich habe Daten zu den im Projekt verwendeten Chips auf einem Netzlaufwerk gesammelt.