Les Périphériques


1 - Qu’est-ce qu’un périphérique ?

Jusqu’à présent nous avons considéré le processeur et sa mémoire.
Pour illustrer les notions d’architecture des microcprocesseurs, nous avons codé des programmes de calcul ou de traitement de chaines de caractère.

Nous allons maintenant voir comment le processeur peut interragir avec le monde extérieur, dans une application de robotique ou d’objet connecté.

Pour cela le processeur utilise des périphériques pour :

  • forcer un état logique d’une broche ou récupérer l’état d’un bouton poussoir : GPIO, EXTI
  • Mesurer ou générer des signaux analogiques : ADC, DAC
  • Se repérer dans le temps, générer un signal PWM : RTC, TIMERS
  • Communiquer :
    • avec un composant proche ( ex: capteur, écran ) : I2C, SPI, …
    • avec un autre processeur : UART, Bus CAN, Ethernet, USB, …

peripherique_f411.svg

Quels documents utiliser

Le Reference Manual ou User Manual d’un microcontrôleur détaille l’ensemble des composants le constituant. Ce document étant volumineux, il faut bien faire apparaitre l’index pour accéder à la documentation du périphérique étudié.

ref_man.png


2 - Dialogue entre un processeur et un périphérique

Il faut voir la programmation d’un périphérique comme un dialogue entre le processeur et ce périphérique.
Le périphérique contient des registres permettant de récéptionner des ordres depuis le périphérique, ou d’indiquer des états.

Prenons l’exemple d’un périphérique pour une liaison série :

  1. Le Processeur veut envoyer la valeur 45, il la copie donc dans le registre Data
  2. Le Processeur donne l’ordre au périphérique d’envoyer la donnée en écrivant 1 dans le registre de contrôle.
  3. Une fois la donnée envoyée, Le périphérique écrit 1 dans le registre Status afin d’indiquer qu’une autre donnée peut être transmise si nécessaire.

dialogue_peripherique.svg


3 - Accès aux registres des périphériques

Comme pour la mémoire, les liens entre un périphérique et le microcontrôleur sont les bus d’adresse et de données.

Si le processeur veut relever le registre Status du périphérique 0, il force la valeur 9 sur le bus d’adresses.

  • Les 2 bits de poids fort de l’adresse permettent de sélectionner le composant ‘périphérique 0’, via le signal CS
  • Les 2 bits de poids faible de l’adresse permettent alors de forcer le signal oe_SR à 1 pour autoriser l’écriture sur le bus de données.

Le contenu du registre Status est alors visible sur le bus de données et lisible par le processeur.

lien_peripherique.svg

L’accès au périphériques s’apparente donc à un accès à une case mémoire.
Le seule différence réside dans le fait que certains registres peuvent être accessibles uniquement en lecture ou en écriture.
Le schéma ci-dessus fait apparaitre la Memory Map, à savoir les plages d’adresse correspondant aux différents composants adressables ( mémoires ou périphériques ).

L’extrait ci dessous montre la Memory Map du microcontrôleur STM32F411 :

memory_map.svg


4 - Modèle de programmation pour accéder aux périphériques

Dès lors qu’accéder à un registre de périphérique revient à accéder à une case mémoire, il suffit donc de réaliser une opération de lecture ou d’écriture en envoyant la bonne adresse.

On considère le registre GPIOA_MODER ; pour accéder à ce registre, il faut envoyer l’adresse 0x40020000.

En assembleur, les opérations d’accès mémoires sont réalisées par ldr et str.

Je peux donc écrire :

#define GPIOA_MODER 0x40020000  
		...
		
		ldr r0,=GPIOA_MODER  // Chargement de l'adresse de GPIOA_MODER dans r0  
		ldr r1,[r0]			 // r1 <-*r0; lecture du registre GPIOA_MODER 
		...
		mov	r2,#1
		str r2,[r0]			 // écriture dans le registre GPIOA_MODER  
		... 

En langage C, cela donne :

#define GPIOA_MODER (*((volatile uint32_t *) 0x40020000 )) 

		uint32_t val = 0;
		
		val = GPIOA_MODER;     // Lecture du registre 
		GPIOA_MODER  = 0x1;	   // Ecriture dans le registre  

Quand dans mon code, j’utilise GPIOA_MODER, je peux le remplacer par *((volatile uint32_t *) 0x40020000 )
Je désigne donc le contenu d’une case mémoire de taille uint32_t d’adresse spécifique 0x40020000.
Le terme volatile indique au compilateur de ne pas faire d’optimisation ( remplacement d’un accès mémoire par un transfert de registre ).

Une autre solution ( que nous adopterons ) en langage C est d’utiliser des structures :

#define PERIPH_BASE           0x40000000U
#define AHB1PERIPH_BASE       (PERIPH_BASE + 0x00020000U)

#define GPIOA_BASE            (AHB1PERIPH_BASE + 0x0000U)
#define GPIOB_BASE            (AHB1PERIPH_BASE + 0x0400U)
#define GPIOC_BASE            (AHB1PERIPH_BASE + 0x0800U)
#define GPIOD_BASE            (AHB1PERIPH_BASE + 0x0C00U)
#define GPIOE_BASE            (AHB1PERIPH_BASE + 0x1000U)
#define GPIOH_BASE            (AHB1PERIPH_BASE + 0x1C00U)

typedef struct
{
  volatile uint32_t MODER;    /*!< GPIO port mode register,               Address offset: 0x00      */
  volatile uint32_t OTYPER;   /*!< GPIO port output type register,        Address offset: 0x04      */
  volatile uint32_t OSPEEDR;  /*!< GPIO port output speed register,       Address offset: 0x08      */
  volatile uint32_t PUPDR;    /*!< GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
  volatile uint32_t IDR;      /*!< GPIO port input data register,         Address offset: 0x10      */
  volatile uint32_t ODR;      /*!< GPIO port output data register,        Address offset: 0x14      */
  volatile uint32_t BSRR;     /*!< GPIO port bit set/reset register,      Address offset: 0x18      */
  volatile uint32_t LCKR;     /*!< GPIO port configuration lock register, Address offset: 0x1C      */
  volatile uint32_t AFR[2];   /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
} GPIO_TypeDef;


#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD               ((GPIO_TypeDef *) GPIOD_BASE)
		
  
		val = GPIOA->MODER;        // Lecture du registre 
		GPIOA->MODER  = 0x1;	   // Ecriture dans le registre  

La définition des adresses de tous les périphériques est faite dans le fichier stm32f411xe.h