Sur une architecture comme l’ESP32 vous avez 2 processeurs et une mémoire partagée, en un cycle vous pouvez toucher la même case mémoire.
Vous n’êtes pas garanti que l’écriture d’un bit dans une variable en C++ soit atomique. A ma connaissance la norme ne le dit pas. C’est sans doute toujours vrai en pratique avec une variable sur un octet dans une architecture 8 bits mais pas sûr avec une variable sur plusieurs octets (si architecture 8 bits)
La remise à zéro hors zone critique pourrait cependant détruire le flag d’une interruption concomitante qui aurait dû être traitée.
L’avantage de l’interruption c’est que vous enregistrez l’événement instantanément, mais l’inconvénient c’est aussi cela. Ce n’est faut qu’une seule fois, et si vous avez écrasé le flag vous ratez la demande.(qui pourrait être vue par polling de l’état si cet état dure assez longtemps par exemple)
à 1 cycle d'horloge près si j'écrase (comme tu dis) l'indicateur c'est qu'il a été modifié avant la fin de la période demandée par @alinus06640 donc je le considère comme valide.
et arrête de modifier tes messages pendant que je réponds, c'est pénible de revenir sans arrêt en arrière pour essayer de comprendre les réponses que tu modifies en permanence !
Il peut tomber pile au moment où vous faites la remise à zéro du flag, c’est à dire après avoir vérifié que les 3 secondes étaient écoulées et donc il aurait dû être pris en compte…
L’appel à millis() puis la comparaison en unsigned long puis la remise à zéro est loin d’être atomique
C’est bien sûr très théorique et dans l’absolu on peut se dire pas de bol c’est dans l’épaisseur du trait mais c’est possible.
si, je ne parle pas d'effectuer un masquage mais simplement de lire la valeur, opération qui ne prend qu'un seul cycle : si cette valeur doit changer entre temps je n'y peux rien, elle aura évolué.
même en VHDL sur un FPGA (même le moins rapide) je ne peux pas empêcher une valeur d'être modifiée entre deux cycles d'horloge.
Je n’ai rien contre les interruptions, je dis juste qu’il faut écrire cela "correctement" pour la plateforme choisie et que c’est souvent plus compliqué que de faire du polling pour un débutant et qu’il faut réserver cela aux cas ou c’est nécessaire à mon avis. (Le contexte de cette discussion étant le premier post avec un délai d’une seconde dans l’ISR…)
La lecture du flag sur un octet est atomique mais généralement s’en suit un test sur le flag est des actions puis le raz du flag. Si vous ne protégez pas d’une manière ou d’une autre cette section vous avez théoriquement un risque
On voit souvent dans la loop (bien souvent en ayant aussi oublié le volatile)
if (flag) { // flag venant de l’ISR
xxx
xxx
xxx
flag = false;
}
Ce type de code vous met à risque de rater une interruption. Alors que Si vous faites
>>>Début de zone critique sur flag<<<
flagCopie = flag;
flag = false;
>>>Fin de zone critique sur flag<<<
if (flagCopie) {
xxx
xxx
xxx
}
Vous n’aurez pas le problème (il peut y en avoir d’autres mais c’est pour une autre discussion)
Et Ce que j’essaye de dire c’est que suivant la plateforme., passer en zone critique n’est pas un concept maîtrisé par les débutants
Si une interruption arrive après les actions mais juste avant que vous le mettiez à inactif alors la loop efface le nouveau flag avant de reboucler et vous avez raté cette interruption.
Rater une interruption ce n’est pas pinailler - mais spec c’est la spec ici c’est pas important sans doute mais dans d’autres cas les timing est important
Mon point reste plus sur la gestion correcte des interruptions dans le cas général.
J'ai l'impression que tu prends la "critique pour toi"
De mon point de vue la remarque de @J-M-L est général et ça remarque très juste, il faut de manière générale faire attention lorsque l'on manipule des variables dans des fonctions concurenciel.
Après si tu es sûr que tu ne peut pas tombé dans un cas où tu change la valeur d'une variable au milieu d'un traitement sur cette variable dans une autre partie de ton code, alors il n'y a pas de soucis et je te fais confiance sur le traitement de ton code.
Mais il en reste qu'un débutant ou programmeur occasionnelle, pourrait ce faire avoir.
Et dans ce cas, ce n'est pas toujours simple à debugger, sur tout sans debuggeur.
Après effectivement on peut se poser la question de choisir entre prévoyance et performance.
Étant un programmeur de PC ou gros système, je suis plus du côté de la prévoyance, mais je comprends que cela peut ne pas être ton cas ,
Merci pour tous vos posts je ne pensais pas générer autant d’intérêt ...
J'ai trouvé une autre solution cette fois non bloquante avec la fonction timer.h qui fait le job.
Donc plus d’hérésie dans le code j’espère J-M-L...
/*
_ _ _ ____ _ _ _ _ _
/ \ | (_)_ __ _ _ ___ | __ )(_) __| | ___ _ _(_) | | ___
/ _ \ | | | '_ \| | | / __|_____| _ \| |/ _` |/ _ \| | | | | | |/ _ \
/ ___ \| | | | | | |_| \__ \_____| |_) | | (_| | (_) | |_| | | | | __/
/_/ \_\_|_|_| |_|\__,_|___/ |____/|_|\__,_|\___/ \__,_|_|_|_|\___|
Ce sketch génère une impulsion de 3s sur la sortie dès que le contact entre masse et et pin 02 est ouvert
si le contact 02 se referme il n'y a pas de nouvelle génération d'impulsion.
L’interruption 0 est configure l'entrée 02 de L'Arduino Méga.
Utile pour signaler une alarme une seule fois et ne rien faire si la situation redevient normale jusque à la prochaine alarme.
Le clignotement est la pour prouver que nous ne sommes pas dans une fonction bloquante.
29-05-2023
*/
#include "Timer.h"
Timer t;
int Relais = 12;
unsigned long timer;
boolean ledState = 0;
void setup()
{
pinMode(13, OUTPUT);
pinMode(Relais, OUTPUT);
attachInterrupt(0, blink, RISING); // attache l'interruption externe n°0 à la fonction blink
}
void loop()
{
if (millis() - timer > 500) { //clignotement toutes les 1/2s
ledState=!ledState; // changer l'état de la LED
digitalWrite(13, ledState);
timer = millis();}
t.update();
}
void blink() // la fonction appelée par l'interruption externe n°0
{
t.pulseImmediate(Relais,3000,HIGH); // Impulsion immédiate de 3 secondes après ouverture de l'entrée 2.
}
oui la classe Timer avec l'appel de t.update(); effectue la tâche de la petite machine à états qui gère ce que vous demandez de faire.
Il me semble cependant que si une nouvelle interruption arrive pendant la période des 3 secondes, blink est réactivé et vous relancez la periode de 3 secondes non ? donc votre durée d'activation peut être bcp plus longue en fonction de ce qu'il se passe au niveau des interruptions
vous pouvez essayer en appuyant / relâchant le bouton sur la pin 2 pour balancer des fronts
bien sûr que je la prends exclusivement pour moi : ce n'est pas la 1ère fois de sa part
je ne fais que répondre à la demande initiale, et je me retrouve face à un irréductible qui ne jure que par ses propres convictions (comme souvent)
j'avais décidé d'en rester là mais même si le demandeur s'est trouvé une solution qui lui convient (n'en déplaise à ...) voici la mienne :
EDIT : code corrigé
// je suppose par défaut une carte UNO, donc un ATMega328..
// la compil est effectuée sous Arduino 1.8.0
// IO12 sortie relais sur port B4
#define SortieRelaisON PORTB|=0b00010000
#define SortieRelaisOFF PORTB&=0b11101111
#define int0_OFF EICRA&=0b11111110
#define int0_ON EICRA|=0b00000001
#define int0_RisingEdge EIMSK|=0b00000011
uint32_t debutTempoRelais;
#define duree_ms_TempoRelais 3000UL
void setup()
{
DDRB |= 0b00010000; // = pinMode(sortieRelais=IO12, OUTPUT);
int0_RisingEdge; int0_ON; // pas besoin d'expliquer, voir les macros plus haut
}
void loop()
{
if ((millis()-debutTempoRelais) > duree_ms_TempoRelais)
{
SortieRelaisOFF;
int0_ON;
}
// tu mets ensuite ce que tu veux ici
}
ISR(INT0_vect)
{
SortieRelaisON;
int0_OFF;
debutTempoRelais = millis();
}
Il n’y a aucune critique contre vous. Il y a une discussion et des points de vues sur des aspects techniques .
J’exprime le mien, je respecte le votre mais quand je ne suis pas d’accord je le dis,..
Par exemple je ne suis pas d’accord avec cela
Ca peut bugguer dans une cinquantaine de jours (finTempoRelais ferait un rollover alors que millis serait toujours super grand et donc le if de la loop serait déclenché immédiatement)… vous me direz que ce n’est pas grave de rater une tempo de temps en temps sans doute ?
Sinon désactiver les interruptions est un bon moyen de ne pas avoir à gérer la section critique oui.
Sinon merci pour vos messages j'ai beaucoup appris sur les Timers et la programmation en "bits" que je ne connaissais pas.
J'ai galéré aussi sur les ports du Méga mais avec le datasheet ça roule...
Voici donc le code
// IO12 sortie relais sur port B6
#define SortieRelaisON PORTB|=0b01000000 // Mise à 1 de la sortie 12 du MEGA2560
#define SortieRelaisOFF PORTB&=0b10111111 // Mise à 0 de la sortie 12 du MEGA2560
#define int0_OFF EIMSK&=0b11111110
#define int0_ON EIMSK|=0b00000001
#define int0_RisingEdge EICRA|=0b00000011 //Mode d'interruption en détection de front
/*
00 Un niveau bas sur l'entrée INTx
01 Un changement d'état sur l'entrée INTx
10 Un front descendant sur l'entrée INTx
11 Un front montant sur l'entrée INTx
*/
uint32_t finTempoRelais;
#define duree_ms_TempoRelais 3000UL
void setup()
{
DDRB |= 0b01000000; // = pinMode(sortieRelais=12 PORT B, OUTPUT);
int0_RisingEdge; int0_ON; // pas besoin d'expliquer, voir les macros plus haut
}
void loop()
{
if (millis() > finTempoRelais)
{
SortieRelaisOFF;
int0_ON;
}
// tu mets ensuite ce que tu veux ici
}
ISR(INT0_vect)
{
SortieRelaisON;
int0_OFF;
finTempoRelais = millis() + duree_ms_TempoRelais;
}