Décodage de sondes de températures TFA Dostmann 30.3208.02 avec Arduino
Par Pascal le jeudi 12 décembre 2019, 12:35 - Arduino - Lien permanent
Voici donc mon premier billet pour un projet Arduino, en l’occurrence le décodage et exploitation de sondes de températures. Il S'agit d'un module faisant partie d'un ensemble de quatre :
Radio-pilotage DCF77 d'une horloge I2C DS3221.Mise à jour : Le DCF77 tout en monopolisant beaucoup de ressources est loin de garantir des résultats. Mais comme le projet est branché sur l'Internet, rien de plus facile d'obtenir le temps Internet via le NTP.- Affichage sur une matrice LED de l'heure de cette horloge I2C et des éphémérides.
- Réception, décodage et réémission en I2C des sondes de températures. (le module présenté ici).
- Exploitation de ces températures reçues en I2C (affichages LCD et Matrice, stockage SD et envoi FTP).
Les trois premiers modules autour de µC Nano, le quatrième en Méga, tous reliés en I2C (horloge, températures et humidité)
À noter qu'il y a moyen de tout faire ou presque avec un seul module Mega, mais les affichages sur matrices seront fixes. En effet, ces animations consomment du temps machine incompatible avec celui pris pour la réception des valeurs.
Autre remarque : je ne vais pas vous apprendre à programmer de l'Arduino : je partage.
Les sondes, ou satellites
J'avais donc des sondes que l'on trouve (encore) facilement chez Amazon [1] [2]
Vous remarquerez qu'il vaut mieux acheter un ou des ensembles que les sondes séparément : La sonde seule fait +/- €20, mais acheter 3 ensembles ramène la sonde à €16 plus 3 « centrales d'affichage gratuites ».
Les sondes possèdent des micro-interrupteurs DIP permettant de choisir 8 canaux (3 bits) . Les relevés 4 à 8 sont, en plus de la température locale, affichées alternativement sur la dernière ligne de ces récepteurs.
Le décodage des trames
La première difficulté a été de trouver comment fonctionne ces sondes. Un petit tour sur quelques forum m'ont permis de trouver qu'elles utilisent le codage Manchester[3]
Heureusement, il existe un excellent travail commis en 2015 par Rob Ward, permettant d'écouter ce type de satellites [4] et puis de chercher à comprendre la suite des chiffres binaires.
Donc, il faut commencer par brancher un récepteur 433 MHz, et pour le bien superhétérodyne, surtout si vous avez des grandes distances ou/et des murs épais. Vous l'achetez où vous voulez, Amazon, eBay, Ali ou ailleurs. Je dois dire que je ne suis pas déçu par le matériel chinois, d'autant plus que j'ai des conditions environnementales difficiles : parfois deux ou trois murs dont certains d'une trentaine de centimètres et 15 ou 20 mètres de distance. Il est aussi important de placer une bonne antenne sur le récepteur.
Aucune difficulté pour le raccordement, la broche data est en 8. Vous pouvez ajouter une LED sur la broche 13. Vu qu'elle ne reste allumée quie quelques fractions de secondes, elle flashe, je n'ai pas mis de résistance en série. Sinon, placez-en une de 220 Ohms.
Le tableur
Puis, une fois que j'ai eu des suites de 0 et 1, j'ai utilisé un tableur pour m'aider à décoder la trame binaire. D'abord une seule sonde avec des relevés différents, et ensuite une deuxième et plus encore. Il faut faire des comparaison, noter où cela change, où c'est fixe.
J'ai fait de très nombreux relevés, mais gardés ici que quelques uns pour l'exemple.
Il peut bien entendu être utilisé pour d'autres appareil et il est tout à fait compatible en MS-Excel et Open/Libre Office
Rien de tel qu'un bon dessin au lieu d'un long discours, voici quelques copies d'écran :
Il suffit de copier/coller les trames à partir de la cellule A11, puis de les décomposer dans les colonnes C à au grand maximum P en indiquant le nombre de bits en ligne 8.
Par exemple, ici les 10 bits de l'entête sont indiqués en C7. J'utiliserai le 1er octet pour filtrer que ces sondes. Puis je décomposais de 4 en 4 (à cause du supposé BCD[5] ),
Mais je me suis rendu compte avec l'humidité qu'il n'était pas utilisé, car elle était transmise en binaire pur sur 8 bits. D'autres exceptions en colonnes F et G où j'ai repéré l'état de la batterie (?) et les 3 bits du canal.
Pourquoi faire simple ...
Cela s'est nettement corsé pour la température et là encore Internet m'a aidé. J'avais bien repéré 12 bits (!) pour la température mais pas moyen de comprendre comment c'était codé. J'ai écumé la toile assez profondément et je suis tombé sur une formule à tomber par terre : T = (X - 720) * 0.0556
chez « Andrew's WIP sketch for capturing data from Ambient F007th Thermo-Hygrometer »
C'est parti, le croquis !
1. L'Étalonnage !
Hé bien là, c'est pas compliqué. Vous mettez toutes vos sondes dans une boite pendant une paire d'heures, et vous faites le relevé des température et humidité, vous faites la moyenne et notez les différences.
Un petit fichier tableur pour vous aider.
// Étalonnage (mesures dans un endroit confiné et différence par rapport à la moyenne) const float EtalonT[MxCnl] = { -0.3, -0.3, 0.2, 0.2, 0.1, 0, 0.3, -0.4}; const int EtalonH[MxCnl] = { -2, 0, 2, -2, -4, -2, 2, 3};
(MxCnl est le nombre de satellites que vous avez)
2. Manchester
Le code est donc articulé autour de l'excellent travail de Rob Ward. L'original est abondamment documenté, je vous le laisse découvrir. J'ai cependant effectué quelques changements :
- En ligne 54, j'ai modifié
word sDelay = 220;
car cette valeur, donnée par le croquis Debug_Auto.ino, semble donner de bons résultats. - En ligne 67,
byte maxBytes = 6; // Attention, 6 et pas 5
, sinon, la trame ne sera pas complète. - la série
est remplacée En ligne 72 par
byte manchester20;
En fait, je n'utilise que le 1er élément pour discerner nos satellites des autres appareils présents dans l'environnement ( ligne 174if (manchester0 == 81) {// ce sont bien nos sondes (signature, identification dans le 1er octet du header
Du coup, des éléments de code ont été modifiés, comme ci-dessous
2.1. void hexBinDump()
La procédure a été modifiée pour stocker dans l'objet String Spaquet
les '0' et '1' qui seront analysés (void analyseData()
) une fois les 6 octets reçus.
2.2. void analyseData()
Comme déjà indiqué plus haut, on vérifie d'abord que ce sont les bons satellites. Vous comprenez ensuite pourquoi j'utilise l'objet String, car il me permet d'extraire facilement une sous-chaîne (binaire) remise en décimal avec une fonction maison Bin2Dec
Les réceptions consécutives (très rapprochées) sont comparées. Si elles sont pareilles, on corrige selon l'étalonnage, et on transmet.
Durant cette procédure, des Serial.print
de contrôle sont envoyés au moniteur comme suit :
START Module 3 Prod v1 : TFA >=> I2C Décodage : 3 10.40 65 Décodage : 3 10.40 65 3 10.60 63 transmis Décodage : 2 17.70 53 Décodage : 2 17.70 53 2 17.90 55 transmis Décodage : 7 17.90 53 Décodage : 7 17.90 53 7 17.50 56 transmis Décodage : 6 10.20 65 Décodage : 6 10.20 65 6 10.50 67 transmis Décodage : 4 16.80 64 Décodage : 1 187.50 69 Décodage : 1 19.30 46 Décodage : 2 17.70 53 Décodage : 2 17.70 53 2 17.90 55 transmis
Voici le croquis :
Bon amusement !
/*********************************************** Auteur : Pascal Cambier Nom : TFA & Cie, Module 3 Version : Prod v1 décembre 2019 Description : - Réception satellites TFA Dostmann 30.3208.02 basé sur https://github.com/robwlakes/ArduinoWeatherOS - Réemmission en I2C après contrôle validité réception (les satellites émettant deux fois de suite +/- toutes les minutes, il suffit de vérifier si les 2 transmissions sont égales.) Références : https://github.com/robwlakes/ArduinoWeatherOS https://github.com/madsci1016/Arduino-EasyTransfer/tree/master/EasyTransferI2C */ #include <Wire.h> #include <EasyTransferI2C.h> //create object EasyTransferI2C ET; // RTC #include <DS1307RTC.h> tmElements_t dt; //================================== const byte MxCnl = 8 ; // Nombre max de canaux / satellites int iCanal ; //le canal du satellite String Spaquet; //tous les bits dans une String float fTemp; // Température int iHum; // humidité byte Nr = 0; // 0 = 1ère mesure struct ST_TFA { // Réception / Émission même schéma chez l'esclave (module 4) int Canal ; float Temp; byte Hum; }; ST_TFA mydata ; // pour émission i2c ST_TFA Premier ; // Étalonnage (mesures dans un endroit confiné et différence par rapport à la moyenne) const float EtalonT[MxCnl] = { -0.3, -0.3, 0.2, 0.2, 0.1, 0, 0.3, -0.4}; const int EtalonH[MxCnl] = { -2, 0, 2, -2, -4, -2, 2, 3}; //############### MANCHESTER ################ //Interface Definitions int RxPin = 8; //The number of signal from the Rx int ledPin = 13; //The number of the onboard LED pin // Variables for Manchester Receiver Logic: //word sDelay = 250; //Small Delay about 1/4 of bit duration try like 250 to 500 //220 semble donner de meilleurs résultats word sDelay = 220; word lDelay = 500; byte polarity = 1; byte tempBit = 1; byte discards = 0; boolean firstZero = false; boolean noErrors = true; byte headerBits = 10; byte headerHits = 0; byte dataByte = 0; byte nosBits = 0; byte maxBytes = 6; // Attention, 6 et pas 5 byte nosBytes = 0; byte nosRepeats = 0; byte manchester[20]; void setup() { Serial.begin(115200); Wire.begin(); //start the library, pass in the data details and the name of the serial port. Can be Serial, Serial1, Serial2, etc. ET.begin(details(mydata), &Wire); pinMode(RxPin, INPUT); pinMode(ledPin, OUTPUT); lDelay = 2 * sDelay; eraseManchester(); Serial.println(F("START Module 3 Prod v1 : TFA >=> I2C")); } void loop() { // Routine épurée basé sur https://github.com/robwlakes/ArduinoWeatherOS tempBit = polarity ^ 1; noErrors = true; firstZero = false; headerHits = 0; nosBits = 0; nosBytes = 0; digitalWrite(ledPin, 0); tempBit = polarity ^ 1; noErrors = true; firstZero = false; headerHits = 0; nosBits = 0; nosBytes = 0; digitalWrite(ledPin, 0); while (noErrors && (nosBytes < maxBytes)) { while (digitalRead(RxPin) != tempBit) { } delayMicroseconds(sDelay); digitalWrite(ledPin, 0); if (digitalRead(RxPin) != tempBit) { noErrors = false; } else { byte bitState = tempBit ^ polarity; delayMicroseconds(lDelay); if (digitalRead(RxPin) == tempBit) { tempBit = tempBit ^ 1; } if (bitState == 1) { if (!firstZero) { headerHits++; if (headerHits == headerBits) { digitalWrite(ledPin, 1); } } else { add(bitState); } } else { if (headerHits < headerBits) { noErrors = false; } else { if ((!firstZero) && (headerHits >= headerBits)) { firstZero = true; } add(bitState); } } } } digitalWrite(ledPin, 0); } void hexBinDump() { for ( int i = 0; i < maxBytes; i++) { byte mask = B10000000; if (manchester[i] < 16) { } for (int k = 0; k < 8; k++) { if (manchester[i] & mask) { Spaquet += '1'; } else { Spaquet += '0'; } mask = mask >> 1; } } } void analyseData() { // Décodage propre aux satellites TFA Dostmann 30.3208.02 char cTp[5]; if (manchester[0] == 81) {// ce sont bien nos sondes (signature, identification dans le 1er octet du header //Serial.println(Spaquet); iCanal = Bin2Dec(Spaquet.substring(19, 22)); fTemp = (Bin2Dec(Spaquet.substring(22, 34)) - 720) * 0.0556; fTemp *= 10; fTemp = int(fTemp + 0.5); fTemp /= 10; iHum = Bin2Dec(Spaquet.substring(34, 42)); Serial.print("Décodage : "); Serial.print(iCanal); Serial.print(" " ); Serial.print(fTemp); Serial.print(" " ); Serial.println(iHum); Spaquet = ""; //À ce stade, c'est décodé if (Nr == 0) { // 1ère réception, mise en mémoire Premier.Canal = iCanal; Premier.Temp = fTemp; Premier.Hum = iHum; Nr = 1; } else { // 2ème réception, if (iCanal == Premier.Canal ) { // Même satellite if (fTemp == Premier.Temp && iHum == Premier.Hum) { // Ok, Transmission mydata.Canal = iCanal ; // correction étalonnage mydata.Temp = fTemp + EtalonT[iCanal] ; mydata.Hum = iHum + EtalonH[iCanal]; Serial.print(mydata.Canal); Serial.print(" " ); Serial.print(mydata.Temp); Serial.print(" " ); Serial.print(mydata.Hum); ET.sendData(4); // vers le module (esclave) 4 Serial.println(" transmis"); Nr = 0; } else { // pas identique, on reprend à zéro Nr = 0; } }// fin bon satellite else { // Pas le bon satellite Premier.Canal = iCanal ; Premier.Temp = fTemp ; Premier.Hum = iHum ; } }// fin 2nd réception } } void add(byte bitData) { if (discards > 0) { //if first one, it has not been found previously discards--; } else { dataByte = (dataByte << 1) | bitData; nosBits++; if (nosBits == 8) { nosBits = 0; manchester[nosBytes] = dataByte; nosBytes++; } if (nosBytes == maxBytes) { hexBinDump(); analyseData();//later on develop your own analysis routines } } } void eraseManchester() { //Clear the memory to non matching numbers across the banks //If there is only one packet, with no repeats this is not necessary. for ( int i = 0; i < 20; i++) { manchester[i] = 0; } } int Bin2Dec(String Sbin) { int iDec = 0; int iBit = 0; int iRang = 0; for (int i = Sbin.length(); i >= 1; i--) { iBit = Sbin.substring(i - 1, i).toInt(); iDec += (iBit * int(pow(2, iRang) + .5)); iRang++; } return iDec; }
Notes
[1] https://www.amazon.fr/gp/product/B00SIZZBDK
[2] https://www.amazon.fr/Transmetteur-temp%C3%A9rature-dhumidit%C3%A9-3054-3208/dp/B011IMRIZS
[3] https://fr.wikipedia.org/wiki/Codage_Manchester
[4] en anglais : https://github.com/robwlakes/ArduinoWeatherOS
[5] Le binary coded decimal, est un système de numération utilisé en électronique (...) pour coder des nombres dont les chiffres sont codés sur quatre bits :
Commentaires
It is perfect time to make some plans for the future and it's
time to be happy. I have read this post and if I could I want to
suggest you few interesting things or advice. Perhaps you could write next articles referring
to this article. I desire to read even more things about
it!
I just could not leave your wеb site pгior to sugցesting that I extremely enjoyed tһe usual info an іndividual provide to your
ɡuestѕ? Is going to be again regularly in orԀer
to inspect new posts
You ought to be a part of a contest for one of the most useful blogs on the web.
I am going to recommend this web site!
Hello mates, its enormous article regarding cultureand
completely defined, keep it up all the time.
Merci Ipe88 android.
Prenez soin de vous !
Riցht here is the perfect wеbsitе for anybody who really wants to find out about this topic.
You know so much its almost toսgh to argue ᴡith you (not that I personaⅼly will need to…HaHa).
You definitely put a new spin on a subject that has been discussed for ages.
Great stuff, just excellent!
My brother recommended I would poѕsibly like this website.
He used to be totally right. This put սp truly made my
day. You cann't imagine simpⅼy how much time Ι һad ѕpent for this information!
Thаnks!
This is really great! Your code works out of the box without any problems, everything is well explained and documented. The design of your website looks very professional: nice layout, beautiful colors (green is my favorite color) and clear structure. Congratulations and thank you very much!
Wow that was unusual. I just wrote an really long comment
but after I clicked submit my comment didn't appear.
Grrrr... well I'm not writing all that over again.
Regardless, just wanted to say excellent blog!
Howdy! This post couldn't be written any better!
Reading this post reminds me of my previous room mate!
He always kept chatting about this. I will forward this post to him.
Fairly certain he will have a good read. Thanks for sharing!
You are so interesting! I don't think I've read a single thing
like that before. So nice to discover someone with some unique
thoughts on this topic. Really.. many thanks for starting this up.
This site is one thing that is required on the internet, someone with a little originality!
It's a shame you don't have a donate button! I'd most certainly
donate to this brilliant blog! I guess for now i'll settle
for bookmarking and adding your RSS feed to my Google account.
I look forward to fresh updates and will talk about this website with my Facebook
group. Talk soon!
Hello There. I found your weblog using msn. This is a very neatly written article.
I will make sure to bookmark it and come back to learn extra of your
useful info. Thanks for the post. I'll definitely return.
Greetings! Very useful advice in this particular post!
It is the little changes which will make the largest
changes. Thanks a lot for sharing!