Contrôler efficacement 10 boutons avec une seule interruption sur Arduino UNO
Composants et fournitures
- Arduino UNO (ou tout autre microcontrôleur compatible)
- Plaque d'essai (breadboard)
- Fils de connexion (jumper wires) – 21 unités
- Interrupteurs tactiles (boutons poussoirs) – 10 unités (toute taille convient)
Applications et services en ligne
- Arduino IDE
À propos du projet
Introduction
Les interruptions sont très utiles. En plus de simplifier parfois le code, elles permettent un chronométrage précis ou de réveiller l'Arduino depuis un mode veille.
Imaginez une interface utilisateur, par exemple une télécommande, fonctionnant sur batterie. Vous voudrez peut-être mettre l'Arduino (ou un ATmega autonome) en mode power-down pour économiser l'énergie. Lorsqu'un Arduino entre en mode power-down, il ne peut être réveillé que par une interruption externe. La puce ATmega328P utilisée dans l'Arduino Uno ne dispose que de deux interruptions externes sur broches (INT0 et INT1 sur les broches 2 et 3). Comme une interface utilisateur comporte généralement plus de deux boutons, cela pose problème.
La solution classique consiste à connecter tous les boutons normalement, mais aussi à les relier à une broche d'interruption via une diode. Cela complique toutefois considérablement le circuit.

Outre les interruptions externes standard, l'ATmega328P possède des interruptions de changement d'état de broche (pin change interrupts). Des bibliothèques existent pour les gérer et elles constituent une bonne solution.
Cependant, lors d'un concours de programmation, j'ai découvert comment réaliser cela en utilisant les interruptions externes standard, sans aucun composant électrique supplémentaire.
Le circuit



Nous utilisons quelques boutons (dix dans mon cas, ce qui tient bien sur ma plaque d'essai). Vous pouvez connecter un bouton par broche, soit jusqu'à 20 sur un Uno et jusqu'à 70 sur un Mega ! (Si vous avez réellement besoin de 70 boutons, je recommande d'utiliser le multiplexage, vous n'avez pas besoin d'un Mega entier pour cela.)
Chaque bouton a un côté connecté à une broche arbitraire (broches 4 à 13 dans mon cas). Les autres côtés de tous les boutons sont connectés ensemble à une seule broche capable d'interruption (broche 2 dans mon cas).

Le code
Le code est fourni ci-dessous. Pour tester cet exemple, téléversez-le sur votre carte. Ouvrez le moniteur série. Lorsque vous appuyez sur un bouton, son numéro s'affiche. Comme vous pouvez le constater, la fonction loop() n'est pas utilisée du tout.

Comment cela fonctionne-t-il ?
Il y a évidemment une interruption. Dans mon cas, elle est attachée à la broche 2 et configurée en mode FALLING (front descendant).
Pour éviter d'utiliser des diodes, l'Arduino reconfigure le circuit à la volée. Il existe deux configurations possibles : Mode commun et Mode distinct.
Mode commun
La plupart du temps, le circuit est en mode commun. La broche d'interruption est configurée en INPUT_PULLUP et les autres broches en OUTPUT et LOW.

void configureCommon() {
pinMode(commonPin, INPUT_PULLUP);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], OUTPUT);
digitalWrite(buttonPins[i], LOW);
}
}
En mode commun, appuyer sur n'importe quel bouton tire la broche d'interruption vers le bas et déclenche l'interruption. Une fois cela fait, la routine de service d'interruption (ISR) reconfigure les broches pour le mode distinct.
Mode distinct
Dès que l'interruption se déclenche, on bascule rapidement en mode distinct.
Le mode distinct est l'opposé du mode commun. La broche d'interruption devient OUTPUT et LOW, tandis que les autres deviennent INPUT_PULLUP.

void configureDistinct() {
pinMode(commonPin, OUTPUT);
digitalWrite(commonPin, LOW);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
}
En mode distinct, seules les broches correspondant aux boutons réellement pressés sont tirées vers le bas. On peut alors parcourir toutes les broches pour identifier laquelle a déclenché l'interruption.
Une fois cela fait, l'Arduino peut revenir en mode commun et attendre une nouvelle interruption. Ou, selon votre application, il peut rester en mode distinct et traiter les entrées utilisateur normalement, en revenant au mode commun avant de mettre l'Arduino en veille.
Un exemple plus complexe
Essayons quelque chose de plus élaboré. Nous connecterons un servomoteur et associerons chaque bouton à un angle différent (1=0°, 2=20°... 10=120°). Nous alimenterons également l'Arduino avec quelques piles.



Dans cet exemple, nous mettons l'Arduino en mode power-down après cinq secondes d'inactivité pour économiser l'énergie. Vous trouverez des tutoriels sur le mode veille en ligne. De plus, nous alimentons le servo via un transistor pour l'éteindre lorsqu'il n'est pas utilisé.
Le code pour cet exemple est également fourni ci-dessous.
Code
Enregistreur série (Serial Logger)
const int commonPin = 2;
const int buttonPins[] = {4,5,6,7,8,9,10,11,12,13};
unsigned long lastFire = 0;
void setup() {
configureCommon();
attachInterrupt(digitalPinToInterrupt(commonPin), pressInterrupt, FALLING);
Serial.begin(9600);
}
void loop() {
// Vide !
}
void pressInterrupt() {
if (millis() - lastFire < 200) { // Anti-rebond
return;
}
lastFire = millis();
configureDistinct();
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
if (!digitalRead(buttonPins[i])) {
press(i);
}
}
configureCommon();
}
void configureCommon() {
pinMode(commonPin, INPUT_PULLUP);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], OUTPUT);
digitalWrite(buttonPins[i], LOW);
}
}
void configureDistinct() {
pinMode(commonPin, OUTPUT);
digitalWrite(commonPin, LOW);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
}
void press(int button) {
Serial.println(button + 1);
}
Servo avec mode veille
#include
#include
#include
const int commonPin = 2;
const int buttonPins[] = {4,5,6,7,8,9,10,11,12,13};
const int servoEnablePin = A1;
const int servoPin = A0;
Servo servo;
unsigned long lastFire = 0;
int status = 0;
void setup() {
pinMode(commonPin, INPUT_PULLUP);
configureCommon();
attachInterrupt(digitalPinToInterrupt(commonPin), pressInterrupt, FALLING);
servo.attach(servoPin);
pinMode(servoEnablePin, OUTPUT);
}
void loop() {
if (millis() - lastFire > 5000) {
digitalWrite(servoEnablePin, LOW);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_mode();
}
delay(10);
}
void pressInterrupt() {
sleep_disable();
power_all_enable();
if (millis() - lastFire < 200) return;
lastFire = millis();
configureDistinct();
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
if (!digitalRead(buttonPins[i])) {
press(i);
}
}
configureCommon();
}
void configureCommon() {
pinMode(commonPin, INPUT_PULLUP);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], OUTPUT);
digitalWrite(buttonPins[i], LOW);
}
}
void configureDistinct() {
pinMode(commonPin, OUTPUT);
digitalWrite(commonPin, LOW);
for (int i = 0; i < sizeof(buttonPins) / sizeof(int); i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
}
void press(int button) {
digitalWrite(servoEnablePin, HIGH);
servo.write(map(button, 0, 9, 0, 180));
}
Schémas


Fichiers Fritzing : multiinterrupt_hoF76Oc4T5.fzz, servo_with_sleep_u9ZqxF0jhY.fzz
Processus de fabrication
- Construire un décodeur DTMF avec un Arduino seul – Aucun CI supplémentaire nécessaire
- Créer un système Ambilight spectaculaire pour moniteur avec Arduino
- Construire un dispositif de lévitation ultrasonique avec Arduino – Guide étape par étape
- Construire un Voltmètre Intelligent avec Arduino et Smartphone – Projet DIY Facile
- Moniteur de Fréquence Cardiaque IoT en Temps Réel avec Arduino et MAX30100
- Serveur Web Arduino Uno WiFi : Commander une LED via Navigateur
- Compteur de stationnement intelligent : Suivi véhicule en temps réel avec Arduino, Processing et PHP
- Automatiser le Jeu Chrome Dino avec Arduino et LDR
- Construire une Radio FM Compacte avec Arduino Nano et RDA5807M