Dans cette publication, je partagerai mon expérience sur la création d'un appareil IoT à partir de zéro: de l'émergence d'une idée et de sa mise en œuvre matérielle à la création de firmware pour un contrôleur et d'une interface web pour gérer un appareil créé via Internet.
Avant de créer cet appareil, je:
- Presque ne comprenait pas les circuits. Seulement au niveau des principes de travail
résistance / transistor ... Je n'avais aucune expérience dans la création de circuits compliqués. - Cartes de circuits imprimés jamais conçues.
- Composant CMS jamais soudé. Le niveau du fer à souder était au niveau des fils à souder et d'une sorte de relais.
- Je n'ai jamais écrit de programmes aussi complexes pour un microcontrôleur. Toute l'expérience était au niveau «allumer la LED en Arduino», et j'ai rencontré pour la première fois le contrôleur ESP8266.
- J'ai écrit pas mal de C ++ pour le "grand frère", mais c'était il y a plus d'une douzaine d'années et tout a été oublié il y a longtemps.
Bien sûr, l'expérience de travailler en tant que programmeur (principalement Microsoft .NET) et la pensée systémique m'ont aidé à comprendre le sujet. Je pense que le lecteur pourra. Liens et articles utiles sur la mer Internet. Le plus, à mon avis, intéressant et aidant à comprendre le sujet, j'apporte l'article.
Énoncé du problème
Je vis dans une maison privée près de Minsk, et ma propre piscine, bien que la plus simple, fait partie intégrante de l'ensemble des «avantages» que de nombreuses personnes vivant dans une maison de campagne obtiennent. Dans notre climat instable, il s'est avéré que la baignade dans la piscine est inconfortable si elle est à l'extérieur: l'eau se refroidit la nuit et le temps venteux pendant la journée ne rend pas la natation confortable. L'année dernière, de mes propres mains, j'ai construit un dôme géodésique d'un foulon au-dessus de la piscine, mis une colline et accroché un élastique - les enfants sont heureux.

Reportage photo de la construction du dôme sur Flickr.
Cette année, je suis allé encore plus loin et j'ai décidé d'organiser un chauffe-piscine à partir d'une chaudière à gaz,
qui sert au chauffage de la maison en hiver et au chauffage de l'eau chaude en été.
En été, le circuit "chauffage" de la chaudière à l'aide de vannes passe en chauffage
piscine. L'eau de la piscine est chauffée à l'aide d'un échangeur de chaleur en titane, dont le circuit primaire fait passer le liquide de refroidissement (eau chaude sans impuretés) du circuit de chauffage, et le secondaire - l'eau de la piscine, pompée par une pompe de recirculation du système de filtration. Puisque j'utilise la piscine avec un électrolyseur (beaucoup de sujets intéressants sont décrits sur ForumHouse ), l'eau contient un peu de sel et un échangeur de chaleur en titane est nécessaire. Vous ne pouvez pas simplement prendre et laisser l'eau directement à travers la chaudière - sinon vous corroderez tous les tuyaux avec du sel.

En passant à travers l'échangeur de chaleur, le caloporteur chauffé par la chaudière à une température d'environ 70-90 ° C dégage de la chaleur à l'eau de la piscine, la chauffant de quelques degrés. Le liquide de refroidissement lui-même refroidit de quelques dizaines de degrés et retourne à la chaudière pour être à nouveau
réchauffé. Le rapport du refroidissement de l'eau de la chaudière au chauffage de l'eau de la piscine dépend de nombreux facteurs: la capacité de l'échangeur de chaleur et la vitesse de circulation de l'eau dans les circuits primaire et secondaire.
Les tuyaux reliés de la piscine à l'échangeur de chaleur sont des tuyaux en polyéthylène ordinaires, ceux qui
actuellement utilisé pour fournir de l'eau froide aux maisons privées. Bon marché, la capacité de résister à une pression décente, l'absence de corrosion - ce sont les principaux avantages de ces tuyaux. Pour tous, sans exception, les tuyaux en polyéthylène, la température de fonctionnement est limitée à 40 degrés Celsius. En principe, c'est plus que suffisant pour la piscine.
Cependant, il y a une forte probabilité d'urgence en cas de pompe
la recirculation de l'eau de l'eau de la piscine s'arrêtera pour une raison quelconque, et la chaudière continuera à chauffer l'échangeur de chaleur: dans ce cas, l'eau dans le circuit secondaire de l'échangeur de chaleur augmentera rapidement à la température du circuit primaire, ce qui signifie que les sections de tuyaux en polyéthylène adjacentes à l'échangeur de chaleur fondront et l'eau de la piscine sera inondée tout l'espace autour.
Il doit être possible de protéger la surchauffe de l'échangeur de chaleur.
Solution rapide
Pour résoudre ce problème, un capteur de débit fonctionnant sur le principe de l'effet hall a été intégré au circuit du circuit de recirculation de l'eau de la piscine. De plus, des capteurs de température situés sur le circuit secondaire
échangeur de chaleur, fournir un deuxième niveau de défense, suivi de la surchauffe possible.
Il est impossible de contrôler la surchauffe uniquement par des capteurs de température: le système a une grande inertie: après un arrêt brutal de l'eau dans le circuit de la piscine, à
l'arrêt de la chaudière, la température continue d'augmenter pendant un certain temps, comme la chaudière entraîne toujours l'eau chauffée le long du circuit par inertie, empêchant la surchauffe de «moi, mon bien-aimé».
Par conséquent, il est important de répondre le plus tôt possible: à savoir, d'arrêter le débit d'eau dans le circuit
piscine.
Un capteur de débit a été utilisé tel . Le boîtier en plastique et le manque de contact du capteur avec l'eau lui permettent d'être utilisé dans l'eau salée.
Capteurs de température, il a été décidé d'utiliser le Dallas DS18B20, ils sont faciles à connecter plusieurs pièces à la fois sur un bus 1-Wire .

Il a été décidé de suspendre une paire de capteurs à l'entrée et à la sortie du secondaire et du primaire
circuit: 4 capteurs au total. Un avantage supplémentaire de cette approche est
la possibilité de surveiller les paramètres du système: vous pouvez surveiller la quantité de liquide de refroidissement dans le circuit primaire et la quantité d'eau de la piscine qui est chauffée dans le circuit secondaire. Donc - pour surveiller l'optimalité du chauffage et pour prédire le temps de chauffage.
Emplacements des capteurs sur l'échangeur de chaleur et les tuyaux d'entrée Paramètres de l'appareil
Le premier prototype de l'appareil a été construit sur la base d'Arduino Uno et lancé avec succès.

Mais il est devenu clair que j'aimerais plus. Chauffé 16 mètres cubes d'eau, même juste
quelques degrés n'est pas rapide. Et je voudrais surveiller directement les paramètres de chauffage du travail, allumer / éteindre. Mais en même temps, il serait intéressant de prendre des plans de chauffage, par exemple, par jour.
Eh bien, puisque nous avons déjà un appareil IoT, alors pourquoi ne contrôlons-nous pas en même temps l'activation à distance du chlorateur de piscine et de la pompe pour cela?
Mandat
Il a donc été décidé de développer un appareil - un contrôleur de piscine multifonctionnel. Il doit pouvoir:
- Pour contrôler le chauffage de la piscine à travers l'échangeur de chaleur, allumer / éteindre la chaudière à gaz pour chauffer l'eau.
- Empêcher la surchauffe de l'échangeur de chaleur en surveillant la présence d'un débit d'eau de piscine dans le circuit secondaire et une surchauffe du circuit secondaire.
- Affichage des statistiques de chauffage en temps réel (température à l'entrée et à la sortie des deux circuits).
- Enregistrer (enregistrer) les valeurs de température dans la mémoire flash. Afficher les données pour
une certaine période sous forme de graphique. - À l'aide d'un relais, pouvoir allumer / éteindre les pompes de la piscine et le chlorateur.
- Gérez tous les paramètres de l'appareil à distance via le serveur micro-Web intégré.
Il y avait aussi la tentation de visser Blink, MQTT. Mais à partir de ces "cloches et sifflets" dans la première étape
Il a été décidé de refuser. Et plus encore, je ne voudrais pas prendre la possibilité d'un contrôle quelque part vers l'extérieur. Le serveur Web intégré à mes fins est tout à fait suffisant. Et la sécurité est assurée par le fait que vous ne pouvez accéder au réseau domestique depuis le monde extérieur que via un VPN.
Matériel informatique
En tant que contrôleur, il a été décidé d'utiliser l'ESP8266, bon marché et populaire. C'était parfait pour mes besoins, sauf pour une chose: faire correspondre les niveaux de signal des capteurs 5 volts avec la logique du contrôleur 3,3 volts. En principe, les capteurs de Dallas semblent fonctionner à 3 volts, mais j'ai une ligne assez longue entre le contrôleur et les capteurs, environ 7 mètres. Par conséquent, il est préférable d'augmenter la tension.
Il a été déterminé qu'il est nécessaire d'avoir le matériel:
- Contrôleur ESP8266 ou son frère aîné ESP32 (en tant que module DevKit ).
- Alignement des niveaux de signal pour les capteurs.
- Le régulateur de puissance est une partie de 5 volts du circuit.
- Module de commande de relais.
- Horloge RTC + mémoire flash pour la journalisation.
- L'écran LCD 2 lignes le plus simple pour afficher les valeurs actuelles des capteurs et l'état de l'appareil et du relais.
- Plusieurs boutons physiques pour contrôler l'état de l'appareil sans accès via le web.
De nombreux composants de la liste sont vendus comme modules pour Arduino et de nombreux modules sont compatibles avec la logique 3.3v. Cependant, je ne voulais pas "coincer" tout cela sur la planche à pain avec des faisceaux de fils, parce que je veux avoir un bel "appareil" soigné. Oui, et pour l'argent donné aux Chinois pour les modules, vous pouvez complètement dessiner et commander votre carte de circuit imprimé individuelle, et l'attente de son arrivée sera compensée par une installation relativement rapide et fiable.
Encore une fois, je note que c'est ma première expérience dans les circuits et dans la conception du matériel de ces choses. J'ai dû beaucoup étudier. En effet, dans ma spécialité, je suis un peu à l'écart des microcontrôleurs. Mais tout faire "à genoux" ne permettait pas à l'esprit de perfectionnisme qui habite en moi.
Schéma du circuit
Il existe un grand nombre de programmes sur le marché qui vous permettent de dessiner un circuit et une carte de circuit imprimé. Sans expérience dans ce domaine, j'ai immédiatement aimé EasyEDA - un éditeur en ligne gratuit qui vous permet de peindre magnifiquement un schéma de circuit, de vérifier que rien n'a été oublié et que tous les composants ont des connexions, de dessiner une carte de circuit imprimé, puis de commander immédiatement sa production.
La première difficulté que j'ai rencontrée: il existe de nombreuses options pour le contrôleur DevKit ESP8266 ou ESP32, certaines diffèrent par l'emplacement des broches et leur fonction, et certaines même par leur largeur. Il a été décidé de dessiner le circuit de sorte qu'il soit possible de placer DevKit de n'importe quelle largeur et à n'importe quel emplacement des bornes, et sur les côtés de celui-ci - 2 rangées de paires de cavaliers, puis le câblage pour connecter les bornes nécessaires, par rapport au contrôleur spécifiquement acheté.
Placer sous le contrôleur et 2 rangées de cavaliers jumelés: JH1 et JH2 dans le schéma:

L'emplacement des broches d'entrée 5v et de sortie 3.3v de l'alimentation du stabilisateur intégré, ainsi que de GND, me semblait le même pour différents DevKit, mais j'ai quand même décidé de le jouer en toute sécurité et de les faire des cavaliers: JP1, JP2, JP3 dans le diagramme.
J'ai décidé de signer les cavaliers en les connectant aux composants du circuit avec des fonctions qu'ils vont probablement remplir.
Et voici à quoi ça ressemble avec le DevKit ESP8266, que j'ai finalement acheté et installé Ici, D1 (GPIO5) et D2 (GPIO4) sont responsables du bus I2C, D5 (GPIO14) pour 1-Wire, D6 (GPIO12) - pour recevoir les impulsions du capteur de débit.
Schéma du circuit:

(image cliquable)
Malgré la présence à bord de l'ESP8266 d'un régulateur de puissance intégré pour 3,3 V, nous avons encore besoin d'avoir 5 volts pour alimenter les capteurs et l'écran LCD, et 12 volts pour alimenter le relais. Il a été décidé de rendre la carte d'alimentation 12 volts, et de mettre le régulateur de tension AMS1117-5.0 à l'entrée, donnant les 5 volts souhaités à la sortie.
Pour faire correspondre les niveaux de signal sur le bus à 1 fil, j'ai utilisé un transistor à effet de champ BSS138 c avec des «tractions» de tension des deux côtés.

Très bien sur la correspondance de niveau est écrit dans l'article Correspondance des niveaux logiques des appareils 5V et 3,3V .
Pour faire correspondre les niveaux de signal du capteur de débit, je viens d'utiliser un diviseur de tension entre les résistances. Le capteur de débit est simplement un dispositif à collecteur ouvert . Certains capteurs peuvent déjà avoir une résistance de rappel intégrée, cela doit être pris en compte:

Le bleu dans le diagramme est une désignation schématique de l'ensemble capteur de débit. À droite du connecteur se trouvent des diviseurs de tension sélectionnés par moi afin d'avoir un niveau maximum de 3,3 volts en sortie.
Sur le bus I2C, j'ai accroché une horloge en temps réel DS3231SN et une mémoire flash AT24C256C pour stocker les journaux. La mémoire flash intégrée à l'ESP8266 ne convient pas, car elle a un petit nombre de cycles de réécriture (10 000 contre 1 million pour AT24Cxxx, selon les fiches techniques).
Le contrôle des relais est organisé sur un tas de puces PCF8574AT et ULN2803A.

La première puce est un extenseur de port de microcontrôleur I2C. L'état de la sortie ou de l'entrée active PCF8574AT est sélectionné en sélectionnant une adresse sur le bus I2C.
La puce possède des fonctionnalités intéressantes, bien décrites dans l'article d' extension de port I2C PCF8574 .
La puce ne peut pas contrôler directement la charge (relais). Pour cela, une matrice de transistor ULN2803A est utilisée. Il y a une caractéristique: la matrice peut facilement tirer ses sorties avec une charge au sol, ce qui signifie que si une tension d'alimentation est appliquée au deuxième pôle du relais, le courant traversera l'enroulement du relais et les contacts du relais se fermeront. Malheureusement, avec cette inclusion, nous obtenons un effet secondaire: la valeur du signal du contrôleur est inversée, et tous les relais "cliquent" lorsque le circuit est allumé. Je n'ai pas encore compris comment supprimer cette fonctionnalité.
Plus d'informations sur la puce sont décrites ici .
L'extenseur de port PCF8574AT peut également être utilisé comme entrée: des boutons matériels peuvent être accrochés à certaines entrées, lisant leurs valeurs sur le bus I2C. Dans le diagramme, les broches 4-7 peuvent être utilisées pour lire l'état des boutons. L'essentiel est de ne pas oublier de permettre par programme le serrage intégré des jambes correspondantes à la nutrition.
En même temps, j'ai laissé le câblage à la matrice du transistor, au cas où vous voudriez brusquement connecter des relais supplémentaires. Pour d'éventuelles connexions, j'ai apporté tous les fils aux connecteurs (plus précisément, aux trous sous eux où les fils peuvent être soudés ou le connecteur DIP standard de 2,54 mm peut être soudé).
La broche de l'extenseur de port INT peut être utilisée pour répondre rapidement à la pression d'un bouton. Il peut être connecté à un port libre sur le contrôleur et définir le déclencheur d'interruption pour changer l'état de cette broche.
L'écran LCD à deux lignes est également contrôlé via le module d'extension PCF8574AT. Le point principal: l'affichage est alimenté par 5 volts, tandis que l'affichage lui-même est contrôlé par une logique 3 volts. Soit dit en passant, les adaptateurs Arduino standard pour I2C ne sont pas conçus pour la double tension. J'ai trouvé l'idée d'une telle connexion quelque part sur Internet, malheureusement, j'ai perdu le lien, donc je ne cite pas la source.
Circuit imprimé
Lors de la conception de la carte, il s'est avéré que les pièces ordinaires avec pieds prennent trop de place et que de nombreuses puces dans la conception DIP ne sont pas faciles à trouver. Après avoir lu sur Internet que l'installation SMD n'est pas si compliquée, et avec les compétences appropriées, elle prend encore moins de temps, j'ai décidé de concevoir la carte pour les pièces SMD. Et je ne me suis pas trompé. Il s'est avéré une belle carte mère compacte, où j'ai facilement placé tout ce dont j'ai besoin. Les pièces SMD, avec un bon fer à souder, un flux et une soudure, se sont avérées vraiment très faciles à monter.
Sur la carte, j'ai ajouté quelques marges carrées de trous pour le prototypage, si je veux soudainement souder autre chose.
J'ai fait un circuit imprimé mesurant 97x97 mm. Il s'intègre facilement dans une boîte électrique de coupe standard. De plus, les planches de dimensions inférieures à 100x100 sont peu coûteuses à fabriquer. La production d'un lot minimum de 5 planches selon la disposition développée a coûté 5 USD, leur livraison au Bélarus a coûté 9 USD supplémentaires.

La conception de la carte se trouve sur le site Web d'EasyEDA et est accessible à tous.
Je note que sur la photo du contrôleur ci-dessous apparaît le premier échantillon de la carte, sur lequel j'ai "tordu" beaucoup de choses inutiles et inutiles (dans l'espoir d'utiliser ce lot minimal de 5 cartes dans d'autres projets). Ici et sur EasyEDA, j'ai posté une version «nettoyée» de toutes ces choses inutiles.

Photos des deux côtés de la plancheFace avant:

Face arrière:

Partie logiciel
Pour programmer le microcontrôleur, étant donné l'arriéré sous la forme d'un prototype sur Arduino Uno, il a été décidé d'utiliser l'environnement Arduino avec le
noyau Arduino ESP8266 installé. Oui, vous pouvez utiliser
Lua sur l'ESP8266, mais ils disent qu'il y a des blocages. Compte tenu de la fonction critique exercée, je ne voudrais pas du tout.
L'environnement Arduino lui-même me semble un peu dépassé, mais, heureusement, il existe une
extension pour Visual Studio de Visual Micro. L'environnement vous permet d'utiliser des astuces de code IntelliSence, de passer rapidement aux déclarations de fonctions, de refactoriser le code: en général, tout ce que l'environnement des ordinateurs «adultes» se permet d'être. La version payante de Visual Micro vous permet également de déboguer facilement le code, mais je me contentais de l'option gratuite.
Structure du projet
Le projet se compose des fichiers suivants:
Structure du projet dans Visual Studio Fichier | Rendez-vous |
---|
WaterpoolManager.ino
| Déclaration des variables et constantes de base. Initialisation. Boucle principale.
|
HeaterMainLogic.ino
| La logique de base du contrôle du relais de la chaudière (en fonction de la température) et des relais auxiliaires.
|
Sensors.ino
| Lire les données du capteur
|
Settings.ino
| Paramètres de l'appareil, les enregistrer dans la mémoire flash du contrôleur
|
LCD.ino
| Sortie d'informations sur LCD
|
ClockTimer.ino
| Lecture d'horloge RTC ou simulation d'horloge
|
Relays.ino
| Contrôle marche / arrêt du relais
|
ButtonLogic.ino
| Logique de réaction à l'état des boutons matériels
|
ReadButtonStates.ino
| Lire les états des boutons matériels
|
EEPROM_Logging.ino
| Enregistrement des données du capteur dans l'EEPROM
|
WebServer.ino
| Serveur Web intégré pour la gestion des appareils et l'affichage de l'état
|
Pages Web
| Les pages du serveur Web sont stockées dans ce dossier.
|
index.h
| La page principale pour afficher l'état de l'appareil. Lecture de l'état actuel avec appel ajax. Actualisez toutes les 5 secondes.
|
loggraph.h
| Affiche un journal des données des capteurs et des états des relais dans un graphique. La bibliothèque jqPlot est utilisée - toute la construction a lieu côté client. La demande adressée au contrôleur ne concerne qu'un fichier binaire - des copies des données de l'EEPROM.
|
logtable.h
| aussi, mais sous forme de tableau
|
settings.h
| Gestion des paramètres de l'appareil: définition des limites de température, de débit d'eau, de fréquence d'enregistrement des données
|
time.h
| Réglage de l'heure actuelle
|
| Bibliothèques
|
EepromLogger.cpp
| Bibliothèque de journaux Flash
|
EepromLogger.h
|
crc8.cpp
| 8- CRC
|
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"); }
Selon la fiche technique, les capteurs nécessitent au moins 750 ms de délai entre la demande d'une valeur de température et la réception d'une réponse du capteur. Par conséquent, le code a introduit un retard avec une petite marge.
Cependant, ce délai, lorsque l'ensemble du périphérique attend simplement une réponse, est acceptable au début, mais il est absolument inapproprié d'attendre à chaque fois avec une interrogation régulière des capteurs. Par conséquent, le code délicat suivant a été écrit, appelé toutes les 50 ms par minuterie:
#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 } }
Au début de chaque cycle tempMeasureCycleCount, les capteurs sont invités à lire leurs valeurs. Après environ 50 cycles de ce type (et au total c'est 50 * 20 = 1000 ms = 1 sec), la valeur de chaque capteur est lue, une à la fois. Tout le travail est divisé en morceaux afin que le code qui s'exécute dans l'interruption du minuteur ne prenne pas beaucoup de temps du contrôleur.
La valeur du capteur de débit est calculée comme suit. Par interruption sur la broche sur laquelle le capteur est accroché, on augmente la valeur du compteur de ticks provenant du capteur de débit:
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++; }
Dans le même temporisateur où les capteurs de température sont interrogés, nous prenons une fois par seconde cette valeur de tick et la traduisons en litres en utilisant la constante FLOW_SENSOR_CONST, dont la valeur peut être trouvée dans les caractéristiques du capteur:
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 }
Enregistrement des données des capteurs et de l'état de l'appareil
Lors du développement du mécanisme de journalisation, le fait que l'appareil puisse être soudainement éteint, c'est-à-dire à presque tout moment. Lorsque vous arrêtez l'enregistrement, nous devons être en mesure de restaurer tout ce qui a été enregistré au tout dernier moment. Dans le même temps, nous ne pouvons pas constamment réécrire la même zone de mémoire flash (par exemple, un certain titre à un certain endroit, en se souvenant de la dernière adresse d'enregistrement), afin d'éviter un "effacement" accéléré du lecteur flash à cet endroit.
Après un certain «cumul», le modèle d'enregistrement suivant a été inventé et mis en œuvre:

Chaque enregistrement est un enregistrement contenant des informations sur la valeur actuelle du débit d'eau, les températures des capteurs, ainsi que l'état de l'appareil codé dans l'octet (les bits individuels indiquent si le relais est activé ou non, si le chauffage est activé ou non):
struct LogEvent { unsigned char litersInMinute = 0; unsigned char tempCelsius[4]{ 0, 0, 0, 0 }; unsigned char deviceStatus = 0; }
Après chaque enregistrement, il y a un octet de somme de contrôle CRC , indiquant si l'enregistrement a été correctement écrit et en général, si au moins quelque chose a été écrit dans cet emplacement de mémoire.
Comme il serait trop coûteux d'enregistrer des données sur l'heure actuelle ( horodatage ) pour chaque enregistrement en termes de volume, les données sont organisées en grands blocs, avec N enregistrements dans chacun. L'horodatage de chaque bloc n'est enregistré qu'une seule fois, pour le reste - il est calculé en fonction des informations sur la fréquence de journalisation.
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;
Par exemple, avec une fréquence d'enregistrement d'une fois toutes les 30 secondes, nous aurons 120 entrées dans un bloc, et la taille du bloc sera d'environ 840 octets. Au total, nous pouvons insérer 39 blocs dans la mémoire d'un lecteur flash de 32 kilo-octets. Avec une telle organisation, il s'avère que chaque bloc commence à une adresse strictement définie en mémoire, et «parcourir» tous les blocs n'est pas un problème.
En conséquence, avec une rupture soudaine du record lors du dernier arrêt de l'appareil, nous aurons un bloc inachevé (c'est-à-dire dans lequel certains des enregistrements sont manquants). Lorsque l'appareil est allumé, l'algorithme recherche le dernier en-tête de bloc valide (horodatage + crc). Et continue l'enregistrement, en commençant par le bloc suivant. L'enregistrement s'effectue cycliquement: le dernier bloc écrase les données du bloc le plus ancien.
Lors de la lecture, tous les blocs sont lus séquentiellement. Les blocs non valides (ceux qui ne passent pas le CRC pour l'horodatage) sont entièrement ignorés. Les enregistrements de chaque bloc sont lus jusqu'à la réunion du premier enregistrement invalide (c'est-à-dire celui sur lequel l'enregistrement a été interrompu la dernière fois si le bloc n'a pas été entièrement enregistré). Les autres sont ignorés.
Pour chaque enregistrement, l'heure actuelle est calculée en fonction de l'horodatage du bloc et du numéro de série de l'enregistrement dans le bloc.
LCD
L'appareil utilise un écran QC1602A, capable d'afficher 2 lignes de 16 caractères. La première ligne affiche les informations actuelles sur les valeurs actuelles des capteurs: débit et températures. Si la limite spécifiée est dépassée, un point d'exclamation apparaît près de la valeur. La deuxième ligne indique l'état du relais de chauffage et de la pompe, ainsi que le temps écoulé depuis la mise en marche ou l'arrêt du chauffage. Toutes les 5 secondes, l'affichage de la deuxième ligne indique brièvement les limites actuelles. Des photos de l'affichage dans différents modes sont présentées à la fin de la publication.
Graphiques
Lorsqu'elles sont demandées via le serveur Web intégré, les données de journalisation sont lues sous forme binaire à l'aide de JavaScript:
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);
Les lire dans un format non binaire populaire, comme ajax, serait un luxe inadmissible pour le contrôleur, principalement en raison de la grande quantité que le serveur http intégré devrait retourner.
Pour la même raison, la bibliothèque jqPlot JavaScript est utilisée pour créer des graphiques et les fichiers de la bibliothèque JS eux-mêmes sont chargés à partir de CDN populaires.
Un exemple de la planification de l'appareil:

On voit clairement qu'à environ 9h35 l'appareil était allumé pour le chauffage, la chaudière a commencé à chauffer progressivement le circuit de chauffage (capteurs T3, T4), après quoi la température du circuit de la piscine a commencé à augmenter (capteurs T1, T2). Vers 10 h 20, la chaudière est passée au chauffage de l'eau chaude dans la maison, la température du circuit de chauffage a baissé. Puis, après 10 minutes supplémentaires, la chaudière s'est remise à chauffer l'eau de la piscine. À 10 h 50, un accident s'est produit: la pompe pour faire circuler l'eau dans la piscine s'est soudainement éteinte. Le débit d'eau a fortement chuté à zéro, le relais de chauffage s'est éteint (ligne pointillée rouge sur le 2ème graphique), empêchant une surchauffe. Mais l'appareil restait toujours dans un état de chauffe (ligne rouge sur le 2ème graphique). C'est-à-dire si la pompe était à nouveau allumée et que les températures étaient normales, l'appareil retournerait au chauffage. Je note qu'après un arrêt d'urgence de la pompe, les températures dans le circuit d'eau de la piscine (T1, T2) ont commencé à augmenter fortement en raison d'une surchauffe de l'échangeur de chaleur. Et sinon pour un arrêt brutal de la chaudière, il y aurait des ennuis.
Serveur Web intégré
Pour communiquer avec le monde extérieur, la classe standard ESP8266WebServer est utilisée . Lorsque l'appareil démarre, il est initialisé en tant que point d'accès avec le mot de passe par défaut spécifié dans #define AP_PASS. Une page Web s'ouvre automatiquement pour sélectionner un réseau Wi-Fi disponible et entrer un mot de passe. Après avoir entré le mot de passe, l'appareil redémarre et se connecte au point d'accès spécifié.
Appareil fini
Le dispositif fini a été placé dans une boîte de coupe standard pour le câblage. Un trou pour l'écran LCD a été découpé, et des trous pour les connecteurs.

Photos de la façade de l'appareil dans différents modesAvec l'affichage du temps écoulé après la mise en marche:

Avec des limites affichées:

Conclusion
En conclusion, je tiens à dire que, en développant un tel appareil, j'ai acquis une grande expérience des circuits, de la conception de circuits imprimés, des compétences d'installation pour les composants SMD, dans l'architecture et la programmation des microcontrôleurs, je me suis souvenu du C ++ presque oublié et de la manipulation soigneuse de la mémoire et des autres ressources de contrôleur limitées. La connaissance de HTML5, JavaScript et les compétences de débogage des scripts dans le navigateur ont également été utiles dans une certaine mesure.
Ces compétences et le plaisir reçu lors du développement de l'appareil sont les principaux avantages obtenus. Et les codes source de l'appareil, le schéma de circuit, les cartes de circuits imprimés - veuillez utiliser, modifier. Tous les codes source du projet sont sur GitHab. Matériel dans un projet public sur EasyEDA. J'ai collecté des données sur les puces utilisées dans le projet sur un lecteur réseau .