Blue Pill - GPIOs - PINs

Table des matières

TL;DR

Configuration des PINs des General Purpose Input Output.

Une PIN

Les PINs sont regroupées par paquet de 16 dans des GPIOs. Avant d'utiliser une PIN, il faut: Les PINs de 0 à 7 se configurent depuis le registre Configuration Register Low, les huit autres depuis le registre Configuration Register High. Il est à noter qu'avec une Blue Pill, toutes les PINs ne sont pas disponibles:

Configuration et mode

La page 171 du manuel de référence (RM0008) décrit la composition (identiques) des registres CRL et CRH. Avec 2 bits pour la configuration et 2 bits pour le mode on obtient:
enum GPIO_PIN_SETTINGS {
    I_ANALOG,   // 00:00 Analog mode : Input mode
    O10_GP_PP,  // 00:01 General purpose output push-pull : Output mode, max speed 10 MHz
    O02_GP_PP,  // 00:10 General purpose output push-pull : Output mode, max speed 02 MHz
    O50_GP_PP,  // 00:11 General purpose output push-pull : Output mode, max speed 50 MHz
    I_FLOATING, // 01:00 Floating input : Input mode (reset state)
    O10_GP_OD,  // 01:01 General purpose output open-drain : Output mode, max speed 10 MHz
    O02_GP_OD,  // 01:10 General purpose output open-drain : Output mode, max speed 02 MHz
    O50_GP_OD,  // 01:11 General purpose output open-drain : Output mode, max speed 50 MHz
    I_PU_PD,    // 10:00 Input with pull-up / pull-down : Input mode
    O10_AF_PP,  // 10:01 Alternate function output push-pull : Output mode, max speed 10 MHz
    O02_AF_PP,  // 10:10 Alternate function output push-pull : Output mode, max speed 02 MHz
    O50_AF_PP,  // 10:11 Alternate function output push-pull : Output mode, max speed 50 MHz
    RESERVED,   // 11:00 Reserved : Input mode
    O10_AF_OD,  // 11:01 Alternate function output push-pull : Output mode, max speed 10 MHz
    O02_AF_OD,  // 11:10 Alternate function output push-pull : Output mode, max speed 02 MHz
    O50_AF_OD,  // 11:11 Alternate function output push-pull : Output mode, max speed 50 MHz
};
On peut maintenant configurer la PIN 2 du GPIO A en Output à 50 MHz et Push-Pull:
GPIOA->CRL |= O50_GP_PP << (2 * 4); // PA2 Output, 50 MHz, general purpose, push-pull
Ce qui n'est pas une bonne idée car une bonne pratique consiste d'abord à remettre à zéro puis de modifier des bits de configuration:
#define GPIO_PIN_RESET_Msk  0xf

GPIOA->CRL &= ~(GPIO_PIN_RESET_Msk << (2 * 4)); // Reset 4 bits [8:11]
GPIOA->CRL |= O50_GP_PP << (2 * 4);             // PA2 Output, 50 MHz, general purpose, push-pull
L'astuce ici consiste à utiliser le numéro de la PIN pour calculer le décalage de bit (rappel: les opérations binaires sont un pré-requis).

Fonctions

Le fichier d'entête stm32f1xx.h met à disposition une structure correspondant à un GPIO ainsi que des pointeurs vers les 3 GPIOs disponibles, on peut écrire une première fonction d'initialisation:
#define GPIO_PIN_RESET_Msk  0xf
#define GPIO_HIGH_OFFSET    32

void
gpio_pin_init(GPIO_TypeDef *gpio, uint8_t pin, enum GPIO_PIN_SETTINGS setting) {
    pin *= 4;
    if (pin < GPIO_HIGH_OFFSET) {
        gpio->CRL &= ~(GPIO_PIN_RESET_Msk << pin);
        gpio->CRL |= setting << pin;
    }
    else {
        pin -= GPIO_HIGH_OFFSET;
        gpio->CRH &= ~(GPIO_PIN_RESET_Msk << pin);
        gpio->CRH |= setting << pin;
    }
}
Note pour moi-même: je sais bien que mixer des paramètres de 8 et 32 bits ne se fera pas dans la joie et la bonne humeur mais il est un peu tôt pour commencer à se prendre la tête avec l'assembleur, le prefetch et les optimisations d'alignement.

Projet

C'est bien gentil de faire clignoter la LED de la carte mais il est temps de passer au niveau supérieur: faire clignoter 3 LEDs à la façon d'un feu rouge. La partie matériel de compose: La résistance va limiter l'intensité à la moitié (grosso-merdo) du maximum des LED (déjà bien suffisant). Comme une et une seule LED est allumée à fois (elles se partagent le "+" amené par le transistor) une seule résistance suffit. Le transistor sert (pour l'instant) d'interrupteur général. J'invoque l'immense mansuétude de Rancune pour l'horreur que doit représenter ce montage (et oui Fritzing tourne sous FreeBSD/Wayland). Un fichier config.h se chargera de définir les différents composants:
$ cat config.h
#pragma once

#define INIT_SECONDS  3

#define SWITCH_GPIO GPIOA
#define SWITCH_PIN  0
    
#define RED_GPIO     GPIOA
#define RED_PIN      1
#define RED_SECONDS  8
    
#define ORANGE_GPIO     GPIOA
#define ORANGE_PIN      2
#define ORANGE_SECONDS  4
    
#define GREEN_GPIO     GPIOA
#define GREEN_PIN      3
#define GREEN_SECONDS  8
    
#define APB2_EN  RCC_APB2ENR_IOPAEN
Le programme principal:
     1	#include "stm32f1xx.h"
     2	#include "gpio.h"
     3	#include "systick.h"
     4	
     5	#include "config.h"
     6	
     7	enum STATES { INIT = 0, RED, ORANGE, GREEN };
     8	
     9	unsigned int state, systick_irqs;
    10	
    11	int
    12	main (void) {
    13	    SystemCoreClockUpdate(); // Update globale variable SystemCoreClock
    14	    RCC->APB2ENR |= APB2_EN; // Enable GPIOs
    15	
    16	    gpio_pin_init(SWITCH_GPIO, SWITCH_PIN, O02_GP_PP);
    17	    SWITCH_GPIO->BRR = 1 << SWITCH_PIN; // PIN low SWITCH off
    18	
    19	    gpio_pin_init(RED_GPIO,    RED_PIN,    O02_GP_PP);
    20	    gpio_pin_init(ORANGE_GPIO, ORANGE_PIN, O02_GP_PP);
    21	    gpio_pin_init(GREEN_GPIO,  GREEN_PIN,  O02_GP_PP);
    22	
    23	    // PINs high LEDs off
    24	    RED_GPIO->BSRR    = 1 << RED_PIN;
    25	    ORANGE_GPIO->BSRR = 1 << ORANGE_PIN;
    26	    GREEN_GPIO->BSRR  = 1 << GREEN_PIN;
    27	
    28	    SWITCH_GPIO->BSRR = 1 << SWITCH_PIN; // PIN high SWITCH on
    29	
    30	    state = INIT;
    31	    systick_irqs = 0;
    32	    systick_delay_irq(SystemCoreClock); // 1 second @8 MHz
    33	
    34	    while (1) {
    35	        __WFI();
    36	        if (state == RED && systick_irqs == RED_SECONDS) {
    37	            systick_irqs    = 0;
    38	            state           = GREEN;
    39	            RED_GPIO->BSRR  = 1 << RED_PIN;   // PIN high RED off
    40	            GREEN_GPIO->BRR = 1 << GREEN_PIN; // PIN low GREEN on
    41	        }
    42	        else if (state == GREEN && systick_irqs == GREEN_SECONDS) {
    43	            systick_irqs     = 0;
    44	            state            = ORANGE;
    45	            GREEN_GPIO->BSRR = 1 << GREEN_PIN;  // PIN high GREEN off
    46	            ORANGE_GPIO->BRR = 1 << ORANGE_PIN; // PIN low ORANGE on
    47	        }
    48	        else if (state == ORANGE && systick_irqs == ORANGE_SECONDS) {
    49	            systick_irqs      = 0;
    50	            state             = RED;
    51	            ORANGE_GPIO->BSRR = 1 << ORANGE_PIN; // PIN high ORANGE off
    52	            RED_GPIO->BRR     = 1 << RED_PIN;    // PIN low RED on
    53	        }
    54	        else if (state == INIT && systick_irqs == INIT_SECONDS) {
    55	            systick_irqs  = 0;
    56	            state         = RED;
    57	            RED_GPIO->BRR = 1 << RED_PIN; // PIN low RED on
    58	        }
    59	    }
    60	
    61	    return 0;
    62	}
    63	
    64	void
    65	SysTick_Handler(void) {
    66	    systick_irqs++;
    67	}

La série de if est "triée", c'est pourquoi l'état "INIT" est testé en dernier (il n'est valable qu'au démarrage de la carte) et celui d'"ORANGE" après ceux de "GREEN" et "RED", bien plus fréquents. Il serait dommage de s'arréter (au feu rouge, ah ah ah ...) en si bon chemin et ajoutons un bouton.

Le code complet: https://gitlab.com/dsx/blue-pill/-/tree/master/stm32f103c8t6/00_gpios