Asservissement-Positionnement
Fonctionnement du
PIC
Présentation
DSP TMS320VC31
Carte d'évaluation TI DSP Starter Kit
Carte d'Extension - Asservissement Positionnement
Soft
Usine à gaz - Utilisation
Usine à gaz - Historique
Introduction
Le PIC sur la carte d'asservissement devait servir comme
périphérique I2C simple. Non seulement ce fut tout
sauf simple (à mettre en oeuvre), mais en plus
ce PIC a dû gérer les données venues de la communication
HF. Son fonctionnement n'est en théorie pas possible (il
est esclave partout, c'est à dire qu'il devrait être
capable de réaliser physiquement plusieurs actions en même
temps.) Cependant en posant des contraintes de temps de traitement
sur chaque entrée, il devient possible de réaliser
la chose .... un bel exemple de temps réel. (et d'usine
à gaz)
Documentations
Datasheet du PIC, et toutes les autres doc à prendre en
compte, c'est à dire : carte d'asservissement, comHF, bus
I2C. Une horreur.
Le code du PIC est disponible dans Asservissement-balisees\i2c_asser\*.C
Synoptique du PIC
Synoptique des communications du PIC
Fonctions à réaliser
Le PIC doit pouvoir effectuer plusieurs actions séparément
et ce à n'importe quel moment :
-
Réception des données issues de
l'I²C (I2C Read)
- Envoi des données issues de l'I²C (I2C Write)
- Lecture de la part du DSP sur le PSP (Parallel Slave Port)
- Ecriture de la part du DSP
- Réception de données de la com' HF.
Sachant que plusieurs événements peuvent arriver exactement
en même temps (réception de données en HF et reception
de données sur l'I²C), le PIC ne peut pas répondre
immédiatement aux 2 événements. Il y aura donc
un certain délai entre le moment de la réception et le
traitement effectif.
D'un autre côté certains événements ne peuvent
pas se produire en même temps (lecture et écriture de la
part du DSP par exemple).
Il va donc être nécessaire de s'intéresser de plus
près au fonctionnement de chaque périphérique,
et de déterminer les délais maximum entre la
réception d'un élément et son traitement. La
chose n'a pas été faite de façon scientifique,
surtout que les éléments se sont rajoutés après
coup (les contraintes aussi). Une utilisation des choses
à l'identique (même fonctions pour ce PIC, nécessite
de remettre TOTALEMENT à plat le fonctionnement et le
code du PIC).
Fonctionnement des périphériques
Parallel Slave Port
Le Parallel Slave Port connecté au DSP, permet
au DSP de venir lire ou écrire un octet sur un registre
du PIC. C'est donc le DSP qui dirige l'opération.
Lorsque le DSP vient écrire sur ce registre, la donnée
est physiquement présente pendant un temps de cycle du DSP
(120 ns) sur les entrées du PIC. La donnée est ensuite
conservée dans un registre du PIC. De plus dès qu'il
y a un accès en écriture, le PIC est interrompu et donc
peut venir récupérer cette donnée (dans le registre)
avant qu'elle ne se fasse effacer (par l'écriture suivante).
Le temps minimum entre 2 écritures de la part du DSP sur
le PIC sera déterminé par le temps nécessaire
au PIC pour venir lire ce registre (donc le temps qu'il finisse
une autre IT par exemple puis qu'il rentre dans celle du PSP)
De même lorsque le DSP vient lire sur le PSP du PIC, il laisse
120 ns au PIC pour lui fournir une donnée. Donc la donnée
que va lire le DSP sera forcément celle présente dans
le registre de sortie du PSP (donnée qui aura été
préalablement placée ici). Le PIC sera interrompu
et lorsqu'il arrivera dans l'IT du PSP (donc bien après
que la lecture soit terminée), il pourra mettre un nouvel octet
pour la lecture suivante de la part du DSP. Ici aussi le temps
qui séparera 2 lectures du DSP devra être supérieur
ou égal au temps que met le PIC pour rentrer dans cette IT
(après avoir eventuellement fini une autre IT)
I2C
Côté I²C, le PIC est esclave, et il peut recevoir
n'importe quand une donnée. Toute la reception de la donnée
est asurée par le hard et l'octet est mis dans un registre
du PIC. A ce moment une IT est levée. Le PIC peut à
partir de là 'faire atttendre' le master en imposant
un état bas sur l'horloge (stretch clock). Il est ainsi
possible au PIC de récupérer sa donnée, de la
stocker dans un buffer et de relacher l'horloge après.
En fait pour la reception (ou émission) I²C, il n'y a
plus de problèmes possibles après être rentré
dans la routine d'IT (et avoir fait le STRETCH CLOCK). Par
contre, entre le moment où l'IT est activée (parce que
le maitre I²C vient de finir d'écrire), et le moment où
le PIC passe dans l'IT, il y a un temps limite. Ce temps limite est
assez difficile à définir.
Cas IDEAL de transmission entre un Master et un
Slave (ni le maitre ni l'escalve sont capables de faire ça)
Dans le cas d'une transmission idéale, le slave
aurait exactement le temps du transfert d'un octet pour arriver au
stretch clock dans l'IT
Cas REEL de transmission I2C avec des PICs
Ici, il faut le temps que le slave arrive dans sa
routine d'IT et exécute le strecth clock. Ce qui laisserait
à cette routine d'IT, uniquement le temps de transmission
d'un octet pour arriver au stretch clock (si on suppose
que le maitre est parfait). Ce qui est extrêmement court ( à
100KHz, un octet est transmis en environ 10 coups d'horloge I²C,
soit 0.1ms. Pour un PIC à 4MHz, qui exécute une instruction
en 4 cycles, il lui faut 1µs pour exécuter une instruction.
Il n'aurait pas le droit d'exécuter plus de 100 instructions
(50 branchements) pour être arrivé au stretch
clock, ceci après que la demande d'IT soit arrivée(activée).
Il n'y a pas de problèmes si le PIC n'était pas dans
une autre IT. Par contre si il doit sortir de son autre IT, rentrer
dans l'IT I²C et arriver au stretch clock, 100 instructions peuvent
être TRES courtes.
Ici, il faut nuancer un peu. On est sûr que l'opération
prendra plus de temps, puisque entre 2 octets il y'a un délai
'naturel' ajouté par le maître. Ce délai
correspond au temps que met le maitre pour rentrer dans son
IT PIC à lui (et coup de bol si les 2 PIC ont des codes du
même tonneau, ce temps sera quasiment identique au temps mis
par le slave pour entrer dans son IT -- si évidemment
il n'était pas dans une autre IT).
Série
La liaison série obéit à peu près aux
mêmes contraintes. C'est le module HF qui est connecté
dessus ce qui veut dire que :
- Il va envoyer des données à 57600 bauds, ceci de façon
totalement continue. (qu'il y ait des données ou pas)
- Perdre une info(un octet) n'est pas trop grave ici, car non seulement
il y a vérification d'erreur, mais en plus, il y a répétition
des données.
A chaque octet reçu (celui ci va être mis dans un
registre), il va y avoir une interruption (IT serie) du PIC. Le
PIC a jusqu'à la réception complète de l'octet
suivant pour aller lire le contenu du registre. Ce qui veut dire
à 57600 bauds, environ 0.170 ms pour aller récupérer
son octet. Ce délai est fixe. Cependant un octet perdu sur
la liaison série n'est pas dramatique.
A ce point, on se rend déjà compte que le PIC ne doit
pas perdre de temps dans les IT. Plus une IT est longue, et plus elle
risque d'empêcher une autre IT d'arriver à temps. Ce que
l'on a pas discuté, c'est ce qui devait arriver aux donnnées.
Pour le moment, on ne s'est soucié que de recevoir ou
envoyer des données, sans préciser ce que l'on
faisait des données reçues, et ce qu'on envoyait comme
données.
Dans ce genre de situation, il y a 2 approches :
- Permettre aux IT d'être interrompues, c'est à dire
que le processeur peut être interrompu pendant le traitement
d'une IT. Cela permet de réaliser un traitement relativement
long dans l'IT. Cependant, il faut prévoir ce cas dans l'IT,
car on peut être interrompu par n'importe quoi.
Cette approche est souvent difficile à gérer car il
faut TOUT prévoir (y compris le cas où l'on rerentre
dans une même IT ...). Dans le cas d'un PIC cette approche est
totalement impossible de part la limitation à 8 niveaux de
pile (on ne peut pas faire plus de 8 appels de fonction - ou d'IT
- imbriqués)
- Laisser les IT non interruptibles et se débrouiller pour
que les IT soit traitées le plus rapidement possible. C'est
à dire qu'une routine d'IT doit se contenter de :
- récupérer les données nécessaires
au traitement ultérieur.
- faire taire l'IT.
- enclencher un mécanisme qui va réaliser le traitement
(lever un drapeau, par ex), plus tard. Ce traitement sera lui
interruptible.
Cette 2ème approche est plus compliquée à
mettre en oeuvre (gestion du traitement 'déporté'),
mais est beaucoup plus fiable et plus facile (tout reste
relatif) à mettre au point. (c'est l'approche utilisée
par NT/2K/XP sur un PC. Une IT effectue le minimum de traitement
et met en queue une procédure déportée (DRPC)).
Dans tous les cas, elle est bien plus adaptée à l'ajout
d'IT par la suite.
Les flux de données
L'affaire va se corser un peu plus (quoi ? c'était super
simple jusque là), on va s'occuper du sort des données
:
Flux des données TRES simplifiés dans
le PIC
Le PIC doit :
- Permettre au DSP de venir lire les données écrites
par un maitre sur l'I2C (ordres) (I2C -> PSP)
- Permettre au DSP de venir lire les données issues de la Com
HF (données balise adverse) (Traitement comHF -> PSP)
- Ecrire les données que le DSP envoie sur le PSP dans un buffer
(PSP -> Buffer)
- Envoyer les données du buffer sur l'I²C lorsqu'un maître
les demande sur le bus I²C (Buffer -> I2C)
- Indiquer quelle sont les données qui doivent être envoyées
vers l'I²C (flèche en pointillés)
Vu comme cela, on peut penser qu'il pourrait n'y avoir
qu'un seul point noir, au niveau du PSP (2 flèches qui arrivent
sur le PSP). Il y'a énormément plus de problèmes.
En fait ici, les flèches représentent des flux de données
(des octets qui transitent). Chaque transmission marche par paquet.
C'est à dire que derrière chaque flèche se cache
un autre buffer. De plus le traitement de la com HF est bien plus
compliqué que ce qui est représenté ici.
Il faut aussi voir qu'il peut se poser un certain nombre
de prolèmes dû au fait que ce sont des paquets qui transitent.
Par exemple, si le DSP est en train d'écrire (via le PSP) dans
le buffer, il faudrait attendre que le DSP ait finit d'écrire
pour pouvoir envoyer ces données sur l'I2C. Il s'agit en fait
d'assurer qu'un certain nombre d'éléments (ici le buffer)
(appelé ressources critiques en temps réel)
sont bien protégés, c'est à dire qu'il n'y a
qu'un seul accès à la fois. De plus il ne faut jamais
oublier que toute donnée doit être lue avant un certain
temps, sous peine d'être perdue ...
Ici un accès correspond à la lecture ou l'écriture
d'un paquet ENTIER. Donc même si le PIC ne peut pas effectuer
2 instructions à la fois, il peut commencer par écrire
un octet du buffer puis en lire un autre du buffer,etc. Ce qui revient
à effectuer 2 accès à la fois sur le paquet
ce qui risque de mal se terminer (ici, on peut par exemple envoyer sur
l'I²C, la moitié de l'ancien buffer et la moitié
du nouveau, c'est à dire un beau b*** à l'arrivée).
(ce qui est drôle c'est que cet exemple disséqué
ici, n'a pas du être codé et le problème peut potentiellement
arriver)(no comment)
Les flux de données - détails
Détail des transferts dans le PIC
Au point 1, si balise_envoi_vers_dsp vaut
1, on envoie les octets de balise_boite_envoi[byte_vers_dsp], à
chaque lecture du DSP. Une fois arrivé au bout, balise_envoi_vers_dsp
est remis à 0.
Au point 2, si balise_envoi_vers_dsp, valait 0, on envoie
le contenu i2c_boite recu. Une fois arrivé au bout, i2c_envoi_vers_dsp
est remis à 0.
La boite balise_boite_envoi est donc 'protégée'
par balise_envoi_vers_dsp. Il est donc interdit de venir écrire
dans balise_boite_envoi tant que balise_envoi_vers_dsp
vaut 1
La boite i2c_boite recu est donc 'protégée' par
i2c_envoi_vers_dsp.(itoo)
Au point 3, on peut violer la contrainte qui dit
qu'on ne doit pas écrire dans i2c_boite_recu si i2c_envoi_vers_dsp
vaut 1. Ici, si on voulait faire respecter la contrainte, il faudrait
soit stocker plus de données (buffer intermédiaire
- lourd à gérer, manque de place), soit bloquer volontairement
le bus I²C pour une durée bien plus grande que le simple
temps nécessaire pour récupérer ses données
(inacceptable). Dans tous les cas, une commande reçue dans
le buffer i2c_boite_recu va directement déclencher l'envoi
du contenu de ce buffer vers le DSP (via l'IT du DSP). On peut donc
*supposer* que ce buffer va se vider assez vite. Dans tous les cas,
s'l n'est pas encore vide, c'est soit que le DSP n'a pas encore réagi,
soit que 2 ordres sont tombés coup sur coup sur l'I²C.
Au point 4, .la variable tiroir indique quelle
portion du tableau on va envoyer vers l'I²C
Au point 5, on envoie le contenu de i2c_boite_envoi
correspondant à la variable tiroir.
Au point 6, on envoie le contenu du PSP vers la
variable i2c_boite_envoi correspondant à la variable tiroir.
Pour les points 5 et 6, il n'y a aucune protection, et on peut très
bien se retrouver amené à envoyer(vers l'I²C) des
données à moitié arrivées du PSP.
Au point 7, on stocke les données brutes
séries venues de la comHF.
L'ensemble des règles est résumé
dans ce tableau.
N° |
Nom |
Nom du buffer
|
Protégé par
|
Compteur de ce buffer
|
Commentaire
|
1 |
envoi buffer balise vers PSP |
balise_boite_envoi |
balise_envoi_vers_dsp |
byte_vers_dsp |
balise_envoi_vers_dsp=1,
interdit d'écrire dans balise_boite_envoi
PSP (READ) réservé à l'envoi des données
de la balise
|
2 |
envoi buffer I2C vers PSP |
i2c_boite recu. |
i2c_envoi_vers_dsp |
byte_vers_dsp |
i2c_envoi_vers_dsp=1
interdit d'écrire dans i2c_boite_recu (peut être
violée par 3)
PSP (READ) réservé à l'envoi des données
de l'I2C |
3 |
envoi I2C vers buffer I2C |
i2c_boite recu. |
|
*debut_data, *fin_data |
aucune protection (donc pourrait être
violée par 2, en pratique impossible 2 apparait toujours
APRES 3) |
4 |
NA |
NA |
NA |
NA |
NA |
5 |
envoi buffer PSP vers I2C |
i2c_boite_envoi |
|
*debut_data, *fin_data |
aucune protection (peut être
violée par 5) |
6 |
evoi PSP vers buffer PSP |
i2c_boite_envoi |
|
*pdp, max |
aucune protection (peut être
violée par 6) |
7 |
série HF vers rx_box |
rx_box |
rx_frame_received |
data_byte_number
rx_push_index,
rx_pull_index
|
rx_frame_received=1
interdit d'écrire dans rx_box et d'exploser rx_push_index
et rx_pull_index. data_byte_number n'est pas protégé
autorise à lire dans rx_box
|
8 |
rx_box vers i2c_box |
i2c_box |
rx_data_ok |
k |
rx_data_ok=1
interdit d'écrire dans i2c_box
autorise à lire dans i2c_box
|
9 |
i2c_box vers balise_boite_envoi |
balise_boite_envoi |
balise_envoi_vers_dsp |
byte_vers_dsp |
|
En boucle principale (et alors là le PIC, il a
pas souvant le temps d'aller se pavaner en boucle principale), on traite
la liaison entre les différents éléments (pseudo-traitement
déporté).
Si on vient de finir de recevoir (reçu au moins
un octet i2c_aux>=1 et y a eu un stop_bit) un paquet
dans i2c_boite recu., on traite ce paquet.
- C'est un ordre SET_TYPE_RETOUR, dans ce cas, on change la
valeur de 'tiroir'. Il y a un risque de violation, si a ce
moment il y a un ordre de lecture de l'I²C qui a réussi
à passer. (il va avoir envoyé x octets correspondant
à l'ancien tiroir, et va envoyer le reste correspondant
au nouveau tiroir). Dans tous les cas, si on évite cette
situation (en attendant la fin de la lecture), on va renvoyer le mauvais
tiroir ... ce qui ne sera guère mieux. D'un autre côté,
on peut estimer que le changement de tiroir va avoir lieu assez vite
après la reception de cet ordre (le temps de revenir en boucle
principale, si il n'y a pas eu d'autres IT d'ici là).
- C'est un autre ordre.On va donc envoyer cet ordre par le PSP
(vers le DSP). On ne peut l'envoyer que si le PSP est libre.
Le PSP est occupé lorsque i2c_envoi_vers_dsp est à
1 ou bien balise_envoi_vers_dsp est à 1.
Si i2c_envoi_vers_dsp est à 1 alors c'est qu'il y a
déjà un autre ordre en train d'être envoyé
vers le PSP. Ce qui veut aussi dire que ce qui est en train d'être
envoyé vers le PSP est le contenu du buffer i2c_boite recu.,
de l'ordre précédant. Ce qui veut dire que l'action
2 (réception des données de l'I2C vers le buffer i2c_boite_recu)
vient déjà d'exploser le contenu de ce buffer .... donc
tant qu'à faire on va recommencer à l'envoyer (ce qui
va être envoyé sera faux, mais au moins, on ne risque
pas de deadlock). Encore une fois, ce cas n'intervient que si 2 ordres
sont reçus coup sur coup (ou bien que l'envoi vers le DSP s'est
trainé, ce qui est louche).
Bon, donc c'est un autre ordre, et si balise_envoi_vers_dsp
vaut 0 (DSP pas occupé), on y va :
- on attend que dsp_ecrit_sur_pic passe à 0, ça
tombe bien cette variable ne sert strictement plus à
rien et est toujours à 0. Son but devait être
à l'origine d'éviter qu'il y ait une lecture et
une écriture en même temps sur le PSP (ce qui n'est
pas censé poser de problèmes en théorie)
- on positionne i2c_envoi_vers_dsp à 1, indiquant
qu'on utilise le PSP (ce qui peut être violé par
un autre ordre venu de l'I²C), et qu'il ne faut pas écrire
dans i2c_boite_recu (ce qui peut AUSSI être violé
par un autre ordre venu de l'I²C), bref y'a pas interêt
à ce qu'il tombe un autre ordre trop vite.
- on initialise le nombre d'octets à transférer
(nb_octet_vers_dsp=i2c_aux)
- on initialise le compteur servant à compter le nombre
d'octets transférés (byte_vers_dsp=1;)
- on met le premier octet sur le PSP : PORTD=i2c_boite_recu[0];
(comme ça à la première lecture le DSP va
lire le premier octet, on poura mettre le second, etc)
- On active l'IT du DSP : var=porta; // interruption du dsp
var&=0xFE;
porta=var;
- Dans le cas, où il n'est pas possible d'envoyer cet ordre
vers le PSP, on poireaute en attendant que l'envoi des données
de la balise soit terminé (c'est la seule chose qui peut bloquer).
A noter que plus on attend plus il risque d'arriver malheur à
ce pauvre buffer non protégé ...
Si une trame HF brute a été reçue correctement
dans rx_box, (indiqué par rx_frame_received=1) qui protège
également cette boite, on exécute la routine reception(),
qui se charge de vérifier la cohérence de la trame.
Si une trame cohérente est présente rx_data_ok
vaut 1.
Dans ce cas, la routine put_vers_dsp() va être appelée.
Elle va :
- Vérifier que cette trame n'a pas déjà été
envoyée (frame_envoyee_ok à 0)
- Vérifier que le PSP est libre, (non occupé par la
balise : balise_envoi_vers_dsp=0) (non occupé par l'I²C
: i2c_envoi_vers_dsp=0)
- Comparer i2c_box avec i2c_box précédent.
Si 'ils sont identiques, la routine libère i2c_box (rx_data_ok=0)
et ne transmet pas.
- Copier le buffer i2c_box dans balise_boite_envoi
- Indiquer que le buffer i2c_box est libre (rx_data_ok=0)
(on vient de le copier).
- Exécuter while( dsp_ecrit_sur_pic ) ; qui ne sert
strictement à rien
- Arrêter les IT (inutile normallement)
- Initialiser le premier élément et lancer l'envoi vers
le DSP
- Réautoriser les interruptions. Ici il pourrait y avoir un
problème car l'IT du DSP pourrait avoir lieu AVANT que les
IT ne soient réautorisées ...
- Indiquer que la trame a correctement été envoyée
(donc ne sera pas renvoyée)
Le code
Situé dans Asservissement-balisees\i2c_asser, il comprend
les fichiers suivants :
- i2_asser.c, fichier principal, contient la boucle principale
- BALISE_FIXE.C, contient la gestion du port série (IT,
reception(), put_vers_dsp())
- i2c_slave.c, IT I2C, init I2C
- psp.c, IT ¨PSP
Inutile de préciser que l'initialisation de toute cette petite
famille doit être correctement réalisée dans le
bon ordre, ce qui n'était pas le cas au début et a causé
des problèmes pendant un certain temps -- et explique les précautions
inutiles rajoutées dans les transferts ...
Ce code a évolué par morceaux en incluant la com HF par
la suite. C'est une horreur à mettre au point, faute de debug
utilisable et surtout de vision globale de la chose au début
du codage. La SEULE solution pour le rendre utilisable est de refaire
tous les mécanismes de 'protection' et de gestion des données
(la gestion des périphériques peut être conservée
par contre).
|