Hydroponik auf einer Fensterbank oder C ++ 11 in AVR-Mikrocontrollern

Das Projekt enthält kein Arduino


Dieses Projekt musste ursprünglich anders aussehen - eine monumentale Struktur bestehend aus einem Sockel mit Dosen und Pumpen, einem darauf montierten Aquarium und einer Tomatenoase darüber. Im Paradies einer Tomatenoase war ein Wasserfall geplant, und im Aquarium bildeten sich Lebensformen der Fische. Die Hauptvoraussetzung dafür war die Fähigkeit, die ungeplanten Bewohner des Aquariums zu essen und das Glas sauber zu halten. Die Hauptkandidaten sind Somiki und Gourami. Wie Sie vielleicht erraten haben, lautet mein Motto "Faulheit ist der Motor des Fortschritts" (und was können Sie tun, damit Sie das Aquarium nicht reinigen und die Tomaten nicht gießen)?

Ein Denkmal für dieses Motto wäre wahrscheinlich errichtet worden, wenn es nicht bereits bei der Koordinierung der Entwurfsentwürfe mit seiner Frau zusammengebrochen wäre. Sie war nicht von der Idee inspiriert, diese Bandura zur Hauptdekoration des Wohnzimmers zu machen, und selbst der Wasserfall überzeugte sie nicht davon. Aber die Idee eines autonomen Systems, einer Symbiose aus Biologie und Elektronik, wollte mir nicht aus dem Kopf gehen, und das Projekt wurde auf die Größe eines Blumentopfs verengt - Aquaponik wurde zu Hydrokultur, Fischleben wurden gerettet.

Die Hauptidee der Hydrokultur ist die Verwendung einer wässrigen Nährstofflösung anstelle des Bodens. Dies ermöglicht eine Größenordnung, um das Pflanzenwachstum zu beschleunigen. Man kann die Wurzeln jedoch nicht einfach ins Wasser senken - sie brauchen Sauerstoff, ohne den sie zu sterben beginnen. In dieser Hinsicht gibt es Optionen - ständig Wasser mit einem Kompressor blasen, wie in einem Aquarium, oder die Wurzeln regelmäßig mit einer Nährlösung überfluten und nach einiger Zeit abtropfen lassen. Die erste Option hat einen Nachteil - das ständige Summen des Kompressors. Die zweite Option hat den Vorteil, dass die meisten Wurzeln in der Luft liegen und aktiv atmen, und der Effekt der Wachstumsbeschleunigung sollte noch größer sein. Darüber hinaus werden sie in ein Substrat aus speziellen porösen Granulaten eingetaucht, die Feuchtigkeit speichern. Die Wahl war offensichtlich, ich nahm die zweite Option als Grundlage.Bei Fischen könnte sich das System als fast vollständig geschlossen herausstellen - Fischsekrete werden von speziellen Bakterien in einem Biofilter verarbeitet, das verarbeitete Produkt wird Pflanzen zugeführt, eine Sandschicht filtert das Wasser, sauberes Wasser wird in das Aquarium zurückgeführt. Im Idealfall wird das Futter gelegentlich in einen automatischen Futterautomaten gestreut, und Tomaten sammeln sich aus den Büschen. Aber es ist nicht zusammengewachsen, vielleicht zum Besseren - wer weiß, wie die Bestellung eines Stammes der notwendigen Bakterien per Post enden würde.

Infolgedessen nahm das Gerät der Tomatenpflanze Konturen an. Zwei Gefäße - das untere mit Wasser, das obere mit einem Substrat und einer Pflanze. Zum Fluten verwenden wir eine kleine chinesische Pumpe mit Gleichstrommotor, zur Entwässerung einen automatischen Siphon. Das Funktionsprinzip des Siphons im Video:

Hydroponik mit einem ähnlichen Siphon:


Das Gehirn des Geräts ist der ATMEGA328P-Mikrocontroller (einfach weil der Placer zur Hand war). Zu seinen Aufgaben gehören das Management der Überschwemmung und des Abflusses nach einem Zeitplan, die Überwachung des Wasserstandes im Tank und das Signalisieren seines Mangels, die Steuerung der Beleuchtung der Anlage (wir möchten eine bestimmte Mindestlänge des Tageslichts; wenn das natürliche Licht endet, wird künstliches Licht allmählich eingeschaltet), eine Benutzeroberfläche zum Betrachten den Status, das Management und die Konfiguration dieser gesamten Wirtschaft. Dies erfordert natürlich eine Lösung für den Wasserstandsensor, Lichtsensoren, eine Echtzeituhr und eine Art Benutzerterminal.

Bevor Sie die Details beschreiben, eine Liste der Projektressourcen:

Hier sehen Sie Fotos des Ergebnisses und des Herstellungsprozesses.

Kurzes Video:



Das Projekt ist auf GitHub verfügbar . Dort wird in den Releases eine Datei mit dem elektronischen Teileprojekt in KiCAD und den Design-Schnickschnackprojekten in SolidWorks angelegt (STL-Dateien zum Drucken sind beigefügt).

Merkmale der Firmware-Baugruppe
« ». , , , USB AVR (, , , , ), . - , , 'ADK_ROOT' , 'scons'.


Schema des elektronischen Teils:



Weitere Details, eine Beschreibung der Fallstricke und ein bisschen Code. Beschreibung der Softwareprobleme ganz am Ende . Vielleicht interessiert sich jemand für ein neues Beispiel für die Arbeit mit I2C, einen Valcoder, ein RTC-Modul und ein Grafikdisplay. Der gesamte Code im Projekt wurde „von Grund auf neu“ geschrieben, ohne Lösungen von Drittanbietern zu verwenden (weil ich kann).

Wasserstandsensor


Das heikelste Thema wurde zuerst entschieden. Es gab natürlich eine Variante einer Art Schwimmer, so dass beispielsweise die Schiene, auf die der Gray-Code angewendet wurde, bewegt und die optischen Sensoren gelesen wurden. Aber es sah wirklich unzuverlässig aus. Die Suche bei eBay ergab kein Ergebnis - es gab entweder Schwimmerschalter (die das gewünschte Niveau erreichten oder nicht) oder eingetauchte Elektroden und Messwerte, die auf der Leitfähigkeit des Mediums basierten. Dies wurde jedoch sofort festgestellt, da sich die Zusammensetzung des Wassers zusammen mit der Leitfähigkeit der zugesetzten Düngemittel und der Auflösung ständig ändern würde Verunreinigungen vom Substrat. Als Ergebnis kam die Idee, einen Ultraschall-Entfernungsmesser zu verwenden, der normalerweise auf verschiedenen Robotern platziert wird. Der Sensor befindet sich wie geplant im Tankdeckel und das Signal wird direkt von der Wasseroberfläche reflektiert. Wurde HC-SR04 gekauft (die Wahl des kleinsten Wertes des Mindestarbeitsabstands - er hat 2cm),und das Konzept wurde an einem Eimer Wasser überprüft. Es stellte sich heraus, dass es für sich selbst funktionierte (es gab Befürchtungen, dass es keine normale Reflexion von der Wasseroberfläche geben würde oder dass es nicht genügend Strahlrichtung geben würde und es unerwünschte Reflexionen von den Tankwänden geben würde). Der Entfernungsmesser war übrigens auch eine Backup-Option, aber Infrarot. Auf die Wasseroberfläche sollte ein Schwimmer mit einem Reflektor geworfen werden. Das einzige Problem ist der Mindestarbeitsabstand von 10 cm (von denen, die ich gefunden habe), was für die angegebenen Abmessungen bereits ein bisschen viel ist.Auf die Wasseroberfläche sollte ein Schwimmer mit einem Reflektor geworfen werden. Das einzige Problem ist der Mindestarbeitsabstand von 10 cm (von denen, die ich gefunden habe), was für die angegebenen Abmessungen bereits ein bisschen viel ist.Auf die Wasseroberfläche sollte ein Schwimmer mit einem Reflektor geworfen werden. Das einzige Problem ist der Mindestarbeitsabstand von 10 cm (von denen, die ich gefunden habe), was für die angegebenen Abmessungen bereits ein bisschen viel ist.




Nach den Ergebnissen des Projekts funktioniert dieser Ansatz und kann in der Praxis angewendet werden, ohne dass Probleme festgestellt wurden. Es lohnt sich, Maßnahmen zu ergreifen, um die Platte vor Feuchtigkeit zu schützen (Versiegelung im Gehäuse). Das ist nur, dass die Sensoren selbst offen bleiben, vielleicht kommt es noch herum.

Die Schnittstelle des Sensors ist einfach - ein Impuls wird an den Triggereingang gesendet, der ein Echosignal auslöst. Am Echoausgang wird ein Impuls erzeugt, dessen Länge gleich der Zeit vom Beginn der Strahlung bis zur Annahme des reflektierten Echosignals ist. Durch Messen der Pulslänge, Kenntnis der Schallgeschwindigkeit und der Tatsache, dass das Signal zum Objekt und zurück geht, können Sie die Entfernung berechnen. Im Projekt wird dies in der LevelGauge-Klasse implementiert. Zur Messung der Pulslänge wird die Hardwarefähigkeit von MK AVR „Input Capture“ verwendet. In diesem Fall wird der Hardware-Timer bei der ansteigenden Flanke des Impulses zurückgesetzt, und beim Abwärtswert des Timers wird die Hardware im Register ICR1 gespeichert und ein Interrupt erzeugt. Somit ist es möglich, die Impulsdauer mit ausreichender Genauigkeit und minimalem Prozessorverbrauch zu messen.

Selbst bei diesem Modell des Sensors wurde ein Fehler festgestellt - beim Anlegen der Stromversorgung blieb die Echolinie konstant aktiv. Er umging, indem er einen Impuls an den Auslöser legte und wartete, bis der erste Echoortungszyklus vergangen war.

Hintergrundbeleuchtung


Die Hintergrundbeleuchtung besteht aus drei Grip-LEDs. Ich bog den dreieckigen Rahmen aus dem Aluminiumprofil und klebte die LEDs mit Epoxidharz darauf. Ich habe einen chinesischen Stromstabilisator mit 700 mA für die Stromversorgung bestellt. Ungefähr drei Volt fallen an jeder Diode ab, der Stabilisator benötigt eine Differenz zwischen der Eingangs- und Ausgangsspannung von mindestens zwei Volt, und ich wollte den gesamten Wunderwafer über eine 12-Volt-Stromversorgung mit Strom versorgen. Von hier aus lässt sich leicht berechnen, warum genau drei LEDs.

Dioden sind warmweiß. Es schien mir natürlich, das Sonnenspektrum und all das. Aber wie ich später herausfand, verwenden Pflanzen nach meiner Bestellung normalerweise eine Kombination aus Rot und Blau. Soweit ich weiß, geht es nur um Effizienz. Wenn Sie einen großen Bauernhof mit Beleuchtung rund um die Uhr haben, interessiert Sie, dass die gesamte aufgewendete Energie für immer ausgegeben wird. Bei weißer Beleuchtung reflektieren die grünen Blätter die grüne Komponente, ein erheblicher Teil der für die Beleuchtung aufgewendeten Energie wird verschwendet.





Ein wichtiges Merkmal des Stabilisators ist das Vorhandensein eines Eingangs für die PWM-Regelung, mit dem ich die Helligkeit anpasse. Hier ist ein weiterer chinesischer Rechen. Erstens stellte sich heraus, dass es sich nur um eine Strom-Ein / Aus-Funktion handelt. Das heißt, ich habe erwartet, dass der Ausgangsstrom nicht moduliert wird und sein Wert vom Tastverhältnis des PWM-Signals abhängt, aber der Strom wiederholt einfach die Impulse am Steuereingang. Aber das ist nicht so schlimm, ein weiterer Hinterhalt war, dass der Regler mit einer ziemlich hohen Frequenz unzureichend auf PWM reagiert. Ich musste es auf 300 Hz senken, bei denen es mehr oder weniger normal funktionierte. Das PWM-Signal wird vom Mikrocontroller in Hardware unter Verwendung eines der Timer erzeugt.



Ein weiterer wichtiger Teil der Hintergrundbeleuchtung sind die Lichtsensoren. In dieser Rolle wurden Fototransistoren ausgewählt. Und ja, es gibt zwei davon - eine über den LEDs zur Messung des natürlichen Lichts, die zweite unter den LEDs zur Rückmeldung. Die automatische Tageslichtverlängerungsfunktion wurde zwar noch nicht wie im Sommer implementiert und war auch nicht erforderlich (und Motivation ist eine ernste Angelegenheit). Es wurde angenommen, dass sobald der erste Sensor eine Abnahme des Beleuchtungsniveaus feststellt (und die für Tageslichtstunden vorgesehene Zeit noch nicht abgelaufen ist), das Licht so geregelt wird, dass der zweite Sensor einen dem gewünschten Beleuchtungsniveau entsprechenden Wert erzeugt. Dazu müssen Sie einen einfachen PID-Regler im Code implementieren. In der Benutzeroberfläche können Sie jedoch nur die aktuellen Sensorwerte anzeigen und die gewünschte Helligkeit der Hintergrundbeleuchtung manuell aufwickeln.

Achten Sie auf den Anschluss der Sensoren. Jeder von ihnen hat zwei feste Bereiche, die durch Anschließen des entsprechenden Widerstands an Null ausgewählt werden. Der Fuß des Mikrocontrollers, der mit dem zweiten Widerstand verbunden ist, wird zu diesem Zeitpunkt in einen Zustand mit hohem Widerstand versetzt. Sie können beide Widerstände gleichzeitig einschalten, dann gibt es drei feste Messbereiche. Das Signal von den Emitterwiderständen wird durch eine RC-Schaltung geleitet, um die Modulationsimpulse zu filtern - das Licht von den LEDs pulsiert zusammen mit dem PWM-Signal am Stromregler.

Pumpe




Das billigste chinesische Getriebe mit Gleichstrommotor. Hinterhalte sind natürlich verfügbar. Trotz der Tatsache, dass 12 V angezeigt werden, funktioniert es bei dieser Spannung nicht lange. Einer brannte vor dem Zusammenbau der Struktur aus. Das Schema sieht PWM dafür vor, die maximale Leistung ist in der Schnittstelle konfiguriert, in der Praxis wurde sie nicht über 70% eingestellt. Bereits auf diesem Niveau heult er wild bei der Arbeit, aber die meiste Zeit arbeitet er mit einer viel geringeren Leistung - etwa 30% und rumpelt ziemlich leise. Über seine Betriebsarten weiter unten in der Beschreibung der Logik der Überschwemmung. Der größere Kondensator (C8 im Diagramm) muss näher am Pumpenstromkreis positioniert werden, da sonst der gesamte Stromkreis stark gestört wird (in der Praxis stellte sich heraus, dass der Stromregler für LEDs am empfindlichsten für sie ist, es beginnt leichte Musik).

Echtzeituhr


Es war eine verrückte Idee, die Ressourcen des Mikrocontrollers für diese Zwecke zu nutzen. Quarzuhrgenerator hat eine ziemlich gute Genauigkeit, in einem anderen Projekt hat dieser Ansatz gut funktioniert. Das Problem ist jedoch, dass absolut alle Hardware-Timer bereits für andere Zwecke verwendet wurden. Es blieb keine andere Wahl, als das externe RTC-Modul zu finden. Loben Sie die Chinesen, sie sind da und sie sind billig.



Das auf dem DS3231 basierende Modul verfügt über eine I2C-Schnittstelle und eine eigene redundante Stromversorgung - bei einem Stromausfall wird keine Zeit schief gehen. Es gibt einen Mäanderausgang bei mehreren festen Frequenzen - 1 kHz, 4 kHz und 8 kHz. Dies war sehr nützlich für Audiosignale - auch hier müssen Sie die MCU nicht laden, und es gab keine freien Timer dafür. Das 32-Kbit-EEPROM ist ein Bonus, wird jedoch in diesem Projekt nicht verwendet.

Überraschenderweise ist es sehr genau - in wenigen Monaten verlor die Zeit für einige Sekunden ihre Kraft. Er gab an, dass er den Einfluss der Temperatur auf die Frequenz des Generators berücksichtigt, und anscheinend funktioniert dies. Wenn dennoch die Zeit vergeht, besteht die Möglichkeit einer Software-Frequenzkorrektur. Die Messwerte des Temperatursensors sind verfügbar und werden in diesem Projekt in der Benutzeroberfläche angezeigt.

Die Rtc-Klasse ist für die Arbeit mit diesem Modul im Code verantwortlich.

Anzeige


Ich wollte schon lange etwas mit einem Grafikdisplay machen. Die Suche nach dem günstigsten mit I2C-Schnittstelle gab diese Option.



Monochromes OLED-Display 128x64 Pixel basierend auf dem recht beliebten Controller SSD1306. Bei der Auswahl müssen Sie die Beschreibung sorgfältig prüfen - derselbe Chip unterstützt andere Schnittstellen außer I2C, und es gibt Optionen ohne I2C. Oder sie schreiben, dass es universell ist, es unterstützt auch I2C, aber in Wirklichkeit wird es notwendig sein, das Board leicht zu modifizieren, indem die Nullen auf andere Sites umgestellt werden. Wenn Sie I2C verwenden möchten, ist es daher besser, eine zu wählen, bei der nur I2C auf der Karte angezeigt wird. Bei einer Karte, die fast keine Dokumentation enthält (weniger Dokumentation nur für den Chip), ist der Aufwand geringer. Diese Version arbeitet mit 5 V, die Karte verfügt über einen 3,3 V-Regler, der für die Steuerung erforderlich ist. Ich habe Bewertungen getroffen, die in einigen Versionen möglicherweise nicht vorhanden sind.

Die Anzeige ist in der Regel zufrieden. Mir ist nur ein unangenehmes Merkmal aufgefallen: Die Helligkeit einer Pixelzeile hängt davon ab, wie viele Pixel darin beleuchtet sind. Je mehr Licht vorhanden ist, desto geringer ist die Helligkeit. Der Kontrast zwischen den Linien kann auffällig sein, wenn sich vollständig ausgefüllte Bereiche mit einigen schmalen Elementen auf dem Bildschirm abwechseln. In der Praxis ist dies jedoch in meinen Bildern nicht sichtbar und fällt nicht auf.

Die Steuerung kann so konfiguriert werden, dass sie in verschiedenen Modi zum Anzeigen des Inhalts des Bildschirmspeichers auf einer Pixelmatrix arbeitet. Für mich war es bequemer, wenn jedes Byte auf eine vertikale Pixel mit einer Höhe von acht Pixeln abgebildet wird und die Spalten horizontal von links nach rechts verlaufen und den Bildschirm mit Zeilen füllen, die acht Pixel hoch sind. In diesem Modus ist es bequemer, Text zu zeichnen.

Oft wird ein Ansatz praktiziert, bei dem der Anzeigespeicher in der RAM-MCU dupliziert wird - zuerst werden alle Aktionen mit dem Bild im RAM ausgeführt, und dann werden alle geänderten Pixel in den Anzeigespeicher kopiert. In diesem Projekt wird dieser Ansatz nicht zum Einsparen von Ressourcen verwendet. Alle geänderten Stellen werden sofort im Anzeigespeicher neu gezeichnet.

Wie in den Kommentaren vorgeschlagen, verblassen OLED-Anzeigen mit der Zeit. Ich vermutete dies auch (erinnerte mich daran, was Bildschirmschoner ist) und sorgte dafür, dass sich die Anzeige nach einigen Minuten nach der letzten Aktivität auf den Steuerelementen ausschaltete. Sie wird beim Drehen oder Drücken des Encoders eingeschaltet.

Im Code ist die Arbeit mit der Anzeige in der Anzeigeklasse implementiert.

Valkoder:



Meiner Meinung nach ist der Valcoder die beste Steuerungsoption für solche Geräte, die mindestens eine Benutzeroberfläche haben. Es ist kompakt und sehr komfortabel. Es ist bequem für sie, durch Menüelemente zu blättern und diese auszuwählen, die Werte von Parametern zu ändern, Modi zu wechseln usw.

Zum Anschließen sind drei Eingangszweige des Mikrocontrollers erforderlich. Eine für den Knopf (Sie können den Griff drücken), zwei für den Valcoder selbst. Es gibt ein Gray-Code- Signal vom Encoder . Bei jedem Umdrehungsschritt ändert sich ein Bit auf zwei Zeilen. Die Reihenfolge bestimmt die Drehrichtung.

Alles scheint einfach zu sein, aber anscheinend sind Entwickler nicht immer in der Lage, qualitativ hochwertige Unterstützung für ein solches Gerät zu leisten. Auf meinem 3D-Drucker befinden sich beispielsweise eine RAMPS-Karte und eine Karte mit einem Display, und genau derselbe Encoder ist angeschlossen. Die Marlin-Firmware funktioniert damit, aber die Erfahrung mit der Verwendung ist sehr schlecht - es gibt kein Gefühl der Zuverlässigkeit - wenn Sie beim Drehen des Knopfes auf den Knopf klicken, stoppt die Benutzeroberfläche häufig bei dem falschen Menüpunkt oder Parameterwert, für den sie erwartet wurde. Bei schneller Rotation fühlt es sich an, als würden Klicks überspringen. Irgendwann beginnt das Umschalten nicht mehr während eines Klicks, aber irgendwo dazwischen ist es sehr unangenehm. Ja, was ist Marlin, ich habe manchmal das gleiche Gefühl beim eingebauten Multimedia-System im Auto. In diesem Zusammenhang einige Tipps (und natürlich den Code in der Nähe der RotEnc-Klasse).

Erstens, ein ziemlich offensichtlicher Punkt für jeden, der irgendwelche Tasten mit dem Mikrocontroller verbindet - Sie müssen sich mit Bounce befassen. Dieser mechanische Encoder und seine Signalleitungen sind in der Tat die gleichen Tasten, und sie haben auch ein Rattern. Zuerst filtern wir das Rattern, dann verarbeiten wir die Folge von Zuständen der Signalleitungen. Es kann Valcoder mit optischen Sensoren geben, dies hängt bereits von den Signalverarbeitungsschemata ab. Wenn die Beine eines Fototransistors direkt herausgebracht werden, kann es dort mit langsamer Rotation klappern, aber wenn es ein Verarbeitungsschema gibt, das eine Hysterese einführt, ist keine Softwareunterdrückung erforderlich. Aber solche Geräte sind teurer und werden selten in Amateurgeräten verwendet, die häufigsten sind mechanische, ein paar Dollar pro Haufen.

Zweitens ein etwas weniger offensichtlicher Punkt, wahrscheinlich einer, an dem Marlin verbrannt wurde - der Griff hat während der Drehung stabile Positionen - Klicks (Klicks). Dieses Modell besteht aus vier Schritten einer Codesequenz für jeden Klick. Sie müssen also auf Klicks reagieren und nicht auf die Schritte der Sequenz. Und das Wichtigste ist, mit stabilen Positionen zu synchronisieren. Viele geben einfach die Konstante STEPS_PER_CLICK ein und reagieren beispielsweise auf jeden vierten Schritt. Das Problem ist jedoch, dass das Signal nicht perfekt ist und die Sequenzen möglicherweise nicht ganz korrekt sind. Mit einer bestimmten Schreibweise kann der Code in die Irre gehen. Infolgedessen wird jeder vierte Schritt irgendwo in der Mitte des Klicks ausgeführt, was für den Benutzer unangenehm ist. Gleichzeitig entspricht die feste Position des Griffs für ein bestimmtes Modell einem festen Codewert.es muss daran befestigt sein.

Drittens wiederum ein ziemlich offensichtlicher Punkt für mehr oder weniger erfahrene Entwickler von Mikrocontrollersystemen: Verwenden Sie Hardware-Interrupts, um den Status der Eingangsleitungen zu ändern. Zumindest besteht ein geringeres Risiko, dass die Schritte der Sequenz "verloren" gehen. Aber im Allgemeinen sind, wie Sie wissen, Unterbrechungen unser Alles. Die MCU sollte nach Möglichkeit schlafen und nur bei Unterbrechungen aufwachen - entweder von der Außenperipherie oder von einem Timer, um eine verzögerte Aufgabe auszuführen. Dies sind die Prinzipien eines guten Entwurfs der Systemarchitektur.

Design als Ganzes


Es besteht aus improvisierten Materialien und verschiedenen Teilen, die auf einem 3D-Drucker aus ABS gedruckt wurden.

Das Funktionsprinzip des Siphons ist im obigen Video dargestellt. Für mich ist es ein externes PVC-Rohr und ein internes Rohr mit einem Trichter am Ende. Für den klassischen Siphon ist ein weiteres Knie erforderlich, aber es war schon schwierig, es konstruktiv für mich zu machen. Wenn Probleme mit dem Abfluss festgestellt wurden, klebte ein kleines Bad an der Wand des unteren Tanks, in das das Ende des Innenrohrs eingetaucht war, und erzeugte Widerstand gegen den Abfluss, so dass der Siphon arbeiten konnte.

Es stellte sich heraus, dass ABS ein sehr hydrophobes Material ist. Wasser läuft buchstäblich nicht durch, ich musste sogar den Siphontrichter erneuern. Es lohnt sich, diese Eigenschaft in Betracht zu ziehen. Es ist unmöglich, einige Miniaturhydrauliksysteme zu erstellen (zum Beispiel wollte ich Führungsflächen am Siphontrichter herstellen, um das Wasser zu verdrehen und die Siphonreaktion zu verbessern. Bei solchen Abmessungen und der Hydrophobizität von ABS ist dies jedoch nicht sinnvoll.) .

Ich habe auch zuerst versucht, alles mit einer Heißklebepistole zusammenzukleben. Es funktioniert nicht - zuerst schien alles festzuhalten, aber nach ein paar Tagen fiel es von selbst ab. Die beste Option ist Zentralasien. Details, auch unter Wasser, halten fest.

Die größte Fehleinschätzung im Design sind transparente Behälter. Ich habe völlig vergessen, dass Wasser im Licht blüht. Ich musste es mit undurchsichtigem Material umwickeln. Nun, Sie können regelmäßig Kaliumpermanganat zur Desinfektion hinzufügen, dies scheint die Pflanzen nicht zu schädigen.

Der Flutungsalgorithmus lautet wie folgt: Zuerst wird die Pumpe bei geringer Leistung eingeschaltet und füllt leise den gesamten oberen Tank. Der Prozess wird von einem Füllstandsensor überwacht. Wenn Wasser durch den Siphontrichter zu fließen beginnt, stoppt der Füllstandabfall im unteren Tank, der vom Sensor erfasst wird. Ein kleiner Durchfluss bei geringer Leistung reicht nicht aus, um einen Siphon auszulösen. Die Pumpe stoppt, das in den oberen Tank gepumpte Volumen wird gespeichert. Die Wurzeln bleiben einige Minuten in der Lösung, danach schaltet sich die Pumpe wieder ein. Erstens schaltet die Pumpe bei geringer Leistung, bis das Wasser den Trichter wieder erreicht (während der Ausfallzeit fällt es aufgrund der Wirkung des Siphons ab), und wenn der Füllstand des Trichters erreicht ist, auf erhöhte Leistung ein, wodurch ein ausreichender Durchfluss für den Siphon bereitgestellt wird. Der Durchfluss durch den Siphon ist garantiert höher als der Pumpenfluss.Infolgedessen beginnt der Füllstand im unteren Tank anzusteigen, dies wird vom Sensor erfasst und die Pumpe stoppt.

Die Hochwasserzyklen beginnen regelmäßig in festgelegten, konfigurierbaren Zeitintervallen von morgens bis abends. Nach dem Plan sollte die Morgendämmerung durch den Lichtsensor festgelegt und die Länge des Tageslichts, falls erforderlich, auf den eingestellten Wert verlängert werden, aber bis dahin hatten die Hände ihn nicht erreicht. Die Dämmerungszeit wird einfach in den Einstellungen eingestellt.


Und wo ist C ++ 11?



Vielleicht wird jemand bezweifeln, dass C ++ 11 beim Programmieren von Mikrocontrollern nützlich sein kann (unter denen, die allgemein wissen, dass Mikrocontroller in C ++ programmiert werden können). Ich werde versuchen, konkrete Beispiele für die Vorteile von C ++ 11 in diesem Bereich zu nennen (zusätzlich zu offensichtlichen angenehmen kleinen Dingen wie constexpr, override, default usw.).

Platzierung von String-Ressourcen

Viele Menschen wissen, dass RAM in Mikrocontrollern eine sehr begrenzte Ressource ist. Dies kann ein Problem sein, wenn Ihre Anwendung beispielsweise über eine Benutzeroberfläche verfügt und Ihr Programm eine relativ große Anzahl von Zeilen verwendet. Wenn im Code so etwas zu schreiben ist
PromptUser("Are you sure you want to format SD-card?");

Dann wird die in den Argumenten übergebene Zeile in den initialisierten Datenabschnitt (im Folgenden das Verhalten des GCC-Compilers für die AVR-Plattform) gestellt, dh in den RAM-Bereich, der beim Start (bevor die Hauptfunktion aufgerufen wird) aus dem Programm-Flash-Speicher initialisiert wird. Der Funktion PromptUser () wird ein Zeiger auf die gewünschte Stelle im RAM übergeben. Wenn Sie im gesamten Programm einen ähnlichen Ansatz verwenden, endet der RAM recht schnell (in dem in diesem Projekt verwendeten ATMEGA328P sind es nur 2 Kilobyte, und dies gilt auch für BSS, Heap und Stack). Um diese Einschränkung zu umgehen, lernen Funktionen wie PromptUser (), nicht mit Zeigern auf RAM, sondern mit Zeigern auf einen Bereich im Programm-Flash-Speicher zu arbeiten. Sie können von dort nur mit Hilfe spezieller Anweisungen lesen, die beispielsweise in avr-libc in Funktionen der Familie eeprom_read_ [byte | word | dword | ...] eingeschlossen sind..
In diesem Fall muss die Zeichenfolge zuerst in eine Variable eingefügt werden, die mit dem PROGMEM-Attribut ausgestattet ist, das dem Compiler mitteilt, dass sie im Programmspeicher abgelegt werden soll .
char prompt[] PROGMEM = "Are you sure you want to format SD-card?";
PromptUser(prompt);

Dies ist unpraktisch, wenn Sie alle Zeilen zentral deklarieren möchten. Dann müssen Sie zuerst ihre Deklaration in der Header-Datei deklarieren:
extern char prompt[] PROGMEM;

Definieren Sie in einer separaten CPP-Datei Folgendes:
char prompt[] PROGMEM = "Are you sure you want to format SD-card?";

Vervielfältigung von Code, was nicht gut und sehr unpraktisch ist, wenn es viele solcher Zeilen gibt. Ja, dies kann umgangen werden, indem ein kniffliges Makro erstellt und die Header-Datei in eine separate CPP-Datei aufgenommen wird, in der das Makro in die Definition erweitert wird, während es in anderen Kontexten in die Deklaration erweitert wird. In C ++ 11 gibt es jedoch eine sauberere Option, wenn Sie beim Deklarieren die Initialisierung von Klassenmitgliedern verwenden. Deklarieren Sie in der Header-Datei die Klasse mit den Zeilen:
#define DEF_STR(__name, __text) \
    const char __name[sizeof(__text)] = __text;

class Strings {
public:
    DEF_STR(Prompt, "Are you sure you want to format SD-card?")
    DEF_STR(OtherString, "...")
} __attribute__((packed));

extern const Strings strings PROGMEM;

In der CPP-Datei:
const Strings strings PROGMEM;

Jetzt werden alle Zeilen an einer Stelle deklariert, im Programmspeicher abgelegt, und Sie können wie folgt darauf zugreifen:
PromptUser(strings.prompt);

In diesem Projekt wird ein Ansatz verwendet, der auf demselben Prinzip basiert, um Bitmaps zu bestimmen - verschiedene Bilder, die auf einer grafischen Anzeige angezeigt werden.
/** Bitmap descriptor. */
struct Bitmap {
    /** Pointer to data array if in data memory. Offset of data array relatively
     * to Bitmaps class instance start address if in program memory.
     */
    const u8 *data;
    /** Number of pages in the bitmap. */
    u8 numPages,
    /** Number of columns in the bitmap. */
       numColumns;
} __PACKED;

template<u8... data>
constexpr static u8
Bitmap_NumDataBytes()
{
    return sizeof...(data);
}

/** Define bitmap.
 * @param __name Name for accessing.
 * @param __numPages Number of pages in the bitmap. Number of columns defined as
 *      total number of data bytes divided by number of pages.
 * @param __VA_ARGS__ Data bytes.
 */
#define DEF_BITMAP(__name, __numPages, ...) \
    const u8 __CONCAT(__name, __data__) \
        [Bitmap_NumDataBytes<__VA_ARGS__>()] = { __VA_ARGS__ }; \
    const Bitmap __name { \
        reinterpret_cast<const u8 *>(OFFSETOF(Bitmaps, __CONCAT(__name, __data__))), \
        __numPages, \
        sizeof(__CONCAT(__name, __data__)) / __numPages};

/** Global bitmaps repository. Stored in program memory. */
class Bitmaps {
public:

    /** Thermometer icon. */
    DEF_BITMAP(Thermometer, 1,
        0b01101010,
        0b10011110,
        0b10000001,
        0b10011110,
        0b01101010
    )

    /** Sun icon. */
    DEF_BITMAP(Sun, 1,
        0b00100100,
        0b00011000,
        0b10100101,
        0b01000010,
        0b01000010,
        0b10100101,
        0b00011000,
        0b00100100
    )
    ...
};
extern const Bitmaps bitmaps PROGMEM;

Der Unterschied besteht darin, dass zusätzlich zu den Bilddaten selbst auch Attribute (Bildgrößen) platziert werden müssen. Jedes Byte definiert eine Spalte mit acht Pixeln. Spalten können eine oder mehrere Zeilen füllen, ihre Nummer wird durch den zweiten Parameter nach dem Namen angegeben. Es stellt sich heraus, dass die Höhe der Bitmaps für eine beliebige Breite ein Vielfaches von acht sein sollte, was für dieses Projekt durchaus akzeptabel ist.

Binäre Literale

Möglicherweise haben Sie bereits bemerkt, dass die Bitmaps im vorherigen Beispiel Binärliterale zur Bestimmung verwenden. Dies ist sehr praktisch - Sie können einfache Bitmaps direkt im Code bearbeiten, insbesondere wenn der Editor das Hervorheben von Bitmaps zulässt. Beispiel: Definitionen von Schriftzeichen in der Datei font.h:



Variadische Vorlagen

Wo dann ohne sie dann. Nun, zum Beispiel können Befehle für den Display-Controller eine Länge von einem bis mehreren Bytes haben. Wird mit folgendem Code gesendet:

SendCommand(Command::DISPLAY_ON);
SendCommand(Command::SET_COM_PINS, COM_PINS | COM_PINS_ALTERNATIVE);
SendCommand(Command::SET_COLUMN_ADDRESS, curVp.minCol, curVp.maxCol);

Praktisch, nicht wahr?
    /** Queue command sending.
     * @param bytes Up to MAX_CMD_SIZE bytes of command data.
     */
    template <typename... TByte>
    void
    SendCommand(TByte... bytes)
    {
        cmdSize = sizeof...(bytes);
        controlSent = false;
        cmdInProgress = true;
        SetCmdByte(sizeof...(bytes) - 1, bytes...);
        i2cBus.RequestTransfer(DISPLAY_ADDRESS, true,
                               CommandTransferHandler);
    }

    template <typename... TByte>
    inline void
    SetCmdByte(int idx, u8 byte, TByte... bytes)
    {
        cmdBuf[idx] = byte;
        SetCmdByte(idx - 1, bytes...);
    }

    inline void
    SetCmdByte(int, u8 byte)
    {
        cmdBuf[0] = byte;
    }


Die Datei variante.h beschreibt eine Klasse, die boost :: variante mit variablen Vorlagen vage ähnelt. Es wird zum Organisieren von Benutzeroberflächenseiten verwendet. Der Punkt ist wieder das Speichern von Speicher - wo dynamische Speicherverwaltung ein unzulässiger Luxus ist, müssen Sie ausweichen (obwohl 2K immer noch viel ist, konnten Sie nicht ausweichen, aber in derselben ATMEGA-Zeile erreicht seine Größe 512 Bytes und jedes Byte pro Konto). In meiner Benutzeroberfläche wird jeweils eine Seite auf dem Bildschirm angezeigt. Dementsprechend können Sie für alle Seiten dasselbe Speicherelement verwenden, was in C als Vereinigung bezeichnet wird. Für Klassen in C ++ wird dies normalerweise als Variante bezeichnet. Im Gegensatz zu union müssen wir daran denken, den Destruktor des vorherigen Inhalts aufzurufen, bevor wir den Konstruktor des neuen aufrufen.

    Variant<MainPage,
            Menu,
            LinearValueSelector,
            TimeSelector> curPage;
    ...
    /** Get type code for the specified page class. */
    template <class TPage>
    static constexpr u8
    GetPageTypeCode()
    {
        return decltype(curPage)::GetTypeCode<TPage>();
    }
...
curPage.Engage(nextPageTypeCode, page);


Für die Kompilierung werden GCC- und GNU-Binutils für die AVR-Plattform verwendet (in Ubuntu gibt es ein fertiges Paket gcc-avr). Die Details des Montageprozesses wurden oben angegeben. Die Parameter für den Compiler sehen ungefähr so ​​aus (projektspezifische Defunds und Einschlüsse werden weggelassen): Link: Konvertieren Sie den Codeabschnitt in das Hex-Format: Erstellen Sie ein EEPROM-Image: Firmware für den Mikrocontroller: PS Die ersten Tomaten waren bereits reif und schmeckten nicht sehr gut. Anscheinend mochten sie etwas in der Diät nicht. Müssen wahrscheinlich die Kultur ändern.
avr-g++ -o build/native-debug/src/firmware/cpu/lighting.cpp.o -c -fno-exceptions -fno-rtti -std=c++1y -Wall -Werror -Wextra -ggdb3 -Os -mcall-prologues -mmcu=atmega328p -fshort-wchar -fshort-enums src/firmware/cpu/lighting.cpp


avr-g++ -o build/native-debug/src/firmware/cpu/cpu -mmcu=atmega328p build/native-debug/src/firmware/cpu/adc.cpp.o build/native-debug/src/firmware/cpu/application.cpp.o …


avr-objcopy -j .text -j .data -O ihex build/native-debug/src/firmware/cpu/cpu build/native-debug/src/firmware/cpu/cpu_rom.hex


avr-objcopy -j .eeprom --change-section-lma .eeprom=0 -O ihex build/native-debug/src/firmware/cpu/cpu build/native-debug/src/firmware/cpu/cpu_eeprom.hex


avrdude -p atmega328p -c avrisp2 -P /dev/avrisp -U flash:w:build/native-debug/src/firmware/cpu/cpu_rom.hex:i


Source: https://habr.com/ru/post/de385135/


All Articles