Ce que nous allons faire - Prérequis
Nous allons voir dans ce cours comment gérer les entrées digitales. Nous aborderons la technique de polling, la question du rebond des touches et 2 manières de s'en débarrasser, et l'utilisation des interruptions externes.
Les prérequis de ce cours sont :
Avoir suivi le cours CH32V003 : premiers pas.
Disposer d'une breadboard et de DuPont jumpers.
Disposer d'un bouton poussoir, d'une résistance de 10kΩ et d'un condensateur de 100nF.
Si vous avez des questions sur les prérequis, vous trouverez des réponses sur cette page.
Utilisation du GPIO en entrée
Nous avons déjà vu comment fonctionne le GPIO configuré en sortie logique. Le schéma ci-dessous représente le fonctionnement d'une entrée logique.
On remarque que le signal présent sur la broche du GPIO passe par un buffer avec trigger de Schmitt (le triangle orienté vers la gauche avec un symbole en forme de S au milieu), dont la fonction est de "nettoyer" le signal d'entrée.
On note également 2 résistances qui peuvent être activées ou désactivées par configuration. La résistance reliée à VDD est appelée pull-up resistor et celle reliée à VSS pull-down resistor.
Dans le cas où la broche d'entrée est reliée à un ou plusieurs dispositifs dotés de sorties open-drain, vous verrez toujours un niveau 0 car la ligne est soit flottante (toutes les sorties open-drain à 0, donc transistors tous bloqués), soit reliée à VSS (au moins une sortie open-drain à 1, c'est-à-dire transistor passant).
Dans ce cas, le seul moyen d'arriver à lire correctement l'état de la broche d'entrée est de la relier à VDD à travers une résistance. Ainsi, lorsque toutes les sorties open-drain sont à 0, la broche d'entrée voit un niveau logique haut au lieu de flotter comme dans le cas précédent. La résistance est dite pull-up car elle "tire" l'état de la broche vers le haut lorsqu'il est indéfini. Le fait que le GPIO intègre des résistances pull-up commutables évite de devoir recourir à un composant extérieur.
Pour les résistances pull-down, c'est l'inverse : si la sortie du système connecté à la broche d'entrée est soit flottante, soit reliée à VDD, mettre en service la résistance pull-down de cette ligne de GPIO permettra de lire correctement l'état correspondant. C'est par exemple le cas avec certains capteurs dont la sortie est contrôlée par un transistor PNP (ou P-MOS).
La data sheet nous indique dans le tableau 3-16 (General-purpose I/O static characteristics) que la valeur des résistances pull-up (RPU) et pull-down (RPD) est comprise entre 35 et 55kΩ, avec une valeur typique de 45kΩ. Si cette valeur convient à votre application, vous n'aurez donc pas besoin d'ajouter de résistance externe.
On parle de "weak pull-up" (ou pull-down) parce que la résistance est relativement élevée et que le circuit externe connecté à l'entrée a donc besoin de fournir peu d'énergie pour faire changer l'état de l'entrée. Si la résistance était plus faible (ex. 5kΩ), on parlerait de "strong pull-up".
Lire l'état d'un bouton poussoir : le polling
Si on fait le point sur l'utilisation des lignes de GPIO de notre carte :
PD1 (SWIO) est utilisée pour programmer le micro-contrôleur
PD5 (UTX) est utilisée pour envoyer des caractères au terminal de MRS
PD6 (URX) pourrait être utilisée pour recevoir des caractères du terminal de MRS
PC4 est utilisée pour la LED
PD2 étant disponible, nous l'utiliserons pour ce cours. Nous allons dans un premier temps connecter un bouton poussoir à notre micro-contrôleur et utiliser la résistance pull-up de PD2, donc en dehors du bouton poussoir, nous n'avons besoin d'aucun autre composant. Le schéma est le suivant :
La convention que j'utilise dans ces schémas est de représenter la carte de développement comme si c'était un circuit intégré. Les noms des broches correspondent aux marquages sur la carte et leur disposition correspond également. J'ai aussi choisi de ne pas utiliser les symboles GND et VCC et d'utiliser des étiquettes à la place. Il m'a semblé que ces conventions pouvaient faciliter la compréhension du câblage.
Si vous avez pris le modèle recommandé de bouton poussoir, il se présente de cette façon :
Bien que n'ayant que 2 pôles, on constate qu'il a 4 pattes, parce qu'une paire est redondante. Utilisez votre multimètre en position testeur de continuité pour déterminer quelles pattes choisir pour le câblage.
Une fois le câblage terminé, nous allons passer au code. Nous allons simplement allumer la LED lorsqu'on appuie sur le bouton et l'éteindre lorsqu'il est relâché.
Pour cela, créez un nouveau projet et modifiez system_ch32v00x.c comme nous l'avons vu lors du cours CH32V003 : premiers pas, puis remplacez le contenu du fichier main.c par le code suivant :
#include <ch32v00x.h> #define LED_GPIO_PIN GPIO_Pin_4 #define LED_GPIO_PORT GPIOC #define LED_GPIO_RCC RCC_APB2Periph_GPIOC #define SWITCH_GPIO_PIN GPIO_Pin_2 #define SWITCH_GPIO_PORT GPIOD #define SWITCH_GPIO_RCC RCC_APB2Periph_GPIOD int main() { GPIO_InitTypeDef ledGpioInit = { 0 }; ledGpioInit.GPIO_Pin = LED_GPIO_PIN; ledGpioInit.GPIO_Mode = GPIO_Mode_Out_PP; ledGpioInit.GPIO_Speed = GPIO_Speed_50MHz; RCC_APB2PeriphClockCmd(LED_GPIO_RCC, ENABLE); GPIO_Init(LED_GPIO_PORT, &ledGpioInit); // Déclare et initialise une structure contenant les paramètres // de configuration de la broche de GPIO reliée au poussoir. GPIO_InitTypeDef switchGpioInit = { 0 }; switchGpioInit.GPIO_Pin = SWITCH_GPIO_PIN; // IPU signifie "Input with Pull-Up" switchGpioInit.GPIO_Mode = GPIO_Mode_IPU; // .GPIO_Speed n'est utilisé que pour les sorties. // Active le port du GPIO. RCC_APB2PeriphClockCmd(SWITCH_GPIO_RCC, ENABLE); // Configure la broche que nous utilisons. GPIO_Init(SWITCH_GPIO_PORT, &switchGpioInit); while (1) { // Lit l'état de PD2. // Si le poussoir est enfoncé, PD2 est reliée à la masse, donc switchStatus == 0. // Si le poussoir est relâché, PD2 est reliée à VDD via la résistance pull-up, // donc switchStatus == 1. uint8_t switchStatus = GPIO_ReadInputDataBit(SWITCH_GPIO_PORT, SWITCH_GPIO_PIN); // Si le poussoir est enfoncé, on veut allumer la LED, // donc si switchStatus == 0, on veut ledStatus == Bit_SET, // et sinon ledStatus == Bit_RESET. BitAction ledStatus = switchStatus ? Bit_RESET : Bit_SET; // Fixe l'état de la broche de GPIO de la LED. GPIO_WriteBit(LED_GPIO_PORT, LED_GPIO_PIN, ledStatus); } }
Cette manière de procéder, à savoir lire périodiquement l'état d'une entrée pour détecter un changement, s'appelle polling. L'inverse du polling consiste à interrompre le traitement en cours en cas de changement d'état et c'est ce que nous verrons dans la section sur les interruptions.
Prenez le temps de suivre ce qui se passe au debugger, ça vous fera une utile révision du cours précédent. N'hésitez pas à descendre dans le source de chaque fonction ("step into" au debugger). Lorsque vous tombez sur des noms de registres, consultez le chapitre 7 "GPIO and Alternate Function (GPIO/AFIO)" du manuel de référence pour comprendre ce qui se passe. Pour les fonctions commençant par "RCC_", c'est le chapitre 3 "Reset and Clock Control (RCC)".
Le rebond des contacts
Mise en évidence du rebond
Lorsqu'on appuie sur une touche, ici notre bouton poussoir, il peut se produire des rebonds mécaniques qui causent des interruptions et rétablissements successifs du contact. Ça se passe trop vite pour qu'on puisse le voir à l'œil nu, mais un micro-contrôleur va suffisamment vite pour en être affecté. Il est donc important de prendre en compte ce phénomène.
Je vous propose ci-dessous une version modifiée de notre code pour mettre en évidence le rebond en envoyant des traces sur le terminal de MRS, comme nous l'avons vu dans le cours précédent :
#include <ch32v00x.h> #include <debug.h> #define LED_GPIO_PIN GPIO_Pin_4 #define LED_GPIO_PORT GPIOC #define LED_GPIO_RCC RCC_APB2Periph_GPIOC #define SWITCH_GPIO_PIN GPIO_Pin_2 #define SWITCH_GPIO_PORT GPIOD #define SWITCH_GPIO_RCC RCC_APB2Periph_GPIOD int main() { USART_Printf_Init(115200); GPIO_InitTypeDef ledGpioInit = { 0 }; ledGpioInit.GPIO_Pin = LED_GPIO_PIN; ledGpioInit.GPIO_Mode = GPIO_Mode_Out_PP; ledGpioInit.GPIO_Speed = GPIO_Speed_50MHz; RCC_APB2PeriphClockCmd(LED_GPIO_RCC, ENABLE); GPIO_Init(LED_GPIO_PORT, &ledGpioInit); GPIO_InitTypeDef switchGpioInit = { 0 }; switchGpioInit.GPIO_Pin = SWITCH_GPIO_PIN; switchGpioInit.GPIO_Mode = GPIO_Mode_IPU; RCC_APB2PeriphClockCmd(SWITCH_GPIO_RCC, ENABLE); GPIO_Init(SWITCH_GPIO_PORT, &switchGpioInit); // Initialiser un compteur de changements uint32_t count = 0; // Lit l'état initial de PD2. uint8_t switchStatus = GPIO_ReadInputDataBit(SWITCH_GPIO_PORT, SWITCH_GPIO_PIN); while (1) { // Lit l'état actuel de PD2. uint8_t newStatus = GPIO_ReadInputDataBit(SWITCH_GPIO_PORT, SWITCH_GPIO_PIN); if (newStatus != switchStatus) { // Si l'état actuel est différent de l'état précédent, // garder trace du changement. printf("Changement %lu de %d -> %d\r\n", count, switchStatus, newStatus); // Incrémenter le compteur. count++; // Prendre en compte le nouvel état. switchStatus = newStatus; } BitAction ledStatus = switchStatus ? Bit_RESET : Bit_SET; GPIO_WriteBit(LED_GPIO_PORT, LED_GPIO_PIN, ledStatus); } }
Sans rebond, ce code affiche une trace quand vous appuyez sur le bouton et une autre quand vous le relâchez. Le compteur est donc à chaque fois incrémenté de 2. Si vous voyez un nombre plus grand de traces et donc un incrément supérieur à 2, c'est que votre bouton présente un rebond qui doit être éliminé.
Élimination logicielle
Dans une application plus complexe que notre exercice, vous utiliserez certainement déjà un timer pour gérer vos différentes fonctionnalités. Il vous sera donc facile de regarder à intervalles réguliers l'état de votre bouton (donc faire du polling sur le bouton) et d'en garder trace dans un petit tableau d'entiers (1 = bouton relâché, 0 = bouton appuyé).
La taille de ce tableau dépendra de la fréquence de votre polling et de la durée moyenne du rebond. Supposons que votre tableau contienne 3 entiers, c'est-à-dire que vous considérez qu'au bout de 3 pollings, le rebond sera terminé.
Votre code conservera le dernier état valide du bouton et observera les changements. Si le dernier état est 0 et que votre tableau contient 3 entiers à 1, c'est que votre bouton vient d'être relâché. Tant qu'il contiendra moins de 3 entiers à 1, c'est soit qu'il est toujours appuyé, soit que le rebond n'est pas encore terminé. Et inversement si le dernier état est 1, le bouton ne sera considéré comme venant d'être appuyé que lorsque le tableau contiendra 3 entiers à 0.
L'avantage de l'élimination logicielle du rebond est qu'elle ne coûte que du temps de développement, qu'on ne paie qu'une seule fois, alors que l'élimination matérielle nécessite des composants supplémentaires qu'on paie à chaque fois qu'un exemplaire du produit est fabriqué.
Élimination matérielle
Il existe des circuits intégrés spécialisés pour l'élimination du rebond des contacts ("bounce elimination" en anglais) comme le MC14490, mais il coûte de l'ordre de 0.50€ par 1000, ce qui n'est pas négligeable.
Une alternative un peu moins efficace mais qui peut cependant s'avérer satisfaisante dans beaucoup de cas ne nécessite qu'une résistance et un condensateur, soit moins de 0.02€ :
Le coût de cette solution pourrait encore être réduit s'il est possible d'utiliser la résistance pull-up du GPIO, ce qui n'a pas été fait dans cet exemple.
Les interruptions
Introduction
Il existe des cas où le polling n'est pas acceptable, comme par exemple pour :
un bouton d'arrêt d'urgence
un appareil qui doit réaliser une opération à un moment programmé (ex. un radio-réveil)
un appareil qui se met en veille pour économiser sa batterie et qui doit se "réveiller" lorsqu'on appuie sur une de ses touches
plus généralement, un système qui doit réagir immédiatement à un message ou événement extérieur
Dans de tels contextes, le système concerné doit interrompre (d'où le nom d'interruption, "interrupt" en anglais) ce qu'il était en train de faire pour traiter l'interruption, puis reprendre ensuite là où il s'était arrêté.
Les interruptions sont traitées par des fonctions un peu particulières en ce sens qu'elles sauvegardent l'état du microcontrôleur avant de commencer leur traitement et le restaurent avant de rendre la main. Ces fonctions sont appelées routines d'interruption, ou ISR (interrupt service routine) en anglais.
Chaque source d'interruption (ex. GPIO, UART, RTC) a sa propre routine d'interruption, identifiée par un nom spécifique. Ces noms figurent dans le fichier startup_ch32v00x.S sur les lignes contenant le mot ".weak", qui vous permet de ne définir une ISR dans votre code que si elle vous est utile (au lieu de définir autant de fonctions vides). Les lignes avec les mêmes noms de fonction mais comportant le mot ".word" définissent un tableau d'adresses de routines d'interruption, encore appelées "vecteurs d'interruption" ("interrupt vectors" en anglais).
Pour vous mettre le pied à l'étrier avec les interruptions, nous allons les utiliser pour changer l'état de notre LED : un premier appui l'allumera, un second appui l'éteindra. Mais tout d'abord, quelques explications supplémentaires s'imposent.
Principe de fonctionnement
Le dispositif interne au micro-contrôleur qui gère les interruptions s'appelle un contrôleur d'interruptions. Il a en charge l'arbitrage en cas d'interruptions multiples, via un système de priorités, l'exécution de la bonne routine d'interruption via la table des vecteurs d'interruption, ainsi que la gestion de la configuration et de l'état des différentes sources d'interruption.
Comme on peut le voir dans la table 6-1 (CH32V003 series vector table) du manuel de référence du CH32V003, tous les périphériques du micro-contrôleur sont susceptibles de produire des interruptions. Nous nous consacrons dans ce cours aux interruptions externes reçues sur les lignes de GPIO, dont le petit nom est EXTI dans la documentation.
Le CH32V003 gère 8 sources d'interruptions externes correspondant aux 8 lignes (maximum) de chaque port de GPIO. La source d'interruption externe 0 correspond à la ligne PA0 ou PC0 ou PD0, à choisir par configuration. De même, a source 1 correspond à la ligne PA1 ou PC1 ou PD1 selon la configuration choisie, et ainsi de suite.
Les grands frères du CH32V003, comme le CH32V203, par exemple, disposent de plus de lignes de GPIO et offrent donc 16 sources d'interruptions externes au lieu de 8, exactement sur le même principe.
Il est important de noter que toutes les sources d'interruptions externes sont gérées par la même routine d'interruption, appelée EXTI7_0_IRQHandler. Cela signifie que lorsque vous écrirez cette fonction, vous devrez tester chacune des sources potentielles afin de savoir quel traitement vous devez effectuer.
De plus, chaque source d'interruption est associée à un indicateur d'interruption (interrupt flag en anglais) qui est mis à 1 lorsque l'interruption survient afin de bloquer d'autres demandes qui surviendraient alors que vous êtes encore en train de traiter la première. Cela signifie que votre routine d'interruption devra remettre cet indicateur à 0, faute de quoi vous n'en recevriez plus d'autres après la première.
Notez enfin que les interruptions externes sont déclenchées par des changements d'état de la ligne de GPIO correspondante. Un passage de 0 à 1 est appelé "front montant" (rising edge en anglais) et un passage de 1 à 0 "front descendant" (falling edge en anglais). On peut choisir par configuration si on veut déclencher notre interruption sur front montant, descendant, ou sur les deux.
Les sections suivantes du manuel de référence donnent des informations détaillées sur les interruptions externes en rapport avec les explications précédentes :
Chapter 6 Interrupt and Events (PFIC)
6.4 External Interrupt and Event Controller (EXTI)
6.5.1.1 Interrupt Enable Register (EXTI_INTENR)
6.5.1.3 Rising Edge Trigger Enable Register (EXTI_RTENR)
6.5.1.4 Falling Edge Trigger Enable Register (EXTI_FTENR)
6.5.1.6 Interrupt Flag Register (EXTI_INTFR)
Chapter 7 GPIO and Alternate Function (GPIO/AFIO)
7.2.3 External Interrupts
7.3.2.2 External Interrupt Configuration Register 1 (AFIO_EXTICR)
Le code
Créez maintenant un nouveau projet et/ou remplacez le contenu du fichier main.c par le code suivant :
#include <ch32v00x.h> #include <stdbool.h> #define LED_GPIO_PIN GPIO_Pin_4 #define LED_GPIO_PORT GPIOC #define LED_GPIO_RCC RCC_APB2Periph_GPIOC #define SWITCH_GPIO_PIN GPIO_Pin_2 #define SWITCH_GPIO_PORT GPIOD #define SWITCH_GPIO_RCC RCC_APB2Periph_GPIOD // La variable toggleLED indique si on doit inverser l'état de la LED. // Elle sert à la communication entre la routine d'interruption et la // boucle principale. // IMPORTANT : voir l'explication sur "volatile" dans le cours. volatile bool toggleLED = false; // Cet attribut indique que notre fonction est une routine d'interruption // et qu'elle utilise une optimisation de WCH pour accélérer la sauvegarde // et la restauration de l'état du micro-contrôleur. __attribute__((interrupt("WCH-Interrupt-fast"))) // EXTI7_0_IRQHandler est le nom figurant dans startup_ch32v00x.S pour ce vecteur. void EXTI7_0_IRQHandler() { // Est-ce que l'interruption à traiter concerne PD2 ? if (EXTI_GetITStatus(EXTI_Line2) != RESET) { // Oui, alors signaler à la super loop qu'elle a quelque chose à faire. // Il est fortement recommandé de faire le moins de choses possible // dans une routine d'interruption car il est crucial qu'elle rende // la main le plus vite possible. // Assigner une valeur à une variable booléenne est une des choses les // plus brèves qu'on puisse imaginer. :) toggleLED = true; // Et ne pas oublier de remettre à 0 l'indicateur d'interruption. EXTI_ClearITPendingBit(EXTI_Line2); } } int main() { RCC_APB2PeriphClockCmd(LED_GPIO_RCC, ENABLE); GPIO_InitTypeDef ledGpioInit = { 0 }; ledGpioInit.GPIO_Pin = LED_GPIO_PIN; ledGpioInit.GPIO_Mode = GPIO_Mode_Out_PP; ledGpioInit.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(LED_GPIO_PORT, &ledGpioInit); // Utiliser une ligne de GPIO comme source d'interruption externe // est une "alternate function" de la broche, donc il faut activer // le bloc AFIO (chapitre 7 du manuel de référence). RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // On peut aussi remplacer l'appel précédent et le suivant par une // seule opération comme ceci : // RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | SWITCH_GPIO_RCC, ENABLE); // C'est exactement équivalent, mais peut-être moins lisible. RCC_APB2PeriphClockCmd(SWITCH_GPIO_RCC, ENABLE); GPIO_InitTypeDef switchGpioInit = { 0 }; switchGpioInit.GPIO_Pin = SWITCH_GPIO_PIN; switchGpioInit.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(SWITCH_GPIO_PORT, &switchGpioInit); // Configurer la source d'interruption externe 2 comme venant du port D, // c'est-à-dire PD2, là où notre bouton poussoir est connecté. GPIO_EXTILineConfig(GPIO_PortSourceGPIOD, GPIO_PinSource2); // Activer les interruptions sur front descendant (lorsque notre poussoir // est au repos, PD2 voit un niveau logique haut, donc appuyer sur le // bouton produit un front descendant). EXTI_InitTypeDef extiInit = { 0 }; extiInit.EXTI_Line = EXTI_Line2; extiInit.EXTI_Mode = EXTI_Mode_Interrupt; extiInit.EXTI_Trigger = EXTI_Trigger_Falling; extiInit.EXTI_LineCmd = ENABLE; EXTI_Init(&extiInit); // Activer les interruptions externes. NVIC_InitTypeDef nvicInit = { 0 }; nvicInit.NVIC_IRQChannel = EXTI7_0_IRQn; nvicInit.NVIC_IRQChannelPreemptionPriority = 0; nvicInit.NVIC_IRQChannelSubPriority = 1; nvicInit.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvicInit); BitAction ledStatus = Bit_RESET; while (1) { // Est-ce que la routine d'interruption nous donne du travail ? if (toggleLED) { // Oui : inverser l'état de la LED ledStatus = (ledStatus == Bit_SET) ? Bit_RESET : Bit_SET; GPIO_WriteBit(LED_GPIO_PORT, LED_GPIO_PIN, ledStatus); // Et ne pas oublier de remettre toggleLED à false pour ne // faire ce traitement qu'une fois. La routine d'interruption // se chargera de remettre toggleLED à true au prochain appui // sur le bouton. toggleLED = false; } } }
Dans ma configuration, j'ai utilisé la résistance pull-up de la ligne de GPIO avec un condensateur de 100nF en parallèle sur le bouton poussoir pour éliminer le rebond et ça fonctionne très bien. Si dans votre cas vous préférez utiliser une résistance externe, remplacez simplement GPIO_Mode_IPU par GPIO_Mode_IN_FLOATING.
Utilisation de "volatile"
Le mot-clé volatile est ici très important et mérite une explication particulière.
Un compilateur est conçu pour produire le code le plus dense et efficace possible et il met en œuvre de nombreuses techniques d'optimisation de la génération de code. L'une d'elle consiste à essayer de prévoir la valeur des variables et expressions et de ne générer que le code qui est réellement exécuté. Ainsi, si vous placez un bloc d'instructions dans un if (0), le compilateur traitera votre code comme si c'était un commentaire.
Ceci pose un problème lorsqu'une même variable est utilisée par plusieurs flux d'exécution séparés, comme par exemple votre boucle principale et votre routine d'interruption. Le compilateur ne le sait pas et comme vous n'appelez jamais explicitement la routine d'interruption, le compilateur considérera seulement votre boucle principale, où la valeur de la variable n'est changée qu'à l'intérieur du test. Le compilateur supprimera donc purement et simplement votre if () !
Le mot-clé "volatile" demande au compilateur de ne surtout pas chercher à optimiser les accès à cette variable afin que le code puisse fonctionner comme souhaité. C'est en quelque sorte un "touche pas à ça p'tit con", comme dans le film "La 7ème compagnie". :)
A propos de WCH-Interrupt-fast
Le compilateur fourni avec MRS est une version de GCC spécifique à WCH intégrant quelques extensions propriétaires. L'une d'elle est une sauvegarde/restauration accélérée de l'état du micro-contrôleur lors du traitement des interruptions, extension dont la pertinence est sujette à discussion.
Vous pouvez si vous le souhaitez utiliser la version officielle de GCC, ou encore clang, pour compiler votre application, elle fonctionnera très bien, mais ne pourra pas utiliser les extensions propriétaires de WCH.
Dans ce cas, vous devrez remplacer :
__attribute__((interrupt("WCH-Interrupt-fast")))
par :
__attribute__((interrupt))
Reportez-vous à cet article pour une discussion détaillée de ce point s'il vous intéresse.
Cas particulier de PD7
Sur la plupart des cartes de développement, PD7 est utilisée pour faire une remise à zéro (reset) du micro-contrôleur à l'aide d'un bouton poussoir. Cette ligne de GPIO n'est donc pas disponible pour un autre usage, à moins de dessouder le condensateur de 100nF et le poussoir qui y sont reliés.
notez au passage que le si circuit de reset ne nécessite qu'un poussoir et un condensateur, c'est parce que PD7 intègre une résistance pull-up qui permet de charger le condensateur.
Si vous choisissez cette voie, il vous faudra encore désactiver la fonction de reset de PD7 en ajoutant les lignes suivantes aux initialisations faites dans votre main() :
FLASH_Unlock(); FLASH_EraseOptionBytes(); FLASH_UserOptionByteConfig(OB_IWDG_SW, OB_STDBY_NoRST, OB_RST_NoEN, OB_PowerON_Start_Mode_BOOT); FLASH_Lock();
Le paramètre important est OB_RST_NoEN, les autres pourraient être modifiés si vous aviez des besoins spéciaux, mais les valeurs choisies ici vous conviendront très probablement. Notez pour finir que cette particularité de PD7 doit être prise en compte qu'on l'utilise en entrée ou en sortie.
Ce que nous avons appris
Vous savez maintenant :
Comment utiliser le GPIO en entrée.
Ce qu'est le rebond d'un contact et comment le neutraliser.
Ce que sont les interruptions.
Comment utiliser les lignes du GPIO comme sources d'interruption.
Pourquoi et comment utiliser le mot-clé "volatile".