Developpez.com - Raspberry Pi

Le Club des Développeurs et IT Pro

Raspberry Pi Pico - Après le 'Hello World', apprendre à piloter simultanément plusieurs GPIO,

Un billet blog de f-leb

Le 27/04/2023, par f-leb, Responsable Arduino et Systèmes Embarqués
Après avoir découvert la carte Raspberry Pi Pico et réalisé votre premier "Hello World !" (un blink, quoi !) avec le SDK C/C++ (voir le billet précédent), je vous propose de découvrir des fonctions plus avancées pour piloter simultanément plusieurs broches GPIO.

À titre de démonstration, je vous présenterai une petite application de pilotage d'un afficheur 7-segments à cathode commune :


Parenthèse... Arduino : avec le « langage Arduino », si l'on considère une broche configurée en sortie avec pinMode(pin, OUTPUT), vous passerez par un digitalWrite(pin, HIGH) ou digitalWrite(pin, LOW) pour mettre la broche (pin) respectivement à l'état haut ou à l'état bas. Mais si vous devez piloter plusieurs broches, vous ne pouvez le faire que séquentiellement, broche par broche, en multipliant les pinMode() et digitalWrite().
De façon plus astucieuse, vous parcourrez un tableau dans une boucle for, par exemple :
Code arduino :
1
2
3
4
5
6
int pins[] = {2, 3, 4, 5, 6, 7, 8, 9}; // tableau des broches 
// ... 
for (int i=0; i<8; i++) { 
  pinMode(pins[i], OUTPUT); // les broches du tableau configurées en sortie numérique... 
  digitalWrite(pins[i], HIGH); // ... initialement à l'état haut. 
}
Mais que se passe-t-il lorsque vous devez synchroniser deux signaux qui basculent d'état en écrivant :
Code arduino :
1
2
3
4
5
digitalWrite(8, LOW); 
digitalWrite(9, HIGH); 
// ... 
digitalWrite(8, HIGH); 
digitalWrite(9, LOW);
Quand la broche 8 passe à l'état haut, 9 doit passer à l'état bas... Mais dans des instructions séparées, il y aura une période de chevauchement (négligeable ?) où 8 et 9 sont tous les deux à l'état haut ! Cela n'a aucune importance en pratique si vous jouez avec des LED, mais en est-il pareil si vous manipulez les signaux de ponts pour contrôler des moteurs ?

Note : si l'API Arduino ne permet pas d'agir sur plusieurs broches simultanément, vous pouvez toujours le faire en attaquant directement les registres des ports d'E/S du microcontrôleur. Consultez le manuel pour l'ATmega328P de l'Arduino Uno.

... fin de la parenthèse.

Avec le SDK C/C++ de la Raspberry Pi Pico, vous pouvez agir simultanément sur plusieurs broches de la carte, en définissant un masque.
Sur la Raspberry Pi Pico, vous pouvez jouer avec 30 broches GPIO numérotées GP0 à GP29.
Le masque est donc une valeur entière définie sur 32 bits (uint32_t) où le bit 0 de poids faible concerne la broche GP0, le bit 1 pour la broche GP1, le bit 2 pour la broche GP2, etc. Tout simplement.

Si vous voulez par exemple attaquer les broches GP1, GP4 et GP8, on définira le masque :
Code C :
1
2
// broches GP1, GP4 et GP8 
uint32_t mask = (1 << 1) | (1 << 4) | (1 << 8);

Pour initialiser les broches, on écrira alors :
Code C :
gpio_init_mask(mask);

et pour les configurer toutes en sorties numériques :
Code C :
gpio_set_dir_out_masked(mask); // GP1, GP4 et GP8 = output

ou toutes en entrées numériques :
Code C :
gpio_set_dir_in_masked(mask); // GP1, GP4 et GP8 = input

En renseignant une valeur supplémentaire, on peut même choisir entre certaines broches configurées en entrées et d'autres en sorties :
Code C :
gpio_set_dir_masked(mask, value);
value est aussi un entier 32 bits, avec le bit de la broche à 0 (input) ou à 1 (output).

Pour des sorties numériques, vous pouvez mettre toutes les broches définies dans le masque à l'état haut (set) ou à l'état bas (clr) en une seule opération :
Code C :
1
2
gpio_set_mask(mask); // GP1, GP4 et GP8 à l'état haut  
gpio_clr_mask(mask); // GP1, GP4 et GP8 à l'état bas

Mais on peut aussi définir certaines broches à l'état haut et d'autres à l'état bas, toujours simultanément. Par exemple :
Code C :
1
2
3
4
5
6
uint32_t mask =  (1 << 1) | (1 << 4) | (1 << 8); // broches GP1, GP4 et GP8 
gpio_init_mask(mask); 
gpio_set_dir_out_masked(mask); // GP1, GP4 et GP8 = output 
  
uint32_t value = (1 << 1) | (1 << 4);   
gpio_put_masked(mask, value); // GP1 et GP4 à l'état haut, GP8 à l'état bas.

Ainsi, pour s'assurer que les états de deux signaux basculent simultanément, on écrira :
Code C :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
uint32_t mask =  (1 << 4) | (1 << 8); // broches GP4 et GP8 
gpio_init_mask(mask); 
gpio_set_dir_out_masked(mask); // GP4 et GP8 = output 
  
uint32_t state1 = (1 << 4); 
uint32_t state2 = (1 << 8); 
  
gpio_put_masked(mask, state1); // GP4=1, GP8=0 
  
// ...autre action 
  
gpio_put_masked(mask, state2); // GP4=0, GP8=1, les signaux sur GP4 et GP8 basculent simultanément 
  
// ...

______________________________
Dans l'exemple d'application ci-dessous, on montre comment piloter un afficheur 7-segments (à cathode commune) avec un compteur qui évolue entre 0 et 9 (voir photo du montage plus haut).

Les anodes des segments (7 segments + point décimal) sont reliées aux broches GP08 à GP15 (n'oubliez pas les résistances 330Ω) et seront donc pilotées simultanément grâce à gpio_put_masked.

Fichier source digit7seg.c :
Code C :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/** 
 * Démonstration pilotage digit 7-segments 
 */ 
  
#include "pico/stdlib.h" 
  
int main() {     
    /* 
     * seg a  --> GP15 
     * seg b  --> GP14 
     * seg c  --> GP13 
     * seg d  --> GP12 
     * seg e  --> GP11 
     * seg f  --> GP10 
     * seg g  --> GP09 
     * seg dp --> GP08  
     */ 
  
    //                            abcdefgp 
    const uint8_t digit[] = {   0b11111100, // 0 
                                0b01100000, // 1 
                                0b11011010, // 2 
                                0b11110010, // 3 
                                0b01100110, // 4 
                                0b10110110, // 5 
                                0b10111110, // 6 
                                0b11100000, // 7 
                                0b11111110, // 8 
                                0b11110110  // 9 
                            };       
  
    const uint32_t mask = 0x0000FF00; // masque GP08 à GP15 
    gpio_init_mask(mask); // GPO8 à GP15 = I/O 
    gpio_set_dir_out_masked(mask); // GPIO8 à GPI15 = sorties 
  
    while (true) { 
        for (uint8_t counter=0; counter<10; counter++) { // compteur 0 à 9 
            uint32_t value = digit[counter] << 8; 
            gpio_put_masked(mask, value); 
            sleep_ms(1000); 
        } 
    } 
  
}

Fichier CMakeLists.txt :
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cmake_minimum_required(VERSION 3.13) 
 
# initialize the SDK based on PICO_SDK_PATH 
# note: this must happen before project() 
include(pico_sdk_import.cmake) 
 
project(digit7seg C CXX ASM)  
 
set(CMAKE_C_STANDARD 11)   
set(CMAKE_CXX_STANDARD 17)  
 
# initialize the Raspberry Pi Pico SDK 
pico_sdk_init() 
 
# rest of your project 
add_executable(digit7seg 
    digit7seg.c 
) 
 
# Add pico_stdlib library which aggregates commonly used features 
target_link_libraries(digit7seg pico_stdlib) 
 
# create map/bin/hex/uf2 file in addition to ELF. 
pico_add_extra_outputs(digit7seg)
Voir le billet précédent pour la construction du projet.

Pour plus de détails Pico C SDK : hardware_gpio
  Billet blog