Problème Transmission données Tableau

Bonjour à Tous,

Je suis novice en Arduino mais par nécessité j'ai décidé de me lancer dans un projet de Routeur d'énergie pour excédent de production de panneaux solaires.
J'en suis à l'état de premier prototype de programme en cours d'implémentation. La carte support Arduino est une Nano (Atmega 328P).
Je lui ai adjoint une carte Display OLED 128x64 SH1106.

Les informations de Puissance électrique sont récupérées à partir d'un petit compteur d'énergie Eastron SDM230, installé au niveau de l'arrivée Enedis du tableau électrique général de la maison.

La transmission des données entre compteur d'énergie et Arduino se fait par une ligne RS485 bi-directionnelle en utilisant le protocole Modbus RTU (vitesse de transmission 9600 bauds).

Le but du routeur est de diriger l'excédent d'énergie produit par les panneaux solaires sur une résistance éléctrique (exemple : un ballon de production d'eau chaude sanitaire).

J'ai donc commencé à écrire un programme composé pour le moment des fonctionnalités suivantes :

  • Récupération des données du compteur d'énergie, en l'occurrence pour l'application : la Puissance.
    = > Testée, fonctionne parfaitement
  • Affichage de la Puissance (pour l'instant celle consommée)
    => Testée, fonctionne parfaitement
  • Commande du Relais Statique (SSR) par contrôle du déphasage par rapport au Zero Crossing
    du Secteur 50Hz. Cette Fonctionnalité utilise l'interruption de l'entrée Arduino D2
    (Rising edge ZeroCrossing) et le Timer 2 pour la commande de déphasage.
    => Testée, fonctionne parfaitement

Le contrôle du déphasage n'étant pas linéaire par rapport à la puissance, j'ai du utiliser un tableau de conversion de 256 valeurs correspondant au déphasage par pas de 64µS couvrant env. (x256) 9mS soit une demie période 50Hz.

Et c'est là que se situe mon problème. Le fait de transmettre la valeur d'une variable comme index du tableau bloque les fonctions RS485 Modbus et affichage OLED (l'affichage reste vierge).
La fonction commande SSR fonctionne mais avec une valeur inadaptée du déphasage.

Si je remplace, dans la fonction "retard", la variable d'index du tableau (valRetard) par une constante tout fonctionne et avec la bonne valeur de déphasage (celle correspondant à la constante).

Le compilateur ne détecte pas d'erreur (IDE Arduino V2.03), le téléversement se passe sans problème.

Je suis désolé mais entant que nouvel utilisateur je ne peux pas envoyer de fichier.
Copie de programme ci-après.

merci pour votre aide !

/*
                                       ROUTAGE PUISSANCE AVEC COMPTEUR ENARGIE EAstron SDM230 (Library U8g2lib Module OLED 128x64) _Tst Pw 01_
                                       -------------------------------------------------------------------------------------------------------
                                       
 !!!! Programme en cours d'écriture et de mise au point !!!!

Ce programme est intégré dans Arduino Nano pour gérer la puissance excedentaire produite par des panneaux solaires. Cette puissance excedentaire
est routée sur une résistance électrique ex/ Ballon Sanitaire). L'étage de puissance est géré par Relais statique (SSR) commandé par une impulsion basse tension
déphasée en fonction de la valeur de la puissance à router.

La valeur de la puissance (Pw) est transmise via Modbus (RTU) à 9600 bauds, par un compteur d'énergie Eastron SDM230. La valeur de cette puissance est fournie
en flottant par le compteur.

*/

#include "U8g2lib.h"                  // Library Afficheur Oled                      


#define MAX485_RE_DE      4           // Output : Détermination Sens de transmission sur ligne RS485                  
#define Pulse_SSR         5           // Output : Impulsin de déclenchement SSR/Triac
#define ZeroCross         2           // Input :  Détection du passage à Zéro

#define Trace             6           // Output : Trace pour calcul des temps d'exécution



byte Pw_TX[8] {0x01,0x04,0x00,0x0C,0x00,0x02,0xB1,0xC8};   // Tableau Requête SDM230 
byte Pw_RX[11];                                            // Tableau de REception SDM230
 
int Pw;                                                    // Valeur de la puissance 
int Pwbar;                                                 // Valeur de la puissance (affichage bargraph OLED 128x64)
byte Count;                                                // Compteur d'affichage (Rafraichissement affichage OLed env 1fois/sec)
byte valRetard;                                   // Variable index du tableau Delay_Puissance


/*
La puissance à routée se fait par découpage de la sinusoïde (déphasage de l'impulsion SSR par pas de 64µS de 0 à 255). Ce déphasage n'est pas linéaire.
Le pas 255 correspond à: 64µS x 8 = 512µS qui est la pleine puissance routée. Le pas 0 à: 64µs x 144 = 9216µS (0,9mS) qui est la puissance minimum routée.
La demie période à 50Hz étant de 20/2 = 10mS
*/
   

byte Delay_Puissance [256] {                                                        
  144, 143, 141, 140, 139, 137, 136, 135, 134, 133, 132, 131, 130, 129, 128, 128,
  127, 127, 126, 125, 124, 123, 123, 122, 122, 121, 121, 120, 119, 119, 118, 118,
  117, 117, 116, 116, 115, 115, 114, 114, 114, 113, 113, 112, 112, 112, 111, 111,
  110, 110, 109, 109, 109, 108, 108, 108, 107, 107, 107, 106, 106, 106, 105, 105,
  104, 104, 104, 103, 103, 103, 102, 102, 102, 101, 101, 101, 100, 100, 100,  99,
  99,  99,  99,  98,  98,  98,  97,  97,  97,  96,  96,  95,  95,  95,  95,  94,
  94,  94,  93,  93,  93,  93,  92,  92,  91,  91,  91,  91,  90,  90,  90,  89,
  89,  89,  88,  88,  88,  87,  87,  87,  86,  86,  86,  85,  85,  84,  84,  84,
  84,  83,  83,  83,  82,  82,  82,  82,  81,  81,  80,  80,  80,  80,  79,  79,
  79,  78,  78,  78,  77,  77,  77,  76,  76,  76,  75,  75,  74,  74,  74,  73,
  73,  73,  72,  72,  72,  71,  71,  71,  70,  70,  70,  69,  69,  69,  69,  68,
  68,  67,  67,  67,  66,  66,  65,  65,  65,  64,  64,  63,  63,  63,  62,  62,
  62,  61,  61,  61,  60,  60,  60,  59,  59,  58,  58,  58,  57,  57,  56,  56,
  56,  55,  55,  54,  54,  53,  53,  52,  52,  52,  51,  51,  50,  50,  50,  49,
  49,  48,  48,  47,  46,  45,  44,  44,  43,  43,  42,  42,  41,  41,  40,  39,
  39,  38,  37,  36,  35,  33,  32,  31,  27,  25,  20,  15,  12,  10,   9,   8
};

 // Sélection Ecran OLED SH1106 128x64 dans la Library U8g2

U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); 

/*
La gestion du déphasage de l'impulsion de commande du SSR se fait par le Timer 2 de l'arduino. La synchronisation avec le "Zéro" de la sinusoïde se fait sur interruption:
impulsion sur l'entrée 2 au moment  du passage à Zéro du secteur 50Hz.

 
*/

void setup() {


  TCCR2A = 0;                 // Registre Timer 2 TCCR2A (WGM20, = 0)
  
  TCCR2B = 0b00000111;       // Clock Timer 2 / 1024 soit 64 micro-s et WGM22 = 0

  
  attachInterrupt(digitalPinToInterrupt(2), zeroCrossInt, RISING);   // Interruption Detection Zero 

 u8g2.begin();               // Lancement Ecron Oled

  delay(2000);

   

  pinMode(MAX485_RE_DE, OUTPUT);   // Sens de tansmission Buffer RS485
  
  
  pinMode(Pulse_SSR, OUTPUT);
  pinMode(ZeroCross, INPUT);


  pinMode(Trace, OUTPUT);

 
  digitalWrite(MAX485_RE_DE, 0);   // Initialisation en Réception de la ligne RS485 Modbus
  
  
  Serial.begin(9600);             // Vitesse Transm 9600 bauds de la ligne serie RS485 Modbus


   
}

void loop() {
 
 
   
//digitalWrite(Trace, 1);

  switch (Serial.available()) {     // Vérification Etat du Buffer Read : Si 0 Transm OK, Si 11 Réception
    case 0:
      digitalWrite(MAX485_RE_DE, 1);        // Mise en Transmission
      ++Count;  
      Serial.write(Pw_TX, 8);                       // Envoi Requête pour Lecture Tension
      Serial.flush();
        
      digitalWrite(MAX485_RE_DE, 0);      // Mise en Reception
      break;
    case 11:
          Serial.readBytes(Pw_RX, 11);   // Lecture Buffer Reception et remplissage tableau
      break;
  }

 delayMicroseconds(1200);       // Délai de Sortie d"échange Master/Slave

   // Calcul Valeur Pw (La Puissance envoyée par le Meter Eastron est en Float sur 4 octets)

  union {                          // Concaténation de la valeur de la Tension en HEX
    int32_t combined;
    uint8_t bytes[4];
  } u;
  u.bytes[3] = Pw_RX[4]; //0 63 
  u.bytes[2] = Pw_RX[5]; //1 63 
  u.bytes[1] = Pw_RX[6]; //2 3
  u.bytes[0] = Pw_RX[7]; //3 

  int32_t x= (u.combined);    // Conversion en valeur 
  float y = *(float*)&x;
  
  Pw = int(y);                           // Conversion Float en Integer
 
  Pwbar = Pw/2.4;

    switch (Count) {                      // Compteur d'Affichage OLED ttes les secondes
    case 1:

      Aff_Oled();
                
       break;
       
    case 30:
      Count = 0;
      Pw =0;
      
    break;   
    }
    
  Energy();                  
    

}

void Energy () {

  valRetard = Pw*100/16;                  // Calcul de l'index (du tableau) de déphasage 
                                                             // Cette valeur doit être comprise entre 0 et 255  
  
  if (valRetard <= 0) { 
        valRetard = 0;
    
   }

  if (valRetard > 255) {

    valRetard = 255;       

  }
}  


void Aff_Oled () {                        // Affichage OLED 128x64 Valeur de la puissance routé + bargrapf

      u8g2.clearBuffer();
      u8g2.setFont(u8g2_font_7x13_tf);
      u8g2.drawStr(38,20, "Routage");
      u8g2.setCursor(43,36);
      u8g2.print(Pw);
      u8g2.drawStr(78,36, "w");
      u8g2.drawFrame(14,45,104,10);
      u8g2.drawBox(16,47,Pwbar,6);
      u8g2.sendBuffer(); 


}

void zeroCrossInt() {                   // Routine interruption ZeroCrossing

  retard();

}


void retard() {                         // Initialisation et lancement Timer 2 Calcul retard déclenchement SSR

  TIMSK2 = 0;                           // Inhibition interruption TIMER 2
  TCNT2  = 0;                                 // Mise à 0 Timer 2         
   
  OCR2A  = Delay_Puissance[valRetard];          // Déphasage en multiple de 64µS déclench SSR
  valRetard = 0;
  TCNT2  = 0;                                      // Mise à 0 Timer 2
  TIMSK2 = 0x02;                              // Autorisation interruption Timer 2
 
}


ISR(TIMER2_COMPA_vect){                 // Routine interruption Comparaison Timer 2 au registre OCR
    
  pulse();

}

void pulse() {                          // Génération Impulsion SSR

  
  TIMSK2=0;                             // Inhibition interruption TIMER 2
  
  digitalWrite(Pulse_SSR, 1);           // Génération de l'impulsion de 100µS pour déclencher le SSR
  delayMicroseconds(100);
  digitalWrite(Pulse_SSR, 0);
 
  
}

votre code doit générer pas mal de warnings à la compilation non? (si vous activez tous les warnings)
➜ il faudrait les corriger tous

pour valRetard elle est déclarée comme cela

byte valRetard;                                   // Variable index du tableau Delay_Puissance

donc sur un seul octet non signé. sa valeur est forcément entre 0 et 255. donc quand vous faites

  valRetard = Pw*100/16;                  // Calcul de l'index (du tableau) de déphasage 
                                                             // Cette valeur doit être comprise entre 0 et 255  
    if (valRetard <= 0) { 
        valRetard = 0;  
   }

  if (valRetard > 255) {
    valRetard = 255;       
  }

d'une part les tests ne servent à rien puisque on ne sera jamais négatif ni supérieur à 255 mais la valeur que vous avez obtenue dans valRetard et le résultat du calcul en entier sur 16 bits signés de Pw*100/16 qui a pu déborder donc si Pw est supérieur à 328 (je ne sais pas si c'est possible) tronqué sur l'octet de poids faible... est-ce que c'est ce que vous voulez ?

d'autre part, vous utilisez cette variable dans une interruption (la fonction retard() est appelée par l'interruption zeroCrossInt()) mais vous la modifiez dans la loop() (par appel à Energy()).
➜ vous êtes dans le cas typique où il faut déclarer valRetard en volatile

PS:
en C++ cette gymnastique là n'est pas autorisée

  union {                          // Concaténation de la valeur de la Tension en HEX
    int32_t combined;
    uint8_t bytes[4];
  } u;
  u.bytes[3] = Pw_RX[4]; //0 63 
  u.bytes[2] = Pw_RX[5]; //1 63 
  u.bytes[1] = Pw_RX[6]; //2 3
  u.bytes[0] = Pw_RX[7]; //3 

  int32_t x= (u.combined);    // Conversion en valeur 
  float y = *(float*)&x;
  
  Pw = int(y);                           // Conversion Float en Integer

qu'avez vous dans la trame aux index 4,5,6 et 7? un nombre entier ou décimal?


Tout d'abord, merci de l'intérêt porté à mon problème !

Concernant les warnings (en activant "all" dans préférence) je n'ai que celui-ci :
"147:23: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing] => Peut être est ce la raison du pb ??

2- Tout à fait d'accord avec vous sur le Test de valRetard, il ne sert à rien. J'avoue avoir mis ça en dernier lieu sans y croire.

3- La valeur calculée de Pw est basée sur une résistance de 40w (ampoule de 40w pour le test).
Je testerais plus tard avec des valeurs de 1.5kw et 2kw en adaptant le calcul. Donc actuellement
la valeur de Pw est 40 max et c'est bien cette valeur que je récupère du compteur d'énergie.
j'avais déclaré valRetard en volatile sans que cela corrige le pb. Mais vous avez raison, c'est ainsi qu'il faut la déclarer.

4- Vous avez sans doute raison mais cette méthode fonctionne. Si vous avez un meilleure méthode, je suis preneur. La valeur transmise par compteur d'énergie est en flottant sur 4 octets.

Une valeur en Entier me suffit pour la puissance. Par contre c'est une valeur signée qu'il me faudra. Ce n'est pas un problème, pour tester cela fonctionne en inversant le sens du courant sur le compteur d'énergie, je récupère une valeur négative de la puissance simulant un renvoi d'énergie sur le réseau Enedis.

En fait, la seule chose qui bloque le programme, c'est l'utilisation de ce tableau avec une variable comme index. si je met directement une valeur comprise entre 0 et 255 tout fonctionne.
D'autre part si je passe directement la valeur reçue de Pw (ex 40) au registre de comparaison OCR2A tout va bien. ... ???

Encore merci !

En c++ la façon de faire qui est conforme c’est un memcpy des 4 octets à l’adresse d’un float.

Avec volatile ça devrait aller mieux et il faudrait dans la loop effectuer la modification en section critique pour bien faire (bien qu’elle ne soit pas modifiée ailleurs)

Bonjour,
Hier, j'ai fini par trouver le point bloquant dans mon programme. Il s'agit de l'occupation de la RAM qui est à 97%. Je n'avais pas fait attention à cette info en fin de compilation. Le tableau lui même consomme 256 octets. En remplaçant la variable index du tableau par une constante, l'occupation RAM passe à 85%; c'est suffisant pour faire tourner le programme.
Mais c'est surtout l'affichage Oled avec la library U8g2 qui sature la RAM. En supprimant la fonctionnalité d'affichage, l'occupation RAM passe à 22%.
Je dois donc revoir ma copie ... A suivre

bonsoir
sur des projet juste en ram il faut utiliser des librairies minimaliste pour l'oled qui ne réservent pas de place en ram pour l'écran.
passer un max de chaine de caractère en eprom (quitte a ecrire une fonction d'affichage a partir de l eprom).
pour gagner de la place aussi en eprom on peut utiliser des fonts minimalistes en supprimant les caractères exotiques inutiles.

une recherche sur : 'oled no ram buffer" donne un tas d'exemple.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.