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 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.
|