Aller au contenu

GPIO

Le STM32F412ZG possède jusqu’à 114 entrées et sorties de type GPIO (General Purpose Input/Output) regroupées sur 7 ports (A, B, C, D, E, F, G et H). Les ports A à G possèdent 16 entrées/sorties et le port H n’en possède que 2.

Le STM32F412ZG fonctionne en 3.3V et les broches des GPIOs configurées en sortie donnent soit 0V, soit 3.3V. Les broches configurées en entrées sont capables de supporter des tensions jusqu’à 5V. On dit que ces broches sont “5V tolerant” et c’est très pratique pour lire des signaux provenant de périphériques qui fonctionnent avec 5V.

La figure ci-dessous illustre les composants et le fonctionnement d’une broche GPIO tolérante à 5V :

GPIO

Les broches du GPIO peuvent également être configurées pour lire des tensions analogiques, mais nous n’utilisons pas cette fonctionnalité dans ce cours. Pour plus d’information à ce sujet, référez-vous à la documentation du microcontrôleur STM32F412ZG.

Utilisation avec Mbed OS

Pour configurer et programmer les broches du GPIO avec Mbed OS, nous utilisons principalement les classes DigitalIn et DigitalOut. Notez que Mbed OS est programmé en C++ et offre une interface orientée objet.

Note

Dans Mbed OS, il y a aussi la classe DigitalInOut qui permet de lire et d’écrire sur une broche et la classe PwmOut qui permet de contrôler une broche en PWM, mais nous ne les utiliserons pas ici.

DigitalOut

Pour utiliser une broche en sortie, il suffit de créer une instance de la classe DigitalOut. Par exemple :

DigitalOut led(PE_0);

Contrairement à LibOpenCM3, vous n’avez pas besoin de spécifier le port de la broche séparément. L’argument PE_0 signifie que la broche est située sur le port E et que son numéro est 0. Vous n’avez pas non plus besoin de vous soucier de la configuration du “clock” (rcc) du GPIO; le constructeur de la classe DigitalOut le fait automatiquement.

Le code ci-dessous est celui du constructeur de DigitalOut et on voit qu’il initialise l’attribut gpio à la ligne 9 et qu’ensuite, il appelle la fonction gpio_init_out à la ligne 11:

https://github.com/ARMmbed/mbed-os/blob/mbed-os-6.9.0/drivers/include/drivers/DigitalOut.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class DigitalOut {

public:
    /** Create a DigitalOut connected to the specified pin
     *
     *  @param pin DigitalOut pin to connect to
     */
    DigitalOut(PinName pin) : gpio()
    {
        // No lock needed in the constructor
        gpio_init_out(&gpio, pin);
    }

    ...

protected:
    gpio_t gpio;
};

Le type gpio_t est spécifiques à une cible donnée. Pour les microcontrôleurs de STM, il est défini comme suit :

https://github.com/ARMmbed/mbed-os/blob/mbed-os-6.9.0/targets/TARGET_STM/gpio_object.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/*
 * Note: reg_clr might actually be same as reg_set.
 * Depends on family whether BRR is available on top of BSRR
 * if BRR does not exist, family shall define GPIO_IP_WITHOUT_BRR
 */
typedef struct {
    uint32_t mask;
    __IO uint32_t *reg_in;
    __IO uint32_t *reg_set;
    __IO uint32_t *reg_clr;
    PinName  pin;
    GPIO_TypeDef *gpio;
    uint32_t ll_pin;
} gpio_t;

La fonction gpio_init_out est dans la partie “HAL” (Hardware Abstraction Layer) de Mbed OS :

https://github.com/ARMmbed/mbed-os/blob/mbed-os-6.9.0/hal/source/mbed_gpio.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
void gpio_init_out(gpio_t *gpio, PinName pin)
{
    gpio_init_out_ex(gpio, pin, 0);
}

void gpio_init_out_ex(gpio_t *gpio, PinName pin, int value)
{
    _gpio_init_out(gpio, pin, PullNone, value);
}

static inline void _gpio_init_out(gpio_t *gpio,
                                  PinName pin,
                                  PinMode mode,
                                  int value)
{
    gpio_init(gpio, pin);
    if (pin != NC) {
        gpio_write(gpio, value);
        gpio_dir(gpio, PIN_OUTPUT);
        gpio_mode(gpio, mode);
    }
}
  • A la ligne 3, gpio_init_out appelle gpio_init_out_ex avec la valeur 0.
  • A la ligne 8, gpio_init_out_ex appelle _gpio_init_out
  • A la ligne 16, _gpio_init_out appelle gpio_init
  • Ensuite _gpio_init_out configure le port en sortie.

La fonction gpio_init est, elle aussi, dépendante de la cible. Pour notre microcontrôleur, elle est définie comme suit :

https://github.com/ARMmbed/mbed-os/blob/mbed-os-6.9.0/targets/TARGET_STM/gpio_api.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
void gpio_init(gpio_t *obj, PinName pin)
{
    obj->pin = pin;
    if (pin == (PinName)NC) {
        return;
    }

    uint32_t port_index = STM_PORT(pin);

    // Enable GPIO clock
    GPIO_TypeDef *gpio = Set_GPIO_Clock(port_index);

    ... skip non relevant code ...

    // Fill GPIO object structure for future use
    obj->mask    = gpio_set(pin);
    obj->gpio    = gpio;
    obj->ll_pin  = ll_pin_defines[STM_PIN(obj->pin)];
    obj->reg_in  = &gpio->IDR;
    obj->reg_set = &gpio->BSRR;
#ifdef GPIO_IP_WITHOUT_BRR
    obj->reg_clr = &gpio->BSRR;
#else
    obj->reg_clr = &gpio->BRR;
#endif
}

À la ligne 11, on voit la commande pour activer le clock du GPIO.

On observe donc une différence importante entre la programmation en C avec une librairie de bas niveau de type LibOpenCM3 où on gère tout nous-même et un système comme Mbed OS en C++ où le travail est fait pour nous dans le code de la classe. On s’assure ainsi que l’état d’un objet est en tout temps cohérent.

Si vous souhaitez configurer la valeur initiale de la broche de sortie (et c’est très recommandé de le faire), vous pouvez passer un deuxième argument au constructeur. Par exemple :

DigitalOut led(PE_0, 1);

Vous pouvez ensuite manipuler la broche en sortie avec la méthode write(). Par exemple :

led.write(0);

ou

led.write(1);

Note

Écrire un 1 sur une broche à laquelle est connecté une LED ne signifie pas forcément que la LED va s’allumer car ca dépend de la manière dont la LED est connectée au micro-contrôleur. La bonne pratique consiste à utiliser des constantes à la place de des valeurs numériques. Le premier travail pratique vous donne plus de détails sur cette pratique.

Mais comme Mbed OS profite des avantages de C++ pour simplifier l’interface, vous pouvez également manipuler les sorties avec une simple assignation :

led = 0; // same as led.write(0);

ou

led = 1; // same as led.write(1);

Cette fonctionnalité est possible grâce à la surcharge d’opérateurs en C++.

Les opérateurs surchargés sont les suivants :

  DigitalOut &operator= (int value);
  DigitalOut &operator= (DigitalOut &rhs);
  operator int();

Les deux premiers opérateurs sont des surcharges de l’opérateur d’affectation et permettent d’assigner un entier ou la valeur d’un autre objet de type DigitalOut à l’objet courant. Le troisième opérateur (operator int()) permet de consulter la valeur du DigitalOut en tant qu’entier.

Ces opérateurs permettent de facilement inverser la valeur de la broche en utilisant l’opérateur logique “NOT” (!) :

led = !led;

Notez que dans l’exemple ci-dessus, l’opérateur ! agit sur des entiers et non sur des objets de type DigitalOut.

Note

En Java, l’opérateur logique ! ne peut être utilisé qu’avec un opérande de type boolean. En C/C++, cet opérateur est également défini pour un opérande de type entier.

Pour plus d’information concernant la classe DigitalOut, consultez la documentation de Mbed OS.

DigitalIn

Pour utiliser une broche en entrée, nous créons une instance de la classe DigitalIn. Par exemple :

DigitalIn button(PA_0);

Normalement, nous configurons une broche d’entrée en spécifiant une résistance de pull-up ou de pull-down :

DigitalIn button(PA_0, PullDown);

Pour lire l’état de la broche, on peut utiliser la méthode read() :

int state = button.read();

ou alors, utiliser la surcharge de operator int() :

int state = button;
Pour plus d’information concernant la classe DigitalIn, consultez la documentation de Mbed OS.

Exercice

Exercice GPIO/1

Écrivez un programme en C++ qui inverse l’état d’une LED connectée à la broche PE_0 lorsqu’un bouton connecté sur la broche PA_0 est appuyé. Notez que la LED est allumée lorsqu’on écrit un 0 dans la broche et éteinte lorsqu’on écrit un 1. On aimerait qu’elle soit éteinte au démarrage. Notez aussi que le bouton a besoin d’une résistance de pull-down pour fonctionner. Faites attention à ne pas utiliser de “Magic numbers” dans votre code (respect des 10 commandements)

Solution
simple_polling.cpp (solution)
#include "mbed.h"

constexpr int kLedOn  = 0;
constexpr int kLedOff = 1;

constexpr int buttonReleased = 0;
constexpr int buttonPressed  = 1;

int main()
{
    DigitalOut led(PE_0, kLedOff);
    DigitalIn button(PA_0, PullDown);

    int prevState = button;

    while (true) {
        int state = button;
        if (prevState == buttonReleased && state == buttonPressed) {
            led = !led;
        }
        prevState = state;
    }
}