Asservissement-Positionnement

Code sur le DSP


Présentation
DSP TMS320VC31
Carte d'évaluation TI DSP Starter Kit
Carte d'Extension - Asservissement Positionnement
Soft

Usine à gaz - Utilisation
Usine à gaz - Historique


Introduction

Ici, il s'agit de voir le code sur le DSP. Ce qui est en fait le plus important, car celui qui est utilisé sur le robot au final. Il serait impossible de détailler complètement ce code, même si sa taille se limite à 3700 lignes environ. Il a été tenu un peu plus organisé que l'usine à gaz. Il a connu autant d'évolution. Le code n'est peut être pas forcément lisible, mais garde une structure à peu près cohérente.

Documentations

Le code en lui même, les différentes doc matérielles, hard, etc


Aperçu

Le code se compose de plusieurs fichiers et ne dépend que des fichiers présents dans son répertoire. Ce code est en language C TI.

Il est disponible sur le CD dans : Asservissement-balisees\asser_dsp_new\Asser_DSP

Il se compose de 7 fichiers C :

  • main.c - fichier principal
  • lm629.c - envoi des ordres aux LM629
  • math.c - quelques fonctions mathématiques refaites
  • balise.c - calcul de la position de l'adversaire graçe aux balises (calcul)
  • clothoid.c - trajectoires courbes (calcul)
  • ligne.c - recalage de ligne (calcul)
  • uart.c - gestion de l'uart

Un fichier assembleur helper.asm qui contient tout ce qui touche aux interruptions

9 fichiers H

  • balise.h - données spécifiques aux balises + structures servant au debug
  • clothoid.h - données spécifiques aux trajectoires courbes + structures servant au debug
  • ligne.h - données spécifiques aurecalage de ligne + structures servant au debug
  • dbg.h - emplacement en mémoire des données de debug
  • hard.h - adresse des périphériques, IT
  • lm629.h - constantes du système mécanique, états des différentes machines à état internes
  • math.h - macros mathématiques et changement de quelques fonctions de math
  • robot.h - constantes communes à toutes les cartes (communications)
  • uart.h - gestion de l'uart

Les fichiers balise.h, clothoid.h, ligne.h, dbg.h sont inclus aussi dans l'usine à gaz, ce qui permet d'avoir la même définition des structures servant au debug.


Synoptique - organisation

Le programme fonctionne sur un DSP. Il n'y a pas de système d'exploitation, ni rien d'autre. Le programme gère complètement le DSP. Le Kernel fonctionnant en arrière plan, n'intervient pas. Il n'agit uniquement que lorsqu'on l'interroge via la com PC<->DSP. Le Kernel ne demande rien et ne fournit rien au programme qui tourne à côté.

Le programme comporte

  • Une initialisation
  • Une boucle infinie, effectuant uniquement quelques traitements pour le recalage de ligne lorsque des données sont présentes.
  • La gestion des IT

Synoptique simple - organisation

Il peut donc à n'importe quel moment tomber une des 3 IT. La boucle principale est interrompue et le code de l'IT est exécuté.

Synoptique - répartition

Il peut être intéressant de revenir au but de la carte ..

Plusieurs opérations seront nécessaires :

  • Maintenir la position à jour et fournir ces informations : réalisé par l'IT timer
  • Recevoir des ordres : réalisé par l'IT PIC
  • Effectuer les ordres, gérer les problèmes (collisions,etc) : IT Timer
  • Recevoir des infos de lignes ou autres capteurs (moustaches) : IT Externe

 

Synoptique - gestion des resources

La carte peut communiquer avec plusieurs composants. Pour la pluspart, ces communications ne doivent pas être interrompues :

  • Communication avec les LM : lecture de l'état
  • Communication avec les LM : envoi de l'ordre
  • Communication avec le PIC : envoi de l'état de la carte vers le PIC (le PIC pourra alors fournir à tout moment cet état sur le bus I2C
  • Communication avec le PIC : lecture de la commande

Fonctionnement interne

Le code est donc découpé en IT, qui se chargent chacune, d'une ou plusieurs actions. Dans ces IT, il est nécessaire de tenir compte de l'accès aux différentes ressources.

Le code est ensuite architecturé autour de plusieurs machines d'état. Une machine d'état est ici un bout de code executé périodiquement (dans une boucle ou une IT appelée périodiquement, Timer par ex) qui en fonction d'une variable (état du système), va effectuer telle ou telle action. Tant que l'état ne change pas le système continue à effectuer la même action. L'état ne pourra changer que selon certaines conditions.

Les machines d'état sont condées ainsi :

switch (etat_du_systeme){

case ETAT1:
break;

case ...:
break;

case ETAT278:
break;

}

Ce bout de code n'effectue que l'action qui correspond à l'état du système.

Un autre élément important est l'utilisation de flag(drapeau), en tant qu'éléments de synchronisation.Une action ne sera effectué que si une variable a une certaine valeur.

Description des machines d'état

Ordre à envoyer aux LM629

Variable d'état : lm_ordre déclarée dans main.c
Etat à l'initialisation : ORDRE_INIT
Liste des états
déclarés dans lm629.h

Etat Action
ORDRE_RIEN ne fait rien
ORDRE_INIT permet de faire qqchose à l'initialisation (ne fait rien en fait), passe de suite à ORDRE_RIEN
ORDRE_REINIT réinitialise les LM629, puis passe à ORDRE_RIEN, réinitialise à la position de départ
ORDRE_GOTO

1ère étape de va à une position X,Y
Reset des LM, calcul de la rotation, et de la translation
Démarrage de la rotation
passe à ORDRE_GOTO1

ORDRE_GOTO1 si la trajectoire est finie sans problème moteur, passe à ORDRE_GOTO2
ORDRE_GOTO2 effectue la translation, passe à ORDRE_GOTO3
ORDRE_GOTO3 si la trajectoire est finie sans problème moteur, passe à ORDRE_RIEN
ORDRE_GOTO_REVERSE Reset des LM, démarrage de la translation en arrière
ORDRE_GOTO_REVERSE_END si la trajectoire est finie sans problème moteur, passe à ORDRE_RIEN
ORDRE_SET_FILTRE change le filtre de l'asservissement, passe à ORDRE_RIEN
ORDRE_STOP arrête le déplacement, passe à ORDRE_RIEN
ORDRE_GOTO_PANIER 1ère étape de va à une position X,Y
Reset des LM, calcul de la rotation, et de la translation
Démarrage de la rotation
passe à ORDRE_GOTO_PANIER1
ORDRE_GOTO_PANIER1 si la trajectoire est finie sans problème moteur, passe à ORDRE_GOTO_PANIER2
ORDRE_GOTO_PANIER2 effectue la translation, passe à ORDRE_GOTO_PANIER3
ORDRE_GOTO_PANIER3

si la trajectoire est finie sans problème moteur, passe à ORDRE_RIEN
si le capteur droit(moustache) est touché, passe à ORDRE_GOTO_PANIER_STOP_DROIT1
si le capteur gauche est touché, passe à ORDRE_GOTO_PANIER_STOP_GAUCHE1

ORDRE_GOTO_PANIER4 si la trajectoire est finie sans problème moteur, passe à ORDRE_RIEN
si le capteur droit(moustache) est touché, passe à ORDRE_GOTO_PANIER_STOP_DROIT2
si le capteur gauche(moustache) est touché, passe à ORDRE_GOTO_PANIER_STOP_GAUCHE2
ORDRE_GOTO_PANIER_STOP_GAUCHE1 stoppe le moteur gauche, passe en ORDRE_GOTO_PANIER4
si le capteur droit est touché, passe à ORDRE_GOTO_PANIER_STOP_TOUT
ORDRE_GOTO_PANIER_STOP_GAUCHE2 stoppe le moteur gauche, passe en ORDRE_RIEN
ORDRE_GOTO_PANIER_STOP_DROIT1 stoppe le moteur droit, passe en ORDRE_GOTO_PANIER4
si le capteur gauche est touché, passe à ORDRE_GOTO_PANIER_STOP_TOUT
ORDRE_GOTO_PANIER_STOP_DROIT2 stoppe le moteur droit, passe en ORDRE_RIEN
ORDRE_GOTO_PANIER_STOP_TOUT arrête tout, passe en ORDRE_RIEN
ORDRE_ROTATION_RAW lance une rotation de x degrés, passe en ORDRE_GOTO_REVERSE_END
ORDRE_TRANSLATION_RAW lance une translation de x mm, passe en ORDRE_GOTO3
ORDRE_GOTO_TEST 1ère étape de va à une position X,Y
Reset des LM, calcul de la rotation, et de la translation
Démarrage de la rotation
passe à ORDRE_GOTO_TEST1
ORDRE_GOTO_TEST1 si la trajectoire est finie sans problème moteur, passe à ORDRE_GOTO_TEST2
ORDRE_GOTO_TEST2 lance une trajectoire en mode Velocity, passe en ORDRE_GOTO_TEST2bis
ORDRE_GOTO_TEST2bis augmente la vitesse, si la vitesse est maximale, passe en ORDRE_GOTO_TEST3
ORDRE_GOTO_TEST3 si la distance parcourue est telle qu'il faut commencer à décélérer, passe en ORDRE_GOTO_TEST5
ORDRE_GOTO_TEST5 décélère, si la trajectoire est terminée, passe en ORDRE_STOP
ORDRE_VELOCITY_RAW démarre une trajectoire en mode velocity, passe en ORDRE_RIEN
ORDRE_GOTO_COURBE lance un 'pas' de trajectoire courbe, si pas d'enchainement de trajectoire et traj fini, passe en ORDRE_STOP
ORDRE_VELOCITYA_RAW lance une trajectoire en velocity avec info d'accélération, passe en ORDRE_RIEN


Position de l'adversaire, calcul de triangulation

Variables d'état : balise.data_pret,balise.triang_pret déclarée dans main.c
Etat à l'initialisation : data_pret=0, triang_pret=1
Liste des états
déclarés dans rien.

data_pret=0 : aucune donnée prête à être calculée
data_pret=1 : données prêtes à être calculées

triang_pret=0 : donnée calculées prêtes à être envoyées au PIC
triang_pret=1 : aucune donnée calculée prête à être envoyée au PIC

(oui, bon là, devait pas être tôt ce jour là encore ... et c'était pendant les vacances, avec un rythme de vie très heu hum ... simplifié). Serait bon d'y faire le ménage.

 

Pseudo-pipe-line des infos sur la position de l'adversaire

Envoi du status au PIC

send_pic_status_request : 0 , rien à faire , 1 envoyer le status

Recalage de ligne

Variable d'état : recalage_ordre déclarée dans main.c
Etat à l'initialisation : RECALAGE_RIEN
Liste des états
déclarés dans lm629.h

Etat Action
RECALAGE_RIEN aucune action
RECALAGE_LIGNE1 données de recalage de lignes prêtes à être traitées
RECALAGE_LIGNE2 recalage possible, corrections à apporter à la position actuelle

 

Ordres du PIC

lm_ordre peut aussi être modifié par l'interruption int_pic(). En fonction de l'ordre reçu par le PIC (depuis le bus I2C et/ou com HF), lm_ordre va être modifié
Les ordres sont définis dans robot.h

Ordres venu du PIC Action
GOTOXY si lm_ordre==ORDRE_RIEN, passe en ORDRE_GOTO, sinon erreur
GOTO_REVERSE si lm_ordre==ORDRE_RIEN, passe en ORDRE_GOTO_REVERSE, sinon erreur
GOTO_PANIER passe en ORDRE_GOTO_PANIER
SET_VITESSE_TRAJ change la vitesse/accélération de translation, effectif à la prochaine trajectoire
SET_VITESSE_ROT change la vitesse/accélération de rotation, effectif à la prochaine trajectoire
SET_FILTRE passe en ORDRE_SET_FILTRE
SET_FILTRE_RAW passe en ORDRE_SET_FILTRE
SET_NEW_POS ne fait strictement rien.... le positionnement considère que SA position est la meilleure (ou plutôt que l'ordre n'a jamais été implémenté)
STOP passe en ORDRE_STOP
RESET_POS passe en ORDRE_REINIT
ROTATION_RAW si - de 50 tours sur lui même, passe en ORDRE_ROTATION_RAW
TRANSLATION_RAW si - de 5m, passe en ORDRE_TRANSLATION_RAW
HF_TRIANG_DATA

reçoit des données de triangulation du PIC qui vient de la HF qui venait avant du PIC des balises,etc,etc.
Cette reception n'es effective que s'il n'y a pas déjà des données à calculer, (ou bien des données calculées à envoyer au PIC)

 


Assemblage - Code minimal

/************************************************/
main(){
/************************************************/
// Initialisation variables
//Init carte
//Init périphériques
//-*-*-*-*-*-*-*-*
//boucle principale
//-*-*-*-*-*-*-*-*
for (;;)
// recalage de ligne
}

//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
// INTERRUPTION EXTERIEURE
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

/*******************/
void int_ext(){
/*******************/
// détermine quelle IT est active ou non

switch(IT){
//=>traite it capteur droit, en fonction de lm_ordre(pour le goto panier) agit ou non
//=>traite it capteur gauche, en fonction de lm_ordre(pour le goto panier) agit ou non

//=>traite it capteur de ligne, déclenchement du recalage de ligne
}
}

//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
// INTERRUPTION PIC -- RECEPTION I2C - BALISE HF
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

/*******************/
void int_pic(){
/*******************/

// en fonction de la commande; effectue telle ou telle action
switch(buf){
case GOTOXY:
case GOTO_REVERSE:
case GOTO_PANIER:
case SET_VITESSE_TRAJ:
case SET_VITESSE_ROT:
case SET_FILTRE:
case SET_NEW_POS:
case STOP:
case RESET_POS:
case ROTATION_RAW:
case TRANSLATION_RAW:
case HF_TRIANG_DATA:
}

//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
// INTERRUPTION TIMER
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

/*******************/
void int_timer(){
/*******************/

recup_ordre_PC(); // Envoi des ordres direct à partir de la com PC<->DSP

// Communication avec les LM (lecture position)

//lecture LM
//Calcul position actuelle (X,Y,Angle)

// Calcul de la position de l'adversaire( si besoin )
// Mise à jour des infos concernant l'adversaire
if ( balise.data_pret ){
...
}

//Application de la correction du recalage de ligne

if (recalage_ordre==RECALAGE_LIGNE2){
...
}

// Timers anti-rebond

//envoie ordre aux LM (écriture ordre si nécessaire)
switch (lm_ordre){
case ORDRE_INIT:
...
}

// Communication avec le PIC -- ENVOI BUFFER I2C
if (send_pic_status_req){
...
}

// Communication avec le PIC -- envoi position
if (com_PIC++>=100){

// envoi position de notre robot
if( !balise.triang_pret)
//envoi position adversaire
}
}


Détails du code - données stockées

Plusieurs structures, variables, etc stockent en permanence l'état du système, les infos sur les commandes à effectuer, en plus de l'état des différentes machines d'état.

Etat de la carte -- donnée de STATUS

Cette donnée sera transmise sur le bus I²C et indiquera l'état de la carte. Chaque bit a une signigication particulière. L'état des différents bits est défini dans robot.h

#define PB_GRAVE 1 // Pour l'asservissement, il s'agit d'une erreur de protocole ( ex: un ordre de déplacement envoyé alors qu'un autre est déjà en train de s'exécuter)
#define BUSY_TRAJET 2 // Déplacement en train de s'effectuer
#define BUSY_GOTO_PANIER 4 // Déplacement vers le panier en train de s'effectuer
#define CHOC_PANIER 8 // Non mis à jour ..../Inutilisé
#define CHOC_BORD 16 // Non mis à jour ..../Inutilisé
#define PB_MOTEUR 32 //
Erreur d'asservissement. Vraisemblablement, choc contre quelquechose

Etat des LM629 (vu roue codeuse par roue codeuse)

Type de données : LM (structure définie dans lm629.h)
Nom de la variable :
LM lm; (définie dans main.c)

// Etat des LM
typedef struct{
// Distances
FLOT pos_d; // en mm distance que la roue codeuse droite a parcourue
FLOT pos_g; // en mm distance que la roue codeuse gauche a parcourue
// Filtre d'asservissement
int
p; // int P
int i; // int I
int d; // int D
int il; // intégration limit
// Vitesse et accélérations
FLOT
v_rot_d; // en counts per sample
FLOT v_traj_d; // en counts per sample
FLOT a_rot_d; // en counts per sample per sample
FLOT a_traj_d; // en counts per sample per sample
FLOT v_rot_g; // en counts per sample
FLOT v_traj_g; // en counts per sample
FLOT a_rot_g; // en counts per sample per sample
FLOT a_traj_g; // en counts per sample per sample
}LM;

Etat du robot (robot en ENTIER)

Type de données : POS (structure définie dans lm629.h)
Nom de la variable :
POS pos; (définie dans main.c)

typedef struct{
// Etat actuel du robot
FLOT x; // en mm position actuelle
FLOT y; // en mm
FLOT angle; // en radian : Pensez à convertir en degrés avant d'envoyer

// Etat à atteindre
FLOT goto_x; // en mm position à atteindre
FLOT goto_y; // en mm
FLOT goto_angle;
FLOT goto_v; // en mm/s
FLOT goto_vd; // en mm/s
FLOT goto_vg; // en mm/s
FLOT goto_a; // en mm/s^²

// Recalage
FLOT x_a; // en mm point de référence pour le recalage
FLOT y_a; // en mm
FLOT x_b; // en mm position au moment de la coupure
FLOT y_b; // en mm
FLOT angle_b;
FLOT delta_x; // en mm
FLOT delta_y; // en mm
FLOT delta_angle; // en mm
}POS;

Infos sur la position de l'adversaire (balises)

Type de données : TRIANG_DATA (structure définie dans balise.h)
Nom de la variable :
TRIANG_DATA balise; (définie dans main.c)


typedef struct
{
unsigned short diff_1, diff_2; // différence de temps entre les balises
unsigned char bal_conf; // balises touchées
int x, y; // position de l'adversaire
FLOT angle, delta_x, delta_y;
int old_x, old_y;
int data_pret, triang_pret; // pour la synchronisation
} TRIANG_DATA ;

Structure de debug des trajectoires courbes (pour envoi, via la com PC<->DSP sur le PC)

Type de données : TRAJ_COURBE (structure définie dans clothoid.h)
Nom de la variable :
TRAJ_COURBE tc; (définie dans main.c)

Structure de debug du recalage de lignes (pour envoi, via la com PC<->DSP sur le PC)

Type de données : DEBUG_LIGNE (structure définie dans ligne.h)
Nom de la variable :
DEBUG_LIGNE dbg_l[500] ; (définie dans main.c)
Nombre d'éléments contenu dans le tableau : nb_dbg_l

Structure de debug de l'asservissement (pour envoi, via la com PC<->DSP sur le PC)

Type de données : DEBUG_ASSER (structure définie dans ligne.h)(Mmm ..logique quoi.)
Nom de la variable :
DEBUG_ASSER dbg_lm[DEBUG_LM_SIZE]; (définie dans main.c)
Nombre d'éléments contenu dans le tableau : nb_dbg_lm

debug des ordres I2C (log)(pour envoi, via la com PC<->DSP sur le PC)

Type de données : int
Nom de la variable :
int message_i2c[MAX_MESSAGE_I2C]; (définie dans main.c)
Nombre d'éléments contenu dans le tableau : index_message

 

Détails du code - Initialisation

L'initialisation est dans main() (main.c). C'est le premier morceau de code exécuté au démarrage.

  • Initialisation de toutes les variables d'état, de toutes les variables compteur,etc,etc
  • init() (main.c)
    • apelle dsk_init() (helper.asm) qui ne fait rien.
    • configure les paramètres des accès mémoire externes (3 wait states)
  • init_pos_adv() (balise.c)
    • précalcule des constantes nécessaires à la triangulation
    • initialise ses données, en particulier balise.data_pret, balise.triang_pret
  • LM_init() (lm629.c)
    • initialise les LM et les données des structures propres aux LM
  • Attend que le PIC démarre. Le PIC met un certain temps avant d'avoir réalisé sa phase d'initialisation et être prêt à recevoir des données. Il est nécessaire d'attendre, sinon aucune com n'est possible avec le PIC, donc aucun interêt de continuer. délai d'attente : for (i=0;i<DELAI_START_PIC;i++);
    Le délai est 'sur-estimé' ... pour ne pas avoir de problèmes.
  • Envoi d'un ensemble cohérent de données au PIC (status, position adversaire, etc )
  • Initialisation timer init_timer(); (main.c), fréquence 1kHz
  • Activation des IT sur la carte *EN_IT=0; // sur la carte (physiquement)
  • Activation des IT dans le DSP (timer, IT exterieure, Pic + IT Globale)
  • Une fois l'initialisation terminée, le code passe dans la boucle principale (qui ne contient qu'un petit morceau du recalage de lignes), et le DSP se fait interrompre par les différentes interruptions.

Différentes IT (niveau d'IT) activées

Détails du code - Interruptions

Les interruptions consistent en 2 éléments :

  • La routine d'IT
  • L'activation de l'interruption, c'est à dire qu'un événement entraine effectivement une interruption (réalisé juste au dessus, cf schéma).

Lorsqu'une IT tombe, le DSP, dans le mode où il se situe (microcomputer), commence par lire l'@ du vecteur d'interruption correspondant dans sa ROM. Ce vecteur pointe vers une adresse de la RAM (cf mapping IT). Ces adresses de la RAM ont été réservées et nommés (IVECT1 ,IVECT2, IVECT3) lors du mapping mémoire de la carte. On a également précisé dans le mapping qu'il fallait charger dans la section .servect1 (venue de helper.obj) dans IVECT1 (helper.obj(.servec1)), etc, etc

A ces endroits, on va 'jumper' vers les routines d'IT pour chaque interruption. Chose faite dans helper.asm (il serait possible de le faire directement en C, via inclusion d'instructions assembleur directement. Ici, on fait ça à côté dans un fichier assembleur).

;-----------------------------------------------------------------
; Table des vecteurs d'interruptions XINT0 &RINT0
;-----------------------------------------------------------------
.sect ".servec1" ;on indique qu'on souhaite mettre ce code dans la section .servec1
b INT1 ; INT1 ; interruption PIC ; le code mis est un branchement vers la routine INT1
.sect ".servec2" ; idem IT 2, etc
b INT3 ; INT3 ; interruption extérieure
.sect ".servec3"
b TIMER0 ; Timer 0
;*****************************************************************

Seulement il s'est avéré qu'un jump vers la RAM externe à partir d'ici ne semblait pas marcher (???) pourquoi ? (il n'y a pas de raison spéciallement, sinon qu'ici b INT1 indique peut-être un branchement court qui ne peut pas dépasser une certaine taille).
Pour pouvoir utiliser des routines d'IT en mémoire externe, on jump à partir d'ici vers 3 routines qui vont appeler les routines d'IT :

Update: Après un certain nombre de recherches, il s'avère en effet que c'est bien parce qu'il s'agit d'un saut court que ça ne fonctionne pas en appelant directement la routine d'IT (b_int_ext). L'instruction 'b' est une instruction de branchement conditionnelle, toujours vraie (en d'autre terme la condition est toujours vérifie le branchement aura toujours lieu). Sauf que cette instruction utilise une @ relative à la position où l'on se trouve dans le code. Cette adresse est codée sur 16 bits. On ne peut donc atteindre que 64kMot. Ici, l'addresse que l'on souhaite atteindre (_int_ext) est en RAM externe qui est utilisée à partir de l'@ 0x900000, donc largement plus loin que 64Kmot de l'@ de la routine d'IT. La solution serait d'effectuer un 'BR'(qui utilise une @ absolue sur 24 bits et pourra atteindre sans problème la routine d'IT) à la place d'un 'B'. Ainsi en utilisant BR _int_ext, on peut directement appeler la routine en C (qu'il faudrait déclarer en c_int01 par ex, ce qui donnerait BR_c_int01, pour que le compilo effectue la sauvegarde des registre dans cette routine). On enlève ainsi toute la partie 'stub' peu élégante ...


.sect ".it_wra" ; on se met ici dans la section .it_wra, mémoire interne dans le mapping
;---
INT3 ;capteur extérieur
;---
PUSHALL ; on sauvegarde tous les registres
call _int_ext; on appelle la routine d'IT
POPALL ; on restaure tout
reti ; on fait un retour d'interruption

;---
INT1
;---
PUSHALL
call _int_pic;
POPALL
reti

;---
TIMER0
;---
PUSHALL
call _int_timer;
POPALL
reti

les 3 routines _int_ext, _int_pic, _int_timer sont définies cette fois en C, en mémoire externe. Il faut indiquer à l'assembleur qu'elles sont 'externes' au fichier .

.global _int_ext ;routine d'interruption externe (appelée lorsqu'une IT de capteur ligne et choc est activée)
.global _int_pic ;routine d'interruption externe (appelée lorsque le PIC s'annonce)
.global _int_timer ;routine d'interruption externe (appelée lorsque le timer s'annonce)

En C ces routines sont définies ainsi :

void int_ext();

Lorsque le compilateur compile le C, ce nom devient _int_ext(), c'est le compilo qui rajoute le '_'. Quand on apelle la routine de l'assembleur, il ne faut donc pas oublier le '_'. Avant d'appeler la routine, on sauvegarde tous les registres à la main. En théorie, il est possible de donner un nom spécial à la fonction de l'IT en C, pour qu'il s'en charge lui même. Mais apparemment là aussi résultats bizarres. Il est à noter qu'ici 90% du code est en IT, si 100% du code est en IT, la sauvegarde n'est plus indispensable (la boucle principale est vide, il n'y a pas de risque de 'casser' ce qu'elle est en train de faire lorsque l'on passe en IT).

Déroulement d'une Interruption

L'activation des IT est faite à l'initialisation. Il y'a plusieurs activations (IT par IT dans le DSP et 'globale'). L'activation IT par IT est réalisée en positionnant les bits d'un registre du DSP (IE) à 1. Ceci est fait en assembleur. Ces routines sont appelées à partir du C.

;-------
_en_it_exterieur
;-------

push R3
ldi IE,R3
or MASK_IT_EXT,R3
ldi R3,IE
pop R3
rets
;-------
_dis_it_exterieur
;-------

push R3
ldi IE,R3
and (~MASK_IT_EXT),R3
ldi R3,IE
pop R3
rets

La routine _en_it_exterieur, active l'IT extérieure dans le DSP. Pour pouvoir l'appeler du C, il est nécessaire de commencer son nom par '_' (le C quand il va chercher une fonction externe, la cherchera forcément le nom du C + '_' au début. On doit également indiquer à l'assembleur que cette fonction est 'exportée'

.global _en_it_exterieur ;active l'interruption exterieure

et en C, que cette fonction est extérieure au fichier :

extern void en_it_exterieur(void);

Il en est de même pour les autres IT. Pour l'activation 'globale' des IT, le mécanisme est identique sauf qu'il s'agit du registre 'GIE'

Détails du code - Routine Interruption Externe

Le traitement de l'interruption externe(int_ext() - main.c) nécessite une attention particulière. On doit prendre en compte les spécificités du hard (acquittement,etc). Et ne pas oublier que l'on est sur un DSP, donc des temps de traitement très rapides.

Il faut acquitter l'interruption, ce qui nécessite de lire l'état des 4 bits d'interruption et de les récrire à la même adresse (pour faire taire le comparateur). Mais il faut en plus savoir quelle est la ou les entrées responsable(s) de l'interruption.
Une interruption est déclenchée à chaque fois que les 4 entrées du comparateur (4 entrées interruptions) sont différentes des 4 sorties situées à la même adresse, donc à chaque fois qu'une des entrées passe de 1 à 0 ou de 0 à 1.

On a une IT à chaque front montant et chaque front descendant. Pour connaitre l'entrée responsable, on compare l'état des 4 entrées, avec l'état des 4 sorties pilotant le comparateur. Seulement l'état des 4 sorties pilotant le comparateur, on ne peut QUE les écrire ... il faut donc les mémoriser. C'est donc fait dans old_state_int_ext.

Cette variable doit être initialisée correctement au démarrage. Pour l'initialiser, on suppose qu'il n'y a pas encore d'IT, donc que le comparateur est silencieux, donc que les 4 sorties sont à la même valeur que les 4 entrées. On peut donc récupérer l'état des 4 sorties en lisant la valeur des 4 entrées (ceci n'est vrai QUE lorsqu'il n'y a pas d'IT ... donc FAUX dans la routine de l'interruption)

dans main() - main.c

old_state_int_ext=*EXT_DATA; //Initialisation de l'état des IT

 

On peut ensuite en comparant l'état des 4 sorties (via old_state_int) et des 4 entrées, trouver la ou les entrées coupables ... Ceci est fait grâce à un XOR (^). Seuls les bits différents seront mis à 1.

masque_it_active=((*EXT_DATA)^old_state_int_ext)&0xFF;

On acquitte l'interruption par :

data=*EXT_DATA;
*EXT_DATA=data; // acquittement de l'interruption

A ce moment les 4 bits de sortie prennent la valeur des 4 bits d'entrée (les 4 bits de poids fort itoo. Ils ne sont pas utilisés ici. Si ils étaient utilisés, il faudrait les mémoriser, puis les ressortir ici) le comparateur repasse alors à 0 et l'interruption est acquitée.
On n'oublie pas de mémoriser ce qu'on vient d'envoyer sur les 4 sorties pour pouvoir s'en rappeler au prochain coup :

old_state_int_ext=data;

A partir de ce moment, on a 2 infos :

  • L'état physique des entrées (0 ou 1) dans data
  • Les entrées qui ont changées ont un 1 dans masque_it_active
Bit 3
Bit 2
Bit 1
Bit 0
data
Etat entrée 4
Etat entrée 3
Etat entrée 2
Etat capteur de ligne
masque_it_active
Entrée 4 cause de l'IT
Entrée 3 cause de l'IT
Entrée 2 cause de l'IT
Capteur de ligne cause de l'IT

On traite ensuite les différents cas possibles. On peut rentrer en IT à cause de plusieurs entrées à la fois (si elles ont réussi à changer avant que le DSP ne réponde). Il faut donc bien vérifier pour chaque entrée qu'elle n'aurait pas été responsable de l'IT.

Exemple pour le capteur de ligne :

if ((masque_it_active & EXT_INT_MASQUE_LIGNE) && !anti_rebond_ligne){

Si il y'a eu une IT à cause du capteur de ligne... Elle peut avoir eu lieu, parce qu'il y'a eu un front montant ou bien un front descendant. Ici c'est le front montant qui nous intéresse. On ne continue donc que si il s'agit d'un front montant, donc que l'entrée est actuellement à 1 (à 1 après le front quoi, donc front montant)
// Capteur Ligne
if ((data & EXT_INT_MASQUE_LIGNE)){ // entrée sur une ligne

...
}


anti_rebond_ligne=40; // *10ms
}

La rapidité du traitement fait aussi qu'il faut gérer un anti-rebond sur les différents capteurs. Les capteurs mécaniques ont un rebond naturel. Pour le capteur de ligne, il peut sagir soit d'un rebond à l'intérieur du capteur, soit quelquepart sur les pistes. dans tous les cas, on crée un anti-rebond logiciel avec les variables

anti_rebond_xxxx

Ces variables sont initialisées à 0 dans main() - main.c. Quand on passe dans la detection d'une IT sur l'entrée correspondante (front montant ou descendant), on met la variable à une valeur initiale (ex ici 40). Cette valeur sera ensuite décrémentée périodiquement dans l'IT Timer. Un autre traitement de l'interruption ne pourra avoir lieu que lorsque la variable sera revenue à 0. Il y'a une ERREUR sur le commentaire. Le Timer tourne à 1KHz, donc la durée de l'anti rebond sera de 1 ms*anti_rebond_ligne.

Le fonctionnement est identique pour les 2 moustaches de l'avant du robot. Même anti_rebond,etc. Si un capteur est activé, en fonction de l'état de la machine d'état indiquant l'ordre au LM (lm_ordre), on change ou non l'ordre en cours (avec pour effet d'arrêter l'un ou l'autre des moteurs et de déterminer si on a atteint le panier ou pas).

Détails du code - Réception des données depuis le PIC

La communication avec le PIC n'est pas totalement détaillée ici, elle le sera plus loin. Dans tous les cas, lorsque le PIC a un ordre venu de l'I2C ou bien une donnée de triangulation venue de la balise adverse, il interrompt le DSP (via une de ses sorties). Cette interruption est celle qui arrive au final dans la routine d'IT int_pic() - main.c. Il est à noter que cette routine à servi par la suite aussi à l'UART.

Pour déterminer qui est la cause de l'interruption, on commence par regarder si l'IT est causée par l'UART (qui dispose d'un registre pour)

if (!(UART(2)&1)){
//*(DBG+DBG_GEN2)=123456;
int_uart();
return;
}

Si l'IT vient de l'UART, on va vers une routine d'IT pour l'UART. Sinon on continue la routine du PIC. Le PIC interrompt le DSP jusqu'à temps que celui ci soit venu le lire, de même pour l'UART. Donc dans le cas où il y'a eu en même temps IT de l'UART et du PIC, on s'occupera d'abord de l'UART, puis on sort de la routine d'IT. Le PIC n'ayant pas été lu, l'IT est toujours active, donc on va rerentrer dedans, ce coup ci l'UART ne sera plus en IT et c'est le PIC qui sera servi.

Une fois qu'on est sûr que l'IT vient du PIC :

On lit l'adresse qui correspond au PIC. Cette adresse est connectée directement sur le Parallel Slave Port(PSP) du PIC. Le PIC y a placé avant d'interrompre le DSP, le premier octet de la commande à envoyer au DSP. Le DSP va lire cette valeur. Il sera ensuite nécessaire d'attendre que :

  • le PIC se rende compte que le PSP a été lu par le DSP.
  • qu'il mette l'octet suivant sur le PSP

Cette attente est réalisée au moyen d'une boucle 'for'

#define DLY_PIC for (i=0;i<DELAI_PIC;i++)

buf=(*REG_PIC)&0xFF; // lecture
DLY_PIC; //attente

Ne pas oublier qu'on va lire 32 bits ... or seuls 8 bits sont utiles, les autres seront indéfinis donc on les remet à 0.
Le premier octet reçu représente la commande. En fonction de la commande le DSP va lire la suite des octets. Chaque commande a un certain nombre d'octets, et le DSP doit savoir en fonction de la commande, combien lire d'octets. De son côté le PIC, à chaque fois que le DSP vient lire un octet, en place un autre sur le PSP. Lorsque le DSP a lu le premier octet, il repasse l'IT à 0. On peut donc ne pas tout lire, enfin ce cas n'est pas bien géré dans le PIC, mais on devrait ..

En fonction de la commande, le DSP effectue les actions nécessaires, notamment des changements dans la machine d'état des ordres aux LM (lm_ordre)

La liste des ordres du PIC donnée plus haut est reprise ici et détaillée

Les positions sont transmises sur 16 bits (short -- données SIGNEES). Le DSP travaille sur 32 bits .... il ne faut donc pas oublier de transformer les données 16 bits signées en données 32 bits signées, ce qui est fait par la macro short_to_int , qui étend le bit de signe ...

Ordres venu du PIC/Octet 0 GOTOXY
Octet 1 8 bits poids fort de la position X de destination (en mm)
Octet 2 8 bits poids faible de la position X de destination (en mm)
Octet 3 8 bits poids fort de la position Y de destination (en mm)
Octet 4 8 bits poids faible de la position Y de destination (en mm)
Octet 5 Options // Inutilisé
Action met la position de destination pos.goto_x, pos.goto_y, passe lm_ordre en ORDRE_GOTO ( si pas déjà d'autre ordre en cours, sinon => PB_GRAVE)
Ordres venu du PIC/Octet 0 GOTO_REVERSE
Octet 1 8 bits de la distance de recul en mm
Action met la distance de recul dans pos.goto_x, passe lm_ordre en ORDRE_GOTO_REVERSE ( si pas déjà d'autre ordre en cours, sinon => PB_GRAVE)
Ordres venu du PIC/Octet 0 GOTO_PANIER
Octet 1 valeur indiquant le panier, définie dans robot.h
Action met la position finale de destination correspondant au milieu du panier indiqué par l'Octet 1, dans pos.goto_x, pos.goto_y, passe lm_ordre en ORDRE_GOTO_PANIER. L'ordre se déroule de façon identique à un ORDRE_GOTO simple, sauf que les 'moustaches' sont prises en compte et permettent de stopper chacun des moteurs.
Ordres venu du PIC/Octet 0 SET_VITESSE_TRAJ
Octet 1 vitesse pour les trajectoires droites sur 8 bits non signés (cm/s)
Octet 2 accélération pour les trajectoires droites sur 8 bits non signés (cm/s²)
Action Ces valeurs sont converties en unitées de LM et collées dans les structures d'état du lm (lm.v_traj_d,etc,etc). On tient compte ici de la différence de circonférence entre les roues codeuses..
Ordres venu du PIC/Octet 0 SET_VITESSE_ROT
Octet 1 vitesse pour les rotations sur 8 bits non signés(cm/s)
Octet 2 accélération pour les rotations sur 8 bits non signés(cm/s²)
Action Ces valeurs sont converties en unitées de LM et collées dans les structures d'état du lm (lm.v_rot_d,etc,etc). On tient compte ici de la différence de circonférence entre les roues codeuses..
Ordres venu du PIC/Octet 0 SET_FILTRE
Octet 1 paramètre de sélection d'un filtre d'asservissement / Inutilisé en pratique
Action Quelquesoit le paramètre (sic ..) initialise le filtre du PID. Des paramètres prédéfinis sont rangés dans lm.p, lm.i, lm.d, lm_ordre va passer à ORDRE_SET_FILTRE ce qui envoie ça aux LM
Ordres venu du PIC/Octet 0 SET_FILTRE_RAW
Octet 1 8 bits poids fort paramètre P (non signé)
Octet 2 8 bits poids faible paramètre P
Octet 3 8 bits poids fort paramètre I(non signé)
Octet 4 8 bits poids faible paramètre I
Octet 5 8 bits poids fort paramètre D(non signé)
Octet 6 8 bits poids faible paramètre D
Action Ces paramètres sont rangés dans lm.p, lm.i, lm.d, lm_ordre va passer à ORDRE_SET_FILTRE ce qui envoie ça aux LM
Ordres venu du PIC/Octet 0 SET_NEW_POS
Action Purement et simplement ignoré
Ordres venu du PIC/Octet 0 STOP
Action Stoppe immédiatement la trajectoire
Ordres venu du PIC/Octet 0 RESET_POS
Action Réinitialise la position. La position interne est initialisée avec la position du robot dans l'aire de démarrage(définie dans robot.h)
Ordres venu du PIC/Octet 0 ROTATION_RAW
Octet 1 8 bits poids fort angle de rotation (signé) (en degrés ?)
Octet 2 8 bits poids faible angle de rotation
Action Valeur d'angle stockées dans pos.goto_x, lm_ordre passe à ORDRE_ROTATION_RAW (si pas déjà d'ordre dans lm_ordre, sinon => PB_GRAVE)
Ordres venu du PIC/Octet 0 TRANSLATION_RAW
Octet 1 8 bits poids fort distance de translation (signé) (en mm)
Octet 2 8 bits poids faible distance de translation
Action Valeurs de distances stockées dans pos.goto_x, lm_ordre passe à ORDRE_TRANSLATION_RAW (si pas déjà d'ordre dans lm_ordre, sinon => PB_GRAVE)
Ordres venu du PIC/Octet 0 HF_TRIANG_DATA
Octet 1 8 bits poids fort différence de temps 1 (non signé) (en une certaine unité de temps ...)
Octet 2 8 bits poids faible distance de temps 1
Octet 3 8 bits poids fort différence de temps 2 (non signé) (en une certaine unité de temps ...)
Octet 4 8 bits poids faible distance de temps 2
Octet 5 Configuration des balises concernées (cf doc système de balise)
Action Cf machine d'état des balises

Une fois ces données reçues, l'IT quitte normallement.Dans le cas où l'ordre était inconnu, on attend histoire que le PIC arrête de folayer ...

Détails du code - Envoi des données au PIC

La carte d'asservissement doit pouvoir fournir n'importe quand des données sur sa position actuelle, sur l'état de la carte, la position de l'adversaire,etc. Le DSP envoie donc périodiquement et/ou à chaque changement, ces données au PIC, qui peut ainsi les envoyer sur le bus I²C lorsque l'on lui demande.

Le DSP doit donc envoyer (rafraichir) ces infos dans le PIC.

  • La position du robot est rafraichie à intervalles réguliers (10 fois par s), dans l'IT Timer
  • L'état de la carte d'asservissement (STATUS de la carte) envoyé à chaque fois que celui-ci change
  • La position de l'adversaire rafraichie lorsque l'on dispose d'une nouvelle position.
  • Un octet contenant des informations sur ce que l'on peut déduire de la position de l'adversaire

Pour envoyer une info au PIC, on commence par écrire un octet magique 0x55 sur le PSP du PIC. Le PIC le détecte, et va être prêt à récupérer ces infos. Il convient toujours d'attendre entre 2 écritures que le PIC ait le temps de réagir....

On indique ensuite l'index des données qu'on envoie(défini dans robot.h) , puis les données en elles-même. L'utilisation de l'octet 'magique', sert ici à resynchroniser le système si il y'a un problème sur la communication entre le DSP et le PIC. Il n'y aucun autre moyen de déterminer le début d'une communication.

Liste des données envoyées au PIC (pouvant les transmettre sur l'I²C n'importe quand)

Octet Magique 0x55
Index POSITION
Octet 1 8 bits poids fort position x signée (en mm)
Octet 2 8 bits poids faible position x signée
Octet 3 8 bits poids fort position y signée (en mm)
Octet 4 8 bits poids faible position y signée
Octet 5 8 bits poids fort angle , en degrés * 128, soit en virgule fixe, 1 bit de signe 8 chiffres . 7 chiffres
Octet 6 8 bits poids faible angle , en degrés * 128, soit en virgule fixe, 1 bit de signe 8 chiffres . 7 chiffres
Octet Magique 0x55
Index CHECK_BUSY
Octet 1

1 octet d'état (8 bits)
bit 0 : problème grave (commande de déplacement demandée alors qu'il y'a déjà un déplacement en cours)
bit 1 : en cours de trajet
bit 2 : en cours de trajet, vers le panier
bit 3 : NA
bit 4 : NA
bit 5 : problème de moteur, vraisemblablement, choc contre un obstacle

Octet Magique 0x55
Index POSITION_BALISE
Octet 1 8 bits poids fort position x signée de l'adversaire(en mm)
Octet 2 8 bits poids faible position x signée de l'adversaire
Octet 3 8 bits poids fort position y signée de l'adversaire(en mm)
Octet 4 8 bits poids faible position y signée de l'adversaire
Octet 5 8 bits poids fort angle de l'adversaire , en degrés * 128, soit en virgule fixe, 1 bit de signe 8 chiffres . 7 chiffres
Octet 6 8 bits poids faible angle de l'adversaire, en degrés * 128, soit en virgule fixe, 1 bit de signe 8 chiffres . 7 chiffres
Octet Magique 0x55
Index INFO_ADV
Octet 1

1 octet d'état (8 bits)
bit 0-bit 1 : indique le N° de panier
bit 2 : l'adversaire se déplace vers ce panier
bit 3 : l'adversaire est prêt de ce panier
bit 4 : l'adversaire se déplace vers notre robot ..... boom.
bit 5 : l'adversaire est prêt de notre robot

L'ensemble de ces paramètres envoyés au PIC, sont idéalement envoyés à la fin de l'interruption timer. C'est d'ailleurs pour celà qu'il existe un certain nombre de send_pic_xx_req. Ces synchros ont souvant été ajoutées pour éviter que certaines conditions fassent que *trop* d'infos soient envoyées au PIC dans certain cas ... on voit déjà ici que le PIC de l'asservissement est une bête sensible ....

Détails du code - Communication avec les LM

Les LM629 sont interfacés sur le bus du DSP. Chaque LM a une patte permettant de sélectionner soit une donnée, soit une commande. Cette patte est reliée au bit d'adresse de poids faible. On a donc 2 adresses pour chaque LM, et 2 LM en tout.

Un certain nombre de macros sont définies pour pouvoir accéder aux LM le plus simplement possible.

long *LM_PORT[2][2]; //,LM_G_CMD};
//{LM_D_DATA,LM_G_DATA} };
#define _LM_CMD (*(LM_PORT[0][nlm]))
#define _LM_DATA (*(LM_PORT[1][nlm]))

Le tableau LM_PORT est initialisé lors de l'appel de lm_init() - lm629.c effectué au démarrage (main() - main.c).
Une fois ces macros définies, en utilisants des fonctions du style :


void LM_xxxxx(int nlm){


_LM_CMD=LM629_RESET_IT;
_LM_DATA=0;
_LM_DATA=0;

}

on définit une fonction qui fonctionnera à la fois pour le LM de gauche et le LM de droite.
_LM_CMD accédera au registre de commande du LM de gauche si nlm=LM_GAUCHE, au registre de commande du LM de droite si nlm=LM_DROITE, etc
de même pour _LM_DATA.

En appelant LM_xxxxx(LM_GAUCHE); on communiquera avec le LM de gauche, en appelant LM_xxxxx(LM_DROITE), on communiquera avec le LM de droite

Un certain nombre de fonctions sont définies dans lm629.c

  • void LM_init(int nlm) - initialise un LM
  • void LM_reinit(int nlm) - recharge le filtre dans un LM
  • FLOT LM_read_pos(int nlm) - retourne la distance parcourue par ce LM depuis le dernier reset - tient compte de la position inversée physiquement d'un codeur par rapport à l'autre
  • void LM_reset_it(int nlm) - réinitialise les IT du LM (IT non connecté sur le DSP)
  • int LM_wait_ready(int nlm) - attend qu'un LM soit prêt à recevoir une commande ou une donnée, ou bien qu'il y ait une erreur,
  • void LM_start_traj(int nlm) - démarre la trajectoire précedemment chargée
  • void LM_stop_traj(int nlm) - tente d'arrêter la trajectoire
  • void LM_define_home(int nlm) - indique la position de départ
  • void LM_reset(int nlm) - redémarre un LM
  • void LM_load_filter(int nlm,int p,int i,int d,int il) - charge un filtre PID dans le LM
  • void LM_load_traj(int nlm,int accel,int vit,FLOT pos) - charge une trajectoire dans le LM (mode position)
  • void LM_load_traj_velocity(int nlm,int accel,int vit) - charge une trajectoire en mode velocity
  • void LM_load_traj_velocity2(int nlm,int vit) - même chose sans charger d'accélération
  • int poll_traj_fini() - indique si la trajectoire est finie ou bien si il y'a eu une erreur (dans le cas d'une erreur, passe lm_ordre en ORDRE_STOP)
  • + quelques autres fonctions pour le debug.

Le fonctionnement des ordres du LM est expliqué dans une autre documentation.

Le LM fonctionne en 16 bits, par contre son interface est en 8 bits. Pour lui envoyer un ordre il faut commencer par attendre que le LM soit prêt (rôle de LM_wait_ready). On peut ensuite envoyer la commande sur 8 bits. Il faut de nouveau attendre que le LM soit prêt. On peut alors envoyer 2 fois 8 bits=16bits de données (ou en lire 16 en fonction de l'ordre). Ce qui donne par ex :


/**************************/
void LM_reset_it(int nlm){
/**************************/
LM_wait_ready(nlm); // Attend que le LM soit prêt
_LM_CMD=LM629_RESET_IT; // Envoie l'ordre

LM_wait_ready(nlm); // Attend que le LM soit prêt
_LM_DATA=0; // Envoie 8 bit de données
_LM_DATA=0; // Envoie 8 bits de données
LM_wait_ready(nlm); // Attend que le LM soit prêt .. histoire de faire joli, ça ne sert à rien
// sauf à être sûr que le LM soit bien en train d'executer l'ordre lorsque l'on ressort d'ici.

}

Les données récupérées du LM sont à des formats plus ou moins différents (virgule fixe, sur 16 - 32 bits, etc), il faut faire très attention aux conversions

Détails du code - Détection des collisions, reprise d'erreur

Le status de la carte détaillé plus haut contient un certain nombre de bits renseignés :

  • problème grave (PB_GRAVE)
  • en cours de trajet (BUSY_TRAJET)
  • en cours de trajet vers le panier (BUSY_GOTO_PANIER)
  • problème asservissement (collision) (PB_MOTEUR)

Chacun des éléments correspondant à un bit du status situé dans la variable status (main.c)

Ce status est mis à jour à chaque fois que nécessaire.
BUSY_TRAJET
est passé à 1 au début d'une trajectoire, et repassé à 0 à la fin de la trajectoire. A noter que rien ne dit que la carte qui interrogera l'asservissement par l'I²C n'aura le temps de voir passer ce bit à 1 ... donc pourra attendre indéfiniment que l'asservissement démarre ...
BUSY_GOTO_PANIER est passé à 1 au début d'une trajectoire vers le panier, et repassé à 0 à la fin de la trajectoire. même remarque
A la réception de chaque ordre, PB_GRAVE et PB_MOTEUR sont remis à 0. Lorsqu'un ordre de trajectoire arrive alors que lm_ordre n'est pas à LM_ORDRE_RIEN, l'ordre n'est pas executé et PB_GRAVE est passé à 1.

Lorsque le robot est en train d'effectuer une trajectoire, il vérifie périodiquement que celle-ci n'est pas finie (en fonction de lm_ordre en fait). La routine qui fait cette vérification (poll_traj_fini() - lm629.c) assure aussi la détection des collisions, et passe directement lm_ordre à ORDRE_STOP dans le cas d'une collision. Le bit PB_MOTEUR est levé pour indiquer un problème de moteur à la carte principale.(elle en fait ensuite ce qu'elle veut). Pour pouvoir détecter d'autres problèmes identiques sur l'ordre suivant, il est nécessaire de remettre ce bit à 0.

Le fonctionnement du status n'est pas forcément terrible ... la carte principale peut 'louper' le passage en BUSY_TRAJET par ex. Le fonctionnement serait éclairci si :

  • la carte asservissement ne pouvait passer les flag qu'à 1
  • la carte principale pouvait les resetter (via commande spéciale)

Un certain nombre de problèmes ont été trouvés sur ces bits d'état notamment lors de la reprise d'erreur. (un bit qui restait à 1 ou y passait ou ,etc,etc). Ces bits d'état mis et enlevés par le DSP peuvent ne pas être vus par les autres cartes (entre le moment où ils sont mis et le moment où ils sont enlevés). Le mieux serait que :

  • Le DSP mette ces bits à 1.
  • La remise à 0 fasse suite à une commande issue d'une autre carte.


Détails du code - Debug de base

Lors du mapping mémoire, une zone de mémoire avait été réservée dans le premier bloc de la RAM interne. On avait réservé 0x26 octets qui ne sont pas utilisés par le compilateur pour y mettre des variables ou du code. Cette zone qui commence en 0809800h permet de mettre quelques variables qui vont servir au debug.(via la com PC<->DSP)

Les adresses corespondantes à ces variables sont définies dans hard.h

long *RXTX=(long*)0x0809802;
long *BUF=(long*)0x0809803;
long *DBG=(long*)0x0809804;

Les 2 premières adresses(0x809800 - 0x809801) ne sont plus utilisées. Les 2 suivantes étaient utilisées à l'origine pour 2 registres de debug.

Ensuite pour 0x809804h - 0x809826h, on utilise la notation, *(DBG+xxxx) = valeur. Les valeurs mises dans ces zones mémoire sont mises à jour dans le code régulièrement pour certaines, quand celà est nécessaire pour d'autres. Le décalage xxxx, est fourni par une constante dans le fichier dbg.h
Ce fichier sera inclus dans l'usine à gaz, permettant d'avoir la même définition des différentes cases mémoire et de récupérer le contenu de ces cases, via la com' (PC<->DSP)

Adresse
Constante
Contenu
0x809804 DBG_POSX Position x du robot (en mm ? 10ième de mm ?)
0x809805 DBG_POSY Position y du robot
0x809806 DBG_ANGLE Angle du robot (unité ?)
0x809807 DBG_DESX Position à atteindre en X ?
0x809808 DBG_DESY Position à atteindre en Y ?
0x809809 DBG_STATUS Status de la carte
0x80980A DBG_GEN1 Debug à usage général cf doc utilisation usine à gaz
0x80980B DBG_GEN2 Debug à usage général cf doc utilisation usine à gaz
0x80980C DBG_STOP ???? euh ???
0x80980D DBG_BALISEX position x de l'adversaire
0x80980E DBG_BALISEY position y de l'adversaire
0x80980F DBG_BALISEANGLE angle de la balise de l'adversaire
0x809810 DBG_INFO_CHOC Inutilisé

Dans le code de l'usine à gaz, on vient lire toute cette zone mémoire (à partir de 809804h), ce qui permet d'afficher l'état de ces variables quasiment en temps réel.

Détails du code - Debug des LM, Recalage Ligne, I2C, Trajectoires courbes

Ces 3 éléments utilisent la même technique. On définit un grand tableau de données (structure ou simples valeurs). Un maximum d'éléments et un pointeur courant dans le tableau.

  • I2c (dans main.c)
    • #define MAX_MESSAGE_I2C 2000
    • message_i2c[MAX_MESSAGE_I2C]
    • int index_message=0; //index courant
  • LM (dans main.c)
    • #define DEBUG_LM_SIZE 4000
    • DEBUG_ASSER dbg_lm[DEBUG_LM_SIZE];
    • int nb_dbg_lm=0; //index courant
    • int dbg_lm_en_cours=0; // L'enregistrement des données ne se fait que si cette variable est à 1
  • Trajectoires courbes (dans main.c)
    • TRAJ_COURBE traj_courbe; (la structure contient plusieurs tableaux contenant des données, la taille de ces tableaux, etc)
  • Recalage de ligne
    • int nb_dbg_l=0;
    • //DEBUG_LIGNE dbg_l[500];

Un certain nombre de ces éléments sont pris en compte ou non en fonction de #define associés.

//#define RECALAGE_LIGNE // définit en PLUS l'application du recalage de ligne
#define DEBUG_LM // autorise l'enregistrement des données des lm
#define DEBUG_MSG_I2C // debug des messages I2C

Tous ces tableaux de debug ne doivent pas dépasser en taille la mémoire dispo.

Ces tableaux seront ramenés par la suite via la com (PC<->DSP) et traités/visualisés dans l'usine à gaz

Détails du code - Récupération d'ordres depuis le PC

Il a été rajouté par la suite un envoi d'ordres directement à partir de la com' (PC<->DSP).

Cet envoi d'ordre marche par une zone mémoire du DSP dans laquelle le PC vient écrire via la com PC<->DSP ...
La zone utilisée est le tableau RX_BUF. Le fonctionnement est assez simple(verrou en mémoire partagée sur RX_BUF[0]).

  • Le PC attend que RX_BUF[0] soit à 0.
  • Le PC vient écrire l'ordre à exécuter dans RX_BUF[1] et ses paramètres dans RX_BUF[2], RX_BUF[3], etc
  • Une fois tout terminé, il met 1 dans RX_BUF[0]
  • Le code du DSP vérifie périodiquement (à chaque IT Timer) si RX_BUF[0] est à 1 (void recup_ordre_PC() - main.c).
  • Dans le cas où RX_BUF[0] est à 1 le DSP lit la commande et ses paramètres dans RX_BUF[1], etc
  • Il remet RX_BUF[0] à 0 et exécute la commande.

D'où la possibilité d'envoyer des ordres directement à partir de l'usine à gaz. Les ordres sont les mêmes que ceux disponibles à partir de l'interruption PIC. Quelque-uns ont été rajoutés, notamment ceux ayant trait aux trajectoires courbes.


Détails du code - UART

L'UART a été rajoutée par la suite. L'uart choisie est une UART basique (disponible en sample chez National ...). Elle dispose de 7 registres, donc de 7 adresses. Le décodage réalisé pour l'adresse de l'UART ne comporte qu'une seule adresse(chip select) à l'origine (0xC0000F).Ce chip select est généré en décodant les lignes d'adresse A1,A2,A3. Il est donc actif pour 0xC0000F mais aussi 0xC0001F, 0xC0002F,0xC0003F, etc ..
Pour pouvoir gérer les 7 adresses, on utilise donc le chip select pour l'UART, et on utilise les bits A4, A5, A6, pour fournir les 7 adresses différentes. Pour simplifier la gestion de la chose, une macro est définie :

#define UART(x) (*(long *)(ADD_BASE+0xF+((x&0x7)<<4))) // Registre UART

On accède au registre 0 de l'uart par UART(0), au registre 1 par UART(1), etc. La liste des registres est regroupée ici. Pour une désignation plus approfondie, voir la datasheet de l'UART (PC16550)

Adresse
UART(x)
Read/Write
Registre
Fonction
0xC0000F 0 Read RBR Receiver Buffer Register Octet reçu par la liaison série. (si DLAB=0)
0xC0000F 0 Write THR Transmitter Holding Register Octet à émettre par la liaison (si DLAB=0)
0xC0001F 1 Read/Write IER Interrupt Enable Register Interruptions activées
0xC0002F 2 Read IIR Interrupt Identification Register Identification de l'interruption actuellement active
0xC0002F 2 Write FCR Fifo Control Register Controle de la FIFO de l'UART
0xC0003F 3 Read/Write LCR Line Control Register Contrôle de la config du port série (nb de bits, parité, etc)
0xC0004F 4 Read/Write MCR Modem Control Register Contrôle des lignes de status (DSR, DCD, etc)
0xC0005F 5 Read/Write LSR Line Status Register Erreurs
0xC0006F 6 Read/Write MSR Modem Status Register Controle des lignes de status (DSR, DCD, etc)
0xC0007F 7 Read/Write SCR Scratch Register Registre à usage libre

 

Dans le code il existe une routine d'initialisation void UART_init(int vitesse) (dans uart.c) qui initialise les registres de l'UART. Pour traiter correctement l'UART, il faudrait réaliser la fonction d'interruption qui prend en compte les différentes interruptions qui peuvent intervenir.