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.

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