GPIO
DOCUMENTATION
Des Leds et des Boutons
Les microcontrôleurs disposent de Broches ( Pins ) pour interragir avec l’extérieur.
Le forçage à l’état logique 0/1 de ces broches, ou la lecture de l’état logique sur ces broches se fait par l’intermédiaire du périphérique GPIO.
Une broche fait partie d’un port.
Un port contient 16 broches numérotées de 0 à 15.
La broche N°5 du port B sera donc appelée PB5.
Nous considèrons l’exemple suivant :
L’objectif est d’allumer la LED rouge reliée à la broche PB4
et de récupérer l’état du bouton SW_RIGHT relié à la broche PC0.
REMARQUE : Le document Datasheet_mbed_shield.pdf permet de faire le lien entre le nom des broches du microcontrôleur et les éléments de la carte d’extension.
Pour allumer la LED, il me faut une différence de potentiel pour qu’il y ait circulation d’un courant.
Je dois donc commander mon périphérique de tel sorte qu’il impose l’état logique 0 sur la broche PB4.
Pour récupérer l’état d’un bouton poussoir, Je dois déjà considérer la broche comme une entrée.
Donc l’étage de sortie du périphérique ( output driver ) doit être en haute impédance( déconnecté ).
L’état du bouton est enregisré dans un registre ( plus précisément dans le bit GPIOC_IDR0 du Registre GPIOC_IDR ).
La consultation de ce registre permet au processeur de savoir si on a appuyé sur le bouton.
Le périphérique GPIO
Configuration en Sortie ( OUTPUT )
Un ensemble de registres permettent de configurer le périphérique GPIO.
Ces registres sont propres à chaques port ( GPIOA, GPIOB, GPIOC, … ), et chaque
bit de ces registres configure alors une broche en particulier dans ce registre.
- GPIOx_MODER : Configuration des broches en entrée / sortie / alternate function / analog
- GPIOx_OSPEEDR : réglage du slew rate pour la sortie ( lent / medium / rapide )
- GPIOx_OTYPE : Montage push pull / open drain
- GPIOx_PUPDR : ajout de résistances de pull up/down
RAPPEL : Un montage push pull utilise 2 tansistors, un montage open drain un seul. Dans ce dernier cas il faut alimenter ce transistor via une résistance de pull up ( qui peut être externe au microcontrôleur ).
Une fois la configuration effectuée ( à l’intialisation, en début de programme ), le registre GPIOxODR ( OUTPUT DATA REGISTER ) permet de choisir l’état logique à imposer en sortie.
Configuration en Entrée ( INPUT )
A partir du moment où on a indiqué dans GPIOx_MODER une configuration en input, l’état de sortie est en haute impédance.
Configuration en Sortie Alternate Function
Prenons le cas de la génération d’un signal PWM ( MLI ).
Ce genre de signaux est produit par un périphérique TIMER.
Si l’on veut que ce signal soit présent sur une broche, il faut configurer le périphérique GPIO en mode alternate function.
Supposons que je veuille faire varier l’intensité de l’éclairage de la led bleue avec un signal PWM.
La led bleue est reliée à la broche PA9.
Pour connaitre les périphériques utilisables pour une broche donnée, il faut consulter les tableaux à partir de la p.47 du document DataSheet_STM32F411.pdf
Ce tableau m’indique que sur PA9, je peux aiguiller le signal TIM1_CH2 issu du TIMER1.
Programmation du périphérique GPIO
Activation des périphériques GPIO
Le registre RCC_AHB1ENR doit être configuré pour autoriser l’utilisation des périphériques GPIOB et GPIOC.
Les macros __GPIOB_CLK_ENABLE() et __GPIOC_CLK_ENABLE() permettent de réaliser cette configuration.
Configuration d’une broche en sortie logique
On considère la LED ROUGE, reliée à PB4.
Le type GPIO_TypeDef, dont fait partie _GPIOB permet d’accéder aux différents registres du périphérique GPIO :
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
__IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
__IO uint32_t BSRR; /*!< GPIO port bit set/reset register, Address offset: 0x18 */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
} GPIO_TypeDef;
Pour allumer la LED, il faut réaliser les initialisations suivantes :
_GPIOB -> MODER = ( _GPIOB -> MODER ) & ~(0x3 << 8); // Forçage à 0 des bits 8 et 9
_GPIOB -> MODER = ( _GPIOB -> MODER ) | (0x1<<8); // MODER4 = 0b01
_GPIOB -> OSPEEDR = ( _GPIOB -> OSPEEDR ) & ~(0x3 << 8); // Forçage à 0 des bits 8 et 9
_GPIOB -> OTYPER = ( _GPIOB -> OTYPER ) & ~(1<<4); // TYPE4 = 0, push-pull
_GPIOB -> PUPDR = ( _GPIOB -> PUPDR ) & ~(0x3<<8); // PUPDR4 = 0b00
REMARQUE : Chaque bit de ces différents registres permet de configurer une broche.
Il faut donc veiller à utiliser des masques pour effectuer nos initialisations
sans affecter les autres broches.
Pour forcer à 0, je fais un ET LOGIQUE AVEC 0:
Pour forcer à 1, je fais un OU LOGIQUE AVEC 1:
Le ~ correspond à la fonction logique complément.
Exemple avec MODER :
Pour forcer l’état de la broche, on utilise le registre ODR :
_GPIOB -> ODR = ( _GPIOB -> ODR ) | (1 << 4); // Forçage à 1
_GPIOB -> ODR = ( _GPIOB -> ODR ) & ~(1 << 4); // Forçage à 0
Configuration d’une broche en entrée logique
Pour récupérer l’état logique du bouton poussoir SW_RIGHT, relié à la broche PC0 :
_GPIOC -> MODER = ( _GPIOC -> MODER ) & ~(0x3 << 0); // MODER0 = 0b00
_GPIOC -> PUPDR = ( _GPIOC -> PUPDR ) & ~(0x3<< 0); // PUPDR0 = 0b00
Pour récupérer l’état de du bouton, il faut alors lire le registre IDR, et dans notre cas viser le bit 0 :
int but = 0;
but = ( _GPIOC -> IDR ) & 0x1;
Application
Q1. Compléter la fonction main() du projet ci dessous afin de contrôler l’allumage de la LED rouge avec le bouton Switch Right.
WORKSPACE_F411_HAL_STM32CUBE.zip
Organisation des Fichiers avec l’IDE ST
Système de Fichiers
Afin de ranger nos fonctions relatives aux périphériques, en respectant la nomenclature ‘Hardware Abstraction Layer’ (HAL) de chez ST, nous placerons toutes les fonctions de configuration et d’utilisation des périphériques dans le répertoire STM32F4xx_HAL_Driver.
A plus haut niveau, nous retrouvons dans le répertoire mbed_shield les fonctions relatives aux éléments de la carte mbed ( leds, boutons, capteurs … )
Cette rédaction de fonctions à différents niveaux permettra de composer une API( Interface logicielle de Prograamation d’Application ). Les fonctions de ‘haut niveau’ dans mbed_shield utiliseront les fonctions de ‘bas niveau’ contenues dans STM32F4xx_HAL_Driver.
le fichier stm32f4xx_hal_msp.c ( MSP = MCU Support Package ) contient tout ce qui est relatif aux initialisations des broches.
nucleoF411_base
├── config
│ ├── ocd_st_nucleo_f4.cfg
│ └── STM32F411RE_FLASH.lds
├── include
│ ├── board.h
│ ├── cmsis
│ ├── config.h
│ ├── stm32f411xe.h
│ ├── stm32f4xx_hal_msp.h
│ └── stm32f4xx_it.h
├── lib
├── Makefile
├── mbed_shield
│ ├── inc
│ │ ├── lcd_128x32.h
│ │ ├── leds.h
│ │ ├── lm75.h
│ │ ├── small_7.h
│ │ └── sw.h
│ └── src
│ ├── lcd_128x32.c
│ ├── leds.c
│ ├── lm75.c
│ └── sw.c
├── src
│ ├── main.c
│ ├── stm32f4xx_hal_msp.c
│ └── stm32f4xx_it.c
├── startup
│ ├── rcc.o
│ ├── startup_stm32f411xe.s
│ ├── stm32f411_periph.c
│ ├── sys_handlers.c
│ ├── sys_handlers.h
│ └── system_stm32f4xx.c
└── STM32F4xx_HAL_Driver
├── inc
│ ├── stm32f4xx_hal_adc.h
│ ├── stm32f4xx_HAL_gpio_ex.h
│ ├── stm32f4xx_hal_gpio.h
│ ├── stm32f4xx_hal_i2c.h
│ ├── stm32f4xx_hal_spi.h
│ ├── stm32f4xx_hal_tim.h
│ ├── stm32f4xx_hal_uart.h
│ ├── stm32f4xx_ll_rcc.h
│ └── util.h
└── src
├── spi_poll.c
├── stm32f4xx_hal_adc.c
├── stm32f4xx_hal_gpio.c
├── stm32f4xx_hal_i2c.c
├── stm32f4xx_hal_spi.c
├── stm32f4xx_hal_tim.c
├── stm32f4xx_hal_uart.c
├── stm32f4xx_ll_rcc.c
└── util.c
La fonction HAL_GPIO_Init()
La fonction HAL_GPIO_Init() explicitée dans stm32f4xx_hal_gpio.c permet de configurer les broches.
Ainsi la configuration de la broche PB4 pour la led rouge peut être désormais réalisée ainsi ( dans le fichier stm32f4xx_hal_msp.c) :
void HAL_GPIO_LEDS_MspInit(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
__GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_MEDIUM;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
Pour le bouton poussoir switch right ( PC0 ) :
void HAL_GPIO_SWITCHS_MspInit()
{
GPIO_InitTypeDef GPIO_InitStruct;
__GPIOC_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}
TRAVAUX PRATIQUE : Ecriture de Drivers pour le périphérique GPIO
PROJET SOURCE
WORKSPACE_F411_HAL_STM32CUBE.zip
Q1. Compléter le fichier stm32f4xx_hal_msp.c afin que les 3 broches reliées aux leds de la cartes mbed shield soit configurées en sorties logiques, et que les 5 broches reliés aux boutons de la carte mbed shield soit configurées en entrées logiques.
Q2. Compléter les fonctions suivantes ( dans stm32f4xx_hal_gpio.c ) :
- uint8_t HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) :
Retourne 1 si le bit désigné par GPIO_Pin vaut 1.
Le bit à 1 de GPIO_Pin désigne le bit recherché.
Ex : si GPIO_Pin==0x4 (0b0100), alors je cherche à lire le bit numéro 2
- void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, uint16_t val) :
Force l’état de la broche GPIO_Pin du port GPIOx à la valeur val ( 0 ou 1).
Q3. Compléter les fonctions suivantes ( dans leds.c et sw.c ), en faisant appel aux fonctions de la question précédente :
-
void red_led(uint32_t on) : si on=1, led rouge allumée, sinon led éteinte
-
void blue_led(uint32_t on) : si on=1, led bleue allumée, sinon led éteinte
-
void green_led(uint32_t on) : si on=1, led verte allumée, sinon led éteinte
-
uint32_t sw_right_raw(void) : retourne 1 si switch right est appuyé, 0 sinon.
-
uint32_t sw_left_raw(void) : retourne 1 si switch left est appuyé, 0 sinon.
-
uint32_t sw_center_raw(void) : retourne 1 si switch center est appuyé, 0 sinon.
-
uint32_t sw_up_raw(void) : retourne 1 si switch up est appuyé, 0 sinon.
-
uint32_t sw_down_raw(void) : retourne 1 si switch down est appuyé, 0 sinon.
-
uint32_t sw_input_raw(void) : retourne l’état des 5 boutons sur les 5 bits de poids faibles, dans l’ordre CENTER-DOWN-UP-LEFT-RIGHT_
Q4. En faisant appel aux fonctions ci dessus, compléter le fichier main.c afin de contrôler l’allumage des leds RGB avec les boutons LEFT, UP, RIGHT.
Q5. On souhaite désormais avoir le fonctionnement suivant :
- Le bouton CENTER doit permettre l’allumage des 3 LEDs.
- Le bouton DOWN doit permettre l’extinction des 3 LEDs.
Ce fonctionnement peut être décrit par un diagramme d’états :
Le codage d’une machine d’états en langage C se fait avec un switch case :
static int state = 0;
switch(state)
{
case 0 : if ( sw_center == 1 ) { state = 1;} // détermination de l'état suivant
red_led(0); green_led(0); blue_led(0); // Sorties pour l'état 0;
break;
case 1 : if ( sw_down == 1 ) { state = 0;} // détermination de l'état suivant
red_led(1); green_led(1); blue_led(1); // Sorties pour l'état 1;
break;
default : break;
}
Q6. Proposer un programme permettant de réaliser à la fois le fonctionnement de la question Q4 et celui de la question Q5, à savoir :
- Le bouton CENTER doit permettre l’allumage des 3 LEDs.
- Le bouton DOWN doit permettre l’extinction des 3 LEDs.
- Si les 3 LEDS sont initialement éteintes, l’appui sur LEFT, UP, RIGHT doit allumer respectivement les LEDs Red Green Blue ( et le relachement de ces boutons doit les éteindre ).
Même si de nombreuses solutions sont possibles, il est demandé de proposer un diagramme d’états pour décrire ce fonctionnement.