incompatibilité Timer2 et horloge RTC DS3231 ?

Bonjour à tous,

Je suis bloqué dans mon projet avec ce que je suppose une incompatibilité, mais je ne vois pas trop où.

J’ai besoin d’une interruption toutes les minutes pour effectuer la procédure « Ecriture() »

Pour ce faire, j’utilise la librairie https://playground.arduino.cc/Main/MsTimer2

MsTimer2::set(60000, Ecriture);

Cela fonctionne très bien, merci.

Dans cette procédure, il me faut la date et heure et j’ai donc une horloge RTC DS3231. avec une fonction qui fait merveille :

void DateHeure() {
  DateTime now = rtc.now();
  sprintf (cDate, "%02d/%02d/%04d %02d:%02d:%02d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
}

Elle place l’heure dans « char cDate[20]; » définie en haut.

Mais quand je fais fonctionner les deux ensemble, l’Arduino plante et se fige.
Pouvez-vous me donner une indication ?

Le code complet :

// ########################################################################
// RTC - horloge

#include <Wire.h> //include Wire.h library
#include "RTClib.h" //include Adafruit RTC library
RTC_DS3231 rtc; //Make a RTC DS3231 object

// ########################################################################

// ########################################################################
// Gestion de l'interruption du Timer 2 pour l'écriture du tableau toutes les minutes
// https://playground.arduino.cc/Main/MsTimer2

#include <MsTimer2.h>

// ########################################################################

char cDate[20]; //date et heure textuelle complète jj/mm/aaaa hh:mm:ss

void setup() {

  Serial.begin(115200);//make it fast so it dumps quick!

  //========== RTC
  //Print the message if RTC is not available
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }
  //Setup of time if RTC lost power or time is not set
  if (rtc.lostPower()) {
    //Sets the code compilation time to RTC DS3231
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }  //==========


  //============= Interruption Timer2
  MsTimer2::set(10000, Ecriture); // Toutes les 10000 ms (10 secondes) lance la procédure Ecriture
  MsTimer2::start(); // Démarre le processus
  //==============

  DateHeure();
  Serial.println(cDate);

}

void loop() {

}

void DateHeure() {
  DateTime now = rtc.now();
  sprintf (cDate, "%02d/%02d/%04d %02d:%02d:%02d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
}

void Ecriture() {
  DateHeure();
  Serial.println(cDate);
  Serial.println("Toto");
}

À vous lire et avec mes remerciement anticipés :wink:

Bonjour

J'ai vraiment du mal à comprendre pourquoi la gestion par interruptions est si souvent mise en avant, notamment auprès des personnes qui ne sont pas aguerries à arduino, qui se retrouvent ensuite confrontées à la difficulté de maîtriser la bestiole.
Car c'est vraiment semé d'embûches et très rarement indispensable.

Là tu as à ta disposition :

  • une fonction millis()
  • un DS3231 qui te donne l'heure

Et tu voudrais utiliser une troisième possibilité (les interruptions du timer) pour déclencher un traitement toutes les 10 secondes.

J'appelle ça "chercher les ennuis" :smiling_imp:

Blaise032:
J'ai besoin d'une interruption toutes les minutes pour effectuer la procédure « Ecriture() »

Bonjour,

Une interruption doit être le plus courte possible et éviter d'appeler des fonctions qui utilisent des interruptions comme les transmissions série.

Tu poses mal le problème dès le départ.
Tu as besoin d'effectuer la procédure "Ecriture()" toutes les minutes
Quel est la solution?

  • interruption? -> pas du tout adapté car dans une interruption doit être le plus courte possible et faire le minimum de chose, en particulier pas de transmission série. De plus les interruptions sont utilisées pour les actions nécessitant un timing rapide et/ou précis. Dans ton cas quel est le problème si Ecriture() est appelé avec 10 ou 100mS de retard ?
  • appel dans la loop? -> semble adapté car la fonction risque d'être longue (vu du micro), utilise des fonctions qui font appel à des interruptions et ne nécéssite pas un timing rapide ou précis.

Donc tu mets ta fonction dans la loop

void loop() {
 Ecriture();
 delay(1000);
}

Si tu veux un programme un peu plus évolué et qui permet de faire autre chose en même temps, tu peux utiliser millis().

Bonjour,

AU delà de l'approche qui semble posé problème,
les variables qui sont utilisées dans une interruption, doivent être déclaré en volatile.

ex: volatile Char cDate[20];

Bonjour,

Merci pour vos conseils avisés.
Comme de fait, je n'ai pas besoin d'être précis.
Je vais utiliser millis().

Bien à vous.

Bonjour

Effectivement cela semble plus raisonnable, avec de bien meilleures chances d’arriver facilement à un résultat satisfaisant.

En passant, voici une petite lib basée sur millis() et très adaptée à ce cas

#include "simpleMinuteur.h"
simpleMinuteur tempo;

void setup() {
  ...
  tempo.demarrer(10000);
}

void loop() {
  if (tempo) {//et hop on entre ici toutes les 10 secondes
    ...
  }
  ...
}

Leptro:
les variables qui sont utilisées dans une interruption, doivent être déclaré en volatile.

c'est un peu de la généralisation abusive là.

Celles qui ont besoin vraiment d'être volatile sont celles qui sont partagées entre le code principal et le code de l'ISR et pour lesquelles on souhaite avoir une parfaite synchronisation au plus près de l'interruption.

Par curiosité : concrêtement ça fait quoi un volatile (à part picorer du maïs) ?

Dans le code exécutable, cela ajoute une désactivation/réactivation des interruptions juste avant/après chaque modification de la data, lorsque celle-ci prend plusieurs cycles ?

C'est bien ça?

Donc par exemple pour un simple flag d'un octet en variable globale, mis à jour avec une valeur fixe en une seule instruction machine, on peut s'en passer?

bricoleau:
Par curiosité : concrêtement ça fait quoi un volatile (à part picorer du maïs) ?

Non, ça ne vous protège pas des interruptions, ça c'est à vos de gérer cela si vos données ne tiennent pas sur un élément natif de l'architecture (ie 8 bits sur un processeur 8 bits). par exemple si vous avez un int x (donc 2 octets sur un UNO) et que vous faites un x = 2*x + sin(x) et que x peut être modifié dans l'ISR, rien ne vous garantit l'atomicité de cette équation. une partie pourrait être calculée avec un certain x et l'autre avec un autre x.

Volatile, ça garantit que si la valeur est modifiée, elle est immédiatement écrite à sa place mémoire et pas conservée dans une registre entre temps ou quand on la lit on la prend en mémoire et pas dans le registre

imaginez que vous fassiez

byte x = 3; // sur 1 octet
f1(x);
x = 5;
f2(x);
x = 7;
f3(x);

(c'est juste un exemple car le compilateur ici peut faire d'autres optimisations)

il faut comprendre l'architecture des ordinateurs. le micro-processeur a peu d'instructions qui travaillent directement dans la SRAM. pour faire des opérations il copie le contenu de la mémoire dans un registre, effectue l'opération nécessaire puis (si la donnée modifie le registre) remet la donnée à sa place en SRAM en y recopiant le contenu du registre interne du micro-processeur. (l'accès à la SRAM est très lent par rapport à l'accès à un registre)

Donc le compilateur - pour optimiser les accès à la mémoire - pourrait se dire qu'il peut conserver x dans un registre et seulement ré-écrire x à la fin dans sa case mémoire.

le soucis c'est que si ce code est interrompu en cours de route, l'ISR ne sait pas que la bonne info est dans le registre et va donc aller relire en mémoire un x qui vaut 3 alors que vous pouvez en être à 7 par exemple

Donc en simple, volatile prévient le compilateur que cette variable peut être modifiée par un moyen extérieur au bout de code en cours

ok merci c'est très clair.

En guise d'illustration : il y a quelques temps j'ai programmé un arduino en tant qu'esclave I2C.

Celui-ci collecte des données d'état par lecture de capteurs.
Ces données sont stockées dans des variables globales.
Et lorsque le maître du bus I2C le souhaite, l'esclave lui retourne les valeurs courantes.
Jusque là tout va bien c'est tout simple.

Sauf que : côté esclave, la bibliothèque Wire.h déclenche une interruption lorsqu'il faut envoyer quelque chose au maître.
Il faut donc coder le handle "onRequest", c'est-à-dire une fonction qui lit les variables globales et les envoie par Wire.write().

Et paf : si le maître interroge alors que les variables globales sont en cours de mise à jour, le handle peut retourner des résultats incohérents.

J'ai du doubler les variables globales, avec un jeu A et un jeu B, et ajouter un flag sur un octet qui indique quelle est le jeu à exploiter dans le handle.

Ainsi, le programme passe son temps à lire les capteurs, charger le jeu A, basculer le flag sur A, lire les capteurs, charger le jeu B, basculer le flag sur B etc...

Et le handle, selon la valeur du flag, exploite le jeu A ou le jeu B pour remonter l'état courant au maître du bus.

Cela fonctionne bien, mais dans le doute j'avais tout mis en volatile : flag + données du jeu A + données du jeu B.

Alors que si je comprends bien, un volatile sur le seul flag aurait été suffisant.

bricoleau:
Alors que si je comprends bien, un volatile sur le seul flag aurait été suffisant.

sans doute oui puisque vous vous chargez "manuellement" de vous assurer que les valeurs ne sont pas en train de changer (et qu'en plus le callback appelé depuis l'ISR ne modifie le buffer en question)

J-M-L:
c'est un peu de la généralisation abusive là.

oui.. clairement. :slight_smile: .

Je n'ai pas été assez précis.