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é.
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.
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 :
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.
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.