Planete Sciences

forums de Planete Sciences
It is currently Thu 02 Oct 2014, 18:27

All times are UTC + 1 hour [ DST ]




Post new topic Reply to topic  [ 40 posts ]  Go to page 1, 2, 3  Next
Author Message
 Post subject: Risque atomic
PostPosted: Fri 17 Feb 2012, 14:21 
Offline
PMI
User avatar

Joined: Tue 14 Jun 2005, 17:16
Posts: 933
Location: Ville d'Avray
En préambule et en vertu du principe de précaution et pour éviter toute remarque inutile on signale à notre camarade ZEUS que l’orthographe atomic est correct. :wink:
Ce message peut intéresser les utilisateurs de micros 8 bits (Et oui ça existe encore) et qui auraient été victimes de bugs intermittents (les plus redoutables). On en parle en connaissance de cause car ça nous a pourri l’existence depuis plusieurs années.

L’asservissement et l’odométrie nécessitent un échantillonnage à période rigoureusement constante (Tech=10 ms par exemple). Une solution classique consiste alors à cadencer tous ces calculs par un timer qui déclenche l’interruption à des instants parfaitement équidistants.
Classiquement le prog d’interruption va échanger des variables globales avec les autres fonctions. Et c’est là que le risque atomic intervient si ces variables globales ne sont pas de type char .
Prenons l’exemple suivant en langage C :

long volatile horloge ; // Définition d’une variable globale de type long (taille de 4 octets)

void prog_interrupt_par_timer(void)
{
calcul_asservissement() ;
calcul_odometrie() ;

horloge ++ ;
}

void fonction_strategique (void)
{

if(horloge > h_max) // h_max corespondant par exemple à 70s
deplacement_vers_bouton_poussoir() ;

}


Si l’interruption arrive pendant l’exécution du if, l’échange n’étant pas atomic malgré la précaution volatile, le résultat risque d’être surprenant et mon équipe peut vous en parler en connaissance de cause. (Ca peut conduire par exemple à un robot qui oublie de déposer son trésor avant la fin du match).

Le mécanisme diabolique de l’échange peut être le suivant :
Imaginons que la fonction stratégique est en train de lire la valeur de horloge. Cette variable étant sur 32bits et le micro sur 8 bits avec une architecture non adaptée, 4 lectures en interne peuvent être nécessaires. Si l’interruption arrive pendant cet échange le résultat risque d’être inattendu. (L’interruption modifie la valeur de horloge pendant la lecture de celle-ci).

Le bug est d’autant plus infernal qu’il se produit d’une manière intermittente, et donc souvent au plus mauvais moment. La découverte ce phénomène a constituée une révélation pour les néophytes que nous sommes et j’espère que ce message en intéressera d’autres.

Bien entendu il existe des solutions au problème que je me ferai un plaisir au besoin de vous communiquer.

_________________
RCVA: Robot Concept Ville d'Avray
http://www.rcva.fr


Top
 Profile  
 
 Post subject: Re: Risque atomic
PostPosted: Fri 17 Feb 2012, 15:05 
Offline

Joined: Mon 25 Jan 2010, 22:48
Posts: 467
Expliqué comme ca, le problème à l'air presque évident.

Etant un fervant utilisateur de pic 8 bits, je suis très intéressé par
1) la solution pour éviter ce genre de problème, (desactiver les interruptions au moment du test ne me semble pas idéal)
2) les conditions qui vous ont permi d'arriver à la conclusion que c'était bien ce problème que vous rencontriez. Les cas ou le problème peut apparaître me semble plutôt rare... (suffisament frequent pour se produire, mais pas assez pour avoir les données pour trouver le problème). Aviez vous une boucle while très courte ? Le bug était devenu systématique ? Ou aviez vous un système de debug de branché au moment du bug ?

merci encore pour le partage de votre savoir

_________________
Équipe Poivron
Coupe 2014 : Équipe Poivron (67e)
Coupe 2012 : Équipe Poivron (73e)
Coupe 2011 : Équipe Poivron (122e) - Prix des équipes !
Coupe 2010 : Équipe Poivron (78e)


Passez voir le Portail des équipes


Top
 Profile  
 
 Post subject: Re: Risque atomic
PostPosted: Fri 17 Feb 2012, 15:17 
Offline

Joined: Thu 06 Sep 2007, 10:39
Posts: 103
Location: retourne toi je suis là
Personnellement, dès que je dois vérifier la valeur d'une variable globale qui peut évoluer sur une interruption, je stocke toujours sa valeur dans une variable locale et je réalise mes opérations toujours avec la variable locale, la variable globale menant sa vie de son côté.

_________________
L'asservissement c'est simple comme un PI


Top
 Profile  
 
 Post subject: Re: Risque atomic
PostPosted: Fri 17 Feb 2012, 15:18 
Offline

Joined: Mon 25 Jan 2010, 22:48
Posts: 467
j'ai trouvé une partie des mes réponses ici : www.firstwiki.net/index.php/Using_interrupts
je serais quand même curieux de connaître votre choix et ses raisons.
@Dagguy : mais le problème risque alors de survenir au moment de la copie, non ?

_________________
Équipe Poivron
Coupe 2014 : Équipe Poivron (67e)
Coupe 2012 : Équipe Poivron (73e)
Coupe 2011 : Équipe Poivron (122e) - Prix des équipes !
Coupe 2010 : Équipe Poivron (78e)


Passez voir le Portail des équipes


Top
 Profile  
 
 Post subject: Re: Risque atomic
PostPosted: Fri 17 Feb 2012, 15:57 
Offline
PMI
User avatar

Joined: Tue 14 Jun 2005, 17:16
Posts: 933
Location: Ville d'Avray
Dagguy wrote:
Personnellement, dès que je dois vérifier la valeur d'une variable globale qui peut évoluer sur une interruption, je stocke toujours sa valeur dans une variable locale et je réalise mes opérations toujours avec la variable locale, la variable globale menant sa vie de son côté.


Je commence par toi car la réponse est très simple: Tu ne règles rien car le problème se pose au moment du stockage dans la variable locale. Et le risque d'apparition du bug est le même.

Idem pour Keuronde. Comme tu le dis très bien le risque peut se produire au moment de la copie.

_________________
RCVA: Robot Concept Ville d'Avray
http://www.rcva.fr


Top
 Profile  
 
 Post subject: Re: Risque atomic
PostPosted: Fri 17 Feb 2012, 16:07 
Offline

Joined: Thu 06 Sep 2007, 10:39
Posts: 103
Location: retourne toi je suis là
C'est vrai, la question à le problème à le mérite d'être posé.
Dans ce cas il faut traiter les variables de plus de 8 bits en plusieurs variables de 8 bits par exemple ?
ou inhiber les interruptions au moment où on utilise la variable ? mais ça ne me parait pas "sain" comme fonctionnement.

_________________
L'asservissement c'est simple comme un PI


Top
 Profile  
 
 Post subject: Re: Risque atomic
PostPosted: Fri 17 Feb 2012, 16:13 
Offline
User avatar

Joined: Tue 24 Mar 2009, 11:58
Posts: 249
J'aurai deux pistes possibles à proposer :

- si applicable à votre cas : http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html
- sinon, à la main : sauvegarder le registre de status (celui dans lequel se trouve le bit Global Interrupt Enable), désactiver les interruptions au niveau le plus haut, copier la valeur dans une variable locale (ou faire le traitement si c'est super court, et que ça a du sens de faire ça plutot que juste récup la valeur), restaurer le registre de status. Ce qu'on retrouve dans le code d'Aversive par exemple : http://cvsweb.droids-corp.org/cgi-bin/viewvc.cgi/cvs/aversive/include/aversive/irq_lock.h?view=markup .

_________________
Equipe XD
2011: Prix de la Créativité (FR), Design Award (Eurobot)
2010: Prix de la Créativité (FR)


Top
 Profile  
 
 Post subject: Re: Risque atomic
PostPosted: Fri 17 Feb 2012, 16:26 
Offline

Joined: Mon 25 Jan 2010, 22:48
Posts: 467
Le lien que j'ai trouvé donne deux solutions :
1) Désactiver les interruptions (ou au moins celles qui modifient la variable)
2) Faire deux lectures successives de la variable (cela semble applicable facilement pour les timers en ne lisant que les mots de poids faible)

Comment fait RCVA ?

@xevel : la solution de désactiver les interruptions peut être contraignante quand on a besoin de rester synchroniser...

_________________
Équipe Poivron
Coupe 2014 : Équipe Poivron (67e)
Coupe 2012 : Équipe Poivron (73e)
Coupe 2011 : Équipe Poivron (122e) - Prix des équipes !
Coupe 2010 : Équipe Poivron (78e)


Passez voir le Portail des équipes


Top
 Profile  
 
 Post subject: Re: Risque atomic
PostPosted: Fri 17 Feb 2012, 16:33 
Offline
PMI
User avatar

Joined: Sun 04 Dec 2005, 13:32
Posts: 2021
Location: Grenoble
Désactiver les interruptions le temps de copier la valeur dans une variable locale n'est pas gênant en général.

Sinon il y a la solution de faire plusieurs lectures, et c'est parfois la seule possible : pas tant dans le cas où on doit se proteger d'un handleur d'interruption, mais plutôt quand on veut lire un compteur 16 bits matériel non synchronisé sur un micro 8 bits. Dans ce cas lire le poids fort, puis le faible, et verifier que le fort n'a pas changé, sinon recommencer.

_________________
Jeune vieux croulant - Moi - I-Grebot - (nos vidéos)


Top
 Profile  
 
 Post subject: Re: Risque atomic
PostPosted: Fri 17 Feb 2012, 17:54 
Offline
PMI
User avatar

Joined: Tue 14 Jun 2005, 17:16
Posts: 933
Location: Ville d'Avray
Keuronde wrote:
Expliqué comme ca, le problème à l'air presque évident.

Etant un fervant utilisateur de pic 8 bits, je suis très intéressé par
1) la solution pour éviter ce genre de problème, (desactiver les interruptions au moment du test ne me semble pas idéal)


D'accord avec toi. désactiver les interruptions signifie que la période d'échantillonnage n'est plus constante avec des conséquences négatives sur la qualité de l'asservissement et surtout de l'odométrie.

Keuronde wrote:
2) les conditions qui vous ont permis d'arriver à la conclusion que c'était bien ce problème que vous rencontriez. Les cas ou le problème peut apparaître me semble plutôt rare... (suffisament frequent pour se produire, mais pas assez pour avoir les données pour trouver le problème). Aviez vous une boucle while très courte ? Le bug était devenu systématique ? Ou aviez vous un système de debug de branché au moment du bug ?
merci encore pour le partage de votre savoir


Ça durait depuis plusieurs années et avec des compilateurs différents et des micro-processeurs différents (AT89C51 RD2 puis ATMega 128).
Le phénomène se manifestait à des fréquences très variables depuis 1 fois par journée de programmation jusqu'à 4 fois par jour. En 2010, Il pouvait prendre la forme de ce qu'on avait appelé "la rotation lente " et en 2011 "la rotation violente" (La rotation s'effectuait bien de l'angle désiré mais à une vitesse incontrôlée). Assez souvent pendant les phases de mise au point le programme semblait sauter une fonction du genre "stop_pendant (long duree)" ou autre et il suffisait de déplacer le code pour que tout redevienne normal. Ces bugs étaient très perturbants car nous avions tout essayé pour cerner le problème (Je passe sur les grands classiques des suspicions réciproques entre mécaniciens et informaticiens sans oublier les accusations infondées envers le compilateur).
Depuis 2 années, nous avions a peu près cerné les causes du mal en pensant à l'asynchronisme entre la tache d'interruption et les autres mais notre solution ne faisait que reculer le problème.
Pour 2012, on s'est mobilisé une fois pour toutes sur le problème.
Pour démarrer les tests on a commencé par le grand classique de ce qu'on appelle familièrement le "chenillard" et qui consiste à faire exécuter une rotation circulaire de l'état éclairé d'1 diode parmi 8 au rythme de la seconde. Ce programme ne demande qu'une dizaine de ligne de code et permet de tester le bon fonctionnement du chargeur de programme, du timer de 10 ms, des diodes diagnostiques. C’est l’occasion chaque année en cas de succès de faire péter le premier bouchon. Pour la 1ere fois on a eu la chance d’observer le bon fonctionnement sur une durée plus longue. Bien nous en a pris car le bug est apparu après quelques dizaines de secondes de fonctionnement (plusieurs tours de chenillard) et ceci d’une manière systématique. Il se manifestait par le fait que le décalage circulaire sautait parfois une diode qui ne s’allumait que quelques dixièmes de seconde au lieu de la seconde attendue.

Nous tenions l’explication donc la solution au problème. Pour fabriquer la tempo de 1s notre programme utilisait la fonction attente_pendant(100) qui temporisait pendant 100 fois la période d’échantillonnage de 10 ms. Et les 100 fois étaient comptabilisées par une variable globale « horloge » incrémentée dans le prog d’interruption.


long volatile horloge ; // variable gobale

void chenillard (void)
{unsigned char diode;
horloge=0;
diode=1;
while(1)
{ diag=diode; // sortie sur diodes diagnostiques
diode=diode<<1;
if(diode==0)
diode=1;
attente_pendant(100);
}
}


void attente_pendant(long duree)
{long sauve_h;
sauve_h=horloge;
while(horloge< (sauve_h+duree));
}


void interruption_timer1(void)
{
horloge++ ;
}


Le bug se produit pendant la fonction void attente_pendant(long duree), la variable horloge est récupérée d’une manière non atomic et quand l’interruption survient pendant sa lecture, c’est le BUG.

La solution n’est pas d’interdire l’interruption pendant la lecture des variables globales car dans notre programme l’interruption actualise une trop grande quantité de variables globales et l’interruption serait inhibée trop souvent.
A titre d’exemple je vous présente quelques variables globales actualisées par l’interruption et qui sont utilisées par les fonctions stratégiques

struct type_position_robot
{ long volatile consigne_orientation;
long volatile consigne_distance;
// int volatile integral_lin;
int volatile consigne_vit_lin;
int volatile consigne_vit_rot;
int volatile vit_lin_desiree;
int volatile vit_lin;
int volatile acc_lin;
int volatile vit_rot_desiree;
int volatile acc_rot;
long volatile x; // en unite capteur
long volatile y; // en unite capteur
float volatile x_float;
float volatile y_float;
long volatile orientation_brute;
long volatile orientation_brute_moins1;
long volatile orientation;
long volatile orientation_initiale; // en unite capteur
long volatile distance_32; // en unite capteur
int volatile seuil_rot,seuil_lin;
volatile unsigned char type_asserv;
} ;

La solution qui nous donne entière satisfaction et qui est appliquée pour 2012 est la suivante :

On pratique le temps partagé c'est-à-dire qu'on synchronise toutes nos fonctions avec la fonction d’interruption.
L’idée consiste à n'exécuter nos fonctions que pendant la pose entre 2 interruptions successives.
Par exemple l’interruption s’exécute toutes les 10 ms et dure 4 ms. Toutes les fonctions autre que l’interruption s’exécutent pendant la pose de 10-4 = 6ms.

Ça donne ;

unsigned char volatile interruption_en_court ;

void prog_interruption (void)
{
interruption_en_court =1 ;
saisie_environnement() ;
asservissemen() ;
odométrie() ;

interruption_en_court =0 ;
}


void synchro (void)
{interruption_en_cours=1;
while( interruption_en_cours==1);
}



void attente_pendant(long duree)
{long sauve_h;
synchro();
sauve_h=horloge;
while(horloge< (sauve_h+duree))
synchro() ;
}

Autre exemple :

char tout_droit_sans_freinage(float d_max,float vit_max,char controle_US,char sens)
{
synchro();
… // initialisations
while(1)
{synchro();
//synchro_sur_fin_interrupt(); /

if((labsolu(ROBOT.distance_32-sauve_d)) >= distance_max)
return 0;
}

}


A remarquer que cette manière de procéder nous permet de régler un autre problème, la réentrance.
Une fonction utilisée par l'interruption et en dehors est dite réentrante et en général les compilateurs de micro-controleurs gèrent assez mal la chose.
Le fait que les fonctions autre que l'interruption ne s'exécute que pendant la pose supprime la réentrance.

_________________
RCVA: Robot Concept Ville d'Avray
http://www.rcva.fr


Last edited by GARGAMEL on Fri 17 Feb 2012, 18:15, edited 1 time in total.

Top
 Profile  
 
 Post subject: Re: Risque atomic
PostPosted: Fri 17 Feb 2012, 18:13 
Offline
PMI
User avatar

Joined: Sun 04 Dec 2005, 13:32
Posts: 2021
Location: Grenoble
Si vous ne voulez vraiment pas masquer les interruptions, une autre possibilité est celle-ci : avoir une seconde variable partagée entre l'interruption et le code principal, et qui ne sert qu'à communiquer entre ces deux bouts de code.

Avant de lire cette variable, le code principal met un flag (une troisieme variable) à 1. Il devient "proprietaire" de la copie commune. Le code d'interruption, lui, ne met à jour la variable partagée que si elle n'est pas en train d'être lue.

Interruption:
valeur_variable++
si (flag == 0) alors copie_commune = valeur_variable

Code principal:
flag = 1
ma_copie = copie_commune (ou bien travailler directement avec copie_commune)
flag = 0


Evidemment ça ne peut pas toujours être utilisé. Surtout si votre code principal doit être précis à un tick d'horloge près.

_________________
Jeune vieux croulant - Moi - I-Grebot - (nos vidéos)


Top
 Profile  
 
 Post subject: Re: Risque atomic
PostPosted: Fri 17 Feb 2012, 18:46 
Offline
PMI

Joined: Wed 07 Jun 2006, 10:48
Posts: 832
Location: Grenoble
Avant d'avoir l'explication de gargamel, j'aurai proposer ceci:
Dans le programme principal, tu fais une acquisition du temps partagé et tu test si tu as le temps de toucher à la variable globale (ie si l'interruption va péter), si tu n'as pas le temps, tu boucle sur le test.

La synchronisation que vous proposez est assez élégante et me rappel un peu la phylosophie de programmation des process dans les FPGA.
Merci pour le partage!! :wink:

_________________
Président de l'association de robotique I-Grebot de Grenoble
http://www.igrebot.fr | Wiki | Forum| Vidéos


Top
 Profile  
 
 Post subject: Re: Risque atomic
PostPosted: Fri 17 Feb 2012, 19:25 
Offline
User avatar

Joined: Tue 31 Aug 2010, 18:35
Posts: 136
Location: Berkeley, CA
Une autre solution pas trop mal et d'utiliser deux variables et un pointeur.

On fait pointer le pointeur vers l'une des deux variables. L'interruption modifie la seconde variable, et une fois que c'est fait modifie le pointeur sur la seconde variable.

Le code principal lit uniquement la variable indiquée par le pointeur.

Ca ressemble à ça:

int32 v1,v2;
int32* p;

interruption:
if (p == &v1)
{
v2 = nouvelle_valeur;
p = &v2;
}
else
{
v1 = nouvelle_valeur;
p = &v1;
}

code principal:

valeur = *p;


vu que le pointeur n'est modifié qu'une fois que les données sont valides, et que la lecture d'un pointeur est atomique tout se passe bien. Ca à l'avantage de pouvoir facilement être étendu à n'importe quel type de données (tableaux, etc etc).

_________________
Team Coffee-Machine: http://www.coffee-machine.fr


Top
 Profile  
 
 Post subject: Re: Risque atomic
PostPosted: Fri 17 Feb 2012, 19:33 
Offline

Joined: Mon 25 Jan 2010, 22:48
Posts: 467
N'as tu pas un problème lors de la lecture du pointeur si sa valeur change à ce moment ?
Je connais pas trop ce domaine, mais il me semble que sur un pic 8bits, on a plus de 256 variables accessibles.

Merci énormément à Gargamel et à son équipe pour nous avoir montré le problème, proposé des solutions (et avoir réanimé le forum) !

_________________
Équipe Poivron
Coupe 2014 : Équipe Poivron (67e)
Coupe 2012 : Équipe Poivron (73e)
Coupe 2011 : Équipe Poivron (122e) - Prix des équipes !
Coupe 2010 : Équipe Poivron (78e)


Passez voir le Portail des équipes


Top
 Profile  
 
 Post subject: Re: Risque atomic
PostPosted: Fri 17 Feb 2012, 19:39 
Offline
User avatar

Joined: Tue 31 Aug 2010, 18:35
Posts: 136
Location: Berkeley, CA
Arf merde, c'est vrai que sur un PIC un pointeur fait 2 bytes.

Bin la même chose mais avec un flag sur 1 seul octet, et on vise l'une ou l'autre des variables selon le flag. Du coup c'est assez proche de la solution de simon, mais on peut lire la variable quoi qu'il arrive.

Avec une petite struct et deux fonctions d'accès (inline tant qu'à faire) ca peut devenir quasi transparent.

_________________
Team Coffee-Machine: http://www.coffee-machine.fr


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 40 posts ]  Go to page 1, 2, 3  Next

All times are UTC + 1 hour [ DST ]


Who is online

Users browsing this forum: Bing [Bot], Google [Bot] and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You can post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group