Les GPIO

Introduction

Le premier périphérique qu'on met en marche traditionnellement quand on démarre une carte en bare-metal est le contrôleur de GPIO, de façon à pouvoir allumer / éteindre une LED. Les GPIO sont des broches du processeur qu'on peut configurer à volonté en entrée ou en sortie. Lorsqu'elles sont en entrée, on peut lire leur état dans un registre spécial. Lorsqu'elles sont en sortie, on peut les mettre à l'état haut ou bas, en écrivant dans un registre spécial.

La plupart des GPIO peuvent avoir des configurations supplémentaires :

  • on peut leur demander de générer une interruption si le signal qui arrive dessus change d'état, ou fait un front montant ou un front descendant,
  • on peut configurer l'intensité qu'elles peuvent débiter (ce qu'on appelle le slew-rate), ce qui influe sur leur vitesse de commuttation,
  • activer des résistances de pull-up / pull-down,
  • etc.

Multiplexage des fonctionnalités des broches

Un microntrôleur n'a qu'un nombre limité de broches. Pas assez pour pouvoir utiliser tous les périphériques internes. La plupart des broches peuvent donc être configurées pour être utilisées comme des GPIO, port série, port I2C, port SPI, pour USB, etc. Les broches sont aussi groupées par "ports", groupes de 16 broches, pour faciliter leur repèrage. Par exemple, la broche PA5 est la broche 5 du port A

Notre processeur dispose de neuf ports : GPIOA à GPIOI, mais, en fonction des boîtiers, seulement certaines broches sont accessibles. 

Certains contrôleurs permettent à chaque broche d'être configurée pour servir à n'importe quel périphérique (exemple : nRF52840, ESP32), mais ce n'est pas le cas du nôtre. La table 17 de la datasheet du processeur (pages 69 et suivantes) liste, pour chaque broche, les fonctions disponibles dessus.

La fonction qui sera utilisée sur une broche particulière est déterminée par plusieurs registres. Le registre GPIOx_MODER (cf. manuel de référence page 303) détermine si la broche est :

  • une GPIO en entrée,
  • une GPIO en sortie ou bidirectionnelle,
  • une fonction analogique,
  • une fonction spéciale (I2C, SPI, …). Dans ce cas, les registres GPIOx_AFRL (pour les broches de 0 à 7) et GPIOx_AFRH (pour les broches de 8 à 15) déterminent la fonction en question. Cf. manuel de référence page 307 pour le layout de ces registres, et la datasheet table 17 page 69 pour la liste des fonctions spéciales ("Alternate Functions").

(x = AI dans les lignes ci-dessus en dessous)

Exemples

  • Pour utiliser la broche PA5 comme une GPIO en entrée, on écrira 00 dans les bits [11:10] de GPIOA_MODER.
  • Pour utiliser la même broche comme une GPIO en sortie, on écrira 01 dans les bits [11:10] de GPIOA_MODER
  • Pour utiliser la même broche comme le fil d'horloge du bus SPI1 (Alternate Function 5), on écrira 10 dans les bits [11:10] de GPIOA_MODER, et 0101 dans les bits [23:20] de GPIOA_AFRL.

Pour l'instant, nous allons piloter une seule LED, verte (LED2), branchée sur la broche PB14. Pour allumer cette LED, il faut mettre la broche PB14 à l'état haut, et pour l'éteindre la mettre à l'état bas.

Économie d'énergie : clock gating

Les Cortex-M sont des processeurs faits spécialement pour les applications à faible, voire très faible, consommation. Pour réduire la consommation au minimum, l'utilisateur a la possibilité d'arrêter l'horloge de chaque périphérique : sa consommation devient donc nulle. Par défaut, tous les périphériques ont leur horloge arrêtée. Si on veut en utiliser un, il faut donc d'abord activer son horloge.

Les horloges sont activées dans le module RCC, dans l'un des registres RCC_AHBxENRRCC_APB1ENRx ou RCC_APB2ENR. Voir reference manual, section 6.4.16 (page 249) et suivantes.  

Au travail !

On va déjà commencer par organiser les fichiers de façon propre : chaque périphérique aura son propre fichier source, avec le header associé dans lequel il spécifie les fonctions / variables à exporter. Pour les LED, on créera donc les fichiers led.c et led.h.

Pour chaque périphérique, on créera une fonction d'initialisation (void led_init(void) pour les LED, etc.) qui se chargera d'initialiser le périphérique : activation de l'horloge associée, configuration diverse, etc.

Initialisation des GPIO LED

Écrivez une fonction void led_init(), qui 

  • active l'horloge du port B dans le registre RCC_*ENR* idoine,
  • configure la broche PB14 en mode sortie.

Les adresses des registres sont obtenues en additionnant une adresse de base (table 1 page 77 du manuel de référence) à un offset donné dans la partie "register map" de chaque périphérique. 

Écrivez ensuite les fonctions suivantes :

  • led_g_on() : allume la LED2
  • led_g_off() : éteint la LED2

Indice : la lecture de la documentation du registre GPIOx_BSRR pourra s'avérer utile !

Depuis main, appelez la fonction led_init(), puis faites clignoter la LED2 en faisant des boucles d'attente active ainsi (attention, c'est sale) :

for (int i=0; i<....; i++)
  asm volatile("nop");

On verra plus tard comment implémenter des délais de façon plus précise et (beaucoup) plus propre.

On complique

Vous disposez sur ces cartes de deux autres LED, une bleue (LED4) et une jaune (LED3), toutes les deux pilotées par la même broche du microcontrôleur, PC9.

  • Quand PC9 est en sortie à l'état haut, LED3 est allumée, LED4 est éteinte.
  • Quand PC9 est en sortie à l'état bas, LED3 est éteinte, LED4 est allumée.
  • Quand PC9 est en entrée (= haute impédance), les deux LED sont éteintes.

Et, pour l'instant, on n'a pas de moyen simple d'allumer les deux LED en même temps (on verra comment faire plus tard).

  1. Complétez la fonction led_init() pour pouvoir contrôler la broche PC9.
  2. Écrivez une fonction void led(state) avec state étant une constante (enum ou autre) qui vaut LED_OFF, LED_YELLOW ou LED_BLUE.
  3. Testez cette fonction dans votre main en faisant boucler les leds : vert - jaune - bleu - vert - jaune - bleu, etc.

Conclusion

Félicitations, vous avez mis en marche votre premier périphérique. Passons maintenant à quelque chose de plus complexe !
Mais auparavant, mettez le tag GPIO sur le commit prêt à être corrigé et pushez-le :)