image
image
image

Votre IP : 3.135.205.146
Dernier ajout : 16 avril
Visiteurs connectés : 11

image Conception
Développé sous SPIP
Informations légales

image
image
Recherche image

Pratiques et Techniques de la Plaisance

image

Accueil du site > Articles > Instruments > Electronique de navigation > Un barographe multifonction performant et économique

Rubrique : Electronique de navigation

__________________________________________________________________________________________________________________

Un barographe multifonction performant et économiqueVersion imprimable de cet article Version imprimable

Publié Février 2020, (màj Février 2020) par : achab  image   

Copyright : Les articles sont la propriété de leurs auteurs et ne peuvent pas être reproduits en partie ou totalité sans leur accord
S'identifier pour s'abonner par mail Inscrit aux forum vous pourriez transmettre cette page à un ami plaisancier

Mots-clés secondaires: électronique , navigation_divers , logiciels

Un barographe multifonction performant et économique

Mon précédent barographe était un modèle Naudet acheté en 2007. Il a rendu l’âme après 12 ans de mauvais service : affichage cristaux liquides très peu lisible, énorme dérive du capteur, menus compliqués…

Après recherche sur le web, je me suis dirigé vers une autoréalisation en assemblant les pièces suivantes (prix port compris) :

  • 3.5 pouces TFT LCD module d’écran Ultra HD 320X480 pour Arduino + carte MEGA 2560 R3 (13.89 €).
  • Module de capteur de pression atmosphérique altimètre de GY-BME280-3.3 (2.36 €).
  • Transistors BD 139, diode 1N411, résistors, buzzer et poussoirs.
  • Pile de sauvegarde 9v.
  • Module de capteur de propane-butane MQ6 (1.23 €) optionnel.
  • Thermistance CTN (récupération) optionnel.

L’assemblage

Il est très simple. L’écran s’enfiche au dessus de la carte. J’ai choisi de raccorder les autres éléments à l’aide de platines à trame perforée (Figure ci-dessous). Il serait plus « propre » d’intercaler une platine de prototypage pour carte MEGA 2560 (entre 1 et 2 €), mais cela augmenterait l’épaisseur de la réalisation.

Quelques choix

  • La consommation sur un voilier est toujours critique. La carte et l’écran consomment chacun environ 100 mA. La carte doit être alimentée en permanence pour la capture et l’enregistrement des données. En revanche l’écran peut être allumé sur demande. Il ne parait pas directement possible de commander l’allumage par logiciel. Le choix a donc été fait de commander l’alimentation 5v de l’écran via une ligne de donnée et un transistor.
  • Une alarme en cas de variation de pression de 3mb sur 3h.
  • Une « interface utilisateur » via deux boutons.
    • Bouton G commande l’affichage : allumage (pour 40 s) puis cyclage sur 5 durées (3h, 6h, 12h, 24h, 48h).
    • Bouton D sert à régler l’heure et l’alarme. Un appui long fait défiler le temps (de plus en plus vite). Un appui court permet de basculer l’alarme (sur l’écran actif A, inactif X).
  • Des options. La carte consommant en permanence, il serait dommage de ne pas l’utiliser pour d’autres données.
    • La température de l’eau. Sur mon bateau, une thermistance CTN collée au puits de dérive fait l’affaire. On consultera Wikipedia Steinhart–Hart equation pour déterminer les constantes A, B, C nécessaires au calcul de la température.
    • Une alarme gaz. En quarante cinq ans de navigation, j’ai vu deux voiliers exploser pas loin avec graves dégâts et blessures. Le problème des détecteurs, c’est toujours la consommation (160 mA) car le capteur doit être chaud. L’utilisation de la carte permet une mise en service intermittente : il n’est pas besoin de tester en permanence. J’ai choisi de le faire toutes les 6 minutes.

Le logiciel

L’environnement de développement Aduino IDE [1] est une merveille de possibilités et de simplicité. J’ai pu m’aider de nombreuses références sur le Web pour écrire un code qui corresponde à ce que je voulais. Il est plus bas en annexe. Je l’ai commenté suffisamment pour qu’on puisse comprendre comment ça marche et le modifier selon les besoins. Il ne faut pas oublier d’ajouter à l’IDE les modules « INCLUDE » et choisir le type de carte dans les outils (Arduino/Genuino Mega or Mega 2560 ) ainsi que le numéro du port série virtuel associé au port USB. Ensuite vous copiez le code et vous le transférez à la carte par USB.


Des extensions possibles

Toujours dans l’idée de ne pas laisser le processeur consommer sans rien faire, on pourrait tirer parti des 4 ports série pour connecter des périphériques nmea. Avec un GPS, on pourrait faire une alarme de mouillage. Avec un anémomètre on aurait en plus les données de vent !

Note finale
Je suis plus informaticien qu’électronicien. La bonne nouvelle est que ces cartes suppléent à un manque de connaissances ou d’habileté en électronique. Certainement des navigateurs mieux formés dans ce domaine auront des montages plus canoniques à proposer !

Une photo
Pas facile de prendre une photo d’un écran qui réfléchit. La courbe et les caractères (respectivement jaunes et blancs dans la réalité) sont en fait très lisibles. La chute de pression brutale à 7h correspond à l’allumage du chauffage dans mon bureau. Le baro est malencontreusement placé au dessus d’un radiateur en attendant de naviguer au printemps !

Annexe : le code

/*
display 320*480 jbl 10/2019
*/

#include // pour l’écran
#include
#include « SparkFunBME280.h »// pour le capteur

BME280 Sensor ; //Uses I2C address 0x76 (jumper closed)

TFT_HX8357 tft = TFT_HX8357() ; // Invoke custom library

//couleurs
#define LTBLUE 0xB6DF
#define LTYELLOW 0xFFF8
#define LTGREY 0xE71C
#define WHITE 0xFFFF
#define BLACK 0x0000
//ports
#define THERMISTORPIN A0 //analogique pour la température extérieure
#define BoutonG 3 //pour l’affichage
#define BoutonD 2 //réglages
#define AllumeCapteurGaz 14 //met en service le capteur de gaz via un transistor
#define TestCapteurGaz 15 //la sortie TTL du capteur de gaz (active au niveau bas)
#define ecran 12 //allume l’écran via un transistor
#define piezoPin 4 //le buzzeur

//constante pour l’alarme barométrique
//l’écart barométrique sur 3h maxi qui déclenche l’alarme.
//Il est compté en position verticale sur l’écran (320 pour 80mb)
#define GapBaro 12 //correspond à 3mb

//les modes d’affichage de la courbe
byte state=0 ;// 5 modes respectivement sur 3h, 6h, 12h, 24h, 48h
String Duree[]=« 3h »,« 6h »,« 12h »,« 24h »,« 48h » ;//pour affichage

byte heure=00 ;
//la donnée de pression est mémorisée à chaque intervalle de 22.5 secondes (3/8 de minute)
//de façon que 480 données (largeur de l’écran) correspondent à 3h (state=0)
//chaque heure contient 160 intervalles
byte interval=00 ;
//en principe un intervalle est de 22500 millisecondes.
//Néanmoins, il faut ajuster empiriquement cette valeur en fonction de l’horloge du AT Mega2560
const float delai=22297 ;

//ces deux valeurs comptent respectivement les millisecondes depuis le début
//d’un intervalle de 22.5 secondes et depuis le réveil de l’écran ;
//il est important de les déclarer en unsigned long
//http://arlotto.univ-tln.fr/arduino/...
unsigned long temps=0 ;//Important pour stocker les millis
unsigned long TempsReveil ;

//l’allumage de l’écran provoque un décalage dans la lecture de la pression
bool Reveil=false ;//rattrapage après allumage de l’écran
float gap ;//à rattraper

// la mesure de pression se fait en continu sur chaque intervalle de 22.5s
//on mémorise la moyenne en comptant les relevés
float pression ;
byte nbReleves ;

//les listes où mémoriser les données de pression
//on mémorise des positions verticales sur l’écran (0 pour 1040mb, 319 pour 960mb)
//on stocke 480 données (largeur de l’écran) pour les 3 premières heures
int Values[480] ;
//pour 6h on prend une donnée sur deux dans Values pour les 3 premières heures
//et on a stocké une donnée sur deux pour les 3 heures précédentes dans Values3
int Values3[240] ;
//pour 12h on prend une donnée sur quatre dans Values pour les 3 premières heures
//et on prend une donnée sur deux pour les 3 heures précédentes dans Values3
//et on a stocké une donnée sur quatre pour les 6 heures précédentes dans Values6
int Values6[240] ;
//pour 24h on prend une donnée sur huit dans Values pour les 3 premières heures
//et on prend une donnée sur quatre pour les 3 heures précédentes dans Values3
//et on prend une donnée sur deux pour les 6 heures précédentes dans Values6
//et on a stocké une donnée sur huit pour les 12 heures précédentes dans Values12
int Values12[240] ;
//pour 48h on prend une donnée sur seize dans Values pour les 3 premières heures
//et on prend une donnée sur huit pour les 3 heures précédentes dans Values3
//et on prend une donnée sur quatre pour les 6 heures précédentes dans Values6
//et on prend une donnée sur deux pour les 12 heures précédentes dans Values12
//et on a stocké une donnée sur seize pour les 24 heures précédentes dans Values24
int Values24[240] ;

//Pour utiliser les listes Values comme des files (piles LIFO) on utilise des index
//qui mémorisent, dans chaque liste, la position de la dernière donnée entrée
word index=0 ;//pour Values
byte index3=0 ;//pour Values3
byte index6=0 ;//pour Values6
byte index12=0 ;//pour Values12
byte index24=0 ;//pour Values24
byte delaiBoutonD=200 ;
//Ces booléens servent pour le stockage d’une donnée sur deux dans les listes Values3 à Values12
bool Bshift3=false ;
bool Bshift6=false ;
bool Bshift12=false ;
bool Bshift24=false ;

//variables pour les alarmes
bool alarmBaro=false ;//activée/désactivée
bool alarmGaz=false ;//activée/désactivée
bool AlarmOn=true ;//false desactive toutes les alarmes
//l’index jusqu’auquel poursuivre la recherche d’un GapBaro dans la liste Values
word indexAlarm=0 ;

//variables pour la température extérieure
float ValPortThermistance=500 ;//la valeur relévée sur le port THERMISTORPIN
int TempExt=20 ;//la température affiché ; elle est mémorisée pour le cas où le calcul donne une valeur aberrante

bool Changestate=false ;//gestion rebond

void axes(TFT_HX8357 &tft)
int i ; int h ;
//une ligne horizontale tous les 20 pixels (soit 5mb)
//grise sauf pour la ligne des 1000mb en bleu.
//avec graduation à gauche
for ( i = 19 ; i <= 299 ; i += 20)
if (i == 159) tft.drawLine(0, i, 479,i, LTGREY) ;
else tft.drawLine(0, i, 479,i,LTBLUE) ;
tft.drawNumber(1040-(i+1)/4, 0, i, 1) ;

//une ligne verticale tous les 160 pixels pour state=0 (3h)
//80 pixels pour state=1 (6h), etc. Correspond aux heures rondes
// avec étiquette toutes les 2 heures
for (i=479-interval/pow(2,state) ;i>0 ;i-=160/pow(2,state))
tft.drawLine(i,0, i, 319, LTGREY) ;//tracer
h=heure ;
for (i=479-interval/(pow(2,state)) ;i>0 ;i-=320/pow(2,state))
tft.drawNumber(h, i, 310, 1) ;h=22+h ;h=h%24 ; ;

//Fonctions pour adresser dans les listes à partir de la dernière donnée entrée.
int Lvalues(int i)
int j=index-i ; if (j<0)j+=480 ;
return Values[j] ;

int Lvalues3(int i)
int j=index3-i ; if (j<0)j+=240 ;
return Values3[j] ;

int Lvalues6(int i)
int j=index6-i ; if (j<0)j+=240 ;
return Values6[j] ;

int Lvalues12(int i)
int j=index12-i ; if (j<0)j+=240 ;
return Values12[j] ;

int Lvalues24(int i)
int j=index24-i ; if (j<0)j+=240 ;
return Values24[j] ;

//procédures de tracé paramétriques à partir des listes Values
//celle-ci va tracer les données des 3 dernières heures,
//avec un pas de 1 pour 3h, de 2 pour 6h, etc.
void trace(TFT_HX8357 &tft, int pas)
int i ;
for ( i = 0 ; i < 480 ; i += pas) tft.drawPixel(479-i/pas,Lvalues(i),LTYELLOW) ;
//celle-ci va tracer les données des 6 heures précédentes,
//en commençant avec un pas de 2*pas pour les 3 dernières heures, puis de pour les 3 heures suivantes
void trace3(TFT_HX8357 &tft, int pas)
int i ;
trace(tft,2*pas) ;
for ( i = 0 ; i < 240 ; i += pas) tft.drawPixel(479-240/pas-i/pas,Lvalues3(i),LTYELLOW) ;
//de même en commençant avec un pas de 2*pas pour les 6 dernières heures, puis de pour les 6 heures suivantes
void trace6(TFT_HX8357 &tft, int pas)
int i ;
trace3(tft,2*pas) ;
for ( i = 0 ; i < 240 ; i += pas) tft.drawPixel(479-240/pas-i/pas,Lvalues6(i),LTYELLOW) ;
//etc.
void trace12(TFT_HX8357 &tft, int pas)
int i ;
trace6(tft,2*pas) ;
for ( i = 0 ; i < 240 ; i += pas) tft.drawPixel(479-240/pas-i/pas,Lvalues12(i),LTYELLOW) ;
//etc.
void trace24(TFT_HX8357 &tft, int pas)
int i ;
trace12(tft,2*pas) ;
for ( i = 0 ; i < 240 ; i += pas) tft.drawPixel(479-240/pas-i/pas,Lvalues24(i),LTYELLOW) ;

//la procédure de tracé qui dépend de l’état (3h, 6h, 12h, 24h, 48h)
void Trace()
switch (state)
case 0:trace(tft,1) ;break;
case 1:trace3(tft,1) ;break;
case 2:trace6(tft,1) ;break;
case 3:trace12(tft,1) ;break;
case 4:trace24(tft,1) ;break;

//les procédures pour l’empilement des données dans les listes Values

//celle-ci empile une fois sur deux la donnée la plus ancienne de Values12
void shift24()
if (Bshift24) if (index24++==239)index24=0 ;
Values24[index24]=Values12[index12] ;

Bshift24= !Bshift24 ;

//empile une fois sur deux la donnée la plus ancienne de Values6 après avoir appelé shift24()
void shift12()
if (Bshift12) if (index12++==239)index12=0 ;shift24() ;
Values12[index12]=Values6[index6] ;

Bshift12= !Bshift12 ;

//empile une fois sur deux la donnée la plus ancienne de Values3 après avoir appelé shift12()
void shift6()
if (Bshift6) if (index6++==239)index6=0 ;shift12() ;
Values6[index6]=Values3[index3] ;

Bshift6= !Bshift6 ;

//empile une fois sur deux la donnée la plus ancienne de Values après avoir appelé shift6()
void shift3()
if (Bshift3) if (index3++==239)index3=0 ;shift6() ;
Values3[index3]=Values[index] ;

Bshift3= !Bshift3 ;

//la procédure appelée à chaque intervalle de temps (22.5s)
//elle calcule la donnée correspondant à la pression moyenne sur l’intervalle et l’empile dans Values
//et teste le gap barométrique
void shift()
if (index++==479)index=0 ;
shift3() ;
alarmBaro=false ;
Values[index]=int((104000-pression)/25) ;
//recherche d’une différence supérieure à GapBaro
for (int i=1 ; i <= indexAlarm ; i++)
if (abs(Values[index] - Lvalues(i))>GapBaro) alarmBaro=true ; break ;
if (indexAlarm++==639)indexAlarm=639 ;
//rattrappage progressif d’une erreur systématique à chaque allumage (réveil) de l’écran
if (Reveil)Reveil=false ;gap=Lvalues(1)-Lvalues(0) ;// à éventuellement rattraper
if (abs(gap)>=1)Values[index]=Values[index]+gap ; gap=gap*0.99 ;

void RempliTFT()
//à partir de la valeur de la thermistance, la température se calcule avec la formule de steinhart
//dans cette formule, les constantes A, B et C sont déterminées empiriquement
//à partir de 3 couples (résistance, température)
//voir https://en.wikipedia.org/wiki/Stein...
const float A=0.782302003 ;//-1.990861069 ;//multiplié par 1000
const float B=0.2648965 ;//0.697499306 ;
const float C=0.000176909 ;//-0.001344519 ;
//la température donnée par la formule
tft.setRotation(1) ;
tft.setTextColor(WHITE, BLACK) ;
tft.fillScreen(BLACK) ;
tft.setTextSize(1) ;
axes(tft) ;
affiche_heure() ;
tft.print(« H : ») ;
tft.print(Sensor.readFloatHumidity(), 0) ;
tft.print(« % P : ») ;
tft.print(1040-Lvalues(0)/4) ;
tft.print(« mb T : ») ;
tft.print(Sensor.readTempC(), 0) ;
//calcul de la résistance R en Ohm R0/(R+R0)=ValPort/1024, avec R0=1kOhm
float ValThermistance = 10000 *(1024/(1024-ValPortThermistance)-1) ;
//calcul de la température par la formule de steinhart
float steinhart=1000 /(A+B*log(ValThermistance)+C*pow(log(ValThermistance),3))-273.15 ;
if (not isnan(steinhart)) TempExt=int(steinhart) ;//valeurs aberrantes quand on relance l’écran
tft.print(« Ex : ») ;
tft.print(TempExt) ;
tft.print(Duree[state]) ;
affiche_alarm() ;
Trace() ;

void setup()
pinMode(BoutonG,INPUT_PULLUP) ;
pinMode(BoutonD,INPUT_PULLUP) ;
pinMode(ecran,OUTPUT) ;
pinMode(AllumeCapteurGaz,OUTPUT) ;
pinMode(TestCapteurGaz,INPUT_PULLUP) ;
Serial.begin(9600) ;
Sensor.setI2CAddress(0x76) ;
digitalWrite(ecran,HIGH) ;// pour l’allumage de l’écran
tft.begin() ;
if(Sensor.beginI2C() == false) tft.print(« Sensor non connecté ») ;

void maj_heure()
if(interval++ == 159)// il faut 160 intervalles pour 1h
interval =0 ;
if (heure++==23)
heure=0 ;


void affiche_heure()
tft.setTextSize(2) ;
tft.setCursor(20,10) ;
if(heure<10)tft.print("0") ;
tft.print(heure) ;
if(interval<26)tft.print(":0") ;
else tft.print(«  : ») ;
tft.print(interval*3/8) ;

void affiche_alarm()
tft.setTextSize(2) ;
tft.setCursor(450,10) ;
if (AlarmOn) tft.print(« A ») ; else tft.print(« X ») ;

void loop(void)
String message=«  » ;
if (digitalRead(ecran)==HIGH) RempliTFT() ;
if (alarmBaro && AlarmOn ) tone(piezoPin, 1000, 1000) ;//une seconde toutes les 3/8 de mn
if (alarmGaz && AlarmOn) tone(piezoPin, 4000, 3000) ;// trois secondes pour le gaz plus aigu
temps=millis() ;nbReleves=1 ;
//boucle pour un intervalle de 22.5 s
while (millis()-temps<=delai) // syntaxe pour tenir compte de ce que millis retourne à zéro après 50jours !
if (millis()-temps>nbReleves*1000) //on fait une mesure toutes les secondes pour moyenner
pression +=(Sensor.readFloatPressure()-pression)/nbReleves ;//moyennes calculées itérativement pour éviter dépassement
ValPortThermistance += (analogRead(THERMISTORPIN)-ValPortThermistance)/nbReleves++ ;

if ((message==«  ») && Serial.available())
message =Serial.readString() ;//pour mise à l’heure via série HH:MM, et
if (alarmBaro &&(digitalRead(BoutonG)==LOW)) indexAlarm=0 ;//pour stopper l’alarme
if (alarmGaz &&(digitalRead(BoutonG)==LOW)) alarmGaz=false ;//pour stopper l’alarme
// réveil de l’écran et préparation de la correction d’un gap
if ((digitalRead(ecran)==LOW) &&(digitalRead(BoutonG)==LOW))
digitalWrite(ecran,HIGH) ;tft.begin() ;RempliTFT() ;TempsReveil=millis() ;Reveil=true ;
//changement de mode et réaffichage dans le cas où l’écran est réveillé
if ((digitalRead(ecran)==HIGH) &&(digitalRead(BoutonG)==LOW))
Changestate=true ;delay(200) ;//600
//délai de 0.2 s pour éviter un rebond
if (Changestate)
Changestate=false ;if (state++==4) state=0 ;RempliTFT() ;
//extinction de l’écran au bout de 40s sauf si appui BoutonD
if ((digitalRead(ecran)==HIGH) &&(millis()-TempsReveil>40000)&& (digitalRead(BoutonD)==HIGH))
digitalWrite(ecran,LOW) ;
//test de l’alarme de gaz
if ((digitalRead(AllumeCapteurGaz)==HIGH)and(digitalRead(TestCapteurGaz)==LOW)) alarmGaz=true ;
if ((digitalRead(BoutonD)==LOW)&& (digitalRead(ecran)==HIGH))
delay(delaiBoutonD) ; if (digitalRead(BoutonD)==HIGH)AlarmOn= !AlarmOn ;affiche_alarm() ; else
maj_heure() ;affiche_heure() ;if (delaiBoutonD—<5)delaiBoutonD=5 ;
else delaiBoutonD=200 ;//appui court ->toggle alarm long->cycle heure

//à l’issue de l’intervalle
// mise à l’heure après reception d’un message HH:MM
if ((message.length()>3))// and (message[3]==«  : »))
heure=message.toInt() ;message=message.substring(3) ;interval=8*message.toInt()/3 ;
if (message==« t ») AlarmOn= !AlarmOn ;affiche_alarm() ; //(t)oggle alarm
// messages pour des tests (en réserve)
// if (message==« + ») Serial.println(++delai,DEC) ;
// if (message==« - ») Serial.println(—delai,DEC) ;
// if (message==« M ») Values[index]=319 ;//pour test
// if (message==« m ») Values[index]=0 ;
//allumer le capteur de gaz un intervalle tous les 16 (6 mn)
if (interval % 16==0) digitalWrite(AllumeCapteurGaz,HIGH) ;
else digitalWrite(AllumeCapteurGaz,LOW) ;
//mise à jour de l’heure
message=«  » ;
maj_heure() ;
shift() ;
//empilage de la pression


[1] Dans un environnement de développement « intégré » (abrégé EDI en français ou IDE en anglais, pour integrated development environment), les outils sont prévus pour être utilisés ensemble (le produit d’un outil peut servir de matière première pour un autre outil).

UP


Répondre à cet article
(pour répondre à un message en particulier, voir plus bas dans le fil)

13 Messages de forum

__________________________________________________________________________________________________________________

__________________________________________________________________________________________________________________

Répondre à cet article

UP

Copyright et informations légales