Utiliser le langage C pour programmer les entrées/sorties



Les pointeurs et les adresses


Un pointeur est un groupe de cases mémoires (2 ou 4 octets suivants les implémentations) pouvant contenir une adresse, exemple:
   char C;
    char *PtC;

C est une variable de type char et PtC est un pointeur pouvant pointer des objets de type char.
L'opérateur & donne l'adresse d'un objet; pour faire pointer PtC vers C il faut donc utiliser l'instruction:

    PtC = &C;    /*   l'adresse de C est rangée dans PtC. */

L'opérateur étoile (*) représente l'opérateur d'indirection. Si on l'applique a un pointeur, il donne accès a l'objet pointe par ce
pointeur. Les 2 exemples suivants montrent comment utiliser les opérateurs & et *:

exemple1:
    char c;
    char *p;
    p = &c;      /* l'adresse de c est rangée dans p */
    *p = 'd';     /* c vaut maintenant le caractère 'd' */
   int tab[10];
    int *pt;

exemple2:
    pt = &tab[5];   /* l'adresse de la case 5 du tableau tab est rangée dans pt */
    *pt = 12;          /* la case 5 de tab vaut maintenant 12 */
    pt = pt + 1;     /* pt pointe maintenant la case 6 de tab */
    *pt = 34;         /* la case 6 de tab vaut maintenant 34 */


Un exemple applique au PIA 6821

Sur le kit 68040 nous avons un circuit d'entrée/sortie le PIA 6821. Sur ce kit l'adresse de base du PIA est 0xF00043A1 (le préfixe 0x précise au compilateur C que le chiffre aui suit est en hexadécimal). En claire le premier registre du PIA est a l'adresse F00043A1 le second a l'adresse F00043A3h et ainsi de suite pour les registres suivants du PIA. On déclarera donc les defines suivants:

    #define PIA 0xF00043A1  /* adresse de base du PIA sur le kit 68040 */

    /* deplacement a ajouter a l'adresse PIA pour atteindre
     * les registres du PIA:
     */

    #define CRA  2       /* reg de contrôle du port A */
    #define DDRA 0    /* reg de contrôle du port A */
    #define ORA  0      /* reg de lecture/écriture du port A */

    #define CRB  6     /* reg de contrôle du port B */
    #define DDRB 4   /* reg de direction du port B */
    #define ORB  4     /* reg de lecture/écriture du port B */

Vous devez commencer par déclarer un pointeur capable de pointer les registres du PIA.

Une simple question: qu'elle est le type de donnée des registres d'un circuit PIA 6821 ?

Les registres du PIA sont sur 8 bits soit 1 octet.

Quel est le type dans du langage C qui code des données sur 1 octet ?

 Et bien la réponse n'est pas trivial car elle dépend de du compilateur C que vous utilisez. Un petit truc pour résoudre cette question: l'opérateur sizeof applique a une variable ou bien directement a un type vous retourne la taille en octets de cette variable ou de ce type. Le programme suivant vous montre l'utilisation de l'opérateur sizeof:
    #include <stdio.h>

    main() {

   char TabChar[10];
    int TabInt[10];

    printf("taille d'un          char = %d octet\n",sizeof( char ));
    printf("taille d'un unsigned char = %d octet\n",sizeof( unsignedchar ));

    printf("taille d'un          int = %d octets\n",sizeof( int ));
    printf("taille d'un unsigned int = %d octets\n",sizeof( unsigned int ));
    printf("taille d'un short    int = %d octets\n",sizeof( short int ));

    printf("taille d'un  float = %d octets\n",sizeof( float ));
    printf("taille d'un double = %d octets\n",sizeof( double ));

    printf("taille du tableau char TabChar[10] = %d octets\n",sizeof( TabChar ));
    printf("taille du tableau int  TabInt[10]  = %d octets\n",sizeof( TabInt ));
}

Remarque: sur le kit 68040 la fonction printf n'est pas disponible, vous pouvez utiliser les fonctions suivantes:

    puts( uitoa( sizeof( short int ) ) );

L'exécution de ce programme donne:

taille d'un          char = 1 octet
taille d'un unsigned char = 1 octet
taille d'un           int = 4 octets
taille d'un unsigned  int = 4 octets
taille d'un short     int = 2 octets
taille d'un  float = 4 octets
taille d'un double = 8 octets
taille du tableau char TabChar[10] = 10 octets
taille du tableau int  TabInt[10]  = 40 octets

On prendra le type char pour coder les registres 8 bits du PIA. La déclaration du pointeur peut être la suivante:
    char *PtRegPIA;

 On peut initialiser (faire pointer) PtRegPIA soit dans le code principal avec l'instruction:
   PtRegPIA = (char *) PIA;

Soit directement dans la déclaration:
  char *PtRegPIA = (char *) PIA;

Remarque au sujet du "cast" (char *): Ce cast est nécessaire pour signifier au compilateur que PIA est une adresse d'un objet de type char (char = 1 octets ou 8 bits pour les registres de notre PIA).

Si l'on veut affecter la valeur 00 dans le registre CRB il suffit d'utiliser l'instruction suivante:
   *(PtRegPIA + CRB) = 0x00;
 

PtRegPia pointe sur le premier registre du PIA, on ajoute le déplacement CRB pour pointer sur CRB. L'opérateur * permet de modifier le contenu pointer c'est a dire mettre la valeur 0x00 dans le registre CRB. Cette instruction est relativement lourde, mais en utilisant la notation tableau l'instruction devient limpide:

   PtRegPIA[ CRB ] = 0x00; /* qui est équivalent a *(PtRegPIA + CRB) = 0x00; */

On peut encore simplifier la notation *(PtRegPIA + CRB) en masquant sa complexité a l'aide d'un define:
    #define PtRegPIA 0x05CEF1               /* adresse de base du PIA */

    #define CRB  *(char *) (PtRegPIA + 6)   /* reg de controle du port B */

et voici son utilisation:

    CRB = 0x00;

Après l'action du pré-processeur (qui est automatiquement lance avant la compilation) l'instruction précédente devient:

*(char *) (0x05CEF1 + 6)  = 0x00;
 |     |     |         |                 |
 |     |     |         |                 - déplacement pour attendre le registre CRB.
 |     |     |         - l'adresse de base du PIA.
 |     |    - déclaration du pointeur.
 |     - pointeur sur char.
 |- opérateur d'indirection: pour avoir accès au contenu pointé.

Trouver la déclaration du define n'est pas facile mais l'utilisation devient est extrêmement simple. Maintenant vous devez pouvoir aisément manipuler les pointeurs pour atteindre tous les adresses du kit 68040.


La gestion des interruptions sur kit 68332

Sur le kit 68332, les demandes d'interruptions des peripheriques arrivent sur les signaux IRQ7\ à IRQ1\.  La demande ne sera acceptée par le CPU32 (processeur) qu'après l'exécution de l'instruction en cours et si le niveau de priorité de la demande est strictement supérieur au masque d'interruption du registre SR. Une interruption de niveau 7 ne peut être masquée: elle sera toujours acceptée par le microprocesseur quelle que soit la valeur du masque d'interruption.

 Lors de la reconnaissance d'interruption, une copie interne du SR est effectuée, le processeur passe en mode superviseur le masque d'interruption est mis au niveau de l'interruption prise en compte (le processeur ne pourra alors être interrompu que par une interruption de niveau supérieur). Le processeur attend le numéro de vecteur du circuit qui vient de l'interrompre en effectuant une lecture:

Si le peripherique ne peut pas fournir de numéro de vecteur, une logique externe doit  activer le signal AVEC\ processeur pour que celui-ci génère en interne un numéro de vecteur: c'est l'auto vectorisation. Sur kit 68332 l'interruption provenant du PTM genere un auto vecteur de niveau 4:

Si aucun des signaux DTACKx\ ou  AVEC\ ne s'activent la logique interne de controle active le signal BERR\ pour débloquer le processeur et que celui-ci génère le vecteur d'interruption parasite. Lorsque le numéro de vecteur est obtenu, le processeur sauve les registres PC et SR sur la pile superviseur (SSP). La valeur du PC empilée à l'adresse de l'instruction qui aurait été exécutée en l'absence d'interruption. Le processeur recherche alors dans la table des vecteurs  l'adresse du programme de traitement de l'interruption et l'exécute. L'adresse de début de la table des vecteurs d'interruptions est contenue dans le registre VBR (Vecteur Base Register).

Extrait de la TABLE DES VECTEURS

Numéro de vecteur [VBR] + offset Utilisation
Déc. Hexa.
0 0 000 Initialisation du SSP après un RESET
- 4 004 Initialisation du PC après un RESET
2 8 008 Erreur bus
3 12 00C Erreur d'adresse
4 16 010 Instruction illégale
5 20 014 Division par zéro
6 24 018 Instruction CHK
7 28 01C Instruction TRAPV
8 32 020 Violation de privilège
9 36 024 Trace 
10 40 028 Émulateur ligne 1010
11 44 02C Émulateur ligne 1111
12-14 48 030 réserve 
15 60 03C Interruption non initialisée 
16-23 64-95 04C-05F réservé
24 96 060  Interruption parasite
26 100 064 Auto-Vecteur d'interruption Niveau 1
26 104 068 Auto-Vecteur d'interruption Niveau 2
27 108 06C Auto-Vecteur d'interruption Niveau 3
28 112 070 Auto-Vecteur d'interruption Niveau 4
29 116 074 Auto-Vecteur d'interruption Niveau 5
30 120 078 Auto-Vecteur d'interruption Niveau 6
31 124 07C Auto-Vecteur d'interruption Niveau 7
32-47 128-191 080-0BF Vecteurs d'instruction TRAP
48-63 192-255 0C0-0FF Réservé 
64-255 256-1023 100-3FF Vecteurs d'interruption utilisateur

Implémenter une fonction comme routine d'interruption.

Le cross-compilateur gnu C, ne permet pas de  preciser qu'une fonction est une routine d'interruption. En effet une routine d'interruption doit faire sont retunr a l'aide de l'instruction RTE et non RTS.  Vous devez coder votre routine d'interruption en assembleur et lier vos differents code objets a l'aide de la commande ldkit:


Écrire l'adresse d'un sous-programme dans la table des vecteurs.

Voici une manière très élégante de procéder:
    #define AdrTabVect   0       /* Adresse de base de la table des vecteurs, est toujours égale à  0 sur 68k
                                                   * mais avec un 68010 ou supérieur elle est égale au contenu du registre VBR */
    #define VECT_PTM    64   /* Numéro d'un vecteur utilisateur choisi dans la table des vecteurs */

main()
{
    /* Initialisation de la table des vecteurs d'it */
    static void (**TabVect)() = (void (**)()) AdrTabVect;
 

    /* Mise en place du vecteurs d'it IRQ du PTM */
    TabVect[ VECT_PTM ] = InterruptionPTM;
    .
    .
    .
}

TabVect est un pointeur qui pointe vers un tableau qui contient des adresses de fonctions.


Insérer du code assembleur dans un source C.

La syntaxe est la suivante:
main ()
{
    .
    .
    .
    /* Debut de la séquence assembleur */
     __asm__("
             move.l     #01234560,%a1
             move.w    #1234,%a2
    ");
   /* fin de la séquence assembleur */

    .
    .
    .
}