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.