Initiation aux ports GPIO
publication: 23 décembre 2023 / mis à jour 23 décembre 2023
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.
- 2x SPI
- 2 x UART
- 2 x I2C
- 8 x PWM à deux canaux
il y a aussi:
- 4 x sortie d'horloge à usage général
- 4 x entrée ADC
- PIO sur toutes les broches
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:
3215 myScore !
stocke une valeur à l’adresse mémoire réservée parmyScore
myScore @
récupère le contenu stocké à l’adresse mémoire réservée parmyScore
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:
GPIO_OUT
est accessible en lecture et écriture. La lecture de son contenu permet de récupérer l’état des GPIOs.GPIO_OUT_SET
est accessible en écriture seulement. Le positionnement de un ou plusieurs bits n’agit que sur les GPIOs indiqués dans le masque d’activation.GPIO_OUT_CLR
est accessible en écriture seulement. Le positionnement de un ou plusieurs bits n’agit que sur les GPIOs indiqués dans le masque de désactivation.GPIO_OUT_XOR
est accessible seulement en écriture. Bascule les bits à l’état actif vers les bits à l’état inactif – et inversement. Cette bascule n’agit que sur les GPIOs indiqués dans le masque d’inversion.
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