Boutons et interruption
attachInterrupt()
Pour tester et réagir lors des appuis de boutons, tant en lecture digitale qu'en lecture analogique, on peut utiliser la lecture régulière (spolling en anglais), par exemple en mettant une fonction run() comme font beaucoup de bibliothèques. Mais cela nécessite de lire régulièrement l'état des boutons et le reste du programme ne doit pas passer trop de temps (non bloquant);
On peut aussi vouloir utiliser les interruptions ce qui permet au programme de pouvoir avoir des tâches qui durent longtemps. En cas d'appui sur un bouton, le programme principal est suspendu par une fonction qui lit l'état du boutons, puis reprend.
Le premier moyen dont nous disposons est la fonction attachInterrupt() qui peut permettre de mettre en place facilement (qu'ils disent) la fonction d'interruption. Voici par exemple de programme pour une Uno:
const byte BOUTON = 2; // Un bouton est branché GND-bouton-broche2
void setup()
{
pinMode(LED_BUILTIN, OUTPUT); // Initialisation de la led
pinMode(BOUTON, INPUT_PULLUP); // Mise en place du bouton
attachInterrupt(digitalPinToInterrupt(BOUTON), blink, CHANGE); // Voir explications plus bas
}
void loop()
{
// Ici on mettra le programme principal qui peut avoir des longueurs dans le temps (il peut être bloquant)
}
void blink()
{
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // On met la led dans l'état inverse (allumée <-> éteinte)
}
La fonction attachInterrupt demande trois paramètres:
- le numéro de l'interruption. Comme on n'a pas envie de se prendre la tête, la fonction digitalPinToInterrupt nous le donnera en
fonction de la broche utilisée.
- le nom de la fonction d'interruption
- le mode que l'on veut, pour un AVR (Uno, Nano, Mega):
LOW (pas ou peu utilisé pour un bouton)
CHANGE appel sur un changement d'état du bouton
RISING appel sur le relâchement du bouton
FALLING appel sur l'appui du bouton.
Le bouton est câblé entre GND et la broche 2 avec une résistance de pull-up au 5V. Au repos, la broche 2 est au 5V et si on appui, elle passe à 0V. Sur appui il y a donc un front descendant (FALLING). Et quand le bouton est relâché on a un front montant (RISING).
On ne peut sur une Uno n'utiliser que les broches 2 et 3 avec ce procédé, car cela fait appel à 2 fonctions d'interruptions INT0 et INT1.
ISR(PCINT?_vect)
Avec une Uno ou une Nano, attachInterrupt() ne fonctionne qu'avec les broches 2 (PD2 = INT0) et 3 (PD3 = INT1). Avec une Mega, on ne peut utiliser que les broches 21 (PD0 = INT0), 20 (PD1 = INT1), 19 (PD2 = INT2), 18 (PD3 = INT3), 2 (PE4 = INT4), 3 (PE5 = INT5). les interruptions INT6 (PE6) et INT7 (PE7) ne sont pas accessibles (pas de broches). C'est sans doute pour cela que certains croient que seules ces broches peuvent être utilisées pour faire des interruptions avec des boutons.
Mais on peut utiliser deux ou trois autres interruptions externes. Cela concerne toutes les broches d'une Uno ou d'une Nano et 19 broches de la Mega. Ces broches sont repérées par PCINTx, x variant de 0 à 23.
Contrairement a l'utilisation de attachInterrupt() qui va utiliser une fonction d'interruption par broche, l'utilisation des PCINTx ne peuvent donner que trois fonctions d'interruption, à chacune de ces fonctions de faire éventuellement le tri de la broche qui a délencher l'interruption. Chaque fonction va chapeauter 8 broches.
Une autre limitation est que si avec attachInterrupt() on peut déclencher une interruption sur front montant, descendant ou changement, avec les PCINTx, on ne peut utiliser que le mode changement. A la fonction d'interruption de faire le tri.
Différents montages avec une lecture digitale
Un bouton par broche
En mode digital, un bouton est en général entre une broche et GND, et un appui ou un relâchement va provoquer un changement d'état. On peut donc utiliser simplement les interruptions.
Deux boutons par broche
Avec ce type de montage, on ne peut pas utiliser les interruptions.
Matrices triangulaires
Avec ce type de montage, on ne peut pas utiliser les interruptions.
Matrice carrée, mono ou poly
Avec un keypad classique ou une matrice carrée poly, si l'on veut utiliser les interruptions, il faut mettre la matrice au repos, en mettant les colonnes en INPUT_PULLUP et les lignes à GND (ou l'inverse avec un keypad). Lors d'un appui sur un bouton, une des colonne va passer à LOW et déclencher une interruption. Si on veut une seule fonction d'interruption, il faut alors que les colonnes soit sur des broches déclenchant la même interruption, si on veut faire un pré-tri on peut alors choisir les broches pour les colonnes pour qu'elles activent chacune une interruption différente. Notez que l'on a que trois fonctions possibles sur les AVR.
Par contre, il faut désactiver ces interruptions pour lire la matrice.
C'est donc possible mais c'est un peu plus compliqué qu'avec d'autres montages.
Matrice carrée triple
Avec les 3 claviers, ce n'est pas possible d'utiliser les interruptions, par contre si on n'utilise que les clavier 2 et 3 (ou 1 et 3), on est dans le cas précédent. C'est donc possible...
Différents montages avec une lecture analogique
Il est tout à fait possible d'utiliser une broche comme entrée analogique et en même temps conne entrée pour une interruption PCINTx. Avec une AVR alimentée en 5V, un état est considéré bas entre 0V et 1,5V. Un état haut est entre 3,5V et 5V. Pour utiliser les interruptions, il faut qu'en appuyant sur un bouton, on passe d'un côté à l'autre des 1,5V/3,5V. Cela peut éventuellement amener à adapter le montage pour cela.
Diviseur de tension à résistances parallèles
Dans ma page qui est consacrée à ce montage, j'ai réparti les différentes tension obtenues par appui sur les boutons entre 5V et 0V, 0V étant la tension si aucun bouton n'est appuyé. Si on veut utiliser ce montage, c'est possible si l'appui sur un bouton donne une tension ne dépassant pas 1,5V. Il faut alors changer les résistances pour obtenir par appui des tensions de 0V 0,375V 0,75V 1,125V et 1,5V. Ce qui donne alors pour 5 boutons:
On pourrait aussi avoir au maximum 1V et utiliser la référence interne de 1,1V. Mais en utilisant la référence à Vcc, si le 5V baisse, la tension d'entrée baisse aussi proportionnellement et la valeur numérique lue est la même.
Diviseur de tension à résistances série
Au repos, la résistance de 100kΩ impose un niveau bas. Sous interruption, seuls les boutons "hauts" imposent une tension supérieure à 3,5V. On peut donc garder le même montage en supprimant les boutons du bas, mais pas les résistances que l'on remplacera par une seule.
Par exemple si l'on veut 6 boutons, soit 6 boutons pour 1,5V (de 5V à 3,5V), cela ferait 20 boutons pour 5V. On prends le schéma pour 20 boutons, ceal ferait 20 résistances de 1kΩ. On ne garde que les 6 boutons du haut et les 5 résistances de 1kΩ et on remplace les 15 vieilles résistances de 1kΩ par une seule de 15kΩ.
Le schéma devient alors:
Sommateur de courants à lecture poly
Le montage que j'ai décrit utilise la référence à 1,1V et par conséquent la tension varie de 0V à 1,1V et aucune interruption ne peut être vu. Ce montage n'est pas utilisable directement avec interruptions.
Suppression de résistances mono ou poly
Les montages que j'ai décrit utilisent la référence à 1,1V et par conséquent la tension varie de 0V à 1,1V et aucune interruption ne peut être vu. Ce montage n'est pas utilisable directement avec interruptions.
Keypad ou matrice carrée à lecture analogique
Comme la référence peut être prise à 1,1V et que sans bouton appuyé, la tension d'entrée est à 5V, ce montage se prête bien à une lecture utilisant l’interruption.
ISR(PCINT?_vect)
Je vais détailler ce qu'il faut mettre en place pour un bouton (en digital) ou un ensemble de boutons (avec un câblage analogique) sur la broche A5 d'une Uno. A vous de consulter la documentation complète ou de demander de l'aide pour une autre broche.
Il faut tout d'abord que les interruptions générales soient activées, ce qui est fait par défaut sur les AVR car au moins le timer passe par interruption.
Ensuite il faut que l'interruption associée à A5 soit activée. C'est le registre PCICR qui le permet:
La broche A5 (PC5) est la broche PCINT13. C'est le bit PCIE1 qu'il faut passer à 1 car il gère les broches PCINT[14:8]. Si on n'utilise que cette interruption pour les boutons (pour les PCINT[14:8] pour être plus précis), on peut utiliser simplement:
PCICR = 2; // ou PCICR = 0b00000010;
On peut aussi utiliser la formule suivante qui ne touche pas aux autres bits:
PCICR -= _BV(PCIE1); // Autorise une broche parmi PCINT[14:8] à interrompre
Enfin, il faut dire parmi PCINT[14:8] la ou lesquelles sont autorisées à interrompre. C'est le registre PCMSK1 qui le permet:
il faut positionner le bit PCINT13 à 1. Au choix:
PCMSK1 = 32; // Autorise PCINT13 et pas les autres PCMSK1 = 0b00100000; // Autorise PCINT13 et pas les autres PCMSK1 |= 0b00100000; // Autorise PCINT13 sans toucher aux autres PCMSK1 |= _BV(PCINT13); // Autorise PCINT13 sans toucher aux autres
Tout ceci va demander à la fonction d'interruption associée au vecteur PCINT1;
ISR(PCINT1_vect) // Appelée à chaque changement sur A5
{
...
}
Application: avec un keypad analogique
Cet exemple montre juste un programme qui fonctionne et qui affiche le bouton appuyé. Ce n'est pas à suivre, on ne met pas d'affichage dans une routine d'interruption. C'est juste pour le cadre:
// Clavier matriciel 20 touches
// VCC
// │
// 15kΩ
// │
// ├── Vers A0
// ┌─ 220Ω ─┬─ 220Ω ─┬─ 220Ω ─┬─ 220Ω ─┤
// ├─ / ┐ ├─ / ┐ ├─ / ┐ ├─ / ┐ ├─ / ┐
// │ └───│────┴───│────┴───│────┴───│────┴────┐
// ├─ / ┐ ├─ / ┐ ├─ / ┐ ├─ / ┐ ├─ / ┐ 1,1kΩ
// │ └───│────┴───│────┴───│────┴───│────┴────┤
// ├─ / ┐ ├─ / ┐ ├─ / ┐ ├─ / ┐ ├─ / ┐ 1,1kΩ
// │ └───│────┴───│────┴───│────┴───│────┴────┤
// └─ / ┐ └─ / ┐ └─ / ┐ ├─ / ┐ └─ / ┐ 1,1kΩ
// └────────┴────────┴────────┴────────┴────┤
// GND
void setup()
{
Serial.begin(115200); // Mettre aussi la console sur 115200 bauds
analogReference(INTERNAL); // Vref=1,1V; mettre INTERNAL1V1 pour la Mega
PCICR = 2; // Autorise les interruptions PCINT[14:8]
PCMSK1 = 32; // Autorise uniquement PCINT13, soit la broche A5
}
void loop()
{
}
word valeurLue, // Résultat de la conversion brute puis corrigée puis n° de la touche
oldValeurLue, // Mémorisation pour permettre deux lectures consécutives identiques
oldTouche; // Touche précédente appuyé pour ne pas l'afficher plusieurs fois
ISR(PCINT1_vect)
{
// Mesure
do
{
oldValeurLue = valeurLue; // Sauvegarde de la mesure précédente
valeurLue = analogRead(A5); // Nouvelle lecture
} while (valeurLue!=oldValeurLue); // On recommence jusqu'à avoir deux lectures identiques
if (valeurLue!=1023) // Si une touche est appuyée: correction et extraction
valeurLue = ((68UL * valeurLue + 2035) / (4070 - valeurLue)); // UL car 68 dépasse 64; cela donne le N° de la touche 0..19
// Affichage
if (valeurLue!=oldTouche) // Si changement
{
if (valeurLue!=1023) // C'est une nouvelle touche
{
Serial.print("Bouton appuyé, ligne ");
Serial.print(valeurLue / 5 + 1);
Serial.print(", colonne ");
Serial.println(valeurLue % 5 +1);
}
else Serial.println("Bouton relâché");
oldTouche=valeurLue; // Mémorisation pour ne pas afficher plusieurs fois
}
}