Initiation aux ports GPIO

publication: 23 décembre 2023 / mis à jour 23 décembre 2023

Read this page in english

 


Tous les apprentis programmeurs sont pressés de tester leur carte. L’exemple le plus trivial consiste à faire clignoter une LED. Il se trouve qu’il y a une LED déjà montée sur la carte RP pico, LED associée au port PIO 25. Voici le code qui vous permettra de faire clignoter cette LED avec eForth:

: blink ( -- ) 
    5 $400140CC ! 
    $02000000 $D0000024 ! 
    begin 
        $02000000 $D000001C ! 
        300 ms 
    key? until 
  ; 

Vous pouvez copier ce code et le transmettre à la carte RP pico par l’intermédiaire du terminal TeraTem. Ca fonctionnera.

Une fois ceci testé, on n’a rien expliqué. Car si vous souhaitez maîtriser les ports d’entrée/sortie, il faut commencer par comprendre ce que ce code est censé faire.

Les ports GPIO

Le Raspberry Pi Pico possède 30 broches GPIO, dont 26 sont utilisables.

il y a aussi:

La carte comporte une LED embarquée, reliée au GPIO 25. Elle permet de vérifier le bon fonctionnement de la carte ou tout autre utilisation à votre convenance.

Pour la suite de ce chapitre, nous n’allons voir que le cas de la LED implantée sur la carte RP pico et reliée au GPIO 25.

GPIO et registres

La carte RP pico dispose d’un micro-contrôleur RP2040 dont les données techniques sont accessibles dans ce document au format pdf:
https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf

Le contenu de ce document est très technique et peut rebuter un amateur. On va reprendre notre exemple donné en début de chapitre pour expliquer pas à pas ce qui le fait fonctionner, en associant ces informations au contenu du document « RP2040 datasheet ».

Commençons par expliquer ce qu’est un registre de micro-contrôleur.

Un registre est tout simplement une zone de mémoire accessible par une adresse, mais dont l’usage est réservé au fonctionnement du micro-contrôleur. Si on écrit n’importe quoi dans un registre, au mieux on provoque des dysfonctionnements, au pire, on bloque le micro-contrôleur. Si ça se produit, il faudra mettre la carte RP pico hors tension pour la réinitialiser.

eForth accède aux adresses 32 bits avec les mots ! Et @. Exemple:

0 variable myScore 
3215 myScore ! 
myScore @ .    \ display 3215 

Ici, on définit une variable myScore. Quand on tape le nom de cette variable, on empile en fait l’adresse mémoire pointant sur le contenu de cette variable:

myScore  .    \ display adress of myScore 

Manipulation des données vers cette adresse:

Si l’adresse déposée sur la pile de données par myScore est addr (par exemple $20013D48), on peut remplacer myScore @ par addr @.

Pour accéder aux données d’un registre, ce n’est pas très différent. Prenons le cas de ce registre:

: blink ( -- ) 
    5 $400140CC ! 
    $02000000 $D0000024 ! 
    begin 
        $02000000 $D000001C ! 
        300 ms 
    key? until 
  ; 

Ici, son adresse mémoire est $D0000024. La séquence $02000000 $D0000024 ! stocke simplement la valeur $02000000 dans l’adresse mémoire $D0000024.

Dans le document document «RP2040 datasheet», cette adresse a comme label GPIO_OE_SET. On peut donc rendre notre code FORTH un peu plus lisible en définissant ce label comme constante FORTH:

$d0000024 constant GPIO_OE_SET 
 
: blink ( -- ) 
    5 $400140CC ! 
    $02000000 GPIO_OE_SET ! 
    begin 
        $02000000 $D000001C ! 
        300 ms 
    key? until 
  ; 

Avec l’habitude, on append à déchiffrer les labels. Ici, GPIO_OE_SET signifie quasiment «GPIO Output Enable SET» (Valide Position Sortie GPIO).

Utiliser les labels de registres

Le label GPIO_OE_SET est lui-même un sous-ensemble des registres définis dans le label global SIO_BASE. Dans la documentation technique, dans la première colonne mentionnant le label GPIO_OE_SET on trouve le décalage (offset) par rapport à SIO_BASE. On va tenir compte de ceci pour réécrire une partie du code FORTH. On en profite pour définir deux autres labels comme constantes:

$d0000000 constant SIO_BASE 
SIO_BASE $020 + constant GPIO_OE 
SIO_BASE $024 + constant GPIO_OE_SET 
SIO_BASE $028 + constant GPIO_OE_CLR 

A cette étape, notre code FORTH reste encore dépendant du GPIO25 au travers de ce masque:

: blink ( -- ) 
    5 $400140CC ! 
    $02000000 GPIO_OE_SET ! 
    begin 
        $02000000 $D000001C ! 
        300 ms 
    key? until 
  ; 

On définir une constante reprenant notre numéro de GPIO:

25 constant ONBOARD_LED 

Et un mot effectuant la transformation de ce numéro en un masque binaire:

: PIN_MASK ( n -- mask ) 
    1 swap lshift 
  ; 

Au passage, on va aussi définir une constante correspondant au label ayant l’adresse $D000001C:

SIO_BASE $01c + constant GPIO_OUT_XOR 

Ce registre bascule l’état du GPIO pointé par son masque binaire. Voici le code de blink intégrant ces nouveaux labels:

: blink ( -- ) 
    5 $400140CC ! 
    ONBOARD_LED PIN_MASK GPIO_OE_SET ! 
    begin 
        ONBOARD_LED PIN_MASK GPIO_OUT_XOR ! 
        300 ms 
    key? until 
  ; 

On va maintenant factoriser la ligne de code qui est en rouge ci-dessus. Au passage, on va rajouter les autres labels associés à GPIO_OUT:

SIO_BASE $010 + constant GPIO_OUT 
SIO_BASE $014 + constant GPIO_OUT_SET 
SIO_BASE $018 + constant GPIO_OUT_CLR 
: led.toggle ( gpio -- ) 
    PIN_MASK GPIO_OUT_XOR ! 
  ; 

Vous commencez à comprendre que le but est de rendre le code FORTH de plus en plus lisible. Mais il faut aussi que ce code puisse être réutilisable ailleurs. On va traiter cette ligne de code par une refactorisation:

: blink ( -- ) 
    5 $400140CC ! 
    ONBOARD_LED PIN_MASK GPIO_OE_SET ! 
    begin 
        ONBOARD_LED led.toggle 
        300 ms 
    key? until 
  ; 

On crée le mot gpio_set_dir. Le nom de ce mot reprend celui d’une fonction en langage C équivalente:

1 constant GPIO-OUT 
0 constant GPIO-IN 
 
\ set direction for selected gpio 
: gpio_set_dir  ( gpio state -- ) 
    if     PIN_MASK GPIO_OE_SET ! 
    else   PIN_MASK GPIO_OE_CLR !    then 
  ; 

Notre mot gpio_set_dir agit sur deux registres.

Fonctionnement des registres associés à GPIO_OUT

Détaillons ces registres en se référant au document technique:

Détail de ces registres:

Pour allumer la LED de la carte RP pico attachée au GPIO 25:

ONBOARD_LED PIN_MASK GPIO_OUT_SET ! 

Pour éteindre cette même LED:

ONBOARD_LED PIN_MASK GPIO_OUT_CLR ! 

Si nous n’avions disposé que du seul registre GPIO_OUT, la manipulation des bits serait nettement plus complexe:

GPIO_OUT @ 
ONBOARD_LED PIN_MASK xor GPIO_OUT ! 

Avec nos trois autres registres GPIO_OUT_SET, GPIO_OUT_CLR et GPIO_OUT_XOR, il n’est pas nécessaire de récupérer l’état des autres GPIOs avant de modifier l’état de un ou plusieurs GPIOs.

On peut donc définir un nouveau mot gpio_put:

1 constant GPIO_HIGH    \ set GPIO state 
0 constant GPIO_LOW     \ set GPIO state 
 
\ set GPIO on/off 
: gpio_put ( gpio state -- ) 
    if     PIN_MASK GPIO_OUT_SET ! 
    else   PIN_MASK GPIO_OUT_CLR !    then 
  ; 

Gestion de l’utilisation des GPIOs

Revenons au code source de blink. Il y a encore un registre qui n’est pas traité:

: blink ( -- ) 
    5 $400140CC ! 
    ONBOARD_LED GPIO_OUT gpio_set_dir 
    begin 
        ONBOARD_LED led.toggle 
        300 ms 
    key? until 
  ; 

L’adresse $400140CC correspond au registre GPIO25_CTRL.

Extrait du manuel technique:

Dans la colonne de gauche, un décalage $0CC. Ce décalage doit s’appliquer au registre de base IO_BANK0_BASE que l’on définit ainsi en FORTH:

$40014000 constant IO_BANK0_BASE 
On définit maintenant le mot GPIO_CTRL qui se férère à cette adresse de base : 
: GPIO_CTRL ( n -- addr ) 
    8 * 4 + IO_BANK0_BASE + 
  ; 

Si on exécute ceci:

ONBOARD_LED  GPIO_CTRL 

On récupère bien l’adresse physique du registre GPIO25_CTRL. Les cinq bits de poids faible de cette adresse déterminent la fonction d’un GPIO. Liste des fonctions possibles:

 1 constant GPIO_FUNC_SPI 
 2 constant GPIO_FUNC_UART 
 3 constant GPIO_FUNC_I2C 
 4 constant GPIO_FUNC_PWM 
 5 constant GPIO_FUNC_SIO 
 6 constant GPIO_FUNC_PIO0 
 7 constant GPIO_FUNC_PIO1 
 8 constant GPIO_FUNC_GPCK 
 9 constant GPIO_FUNC_USB 
$f constant GPIO_FUNC_NULL 

La fonction à affecter à notre port GPIO25 est la fonction

GPIO_FUNC_SIO

. Cette fonction indique que notre port GPIO25 sera utilisé simplement en entrée/sortie.

On crée le mot gpio_set_function chargé de sélectionner la fonction de notre port GPIO:

: gpio_set_function ( gpio function -- ) 
    swap GPIO_CTRL ! 
  ; 

En langage C, dans le SDK de Raspberry pico, on retrouve la même fonction gpio_set_function(). Notre mot FORTH gpio_set_function a repris les paramètres dans le même ordre que la fonction équivalente en langage C. Ce n’est pas obligatoire. En FORTH, on fait ce qu’on veut. Ici, l’intéret est de reprendre la méthodologie appliquée au SDK écrit en langage C, car c’est une source d’information qui n’est pas à négliger.

Pour finir, voici le code final qui permet de faire clignoter notre LED:

$D0000000 constant SIO_BASE 
SIO_BASE $020 + constant GPIO_OE 
SIO_BASE $024 + constant GPIO_OE_SET 
SIO_BASE $028 + constant GPIO_OE_CLR 
 
SIO_BASE $010 + constant GPIO_OUT 
SIO_BASE $014 + constant GPIO_OUT_SET 
SIO_BASE $018 + constant GPIO_OUT_CLR 
SIO_BASE $01c + constant GPIO_OUT_XOR 
 
25 constant ONBOARD_LED 
 
\ transform GPIO number in his binary mask 
: PIN_MASK ( n -- mask ) 
    1 swap lshift 
  ; 
 
1 constant GPIO-OUT   \ set direction OUTput mode 
0 constant GPIO-IN    \ set direction INput mode 
 
\ set direction for selected gpio 
: gpio_set_dir  ( gpio direction -- ) 
    if     PIN_MASK GPIO_OE_SET ! 
    else   PIN_MASK GPIO_OE_CLR !    then 
  ; 
 
1 constant GPIO_HIGH    \ set GPIO state 
0 constant GPIO_LOW     \ set GPIO state 
 
\ set GPIO on/off 
: gpio_put ( gpio state -- ) 
    if     PIN_MASK GPIO_OUT_SET ! 
    else   PIN_MASK GPIO_OUT_CLR !    then 
  ; 
 
\ toggle led 
: led.toggle ( gpio -- ) 
    PIN_MASK GPIO_OUT_XOR ! 
  ; 
 
$40014000 constant IO_BANK0_BASE 
 
: GPIO_CTRL ( n -- addr ) 
    8 * 4 + IO_BANK0_BASE + 
  ; 
 
 1 constant GPIO_FUNC_SPI 
 2 constant GPIO_FUNC_UART 
 3 constant GPIO_FUNC_I2C 
 4 constant GPIO_FUNC_PWM 
 5 constant GPIO_FUNC_SIO 
 6 constant GPIO_FUNC_PIO0 
 7 constant GPIO_FUNC_PIO1 
 8 constant GPIO_FUNC_GPCK 
 9 constant GPIO_FUNC_USB 
$f constant GPIO_FUNC_NULL 
 
: gpio_set_function ( gpio function -- ) 
    swap GPIO_CTRL ! 
  ; 
 
: blink ( -- ) 
    ONBOARD_LED GPIO_FUNC_SIO gpio_set_function 
    ONBOARD_LED GPIO-OUT gpio_set_dir 
    begin 
        ONBOARD_LED led.toggle 
        300 ms 
    key? until 
  ; 

Il est évident que ça fait beaucoup de code juste pour faire clignoter une LED. A contrario, les modifications successives ont permis de rajouter des définitions d’usage général, comme gpio_set_function, gpio_put ou gpio_set_dir.

L’exemple de notre mot blink a également permis d’apprendre comment fonctionnent les registres GPIOs. Nous n’avons fait que survoler les incroyables possibilités de la carte RP pico. Tout ce codage, juste pour faire clignoter une LED, permetr de poser les premières pierres d’un immense édifice.


Legal: site web personnel sans commerce / personal site without seling