Logiciels > Programmation PIC > Bases > MP > Lecture d'un bouton poussoir

Dernière mise à jour : 26/01/2014

Présentation

Les exemples qui suivent montrent comment lire le statut logique d'une entrée sur laquelle est raccordé un bouton poussoir. Ils s'appuient sur des routines faites maison ou incluses dans MikroPascal. La lecture de l'entrée peut être réalisée à intervalles réguliers au sein d'une boucle, ou être déclanchée (détectée) à travers une interruption, ces deux cas seront analysés. Nous verrons également comment faire la distinction entre une pression brève et une pression prolongée sur un même bouton-poussoir, pour effectuer deux actions différentes.

Lecture de base

Quand je parle de lecture de l'état d'un bouton poussoir, c'est pour aborder le côté pratique, manipulable. La lecture concerne une entrée logique et il n'y a pas que les boutons poussoir qui peuvent fournir un état logique changeant. La lecture de base objet du présent exemple consiste donc à regarder l'état logique présent sur une ligne configurée en entrée logique, sur un des ports d'un microcontrôleur PIC. Selon l'état logique lu au niveau logiciel, on pourra dire si le bouton poussoir est enfoncé ou s'il est relaché. Mais avant tout, formalisons la connexion du bouton poussoir, qui peut se trouver côté masse ou côté +Valim. Dans le schéma qui suit, deux boutons poussoir sont câblés :
- un premier bouton poussoir SW1 qui amène sur la ligne RA0 du PIC, un état logique haut quand il est enfoncé;
- un second bouton poussoir SW2 qui amène sur la ligne RA1 du PIC, un état logique bas quand il est enfoncé.

pic_tuto_base_lecture_entree_001a

Au repos, quand aucun bouton poussoir n'est actionné, l'entrée RA0 se trouve à l'état logique bas et l'entrée RA1 se trouve à l'état logique haut. Ces états logiques, s'ils changent, indiqueront que les boutons poussoir ont été enfoncés. Sachant cela, on peut lire en boucle l'état logique des entrées RA0 et RA1, puis décider de ce qu'il faut faire si leur état subit une modification. Le code suivant montre une façon de faire, où la lecture des entrées RA0 et RA1 est répétée toutes les 100 ms, dans une boucle de type "while" elle-même située dans le corps de la procédure principale de l'application (main program).

program Test_16F628A_lecture_poussoir;

procedure Init;
begin
TRISA := %11111111; // toutes lignes du port A (RA0 à RA7) configurées en entrées logiques
TRISB := %00000000; // toutes lignes du port B (RB0 à RB7) configurées en sorties logiques
end;

procedure Read_Input_1;
begin
if TestBit(PORTA, 0) = 1 then // lecture ligne RA0
begin
// action si SW1 enfoncé
end;
end;

procedure Read_Input_2;
begin
if TestBit(PORTA, 1) = 0 then // lecture ligne RA1
begin
// action si SW2 enfoncé
end;
end;

// Main program
begin
Init;
while true do
begin
  Read_Input_1;
Read_Input_2;
delay_ms(100);
end;
end.


Ca fonctionne, mais...
Ce code fonctionne, mais présente une particularité qui peut devenir un inconvénient dans certains contextes d'utilisation. L'action dans les procédures Read_Input_1 et Read_Input_2 est exécutée toutes les 100 ms tant que le poussoir est enfoncé. Ce point n'est nullement gênant si le premier poussoir doit activer une sortie et que le deuxième poussoir doit désactiver cette même sortie, mais peut être gênant dans d'autres cas. De plus, aucune vérification de la durée de l'appui sur les poussoir n'est prévue, et un parasite bref sur une entrée pourrait parfaitement être pris en compte s'il arrive juste au moment de la lecture. Même problème pour les rebonds mécaniques des boutons poussoirs, inévitables sur les modèles courants, qui peuvent être vus comme plusieurs pressions successives et rapprochées. Cette façon de lire les entrées doit donc être reservée aux situations où ces petits inconvénients ne portent pas préjudice au bon fonctionnement du logiciel, tel qu'un télérupteur avec deux poussoirs ON et OFF séparés.

Utilisation de la routine BUTTON

Dans beaucoup d'autres cas, on préfèrera utiliser une routine incluse dans le logiciel MikroPascal, qui se charge de tout, de façon quasi transparente pour l'utilisateur. Cette routine, qui s'appelle BUTTON, fonctionne de la façon suivante.

procedure Read_Input_1;
begin
if Button(PORTA, 0, 50, 1) then // lecture ligne RA0
begin
// action si SW1 enfoncé
end;
end;

procedure Read_Input_2;
begin
if Button(PORTA, 1, 50, 0) then // lecture ligne RA1
begin
// action si SW2 enfoncé
end;
end;


Cette routine Button attend de nous qu'on lui fournisse quatre paramètres, qui correspondent aux quatre valeurs situées entre parenthèses :

Pour la lecture de SW1
Button(PORTA, 0, 50, 1)
- PORTA correspond, comme on pourrait un peu s'en douter, au port sur lequel est branché le bouton poussoir.
- 0 correspond au numéro de l'entrée du port spécifié. Ici il s'agit de la ligne RA0, donc entrée numéro 0 (s'il s'agissait de RA5, on indiquerait la valeur 5).
- 50 correspond au temps (en ms) pendant lequel le bouton doit être enfoncé pour que l'action soit réellement prise en compte. Ce délai permet de s'affranchir des parasites et des rebonds mécaniques. Une valeur de 50 ms convient généralement, mais on peut prendre une valeur plus élevée, par exemple 100 ou 150 ms.
- 1 correspond à l'état logique que l'on doit avoir quand le poussoir est enfoncé.

Pour la lecture de SW2
Button(PORTA, 1, 50, 0)
- PORTA correspond au même port que précédement, pas de surprise.
- 1 correspond au numéro de l'entrée du port spécifié. Ici il s'agit de la ligne RA1, donc entrée numéro 1.
- 50 correspond au temps (en ms) pendant lequel le bouton doit être enfoncé pour que l'action soit réellement prise en compte.
- 0 correspond à l'état logique que l'on doit avoir quand le poussoir est enfoncé.

Ca fonctionne aussi, mais...
Les éventuels problèmes liés aux parasites ou rebonds appartiennent au passé, mais l'exécution du code est toujours répétée à chaque appel de la procédure si le bouton reste enfoncé. Ce qui n'est pas toujours voulu. Pour remédier à ce problème, il faut ajouter une variable de type boolean (tout ou rien) qui stocke le changement d'état du bouton poussoir.

Amélioration de la routine Button

Le code suivant permet de n'exécuter qu'une seule fois le code inclus dans la procédure, au moment où le bouton poussoir est enfoncé, et ce même si le bouton est encore enfoncé lors des lectures suivantes de la ligne d'entrée du PIC. De plus, cette façon de faire n'est pas bloquante la procédure Button étant "sauté" si le bouton reste enfoncé (pas d'attente de relachement).

procedure Read_Input_1;
var
bSW1: boolean;
begin
if Button(PORTA, 0, 50, 1) and (bSW1 = false) then // lecture ligne RA0
begin
// action si SW1 enfoncé
bSW1 := true;
end;
if PORTA.0 = 0 then bSW1 := false;
end;

procedure Read_Input_2;
var
bSW2: boolean;
begin
if Button(PORTA, 1, 50, 0) and (bSW2 = false) then // lecture ligne RA1
begin
// action si SW2 enfoncé
bSW2 := true;
  end;
 if PORTA.1 = 1 then bSW2 := false;
end;


Fonctionnement : le code à exécuter lors de l'appui sur les boutons poussoir est exécuté à la condition que la variable associée (bSW1 pour SW1 et bSW2 pour SW2) possède la valeur FALSE. Ce qui est le cas si l'entrée RA0 est à l'état bas (pour SW1) ou si l'entrée RA1 est à l'état haut (pour SW2). Quand un poussoir est actionné, le code de la procédure est exécuté, et les variables bSW1 et bSW2 prennent la valeur TRUE dans la foulée, ce qui interdit l'exécution de la procédure lors des lectures suivantes, tout du moins tant que les poussoirs n'ont pas été relachés. En effet, une ligne a été ajouté à la suite de la procédure d'origine, qui permet de redonner aux variables bSW1 et bSW2 leur valeur FALSE dès que les lignes correspondantes (RA0 et RA1) sont repassées à leur état nominal (de repos, poussoir relaché).

Rebonds mécaniques : procédure Button indispensable ?

Non, bien sûr ! Certaines personnes utilisent un code bien plus simple pour ne pas tenir compte des rebonds mécaniques du bouton poussoir, comme le montre l'exemple suivant :

procedure Read_Input_1;
begin
if PORTA.0 = 1 then Delay_ms(50); // lecture ligne RA0
if PORTA.0 = 1 then 
begin
// action si SW1 enfoncé
 end;
end;


Si un changement d'état est détecté lors de la lecture de l'entrée concernée, on fait une petite pause de quelques dizaines de ms, puis on relit à nouveau l'état de l'entrée. S'il s'agissait d'un parasite, la seconde lecture a fort peu de chance de donner la même valeur que celle obtenue à la première lecture. Et si des rebonds surviennent, on "passe par dessus", la deuxième lecture ayant lieu assez longtemps après le dernier rebond potentiel.

Remarque : la durée exacte du délai à appliquer ici ou avec la routine Button, n'est pas très critique, elle peut en pratique être comprise entre 20 ms et 100 ms. Une durée trop courte risque de laisser passer des rebonds sur certains boutons poussoirs "lents", et une durée trop longue demande à ce que l'utilisateur laisse plus longtemps le doigt appuyé sur le poussoir. Notez en passant que vous disposez là d'une base pour détecter si l'appui à lieu pendant un certain temps, ce qui permet d'exécuter des actions différentes selon le temps d'appui...

Supression des résistances de rappel externes

Une ligne d'un port quelconque configurée en entrée doit présenter un potentiel bien défini pour que sa lecture représente quelque chose de cohérent. Si on relie la sortie d'une porte logique (par exemple la sortie d'une porte NAND CD4011) sur l'entrée d'un PIC, aucun composant supplémentaire n'est requis, car la sortie de la porte logique présente un état logique parfaitement connu, qui est l'état bas ou l'état haut. En revanche, si l'on connecte un bouton poussoir sur une entrée du PIC, ce dernier provoque sur ladite entrée l'apparition d'un état logique bien défini, mais seulement quand il est enfoncé : état haut s'il est relié côté +Valim ou état bas s'il est relié côté masse. Pour que l'entrée du PIC soit à un potentiel maîtrisé quand le poussoir est relaché, il convient d'ajouter une résistance de rappel, qui fixe les règles (en tension) en absence d'action de la part de l'utilisateur.

pic_tuto_base_lecture_entree_001b
Rappel : petit carré gris = état logique indéterminé et non accepté

Vous serez donc heureux d'apprendre que certains PICs intègrent la mécanique nécessaire pour éviter d'ajouter ces résistances externes, sous forme de "composant" appelé "pullup" (ou "pull-ups") toujours raccordé en interne du côté +Valim, et qui autorisent, moyennant une configuration correcte, d'élaborer le montage suivant :

pic_tuto_base_lecture_entree_001c

Le code nécessaire pour que ce type de raccordement des poussoirs soit autorisé, est le suivant :

program electronique_pic_tuto_base_lecture_poussoir_001c;

var
iValue: byte;

// initialisation générale
procedure Init;
begin
TRISA := %00000000; // port A configuré en sortie
TRISB := %11111111; // port B configuré en entrée
CMCON := $07; // désactivation comparateurs
OPTION_REG.NOT_RBPU := 0; // mise en service pullup
end;


Mais comme il est rare qu'une bonne nouvelle ne soit pas suivi d'une mauvaise... La fonction pullup n'est pas forcement disponible sur tous les ports. Par exemple, sur le PIC 16F628A, le port B en bénéficie mais pas le port A. Brancher des poussoirs sur le port A imposera donc l'ajout de résistances externes sur les entrées utilisées. D'un autre côté, les résistances externes peuvent être branchées côté masse ou côté +Valim, ce qui laisse toute latitude sur le câblage des poussoirs. Bien sûr, cela est également possible avec le port B, à condition de ne pas mettre en service le pullup. En résumé, vous pouvez utiliser le pullup interne s'il existe, mais cela impose un câblage du poussoir entre entrée du PIC et masse, et le changement d'état à détecter est alors un passage d'état logique haut vers un état logique bas.

Distinction entre pressions brève et prolongée

Il arrive parfois qu'on veuille donner à un même bouton-poussoir, deux fonctions différentes selon le temps pendant lequel on le presse. Par exemple, on veut appeler une fonction F1 si on enfonce le bouton entre 100 ms et 500 ms, et appeler une fonction F2 si on l'enfonce pendant plus d'une seconde. Une façon de faire consiste à utiliser un compteur (une variable) qu'on commence à incrémenter dès qu'on enfonce le bouton. Au moment où on relâche le bouton, on lit la valeur du compteur et on détermine s'il s'agit d'une pression brève (valeur du compteur faible) ou d'une pression longue (valeur du compteur élevée). Dans le principe, cette façon de faire est simple, et on peut utiliser une interruption pour incrémenter le compteur, comme le montre l'exemple suivant.

pic_tuto_base_lecture_entree_001d

program electronique_pic_tuto_base_lecture_poussoir_001d;
// distinction entre pressions courte et longue sur un bouton-poussoir

var
bBtnPressed: boolean;
iCount: byte;
Out_Led1: sbit at RA0_bit; // LED1 sur RA0, visuale l'action F1
Out_Led2: sbit at RA1_bit; // LED2 sur RA1, visuale l'action F2
In_Btn: sbit at RB4_bit; // bouton-poussoir sur RB4

procedure CPU_Init;
begin
CMCON := 7;
OPTION_REG := 0;
//OPTION_REG.NOT_RBPU := 0; // activation pullup
OPTION_REG.PS2 := 1; // |
OPTION_REG.PS1 := 1; // |- taux division prédiviseur (prescaler) = 256
OPTION_REG.PS0 := 1; // |
INTCON := 0;
INTCON.T0IE := 1; // activation Timer0
INTCON.T0IF := 0; // mise à zéro indicateur interruption T0
INTCON.GIE := 1; // activation générale des interruptions
TRISA := $00; // toutes lignes du port A configurées en sorties logiques
TRISB := $FF; // toutes lignes du port B configurées en entrées logiques
bBtnPressed := false; // indicateur pression bouton-poussoir
iCount := 0; // compteur incrémenté quand appui sur bouton-poussoir
end;

procedure Do_F1; // action à effectuer lors d'une pression courte

begin
Out_Led1 := Out_Led1 xor 1; // si LED1 allumée, on l'éteint, et inversement (bascule)
end;

procedure Do_F2; // action à effectuer lors d'une pression longue

begin
Out_Led2 := Out_Led2 xor 1; // si LED2 allumée, on l'éteint, et inversement (bascule)

end;

procedure Interrupt;
begin
if INTCON.T0IF then // interruption du timer T0
begin
if (bBtnPressed and (iCount < 255)) then
inc(iCount); // incrémentation compteur pression bouton-poussoir
INTCON.T0IF := 0; // mise à zéro indicateur interruption T0

end;
end;

begin
CPU_Init;
while true do
begin
if In_Btn = 0 then // bouton-poussoir enfoncé
bBtnPressed := true; // indicateur pression pour autorisation comptage
if In_Btn = 1 then // bouton-poussoir relâché
begin
bBtnPressed := false;
if ((iCount > 5) and (iCount < 20)) then // pression courte
Do_F1; // action à effectuer lors d'une pression courte
if (iCount >= 20) then // pression longue
Do_F2; // action à effectuer lors d'une pression longue
iCount := 0;
end;
end;
end.


Dans cet exemple, le timer T0 est utilisé sans prépositionnement du compteur interne, ce qui signifie qu'après son débordement il revient à la valeur 0. Le prédiviseur (prescaler) est affecté au timer T0 avec un facteur de division de 256 (PS0 à PS2 à 1), ce qui conduit à une période d'interruption de 65 ms environ pour une fréquence d'horloge de 4 MHz. Au bout de 5 interruptions de T0 (iCount = 5), il s'écoule donc un temps voisin de 325 ms, et au bout de 20 interruptions (iCount =20 ), il s'écoule un temps voisin de 1,3 s. Il faut donc appuyer au moins 325 ms et moins de 1,3 s pour déclencher l'action F1, et appuyer plus de 1,3 s pour déclencher l'action F2. Bien entendu, il est possible de modifier ces valeurs, soit en effectuant des comparaisons avec des valeurs différentes de 5 et 20, soit en modifiant le rythme des interruptions du timer T0, soit les deux.