pave Interruptions4 Interruptions2 INTERRUPTIONS
3/7


ATTINY85


INTRODUCTION

Au départ j'avais quelques codes semblant fonctionner sur attiny85. J'ai tenté avec quelques difficultés de transposer ces codes sur un attiny84 sans une réussite totale. Mais mon but au final était de faire fonctionner des interruptions sur attiny861 sur n'importe quelle broche et là, je me suis cassé les dents. J'ai donc décidé de remettre l'ouvrage sur le métier. Donc après avoir décidé de la méthode à suivre, j'ai commencé par créer un référentiel puis, a débuté le travail sur l'ATTINY85.

Cet article va décrire l'utilisation des interruptions sur la puce attiny85. Trois exemples avec 2 boutons poussoirs :
- le premier en mode ON/OFF.
- le second en mode ON/OFF avec détection du Front Montant et Descendant.
- le troisième avec un mode interrupteur Marche / Arrêt.

Le code est abondamment commenté.

ENTÊTE

L'entête est composé de :
1) Le brochage du microcontrôleur utilisé.
- SPI indiquant la couleur pour brancher le câble de programmation.
- ANALOG les numéros de broches analogiques.
- DIGITAL les numéros de broches digitales.

2) Les vecteurs d'interruptions.

3) Les registres d'états et de contrôle.

4) Schéma de branchement du microcontrôleur avec les autres composants.

Suivi ensuite par le code à proprement parler.

CODE

- Déclarations des bibliothèques #include, si nécessaires (j'ai fait en sorte de ne pas en utiliser dans mes exemples).
- Déclarations des variables, si nécessaires.
- Définition des variables volatiles qui serviront dans la ou les routines d'interruption.
- Routine(s) d'interruption(s).
- Setup où les broches sont définies et initialisées.
- Loop où le programme principal boucle en permanence.

ON / OFF

/* ATMEL ATTINY 25/45/85 - @Alain DORING (2020) - https://www.astrolynx.com/ - Original source
   ┌───────────────────────────────────────── SPI ───────────────────────────────────────────┐
   │                                ┌─────── ANALOG ──────┐                                  │
   │                                │  ┌─── DIGITAL ───┐  │                                  │
   │                                │  │    ┌─┐_┌─┐    │  │                                  │
 JAUNE       RESET/ADC0/PCINT5 PB5         1│     │8          VCC                           ROUGE
                   ADC3/PCINT3 PB3  3  3   2│     │7   2  1   PB2 PCINT2/SCK-L/ADC1/T0/INT0 BLANC
                   ADC2/PCINT4 PB4  2  4   3│     │6   1      PB1 PCINT1/MISO/DO            BLEU
 NOIR                          GND         4│     │5   0      PB0 PCINT0/MOSI/DI/SDA/AREF   VERT
                                            └─────┘         

 Vecteurs externes                 INT0_vect
 Vecteurs de changement de broches PCINT0_vect

            7       6       5       4       3       2       1       0
 GIMSK  -         INT0    PCIE  
 GIFR   -         INTF0   PCIF 
 PCMSK  -                 PCINT5  PCINT4  PCINT3  PCINT2  PCINT1  PCINT0 
 PORTB  -                 PORTB5  PORTB4  PORTB3  PORTB2  PORTB1  PORTB0 
 DDRB   -                 DDB5    DDB4    DDB3    DDB2    DDB1    DDB0 
 PINB   -                 PINB5   PINB4   PINB3   PINB2   PINB1   PINB0 
 WDTCR  - WDIF    WDIE    WDP3    WDCE    WDE     WDP2    WDP1    WDP0
 WDT    0x000C

                                 ┌────────────┐
 Non utilisé             RESET ─◄│ PB5    VCC │►─                 
                                 │            │                    
                 _▄▄_            │            │    1K      „      
 LED PB2 < BP ┌──O  O───────────►│ PB3    PB2 │►─/\/\/\───┤►├───┐  LED VERTE 
              │                  │            │                 │ 
              │  _▄▄_            │            │            „    │
 LED PB0 < BP ├──O  O───────────►│ PB4    PB1 │►─/\/\/\───┤►├───┤  LED  ORANGE    
              │                  │            │                 │  CLIGNOTANTE
              │                  │            │            „    │
              ├─────────────────◄│ GND    PB0 │►─/\/\/\───┤►├───┤  LED ROUGE    
              │                  └────────────┘                 │
              ┴                                                 ┴
             GND                                               GND
*/

volatile int oldEtatB3;
volatile int newEtatB3;
volatile int oldEtatB4;
volatile int newEtatB4;

// Routine d'interruptions
ISR (PCINT0_vect)
{
  newEtatB3 = (PINB & B00001000); // newEtatB3 = digitalRead(3) pour PB3;
  newEtatB4 = (PINB & B00010000); // newEtatB4 = digitalRead(4) pour PB4;

  // Détection d'un changement d'état PB2
  if (oldEtatB3 != newEtatB3) {
    PORTB ^= (1 << PB2);    // bascule la broche PB2 si changement logique PCINT3
    oldEtatB3 = newEtatB3;
  }

  // Détection d'un changement d'état PB0
  if (oldEtatB4 != newEtatB4) {
    PORTB ^= (1 << PB0);    // bascule la broche PB0 si changement logique PCINT4
    oldEtatB4 = newEtatB4;
  }
}

void setup() {
  // Initialisations des broches en entrées ou en sorties = 5 x Pinmode(pin, INPUT ou OUTPUT) 
  DDRB   =  B11000111;      // 1 en sorties PB0-1-2 et 0 en entrée PB3-4 et PB5 (RESET)

  // initialisations des PULLUPs = 2 x Pinmode(pin, INPUT_PULLUP) 
  PORTB |= B00011000;       // active le pullup PB3 et PB4 pour les BPs  
  
  // Activation du port B 
  GIMSK   = B00100000;      // active interruption de changement de broche (PCIE)

  // Déclaration des broches d'interruptions
  PCMSK |= B00011000;       // changement de broche activée pour PB3 et PB4

  // Activation globale des interruptions
  sei ();                   // active les interruptions

  // Inversion logique des 2 leds commandées par les BPs sinon démarrent allumées
  PORTB ^= B00000101;       // bascule PB0 & PB2 pour inverser la logique

  // Initialisations des routines d'interruptions pour forcer le démarrage leds éteintes
  PCINT0_vect();            // Initialise la routine d'interruption de PB3 & PB4     
  
}

void loop() {
  // Mettre ici le code à répéter continuellement
  delay(250);
  PORTB ^= (1 << PB1);      // Led clignotante
}

Téléchargement du code au bas de cette page

Sur la première ligne de code du setup, le commentaire d'entrée explique bien la façon d'utiliser ce genre de programmation et son avantage principal.

  // initialisation des broches en entrées ou en sorties = 5 x pinMode(pin, INPUT ou OUTPUT)
  DDRB   =  B11000111;      // 1 en sorties PB0-1-2 et 0 en entrée PB3-4 et 5 (RESET)

Cette ligne de code remplace, à elle seule, 5 lignes de code (dans cette puce, il n'y a que 6 broches programmables).

Elle serait remplacée par :

  pinMode(0, OUTPUT); // PB0 
  pinMode(1, OUTPUT); // PB1 
  pinMode(2, OUTPUT); // PB2 
  pinMode(3, INPUT);  // PB3 
  pinMode(4, INPUT);  // PB4 

DDRB (Data Direction Register port B). Le B de B11000111 désigne un nombre binaire dont les bits indiquent la direction de la broche (0 pour entrée ou 1 pour sortie). Les bits vont de 0 à 7 en partant de la droite vers la gauche. DDRB se voit affecté par le signe = de la valeur de droite B11000111. Donc les broches PB0, PB1 et PB2 sont affectées en sorties pour allumer les leds et les broches PB3 et PB4 en entrées pour recevoir les ordres des boutons poussoirs. L'exemple en pinMode indique les pins de 0 à 4 donc les cinq broches réellement utilisées. Dans ce cas, la sixième broche (broche digitale 5 - PB5) étant le RESET n'est pas utilisée donc pas programmée. Par contre, elle est mise à 0 dans DDRB parce qu'il faut mettre 0 ou 1.

Les bits 6 et 7 (ceux de gauche) sont mis à 1.

ATTENTION ! Dans les "pinMode()" ci-dessus, j'ai utilisé les numéros de broches digitales qui, pour l'ATTiny85, correspondent au numéros des bits du port B ... Commentés sur chaque ligne.CE N'EST PAS TOUJOURS LE CAS. Si c'est possible, utilisez toujours PB0 ... PB7 pour le PORTB. Suivant les puces utilisées, vous trouverez le(s) PORTA, PORTB, PORTC, PORTD et donc, PAx, PBx, PCx, PDx.

FRONT MONTANT et FRONT DESCENDANT

Dans le code ci-dessus, à chaque appui ou relâchement du bouton poussoir, il se produit une interruption qui active une simple bascule OFF/ON, ON/OFF dans l'ISR. Ici je vous propose de détecter le niveau HAUT ou BAS de la broche, pour avoir la possibilité de mesurer la durée d'impulsion entre l'appui et le relâchement du bouton poussoir ou tout autre capteur. ATTENTION ! Il est conseillé de faire des routines ISR les plus courtes possible et éviter certaines opérations trop longues. Dans ce cas, le plus simple est de charger une variable avec Millis(). Exemple Monte = Millis() et Descend = Millis(). Le traitement se faisant dans la boucle loop.

Remarque : Dans ce code, le front montant est noté avant la montée, il peut bien entendu être déclaré après. A l'inverse, le front descendant est noté après la descente et, il peut également être déclaré avant. Cela est à votre seule appréciation. Les temps de montée et de descente me sont inconnus mais ils doivent être très courts. Sachant qu'un cycle à 8mHz est égal 1 / 8000000 donc 0,125µs, ils doivent être inférieur à la microseconde, sauf erreur de ma part. Tout ceci n'étant que théorique, si vous aviez une réponse plus fiable à me proposer, je me ferai un plaisir de répercuter votre information (cliquez sur le petit bonhomme qui s'agite au bas à droite de la page d'accueil). Le code contient la mise en mémoire DM, FM, DD et FD mais je n'ai pas encore pu tester le retour car l'affichage sur la console série ne fnctionne pas. A l'avenir, je tenterai un montage avec un afficheur I2C. Ci-dessous, la routine d'interruption (ISR) modifiée.

// variables à déclarer
volatile unsigned long DM, FM, DD, FD;

// Routine d'interruptions
ISR (PCINT0_vect)
{
  newEtatB3 = (PINB & B00001000); // newEtatB3 = digitalRead(3) pour PB3;
  newEtatB4 = (PINB & B00010000); // newEtatB4 = digitalRead(4) pour PB4;

  // Détection d'un changement d'état PB3
  if (oldEtatB3 != newEtatB3) {
    if (newEtatB3 == 1) {
      // Début du front montant pour PB3 ici
	  DM = micros();
      PORTB ^= (1 << PB2);    // bascule la broche PB2 si changement logique PB3
      // Fin du front montant pour PB3 ici
	  FM = micros();
    }
    else {
      // Début du front descendant pour PB3 ici
	  DD = micros();
      PORTB ^= (1 << PB2);    // bascule la broche PB2 si changement logique PB3
      // Fin du front descendant pour PB3 ici
	  FD = micros();
    }
    oldEtatB3 = newEtatB3;
  }

  // Détection d'un changement d'état PB4
  if (oldEtatB4 != newEtatB4) {
    if (newEtatB4 == 1) {
      // Insérez le traitement du front montant pour PB4 ici
      PORTB ^= (1 << PB0);    // bascule la broche PB0 si changement logique PB4
    }
    else {
      PORTB ^= (1 << PB0);    // bascule la broche PB0 si changement logique PB4
      // Insérez le traitement du front descendant pour PB4 ici
    }
    oldEtatB4 = newEtatB4;
  }
}

Téléchargement du code au bas de cette page

MARCHE / ARRET

A noter : La désactivation de l'inversion logique des 2 leds dans le setup. Voir lignes ci-dessous.

  // Inversion logique des 2 leds commandées par les BPs sinon démarrent allumées
  //  PORTB ^= B00000101;       // bascule PB0 & PB2 pour inverser la logique

Ci-dessous la routine ISR.

// Routine d'interruptions
ISR (PCINT0_vect)
{
  newEtatB3 = (PINB & B00001000); // newEtatB3 = digitalRead(3) pour PB3;
  newEtatB4 = (PINB & B00010000); // newEtatB4 = digitalRead(4) pour PB4;

  // Détection d'un changement d'état PB3
  if (!newEtatB3 || newEtatB3) {
    if (!newEtatB3) {
      PORTB ^= (1 << PB2);    // bascule la broche PB2 si changement logique PB3
    }
  }

  // Détection d'un changement d'état PB4
  if (!newEtatB4 || newEtatB4) {
    if (!newEtatB4) {
      PORTB ^= (1 << PB0);    // bascule la broche PB0 si changement logique PB4
    }
  }
}

Téléchargement du code au bas de cette page

Que faire de tout ceci ? Au-delà du simple calcul, on peut commander des actions en utilisant des transistors, des mosfets, des ULN 2003 ou des relais. Exemple : télécommande ou programmation d'éclairage, simulateur de présence, réveil du circuit par une action avec envoi de données, etc..., etc..., la seule limite étant l'imagination de chacun.

RECOMMANDATIONS

L'utilisation des interruptions n'est pas systématique. Pour vous lancer dans la programmation des interruptions, il faut être rigoureux, très attentif et ne pas être borné. Le téléversement s'est bien passé mais le montage ne fonctionne pas correctement, ou pas du tout. Première chose, vérifier son câblage et la tension d'alimentation. J'insiste sur cette vérification, le diable se cache dans les détails, un câble présent mais branché sur la patte juste à côté, un petit strap de masse oublié. Ah! Et sur les grandes platines d'essai, n'oubliez pas de ponter les liaisons GND et VCC de chaque côté au milieu, si cela est nécessaire. Parfois on a beau regardé, on ne voit rien. Allez faire autre chose puis, remettez l'ouvrage sur le métier.


La platine du bas nécessite des pontages
pour assurer la continuité des pistes VCC et GND.
Celle du haut n'en a pas besoin.

Le matos et le câblage ça va !... on vérifie son programme, des erreurs ne sont pas forcément indiquées. un exemple : un simple if (x = 1) {} ne se mettra pas en erreur ( ouais ben ce n'est pas = mais == ). Des erreurs comme celle-ci, il y en a d'autres, cherchez sur le net, vous trouverez. Tout s'est bien passé, je vous souhaite bon courage pour la suite et de nombreuses interruptions ... sur ATTINY. Dernière recommandation, prenez garde de ne pas vous tromper dans les lignes de programmation contenant des codes binaires. Cela ne pardonne pas.

Vous avez du mal à vous en sortir, je vous conseille la lecture de : https://www.locoduino.org/spip.php?article174
Ou la version original de Nick GAMMON (en anglais) : http://www.gammon.com.au/forum/?id=12153 que vous pouvez bien entendu, passer en traduction dans votre navigateur préféré.

Si vous reproduisez l'implantation suivante sur une breadboard et utilisez le bon code en téléchargement, alors tout devrait fonctionner, sinon appliquez les recommandations ci-dessus. Lorsque vous aurez compris le mode de programmation, il sera facile de transposer le code sur d'autres puces en tenant compte des spécificités de chacune d'entre-elles. En plus de l'ATTINY85, les pages suivantes décrivent les adaptations pour les puces ATTINY84, ATTINY861, ATTINY88 ce qui vous donne respectivement 5, 11, 15, 23 broches exploitables (exit les broches d'alimentation et RESET). Dans les exemples décrits, suivant les puces, 1 à 3 broches sont utilisées pour les faire clignoter. Elles sont volontairement choisies, par port différent et/ou avec la broche INT0 pour vérifier le fonctionnement des interruptions par changement de broches. Pour le reste chaque bouton poussoir est attaché à une led afin d'en vérifier le fonctionnement. Il est bien entendu possible d'utiliser la totalité des broches - une en interruptions (4, 10, 14, 22 broches), et avec la broche laissée en sortie, communiquer par un code avec une autre puce pour actionner ce que l'on veut. Maintenant à vous d'imaginer ce que vous pourriez en faire.

A NOTER :

Les codes n'utilisent aucune librairie sauf à ajouter un complément de code nécessitant une ou plusieurs librairies.

Voilà ! Je m'excuse pour cette introduction un peu longue mais les connaisseurs liront en diagonale. Pour un débutant ou ceux qui n'ont aucune connaissance sur les interruptions, je leur conseille de lire attentivement ces premières pages avant d'entamer les pages suivantes, à savoir, ATTINY84, ATTINY861 et ATTINY88.

IMPLANTATION

Cette vue permet de voir l'implantation des composants sur une breadboard. Les annotations B0..B4 sont à lire PB0..4. La broche PB5, notée RST pour RESET, n'est pas utilisée.

VIDÉO

Cette vidéo décrit le processus après chargement du code dans l'IDE. Le choix de la puce (suivant le code chargé), la connexion de la puce. La description de la structure des codes. Le téléversement et la démonstration du code.

Cette vidéo montre le fonctionnement des interruptions sur un ATTiny85.
La led verte PB2 commandée par le bouton poussoir PB3.
La led rouge PB0 commandée par le bouton poussoir PB4.
La led orange PB1 dont le clignotement est assuré dans la boucle LOOP.

Télécharger les codes Interrupt_ATTiny85

POUR COMPRENDRE LA LOGIQUE EMPLOYÉE

Dans le code les opérations se font bit à bit.

  !    NON logique  
   	!0			!1
  =	1		  =	0  ->  bit inversé

  &    ET logique  
  X	0	1	0	1
  &	0	0	1	1
  =	0	0	0	1

  Pour exprimer X = X & 0  on utilise plutôt l'écriture suivante :

  X &= 0  ->  force le bit à 0
  X &= 1  ->  valide le bit à 1 si et seulement si X=1

  |    OU logique  
  X	0	1	0	1
  |	0	0	1	1
  =	0	1	1	1

  X |= 1  ->  force le bit à 1
  X |= 0  ->  bit inchangé

  ^    OU EXCLUSIF  
  X	0	1	0	1
  ^	0	0	1	1
  =	0	1	1	0

  X ^= 1  ->  force le changement d'état
  X ^= 0  ->  bit inchangé