Atmel SAM4S

Atmel SAM4S sous Linux - TP n°1 : les E/S numériques

L’objectif est de mettre en oeuvre les E/S numériques de la carte Atmel SAM4S.

Remarque : Pour ce TP, vous aurez besoin de relier un adaptateur USB/RS232 sur le port série UART J7 et d’utiliser un logiciel d’émulation de terminal ($ picocom -b 115200 /dev/ttyUSB0 ou cutecom sous GNU/Linux ou TeraTerm sous Windows) sur le port série virtuel pour visualiser les messages de débuggage. On quitte picocom en utilisant la combinaison de touches Ctrl-A puis Ctrl-X.

Ressources

Introduction

Les E/S numériques sont très utilisés dans les systèmes industriels. Elles sont aussi les plus simple à mettre en oeuvre car il suffit :

  • de lire le bit d’un registre pour connaître l’état d’une entrée
  • d’écrire un bit dans un registre pour fixer le niveau d’une sortie

Par contre, il faut absolumment maîtriser la notion de masques pour gérer les E/S de manière individuelle. Pour cela, on utilise :

  • l’opérateur ET binaire (& bit à bit) pour lire une ou plusieurs entrées parmi d’autres
  • l’opérateur OU binaire (| bit à bit) pour écrire une ou plusieurs sorties à l’état 1 et l’opérateur ET binaire (& bit à bit) l’état 0

La seule difficulté pour le contrôle des entrées est de mettre en oeuvre la gestion des interruptions. Les interruptions permettent d’éviter au programme d’attendre indéfiniment le changement d’état ou de niveau d’une entrée. Elles sont donc souvent indispensables dans un système professionnel.

Dans le monde industriel :

  • Les entrées numériques sont souvent des touches, interrupteurs, boutons mais aussi des détecteurs de présence, des fins de courses etc … En fait ce sont tous les capteurs délivrant une information binaire : à deux états (appui/repos, présent/absent, etc …).

  • Les sorties numériques sont des indicateurs de visualisation (Led, voyant, etc..) ou tout actionneur fonctionnant à partir de deux états (on/off, allumé/éteint, etc …). Pour des actionneurs de puissance, on s’interfacera souvent avec des relais électromécanique ou statique, des thyristors optocouplé.

Remarque : les E/S peuvent être regroupées pour former un bus parallèle. Par exemple, un clavier matriciel 16 touches peut être géré avec un bus parallèle de 8 E/S décomposable en 4 lignes et 4 colonnes.

Pour faire le lien entre les E/S numériques et le MCU (MicroController Unit), il faut un circuit périphérique d’interface des E/S. Ici, c’est le rôle du PIO (Parallel Input Output) qui est un port parallèle d’entrée/sortie.

Un circuit périphérique est accessible à partir d’une adresse mémoire ce qui permet au programme de lire et/ou écrire dans les registres de ce composant et donc de gérer son fonctionnement.

Ceci est fourni au programmeur par l’intermédiaire du fichier /usr/local/asf/sam/utils/cmsis/sam4s/include/sam4s16c.h

#define PIOA       ((Pio    *)0x400E0E00U) /**< \brief (PIOA      ) Base Address */
#define PIOB       ((Pio    *)0x400E1000U) /**< \brief (PIOB      ) Base Address */
#define PIOC       ((Pio    *)0x400E1200U) /**< \brief (PIOC      ) Base Address */

Remarque : le ‘U’ (UNSIGNED) indique que la valeur est non signée (cad strictement positive) et le 0x que la valeur est exprimée en hexadécimale (le système de numération de base des informaticiens). Chaque symbole hexadécimal (de 0 à F) est codé sur 4 bits et représente une valeur décimale comprise entre 0 et 15 (F).

En programmation, on manipule les adresses mémoires en utilisant des variables spéciales appelées “pointeurs”. Un “pointeur” est donc une variable qui peut contenir une adresse mémoire. La notion de “pointeur” apparaît ici par la présence de l’étoile * : (Pio *) et l’adresse du PIOA est 0x400E0E00.

En informatique, une adresse mémoire représente une “case” qui peut contenir une valeur. La taille de base d’une “case” mémoire est de 8 bits soit 1 octet.

Remarque : les capacités mémoire seront donc toujours exprimées en octet en utilisant les multiples kilo, méga, giga … si la taille est importante.

Pour manipuler des valeurs, on utilise des variables “normales”. Par contre, ces variables doivent être “typées”. Ce type précise la convention d’interprétation de la séquence de bits qui constitue la variable. Le type de la variable spécifie aussi sa taille (la longueur de cette séquence de bits) soit habituellement 8 bits, 16 bits, 32 bits, 64 bits, … Par exemple le type int permet de manipuler des valeurs entières signées sur 32 bits (4 octets en mémoire).

Pour manipuler les PIOx, le fichier /usr/local/asf/sam/utils/cmsis/sam4s/include/component/component_pio.h déclare une structure Pio permettant d’accèder à ses registres :

typedef struct {
  __O  uint32_t PIO_PER;        /**< \brief (Pio Offset: 0x0000) PIO Enable Register */
  __O  uint32_t PIO_PDR;        /**< \brief (Pio Offset: 0x0004) PIO Disable Register */
  __I  uint32_t PIO_PSR;        /**< \brief (Pio Offset: 0x0008) PIO Status Register */
  __I  uint32_t Reserved1[1];
  __O  uint32_t PIO_OER;        /**< \brief (Pio Offset: 0x0010) Output Enable Register */
  __O  uint32_t PIO_ODR;        /**< \brief (Pio Offset: 0x0014) Output Disable Register */
  __I  uint32_t PIO_OSR;        /**< \brief (Pio Offset: 0x0018) Output Status Register */
  __I  uint32_t Reserved2[1];
  __O  uint32_t PIO_IFER;       /**< \brief (Pio Offset: 0x0020) Glitch Input Filter Enable Register */
  __O  uint32_t PIO_IFDR;       /**< \brief (Pio Offset: 0x0024) Glitch Input Filter Disable Register */
  __I  uint32_t PIO_IFSR;       /**< \brief (Pio Offset: 0x0028) Glitch Input Filter Status Register */
  __I  uint32_t Reserved3[1];
  __O  uint32_t PIO_SODR;       /**< \brief (Pio Offset: 0x0030) Set Output Data Register */
  __O  uint32_t PIO_CODR;       /**< \brief (Pio Offset: 0x0034) Clear Output Data Register */
  __IO uint32_t PIO_ODSR;       /**< \brief (Pio Offset: 0x0038) Output Data Status Register */
  __I  uint32_t PIO_PDSR;       /**< \brief (Pio Offset: 0x003C) Pin Data Status Register */
  __O  uint32_t PIO_IER;        /**< \brief (Pio Offset: 0x0040) Interrupt Enable Register */
  __O  uint32_t PIO_IDR;        /**< \brief (Pio Offset: 0x0044) Interrupt Disable Register */
  __I  uint32_t PIO_IMR;        /**< \brief (Pio Offset: 0x0048) Interrupt Mask Register */
  __I  uint32_t PIO_ISR;        /**< \brief (Pio Offset: 0x004C) Interrupt Status Register */
  __O  uint32_t PIO_MDER;       /**< \brief (Pio Offset: 0x0050) Multi-driver Enable Register */
  __O  uint32_t PIO_MDDR;       /**< \brief (Pio Offset: 0x0054) Multi-driver Disable Register */
  __I  uint32_t PIO_MDSR;       /**< \brief (Pio Offset: 0x0058) Multi-driver Status Register */
  __I  uint32_t Reserved4[1];
  __O  uint32_t PIO_PUDR;       /**< \brief (Pio Offset: 0x0060) Pull-up Disable Register */
  __O  uint32_t PIO_PUER;       /**< \brief (Pio Offset: 0x0064) Pull-up Enable Register */
  __I  uint32_t PIO_PUSR;       /**< \brief (Pio Offset: 0x0068) Pad Pull-up Status Register */
  __I  uint32_t Reserved5[1];
  __IO uint32_t PIO_ABCDSR[2];  /**< \brief (Pio Offset: 0x0070) Peripheral Select Register */
  __I  uint32_t Reserved6[2];
  __O  uint32_t PIO_IFSCDR;     /**< \brief (Pio Offset: 0x0080) Input Filter Slow Clock Disable Register */
  __O  uint32_t PIO_IFSCER;     /**< \brief (Pio Offset: 0x0084) Input Filter Slow Clock Enable Register */
  __I  uint32_t PIO_IFSCSR;     /**< \brief (Pio Offset: 0x0088) Input Filter Slow Clock Status Register */
  __IO uint32_t PIO_SCDR;       /**< \brief (Pio Offset: 0x008C) Slow Clock Divider Debouncing Register */
  __O  uint32_t PIO_PPDDR;      /**< \brief (Pio Offset: 0x0090) Pad Pull-down Disable Register */
  __O  uint32_t PIO_PPDER;      /**< \brief (Pio Offset: 0x0094) Pad Pull-down Enable Register */
  __I  uint32_t PIO_PPDSR;      /**< \brief (Pio Offset: 0x0098) Pad Pull-down Status Register */
  __I  uint32_t Reserved7[1];
  __O  uint32_t PIO_OWER;       /**< \brief (Pio Offset: 0x00A0) Output Write Enable */
  __O  uint32_t PIO_OWDR;       /**< \brief (Pio Offset: 0x00A4) Output Write Disable */
  __I  uint32_t PIO_OWSR;       /**< \brief (Pio Offset: 0x00A8) Output Write Status Register */
  __I  uint32_t Reserved8[1];
  __O  uint32_t PIO_AIMER;      /**< \brief (Pio Offset: 0x00B0) Additional Interrupt Modes Enable Register */
  __O  uint32_t PIO_AIMDR;      /**< \brief (Pio Offset: 0x00B4) Additional Interrupt Modes Disables Register */
  __I  uint32_t PIO_AIMMR;      /**< \brief (Pio Offset: 0x00B8) Additional Interrupt Modes Mask Register */
  __I  uint32_t Reserved9[1];
  __O  uint32_t PIO_ESR;        /**< \brief (Pio Offset: 0x00C0) Edge Select Register */
  __O  uint32_t PIO_LSR;        /**< \brief (Pio Offset: 0x00C4) Level Select Register */
  __I  uint32_t PIO_ELSR;       /**< \brief (Pio Offset: 0x00C8) Edge/Level Status Register */
  __I  uint32_t Reserved10[1];
  __O  uint32_t PIO_FELLSR;     /**< \brief (Pio Offset: 0x00D0) Falling Edge/Low Level Select Register */
  __O  uint32_t PIO_REHLSR;     /**< \brief (Pio Offset: 0x00D4) Rising Edge/ High Level Select Register */
  __I  uint32_t PIO_FRLHSR;     /**< \brief (Pio Offset: 0x00D8) Fall/Rise - Low/High Status Register */
  __I  uint32_t Reserved11[1];
  __I  uint32_t PIO_LOCKSR;     /**< \brief (Pio Offset: 0x00E0) Lock Status */
  __IO uint32_t PIO_WPMR;       /**< \brief (Pio Offset: 0x00E4) Write Protect Mode Register */
  __I  uint32_t PIO_WPSR;       /**< \brief (Pio Offset: 0x00E8) Write Protect Status Register */
  __I  uint32_t Reserved12[5];
  __IO uint32_t PIO_SCHMITT;    /**< \brief (Pio Offset: 0x0100) Schmitt Trigger Register */
  __I  uint32_t Reserved13[19];
  __IO uint32_t PIO_PCMR;       /**< \brief (Pio Offset: 0x150) Parallel Capture Mode Register */
  __O  uint32_t PIO_PCIER;      /**< \brief (Pio Offset: 0x154) Parallel Capture Interrupt Enable Register */
  __O  uint32_t PIO_PCIDR;      /**< \brief (Pio Offset: 0x158) Parallel Capture Interrupt Disable Register */
  __I  uint32_t PIO_PCIMR;      /**< \brief (Pio Offset: 0x15C) Parallel Capture Interrupt Mask Register */
  __I  uint32_t PIO_PCISR;      /**< \brief (Pio Offset: 0x160) Parallel Capture Interrupt Status Register */
  __I  uint32_t PIO_PCRHR;      /**< \brief (Pio Offset: 0x164) Parallel Capture Reception Holding Register */
  __IO uint32_t PIO_RPR;        /**< \brief (Pio Offset: 0x168) Receive Pointer Register */
  __IO uint32_t PIO_RCR;        /**< \brief (Pio Offset: 0x16C) Receive Counter Register */
  __I  uint32_t Reserved14[2];
  __IO uint32_t PIO_RNPR;       /**< \brief (Pio Offset: 0x178) Receive Next Pointer Register */
  __IO uint32_t PIO_RNCR;       /**< \brief (Pio Offset: 0x17C) Receive Next Counter Register */
  __I  uint32_t Reserved15[2];
  __O  uint32_t PIO_PTCR;       /**< \brief (Pio Offset: 0x188) Transfer Control Register */
  __I  uint32_t PIO_PTSR;       /**< \brief (Pio Offset: 0x18C) Transfer Status Register */
} Pio;

Les membres ou champs de la structure représentent “physiquement” les registres internes du circuit ici un PIO. En théorie, on pourrait trouver des registres :

  • 8 bits soit un unsigned char (ou uint8_t)
  • 16 bits soit unsigned short (ou uint16_t)
  • 32 bits soit unsigned int (ou uint32_t)
  • 64 bits soit unsigned long (ou uint64_t)
  • etc …

En pratique, on ne trouvera que des registres 32 bits car le MCU ici possède une architecture 32 bits. Ceci évitera des problèmes d’alignement et il est donc préférable, pour des raisons de vitesse d’accès d’avoir des registres 32 bits parfois sous-utilisés. En informatique, il y aura toujours une opposition entre vitesse et taille mémoire !

On obtient :

En langage C, pour accèder à un membre d’une structure, on utilise l’opérateur -> dans le cas d’un pointeur. Par exemple pour écrire dans le registre Enable Register du PIOA :

// Activer la sortie numéro 19
PIOA->PIO_OER = PIO_PA19;

Les étiquettes des E/S des PIOx sont définies dans le fichier header /usr/local/asf/sam/utils/cmsis/sam4s/include/pio/pio_sam4s16c.h et celles des registres dans le fichier header /usr/local/asf/sam/utils/cmsis/sam4s/include/instance/instance_pioa.h pour le PIOA par exemple.

Remarque : ces fichiers d’en-têtes (header) sont automatiquement inclus par asf.h.

Les E/S numériques

La carte Atmel SAM4S dispose de 3 x PIO (32 bits) :

Remarque : les E/S ne sont pas toutes laissées disponibles. Pour la carte Atmel SAM4S, il y a 79 E/S accessibles.

On va tout d’abord mettre en oeuvre :

  • les 2 LEDs (Bleue et Verte en PA19/PA20) du PIOA
  • les 2 boutons (USRPB1 et USRPB2 en PB3/PC12) des PIOB et PIOC

Les fonctionnalités essentielles des E/S PIO fournies par Atmel sont :

  • La gestion d’interruption permettant la détection des fronts montants (rising edge), des fronts descendants (falling edge), des niveaux bas (low-level) ou des niveaux hauts (high-level)
  • Un filtre anti-rebond (debouncing filter) permettant le rejet des impulsions indésirables sur des touches ou boutons à contact
  • Le contrôle du pull-up et pull-down de chaque ligne

Le contrôle des E/S PIO peut être réalisé :

  • directement en accèdant aux registres associés
  • par les fonctions de la bibliothèque logicielle ASF

Les registres du PIO

Les adresses mémoires des 3 PIOs sont :

  • PIOA : 0x400E0E00
  • PIOB : 0x400E1000
  • PIOC : 0x400E1200

Documentation : Chapitre 31 Parallel Input/Output Controller (PIO) page 567 du atmel-11100-32-bit-cortex-m4-microcontroller-sam4s_datasheet.pdf

Chaque PIO dispose d’environ 65 registres.

En utilisant seulement 3 registres, il est possible de contrôler basiquement une sortie du PIO :

  • OER (Offset 0x0010) : Output Enable Register, ce registre permet d’activer une sortie du PIO
  • CODR (Offset 0x0034) : Clear Output Data Register, ce registre permet de fixer à un niveau bas une sortie du PIO
  • SODR (Offset 0x0030) : Set Output Data Register, ce registre permet de fixer à un niveau haut une sortie du PIO

Remarque :

  • ODR (Offset 0x0014) : Output Disable Register, ce registre permet de désactiver une sortie du PIO et donc de la configurer en entrée

Les entrées peuvent être gérées par deux modes :

  • en scrutation (polling) : c’est une attente active (qui peut être couplée avec une fonction de pause ou de temporisation) afin de vérifier de façon répétée (boucle) la valeur d’une entrée.
  • en interruption : c’est l’arrêt temporaire de l’exécution normale du programme afin d’exécuter un gestionnaire d’interruption (handler). Une interruption peut être lancée sur un changement d’état d’une entrée.

Pour manipuler une entrée, on aura besoin des registres suivants :

  • PDSR (Offset 0x003C) : Pin Data Status Register, ce registre permet de lire l’état d’une E/S du PIO
  • IER (Offset 0x0040) : Interrupt Enable Register, ce registre permet d’activer la détection d’une interruption sur une entrée du PIO
  • ESR (Offset 0x00C0) : Edge Select Register, ce registre permet de configurer la détection de front
  • LSR (Offset 0x00C4) : Level Select Register, ce registre permet de configurer la détection de niveau
  • FELLSR (Offset 0x00D0) : Falling Edge/Low-Level Select Register, ce registre permet de configurer la détection de front descendant ou de niveau bas
  • REHLSR (Offset 0x00D4) : Rising Edge/High-Level Select Register, ce registre permet de configurer la détection de front montant ou de niveau haut
  • FRLHSR (Offset 0x00D8) : Fall/Rise - Low/High Status Register, ce registre permet de lire le type de détection ayant provoquer l’interruption
  • ISR (Offset 0x004C) : Interrupt Status Register, ce registre permet d’indiquer si une interruption a été détectée sur une entrée du PIO

Les fonctions de la bibliotèque logicielle ASF

Pour simplifier la programmation directe du PIO, la bibliothèque logicielle ASF fournit des fonctions de contrôle : http://asf.atmel.com/docs/latest/sam4s/html/group__sam__drivers__pio__group.html et http://asf.atmel.com/docs/latest/sam4s/html/group__ioport__group.html.

Une fonction, aussi appelée routine, sous-routine ou procédure, contient simplement une série d’instructions à réaliser. N’importe quelle fonction peut être appelée à n’importe quelle étape de l’exécution du programme, y compris à l’intérieur d’autres fonctions, voire dans la fonction elle-même (récursivité).

Remarque : Un module est un ensemble de structure de données et de fonctions. Les bibliothèques sont des modules.

Programmer par fonctions (cad la programmation procédurale) permet :

  • de réutiliser du code, ce qui a pour effet la réduction de la taille du code source et un gain en localité des modifications, donc une amélioration de la maintenabilité (compréhension plus rapide, réduction du risque de régression)
  • d’améliorer la clarté et la lisibilité du programme
  • de fournir un point de vue logique pour suivre plus facilement l’exécution du programme
  • de dissimuler les détails d’un traitement qu’il n’est pas indispensable de connaître

Remarque : Les programmes sont généralement plus facile à écrire, à comprendre et à maintenir lorsque chaque fonction réalise une SEULE ACTION logique et bien évidemment celle qui correspond à son nom.

Ici, les fonctions de la bibliotèque logicielle ASF, en masquant l’utilisation des registres des circuits de la carte, rendent plus facile la programmation.

Manipulations

Commander une sortie numérique

On désire allumer et éteindre la LED bleue de la carte Atmel SAM4S :

La LED Bleue est reliée sur la sortie PA19 du PIOA.

Pour :

  • allumer la led, il faut appliquer un niveau bas sur la sortie (registre CODR)
  • éteindre la led, il faut appliquer un niveau haut sur la sortie (registre SODR)

On crée une première version du programme principal qui utilise les directement registres du PIOA :

$ vim main.c
// Commande la LED Bleue de la carte Atmel SAM4S 
// à partir des registres du PIOA
#include <asf.h>

int main (void)
{
   my_init();

   // Activer la sortie associée à la LED bleue
   PIOA->PIO_OER = PIO_PA19; /**< \brief PIO_OER (Pio Offset: 0x0010) Output Enable Register */
    
   // Allumer la LED bleue    
   PIOA->PIO_CODR = PIO_PA19; /**< \brief PIO_CODR (Pio Offset: 0x0034) Clear Output Data Register */
    
   // Attendre 1000 ms
   mdelay(1000);
    
   // Eteindre la LED bleue
   PIOA->PIO_SODR = PIO_PA19; /**< \brief PIO_SODR (Pio Offset: 0x0030) Set Output Data Register */ 

   return 0;
}

Remarque : l’étiquette PIO_PA19 est définie dans le fichier /usr/local/asf/sam/utils/cmsis/sam4s/include/pio/pio_sam4s16c.h et correspond à la broche 19 du PIOA soit (1 << 19). Il en existe pour chaque broche et cela permet de les manipuler facilement dans des masques.

Code source : tp1-led-bleue.zip

Test :

$ make
$ ./run.sh xxx_flash.elf 

On crée une deuxième version qui utilise l’API IOPORT de la bibliothèque ASF :

$ vim main.c
// Commande la LED Bleue de la carte Atmel SAM4S 
// à partir des fonctions ASF
#include <asf.h>

int main (void)
{
   my_init();

   // Allumer la LED bleue    
   ioport_set_pin_level(LED0_GPIO, IOPORT_PIN_LEVEL_LOW);

   // Attendre 1000 ms
   mdelay(1000);
    
   // Eteindre la LED bleue
   ioport_set_pin_level(LED0_GPIO, IOPORT_PIN_LEVEL_HIGH);

   // Attendre 1000 ms
   mdelay(1000);
    
   // Inverser l'état de la LED bleue
   ioport_toggle_pin_level(LED0_GPIO);

   return 0;
}

Remarque : l’appel board_init() se charge d’initialiser les 2 LEDs en appelant les fonctions ioport_init() et gpio_configure_pin().

À vous de jouer :

  1. Écrire les deux versions du programme pour commander la LED verte (PA20 du PIOA)

  2. Écrire un programme qui permet de faire clignoter les deux LEDs

Lecture d’une entrée numérique

On désire compter le nombre d’appui sur le bouton USRPB1 de la carte Atmel SAM4S :

Le bouton USRPB1 est relié sur l’entrée PB3 du PIOB.

Pour lire simplement l’état d’une entrée, il suffit de faire :

// Désactive la sortie associée au bouton 1 pour la mettre en entrée
PIOB->PIO_ODR = PIO_PB3;

// Affiche l'état 0 ou 1 du bouton 1 à partir du registre PDSR
printf("PIOB PDSR : %ld\r\n", ((PIOB->PIO_PDSR & PIO_PB3) >> 3));

La lecture d’une seule entrée est basée sur une opération de masque (ET bit à bit) :

On va gérer le comptage par interruption sur front descendant du bouton USRPB1.

Remarque : La gestion des interruptions sur le PIOB nécessite d’activer l’horloge du PMC (Power Management Controller) associé à ce périphérique.

On crée une première version du programme principal qui utilise les directement registres du PIOB :

$ vim main.c
// Comptage des appuis sur le bouton USRPB1 de la carte Atmel SAM4S 
// à partir des registres du PIOB
#include <asf.h>

volatile uint32_t appui_bouton_1 = 0;

int main (void)
{
    my_init();
   
    // Désactive la sortie associée au bouton 1 pour la mettre en entrée
    PIOB->PIO_ODR = PIO_PB3;
    
    // Active l'horloge associé au PIOB par le registre PCER0 (Peripheral Clock Enable Register 0)
    PMC->PMC_PCER0 = 1 << ID_PIOB;
    
    // Active la détection de front pour le bouton 1
    PIOB->PIO_ESR = PIO_PB3;
    
    // Active le déclenchement d'une interruption sur l'appui
    PIOB->PIO_FELLSR = PIO_PB3; // front descendant
    //PIOB->PIO_REHLSR = PIO_PB3; // front montant
    
    // Active le filtre anti-rebond pour le bouton 1
    PIOB->PIO_IFSCER = PIO_PB3;
    
    // Active la résistance de pull-up
    PIOB->PIO_PUER = PIO_PB3;
    
    // Active le filtre
    PIOB->PIO_IFER = PIO_PB3;
  
    // Active la détection par front 
    PIOB->PIO_AIMER = PIO_PB3; // cf. PIO_ELSR and PIO_FRLHSR
    //PIOB->PIO_AIMDR = PIO_PB3; // double détection (both-edge detection)

    // Active les interruptions pour le PIOB
    NVIC_EnableIRQ(PIOB_IRQn);

    // Active la gestion d'interruption sur le bouton 1
    PIOB->PIO_IER = PIO_PB3;
    
    while (1) 
    {
        printf("Nb appui bouton 1 : %ld\r\n", appui_bouton_1);      
        mdelay(500); /* Attend 500ms */
    }

    return 0;
}

Le gestionnaire d’interruption à définir est void PIOB_Handler(void) :

void PIOB_Handler(void)
{
    uint32_t status;

    status = PIOB->PIO_ISR & PIOB->PIO_IMR;
    //printf("PIOB status : %ld\r\n", status);
    //printf("PIOB ISR : %ld\r\n", PIOB->PIO_ISR);
    //printf("PIOB IMR : %ld\r\n", PIOB->PIO_IMR);
    if((status & PIO_PB3) == PIO_PB3)
    {
        puts("Appui bouton 1\r");
        appui_bouton_1++;
    }
}

Attention : Il faut retirer de la compilation (cf. config.mk) le fichier source sam/drivers/pio/pio_handler.c car celui-ci définit déjà un gestionnaire void PIOB_Handler(void) par défaut.

Test :

$ make
$ ./run.sh xxx_flash.elf 

On crée une deuxième version qui utilise l’API PIO de la bibliothèque ASF :

$ vim main.c
// Comptage des appuis sur le bouton USRPB1 de la carte Atmel SAM4S 
// à partir des fonctions ASF
#include <asf.h>

/** Niveau de priorité IRQ (plus la valeur est basse, plus la priorité est grande) */
#define IRQ_PRIOR_PIO    0

volatile uint32_t appui_bouton_1 = 0;

int main (void)
{
    my_init();
    
    // Active l'horloge associé au PIOB par le registre PCER0 (Peripheral Clock Enable Register 0)
    pmc_enable_periph_clk(PIN_PUSHBUTTON_1_ID);
    
    // Active le filtre anti-rebond pour le bouton 1
    pio_set_debounce_filter(PIN_PUSHBUTTON_1_PIO, PIN_PUSHBUTTON_1_MASK, 10);
    
    // Active le déclenchement d'une interruption sur l'appui du bouton 1
    pio_handler_set(PIN_PUSHBUTTON_1_PIO, PIN_PUSHBUTTON_1_ID, PIN_PUSHBUTTON_1_MASK, PIN_PUSHBUTTON_1_ATTR, Bouton1_Handler);
    
    // Active les interruptions pour le PIOB
    NVIC_EnableIRQ((IRQn_Type) PIN_PUSHBUTTON_1_ID);
    
    // Fixe un niveau de priorité
    pio_handler_set_priority(PIN_PUSHBUTTON_1_PIO, (IRQn_Type)PIN_PUSHBUTTON_1_ID, IRQ_PRIOR_PIO);
    
    // Active les interruptions pour le bouton 1
    pio_enable_interrupt(PIN_PUSHBUTTON_1_PIO, PIN_PUSHBUTTON_1_MASK);
    
    while (1) 
    {
        printf("Nb appui bouton 1 : %ld\r\n", appui_bouton_1);      
        mdelay(500); /* Attend 500ms */
    }

    return 0;
}

Attention : Il faut maintenant ajouter sam/drivers/pio/pio_handler.c dans la liste des fichiers à compiler (cf. config.mk) car celui-ci définit la fonction pio_handler_set() utilisée dans cette version.

Le gestionnaire d’interruption à définir est void Bouton1_Handler() :

static void Bouton1_Handler(uint32_t id, uint32_t mask)
{
    if (PIN_PUSHBUTTON_1_ID == id && PIN_PUSHBUTTON_1_MASK == mask) 
    {
        puts("Appui bouton 1\r");
        appui_bouton_1++;
    }
}

Test :

$ make
$ ./run.sh xxx_flash.elf 

À vous de jouer :

  1. Écrire les deux versions du programme de test pour le bouton USRPB2 (PC12 du PIOC)

  2. Écrire un programme qui permet de faire clignoter la LED Bleue à partir du bouton USRPB1 (marche/arrêt) et la LED verte à partir du bouton USRPB2 (marche/arrêt)

Travail demandé

  1. À partir de la documentation, rechercher une entrée et une sortie numériques de libre sur les ports PIO A, B et C.

  2. Cabler un montage exploitant une entrée et une sortie numériques.

  3. Écrire un programme de démonstration du montage réalisé.

Bilan

Vous devez être capable de répondre aux questions suivantes :

  1. Définir une E/S numérique, le rôle d’un PIO.

  2. Définir le terme de registre.

  3. Décrire la méthode de programmation d’une E/S numérique.

  4. Quel est le nom du registre qui permet de lire le niveau d’une broche du PIO ?

  5. Décrire le principe de gestion d’une interruption matérielle ?

  6. À quoi sert une résistance de pull-up ? Qu’est-ce qu’une sortie à collecteur ouvert ?

  7. Qu’est-ce qu’un front descendant ou montant ? un niveau haut ou bas ?

  8. À quoi sert la résistance en série avec la LED ?

  9. Quel est le front généré par l’appui sur le bouton USRPB1 ?

  10. Pourquoi est-il habituel d’activer une sortie numérique par un niveau bas (0 actif en logique négative) ?

Glossaire

UART
UART (Universal Asynchronous Receiver Transmitter) est le composant utilisé pour faire la liaison entre l’ordinateur et le port série. L’ordinateur envoie les données en parallèle (autant de fils que de bits de données). Il faut donc transformer ces données pour les faire passer à travers une liaison série qui utilise un seul fil pour faire passer tous les bits de données.
Trame UART
Une trame UART est constituée des bits suivants : un bit de start toujours à 0 (servant à la synchronisation du récepteur), les données (la taille peut varier généralement entre 5 et 9 bits), éventuellement un bit de parité paire ou impaire et un bit de stop toujours à 1 (la durée peut varier entre 1, 1,5 et 2 temps bit). Les vitesses de transmission sont normalisées par multiples et sous-multiples de 9600 baud, l’unité baud correspondant à un bit par seconde.
Information numérique
Elle se caractérise par une grandeur physique qui ne peut prendre qu’un nombre fini de valeurs. On distingue généralement deux états (vrai ou faux) ou deux niveaux : niveau haut ou ‘1’ et niveau bas ou ‘0’. Les niveaux logiques générés par les composants électroniques possèdent en réalité des valeurs de tension définies par les paramètres VOH (High Output Voltage) ou « tension à l’état haut » et VOL (Low Output Voltage) ou « tension à l’état bas ». Les E/S numériques (ou digitales) sont aussi nommées E/S TOR (Tout Ou Rien). Lire : cours_capteurs.pdf
TOR
Signal à deux états stables représentant l’état d’une E/S ou d’un organe (capteur/actionneur), par exemple : vanne ouverte ou fermée, capteur fin de course activée ou non …
MCU
MCU (MicroController Unit) est un microcontrôleur (µc ou uc). C’est un circuit intégré qui rassemble les éléments essentiels d’un ordinateur : processeur, mémoires (mémoire morte pour le programme, mémoire vive pour les données), unités périphériques et interfaces d’entrées-sorties. Les microcontrôleurs se caractérisent par un plus haut degré d’intégration, une plus faible consommation électrique, une vitesse de fonctionnement plus faible et un coût réduit par rapport aux microprocesseurs polyvalents utilisés dans les ordinateurs personnels. Les microcontrôleurs sont fréquemment utilisés dans les systèmes embarqués. Lire : https://fr.wikipedia.org/wiki/Microcontr%C3%B4leur.
PIO
PIO (Parallel Input Output) est un port parallèle d’entrée/sortie. Chaque ligne peut être utilisée comme une entrée ou une sortie standard.
Registre
Un registre est un emplacement de mémoire interne à un circuit intégré (un processeur, microcontrôleur, …). Leur taille est limitée généralement 8, 16, 32 ou 64 bits. Sur certains circuits complexes, leur capacité peut atteindre quelques dizaines d’octets. On y accède comme une case mémoire en les manipulant très souvent à partir de masque. Historiquement, les registres étaient réalisés à partir de bascules D.
Interruption
Une interruption matérielle (Interrupt ReQuest ou IRQ) est une interruption déclenchée par un périphérique d’entrée-sortie d’un microprocesseur ou d’un microcontrôleur. Les interruptions matérielles sont utilisées en informatique lorsqu’il est nécessaire de réagir en temps réel à un événement asynchrone, ou bien, de manière plus générale, afin d’économiser le temps d’exécution lié à une boucle de consultation (polling loop). L’autre sorte d’interruption est l’interruption logicielle (software interrupt), généralement déclenchée par une instruction spéciale du processeur. Les deux sortes d’interruptions déclenchent un basculement de contexte vers le gestionnaire d’interruption (handler) associé.
Horloge
En électronique, et particulièrement en électronique numérique, un signal d’horloge est un signal électrique oscillant qui rythme les actions d’un circuit. Sa période est appelée cycle d’horloge. À chaque cycle d’horloge, des opérations peuvent être effectués et certains calculs nécessitent plusieurs cycles d’horloge. Il est généralement représenté par un signal carré périodique. On considère généralement qu’un circuit est d’autant plus rapide que la fréquence du signal d’horloge qui le synchronise est élevée.
Résistance de rappel
Une résistance de rappel ou une résistance de tirage (pull-up ou pull-down) est une résistance dans un circuit électronique, située entre la source d’alimentation et une ligne, et qui amène délibérément cette même ligne soit à l’état haut (1 en électronique numérique) pour une résistance de tirage, soit à l’état bas (0 logique) pour une résistance de rappel. Les résistances de rappel sont normalement utilisées avec des sorties numériques à collecteur ouvert ou lorsqu’il est indésirable de laisser une ligne flottante dans un circuit.
Collecteur ouvert
Le collecteur ouvert est un type de sortie de circuit intégré logique de technique bipolaire. Le terme équivalent drain ouvert est utilisé dans le cas de technique MOS.

Annexe : les fonctions my_init() et mdelay()

La fonction my_init() fournie permet d’initialiser la carte (board_init()), le système d’horloge (sysclk_init()) et le port série UART (configure_uart()). Une fonction mdelay() fournit un moyen de réaliser des temporisations d’attente en millisecondes.

#define STRING_EOL    "\r"
#define STRING_HEADER \
        "-- Exemple LED Bleue - "BOARD_NAME" --\r\n" \
        "-- "__DATE__" "__TIME__" --"STRING_EOL

/** UART Interface */
#define CONF_UART            CONSOLE_UART
#define CONF_UART_BAUDRATE   115200
#define CONF_UART_PARITY     UART_MR_PAR_NO
#define CONF_UART_CHAR_LENGTH  US_MR_CHRL_8_BIT
#define CONF_UART_STOP_BITS    US_MR_NBSTOP_1_BIT

volatile uint32_t g_ul_ms_ticks = 0;

static void my_init(void);
static void configure_uart(void);
static void mdelay(uint32_t ul_dly_ticks);

/**
 *  Initialise
 */
static void my_init(void)
{
    board_init();
    sysclk_init();

    /* Initialise le port uart */
    configure_uart();

    /* Envoie une chaîne de caractères */
    puts(STRING_HEADER);
    
    /* Configure les tics à 1 ms */
    if (SysTick_Config(sysclk_get_cpu_hz() / 1000)) 
    {
        puts("Erreur systick configuration\r");
        while (1);
    }
}

/**
 *  \brief Gestionnaire d'évènement pour les tics d'interruptions système
 *
 *  Incremente le compteur g_ul_ms_ticks
 *  
 */
void SysTick_Handler(void)
{
    g_ul_ms_ticks++;
}

/**
 * \brief Temporisation d'attente en millisecondes (basée sur les tics systèmes du microcontrôleur)
 *
 * \param ul_dly_ticks  temps d'attente en millisecondes
 */
static void mdelay(uint32_t ul_dly_ticks)
{
    uint32_t ul_cur_ticks;

    ul_cur_ticks = g_ul_ms_ticks;
    while ((g_ul_ms_ticks - ul_cur_ticks) < ul_dly_ticks);
}

/**
 *  Configure UART.
 */
static void configure_uart(void)
{
    const usart_serial_options_t uart_serial_options = 
    {
        .baudrate = CONF_UART_BAUDRATE,
        .charlength = CONF_UART_CHAR_LENGTH,
        .paritytype = CONF_UART_PARITY,
        .stopbits = CONF_UART_STOP_BITS,
    };

    sysclk_enable_peripheral_clock(CONSOLE_UART_ID);
    stdio_serial_init(CONF_UART, &uart_serial_options);
}

Retour au sommaire