Aide spécifique et générale

Portrait de binjch

Bonjour à toutes et à tous,

Je poste mon problème qui concerne le codage d'un exercice ultra débutant sur un atmega32a car il n'y a pas de forum dédié AVR.

Je suis des cours d'électronique et viens de commencer la programmation AVR. J'avoue que je suis complètement perdu. J'ai cherché des sites pour débuter mais tout est orienté arduino et en anglais que je maîtrise bien mais disons que lorsqu'il s'agit de décortiquer un truc aussi complexe que ce type de programmation cela ajoute une couche de difficulté. Le problème est que le prof n'a pas de cours il nous pousse à nous débrouiller avec le web et je trouve qu'il manque un tuto en français hyper clair pour aborder la mécanique de base des sytèmes AVR. Les explications sont toujours trop brouillonnes ou alors c'est que c'est vraiment difficile à expliquer (en tous cas le prof n'y arrive pas on est tous dans les choux).

Mon problème actuel est je pense hyper facile, c'est le stade suivant du blinking led mais je n'arrive pas à aboutir.

On doit réaliser un Quiz game sans faire appel à _delay_ms() : 4 push buttons / Quand un est enfoncé il interrompt les 3 autres  boutons et déclenche une led pendant 4 secondes et un buzzer pendant la moitié du temps de la led donc 2 secondes. Le cahier des charges ne précise pas que chaque bouton à sa propre led donc à priori on peut connecter les boutons en parallèle et utiliser une patte de requête externe (int0 ou int1).

J'ai réussi à ma manière à gérer les delays des leds mais je n'arrive pas à implémenter l'interruption des boutons non enfoncés. 

Voici mon code:

[code]#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/delay.h>    //pour gestion debouncing du bouton
#include <avr/interrupt.h>

// Variable de comptage des dépassements
volatile uint8_t count;

#define BUTTON_HIGH(byte,bit) (byte & (1 << bit))

/** Déclaration des fonctions **/

static void avr_init(void);

void timer1_init(void);

/** Gestion de l'interruption **/

ISR(INT0_vect)
{
    PORTA |= (1 << PA0);
    PORTA |= (1 << PA1);
    timer1_init();
}

ISR(TIMER1_OVF_vect)
{
    // keep a track of number of overflows
    count++;
}

//MAIN
int main(void)
{
    avr_init();

    while(1)
    {
        if (count >= 2)                    // 2 X count X 0,2500112 = 1,00045sec
        {
            PORTA &= ~(1 << PA0);
            if (count >= 4)                // 2 X count X 0,2500112 = 2,0009sec
            {
                PORTA &= ~(1 << PA0);
                PORTA |= (0 << PA0);
                PORTA &= ~(1 << PA1);
                PORTA |= (0 << PA1);
                TCNT1 = 0;                // reset counter
                TCCR1B = 0x00;            //stops the timer
            }
        }
        else
        {

        }
    }
    return 0;
}

static void avr_init(void)
{
    // Les sorties

    DDRA |= (1 << PA0);        //Les ports D sont conf. comme sorties
    DDRA |= (1 << PA1);

    // Les entrées

    DDRD &= ~(1 << PD2);
    PORTD |= (1 << PD2);        // configure PORTD bit 2 comme résistance de tirage

    // Active l'interruption externe "INT0" qui allume les leds et active le timer1
    GICR |= (1 << INT0);

    // Définit un déclenchement sur front descendant

    MCUCR |= (1 << ISC01);

    // Active les requètes d'interruptions
    sei();

    return;
}

void timer1_init()
{
    // set up timer with prescaler = 1024        // 1 cycle = 0,000977 sec
    TCCR1B |= (1 << CS11)|(1 << CS10);
    // initialize counter
    TCNT1 = 0;                                    // 1 cycle = 256    // 256 X 0,000977 = 0,250112sec
    // enable overflow interrupt
    TIMSK |= (1 << TOIE1);
    // initialize overflow counter variable
    count = 0;
}

[/code[

Merci

Portrait de Spy

Programme testé et fonctionnel !

/*
  Ici on va considérer que Chaque bouton commande une Led qui lui est spécifique, car
  qui peut le plus peut le moins.
De plus cela permet de voir quel bouton a été activé.
Dans le cas contraire il suffit de n'attribuer qu'une seule Led.

 Le programme ci-après teste successivement l'état des boutons poussoir, si l'un d'eux est  actionné il lance un sous-programme qui interrompt la boucle loop() tant que les conditions  de se sous-programme ne sont pas satisfaites de ce fait les boutons ne sont plus testés jusqu'au retour au programme principal ( loop() )
*/
// Déclaration des Variables
int BP1  = 2; // BP1 est un bouton poussoir raccordé entre la pin 2 et la masse (GND)
int Led1 = 3; // Led1 est raccordé sur la pin 3 à travers une résistance de limitation
int BP2  = 4; // idem les lignes précédentes
int Led2 = 5;
int BP3  = 6;
int Led3 = 7;
int BP4  = 8;
int Led4 = 9;
int Buzzer = 10;
unsigned long Timer ;

void setup() {
  // Attribution des pins à chaque éléments, boutons poussoir et Leds, avec définition de la direction entrée ou sortie.
  pinMode(BP1, INPUT_PULLUP); // Le bouton BP1 est sur la pin 2 qui est en entrée et forcée à l'état haut par une résistance de rappel interne (PULLUP)
  pinMode(Led1, OUTPUT); // La Led1 est sur la pin 3 qui est déclarée en sortie, mais ne pas oublier la résistance de limitation
  digitalWrite(Led1, LOW); // Led éteinte
  pinMode(BP2, INPUT_PULLUP); // Idem pour les autres boutons et les Leds
  pinMode(Led2, OUTPUT);
  digitalWrite(Led2, LOW);
  pinMode(BP3, INPUT_PULLUP);
  pinMode(Led3, OUTPUT);
  digitalWrite(Led3, LOW);
  pinMode(BP4, INPUT_PULLUP);
  pinMode(Led4, OUTPUT);
  digitalWrite(Led4, LOW);
  pinMode(Buzzer, OUTPUT); // Le Buzzer est ur la pin 10 qui est déclarée en sortie
  digitalWrite(Buzzer, LOW);
}

void loop() {
  if (digitalRead(BP1) == LOW) { // Si le BP1 est appuyé  on exécute les lignes suivantes sinon on saute toutes les ligne jusqu'au "}" suivant
    Action(Led1); // Si c'est le cas on exécute le programme Action() avec le paramètre Led1 et idem pour les lignes suivantes
  }
  if (digitalRead(BP2) == LOW) { // Si le BP2 est appuyé  on exécute les lignes suivantes sinon on saute toutes les ligne jusqu'au "}" suivant
    Action(Led2);
  }
  if (digitalRead(BP3) == LOW) { // Si le BP3 est appuyé  on exécute les lignes suivantes sinon on saute toutes les ligne jusqu'au "}" suivant
    Action(Led3);
  }
  if (digitalRead(BP4) == LOW) { // Si le BP4 est appuyé  on exécute les lignes suivantes sinon on saute toutes les ligne jusqu'au "}" suivant
    Action(Led4);
  }
}

void Action(int LedX) {// Déclaration d'un sous programme Action qui ne retourne aucune valeur (void) et qui reçoit comme paramètre le numéro de la Led qu'il attribue à la variable int LedX
  Timer = millis() + 4000 ; // millis est la valeur en millisecondes depuis que le programme est en exécution.
  // dans Timer on attribue la valeur de millis') plus 4 secondes (4000 millisecondes)
  digitalWrite(LedX, HIGH); // on passe à l'état haut la pin correspondante à LedX
  digitalWrite(Buzzer, HIGH); //on passe à l'état haut la pin correspondante u Buzzer
  while (Timer > millis()) // tant que la valeur de Timer est supérieure à millis() on crée une boucle et on en sortira pas tant que c'est vérifié
  {
    if (millis() + 2000 > Timer) { // si le millis() plus 2 secondes est supérieur à Timer on arrête le Buzzer
      digitalWrite(Buzzer, LOW);
    }
  } // si millis() est supérieur à Timer on sort de la boucle while ...
  digitalWrite(LedX, LOW); //on éteint la Ledx
} // on retourne au programme dans loop() juste sous "Action(Led...)"

Portrait de Spy

Les explications supplémentaires seront fournies par le professeur Walter !

Portrait de Walter

Tu te trompe je ne suis pas professeur, même si le développement est mon métier!
Par contre je suis étonné d'un programme aussi documenté et non condescendant. peut être à force Maitre Spy commence à comprendre ce que l'on veut lui dire? 

Portrait de Spy

Professeur, tous les programmes de mon cru sont documentés !

Portrait de Walter

Ha, Bien Maître!

Portrait de Spy

Rappelle-toi ce que  tu as dis concernant le programme sur les moteurs pas à pas !!

Portrait de Walter

Donc c'est la preuve que tout tes programmes sont documentés, ok, je ne connaissais pas cette nouvelle définition, je note Maître.

Portrait de binjch

C'est super, grand merci mais... Je dois le faire sous Armel studio en langage C avec syntaxe proche assembleur en utilisant seulement les timers et les interruptions. (DDR*, PORT*, TCNT*,...). En fait je bosse direct sur ma breadboard avec un atmega32a, pas le atmega328p. Mais je suis agréablement surpris de votre réactivité et je vais décortiquer le code pour voir si je n'y trouve pas des pistes :-D

Portrait de binjch

Bon ben j'attends les explications du professeur...

Portrait de Spy

Je pense que tu vas attendre un certain temps !

Les Arduinos utilisent des Atmega 328p et Alex a fait une vidéo superbe pour utiliser ce processeur en langage Arduino regarde la video

Portrait de binjch

Bon pas grave je continue à chercher un bon tuto ou un bon livre. Le but de l'exercice est de le faire en langage pseudo machine donc exit le langage arduino :-/ En arduino ce serait déjà fait :-D

Portrait de Spy

Le langage C c'est très loin du langage machine les commandes que tu cites sont du C pas  une once de langage machine qui est lui même loin de l'assembleur mais qui reste un langage interprété !

Exemple

pour calculer (7+6)%3, on aura le code suivant :

MOV AX, 7	; AX, qui est mis à 7, sera la destination
ADD AX, 6	; On ajoute à AX la valeur 6, le résultat étant stocké dans la destination, soit AX
MOV BX, 3	; On met dans BX la valeur 3 qui servira de source à la division
DIV BX		; DIV divise AX par la source, ici BX, qui vaut 3
;le résultat de la division est stocké dans la destination (BX)
;et le modulo (reste de la division qui nous intéresse ici) est mis dans DX

On joue là déjà sur les registres et l'accumulateur, l'endroit où se font réellement les opérations dans le processeur. Alors ton prof il est sûrement bien gentil mais je ne pense pas que ce soit un bon prof !

Un bon prof enseigne du mieux qu'il le peut par de explications des exemples et des conseils pour que ses élèves arrivent à quelque chose. En france les professeurs comme le tien, si tu dis vrai, c'est à dire qu'ii vous laisse en gros vous démerder tout seul sur le Net, on les appelle des grosses feignasses !

Le pense même que Walter sera d'accord avec moi sur ce qualificatif !

pour ce qui est du lagage machine voilà à quoi cela ressemble :

Bonne chance !!

Walter si tu me lis qu'en penses-tu ?

Portrait de binjch

je suis pas dans la mouize... C'est pas que j'ai pas essayé. De plus je me suis déjà bien intéressé à ce genre de discipline et c'est la première fois que je cale, ce qui me fait penser que si il y'a bien une matière qui demande de l'aide claire au départ c'est celle-là. Le truc est que les profs de l'école pompent quasi tous leurs cours sur des trucs internet et au sujet de l'AVR il n'y a pas grand-chose qui n'est pas destiné à de "déjà ingénieurs qui s'y mettent". Pffff... Bon peut-être que j'ai mal compris. En gros l'exercice est à faire en C avec la config "manuelle" des timers.

Portrait de Spy

Pour plus de compréhension je te donne ci dessous mon code Arduino  en langage machine ! (celui que je donne en grisé un peu plus haut) :

:100000000C9461000C9473000C9473000C947300B6
:100010000C9473000C9473000C9473000C94730094
:100020000C9473000C9473000C9473000C94730084
:100030000C9473000C9473000C9473000C94730074
:100040000C948E010C9473000C9473000C94730048
:100050000C9473000C9473000C9473000C94730054
:100060000C9473000C94730000000000240027001F
:100070002A0000000000250028002B0000000000DE
:1000800023002600290004040404040404040202DA
:100090000202020203030303030301020408102007
:1000A0004080010204081020010204081020000012
:1000B0000008000201000003040700000000000027
:1000C000000011241FBECFEFD8E0DEBFCDBF21E07E
:1000D000A0E0B1E001C01D92AD30B207E1F70E948F
:1000E000D8010C947E020C940000833081F028F437
:1000F000813099F08230A1F008958730A9F08830DE
:10010000B9F08430D1F4809180008F7D03C080915C
:1001100080008F7780938000089584B58F7702C028
:1001200084B58F7D84BD08958091B0008F7703C022
:100130008091B0008F7D8093B0000895CF93DF93BE
:10014000282F30E0F901E255FF4F8491F901E6567E
:10015000FF4FD491F901EA57FF4FC491CC2391F09E
:1001600081110E947500EC2FF0E0EE0FFF1FE458A4
:10017000FF4FA591B491EC91ED2381E090E021F443
:1001800080E002C080E090E0DF91CF9108951F935E
:10019000CF93DF93282F30E0F901E255FF4F849190
:1001A000F901E656FF4FD491F901EA57FF4FC49188
:1001B000CC23C1F0162F81110E947500EC2FF0E0C6
:1001C000EE0FFF1FEE58FF4FA591B4919FB7F89423
:1001D000111104C08C91D095D82302C0EC91DE2B74
:1001E000DC939FBFDF91CF911F910895CF93DF9351
:1001F00090E0FC01E656FF4F2491FC01EA57FF4FC7
:100200008491882361F190E0880F991FFC01E859DF
:10021000FF4FC591D491FC01EE58FF4FA591B491C9
:10022000611109C09FB7F894888120958223888343
:10023000EC912E230BC0623061F49FB7F894888153
:10024000322F309583238883EC912E2B2C939FBFE4
:1002500006C08FB7F894E8812E2B28838FBFDF91DB
:10026000CF910895CF93C82F2FB7F89480910901AB
:1002700090910A01A0910B01B0910C012FBF805603
:10028000904FAF4FBF4F8093000190930101A09317
:100290000201B093030161E08C2F0E94C70061E06E
:1002A0008AE00E94C7002FB7F894809109019091CD
:1002B0000A01A0910B01B0910C012FBF40910001E8
:1002C000509101016091020170910301841795071B
:1002D000A607B707F0F42FB7F89480910901909121
:1002E0000A01A0910B01B0910C012FBF8053984FD0
:1002F000AF4FBF4F40910001509101016091020149
:1003000070910301481759076A077B0760F660E0A0
:10031000C7CF60E08C2FCF910C94C7001F920F9233
:100320000FB60F9211242F933F938F939F93AF9308
:10033000BF938091090190910A01A0910B01B091A6
:100340000C013091080123E0230F2D3720F4019692
:10035000A11DB11D05C026E8230F0296A11DB11DE8
:10036000209308018093090190930A01A0930B0147
:10037000B0930C018091040190910501A0910601B8
:10038000B09107010196A11DB11D809304019093C6
:100390000501A0930601B0930701BF91AF919F9112
:1003A0008F913F912F910F900FBE0F901F90189536
:1003B000789484B5826084BD84B5816084BD85B540
:1003C000826085BD85B5816085BD80916E0081604C
:1003D00080936E00109281008091810082608093F2
:1003E00081008091810081608093810080918000F4
:1003F0008160809380008091B10084608093B1001F
:100400008091B00081608093B00080917A00846018
:1004100080937A0080917A00826080937A00809144
:100420007A00816080937A0080917A00806880935E
:100430007A001092C10062E082E00E94F60061E062
:1004400083E00E94F60060E083E00E94C70062E063
:1004500084E00E94F60061E085E00E94F60060E022
:1004600085E00E94C70062E086E00E94F60061E03D
:1004700087E00E94F60060E087E00E94C70062E02B
:1004800088E00E94F60061E089E00E94F60060E0EA
:1004900089E00E94C70061E08AE00E94F60060E007
:1004A0008AE00E94C700C0E0D0E082E00E949E0087
:1004B000892B21F483E090E00E94320184E00E94C5
:1004C0009E00892B21F485E090E00E94320186E0B5
:1004D0000E949E00892B21F487E090E00E94320167
:1004E00088E00E949E00892B21F489E090E00E9420
:1004F00032012097D1F20E940000D7CFF894FFCFAD
:00000001FF

Bon appétit !

Portrait de Spy

Pour utiliser les instructions dont tu parles : (Traduction du site arduino pas de mon Cru !!!!)

Langue de référence | Bibliothèques Comparaison | Changements

Registres de port

Les registres de ports permettent une manipulation plus rapide et de niveau inférieur des broches d’entrée / sortie du microcontrôleur sur une carte Arduino. Les puces utilisées sur la carte Arduino (les ATmega8 et ATmega168 ) ont trois ports:

  • B (broches numériques 8 à 13)
  • C (broches d'entrée analogiques)
  • D (broches numériques 0 à 7)

Chaque port est contrôlé par trois registres, qui sont également des variables définies dans la langue arduino. Le registre DDR détermine si la broche est une entrée ou une sortie. Le registre PORT contrôle si la broche est HIGH ou LOW, et le registre PIN lit l'état des broches INPUT définies pour une entrée avec pinMode (). Les cartes des puces ATmega8 et ATmega168 indiquent les ports. La nouvelle puce Atmega328p suit exactement le brochage de l’Atmega168.

Les registres DDR et PORT peuvent être à la fois écrits et lus. Les registres PIN correspondent à l'état des entrées et ne peuvent être lus que.

PORTD mappe les broches numériques Arduino 0 à 7

DDRD - Registre de direction de données du port D - lecture / écriture
PORTD - Le registre de données du port D - lecture / écriture
PIND - Le registre des broches d’entrée du port D - en lecture seule

PORTB mappe sur les broches numériques Arduino 8 à 13 Les deux bits de poids fort (6 & 7) mappent sur les broches en cristal et ne sont pas utilisables

DDRB - Registre de direction de données du port B - lecture / écriture
PORTB - Le registre de données du port B - lecture / écriture
PINB - Le registre des broches d’entrée du port B - en lecture seule

Les cartes PORTC correspondent aux broches analogiques Arduino 0 à 5. Les broches 6 et 7 ne sont accessibles que sur le Arduino Mini.

DDRC - Le registre de direction des données du port C - lecture / écriture
PORTC - Le registre de données du port C - lecture / écriture
PINC - Le registre des broches d’entrée du port C - en lecture seule

Chaque bit de ces registres correspond à une seule broche; Par exemple, les bits bas de DDRB, PORTB et PINB se rapportent à la broche PB0 (broche numérique 8). Pour un mappage complet des numéros de broches Arduino sur les ports et les bits, reportez-vous au schéma de votre puce: ATmega8 , ATmega168 . (Notez que certains bits d'un port peuvent être utilisés pour des opérations autres que l'entrée / sortie; veillez à ne pas modifier les valeurs des bits du registre qui leur correspondent.)

Exemples

En se référant à la carte des broches ci-dessus, les registres PortD contrôlent les broches numériques Arduino 0 à 7.

Notez toutefois que les broches 0 et 1 sont utilisées pour les communications série pour la programmation et le débogage de l’Arduino. Par conséquent, il est recommandé d’éviter de les remplacer, sauf si cela est nécessaire pour les fonctions d’entrée ou de sortie en série. Sachez que cela peut interférer avec le téléchargement ou le débogage du programme.

DDRD est le registre de direction pour le port D (broches numériques Arduino 0 à 7). Les bits de ce registre contrôlent si les broches de PORTD sont configurées comme entrées ou comme sorties, par exemple:

  DDRD = B11111110;  // définit les broches Arduino 1 à 7 comme sorties, la broche 0 comme entrée
 DDRD = DDRD |  B11111100;  // cela est plus sûr car cela règle les broches 2 à 7 comme sorties
	                   // sans changer la valeur des pins 0 & 1, qui sont RX & TX 

//See the bitwise operators reference pages and le tutoriel Bitmath dans le terrain de jeu

PORTD est le registre de l'état des sorties. Par exemple;

PORTD = B10101000; // sets digital pins 7,5,3 HIGH

Vous ne verrez cependant que 5 volts sur ces broches si celles-ci ont été définies comme sorties à l'aide du registre DDRD ou avec pinMode ().

PIND est la variable du registre d'entrée. Elle lira toutes les broches d'entrée numériques en même temps.

Pourquoi utiliser la manipulation de port?

Du tutoriel de Bitmath

De manière générale, faire ce genre de chose n’est pas une bonne idée. Pourquoi pas? Voici quelques raisons:

  • Le code est beaucoup plus difficile à déboguer et à maintenir, et beaucoup plus difficile à comprendre pour les autres. Il ne faut que quelques microsecondes pour que le processeur exécute le code, mais il vous faudra peut-être des heures pour comprendre pourquoi cela ne fonctionne pas et le réparer! Votre temps est précieux, non? Mais le temps de l'ordinateur est très bon marché, mesuré en coût de l'électricité que vous lui donnez. Il est généralement préférable d’écrire du code de la manière la plus évidente.
  • Le code est moins portable. Si vous utilisez digitalRead () et digitalWrite (), il est beaucoup plus facile d'écrire du code qui s'exécutera sur tous les microcontrôleurs Atmel, tandis que les registres de contrôle et de port peuvent être différents sur chaque type de microcontrôleur.
  • Il est beaucoup plus facile de provoquer des dysfonctionnements non intentionnels avec un accès direct au port. Notez comment la ligne DDRD = B11111110; ci-dessus mentionne qu'il doit laisser la broche 0 en tant que broche d'entrée. La broche 0 est la ligne de réception (RX) du port série. Il serait très facile de faire accidentellement arrêter votre port série en changeant la broche 0 en une broche de sortie! Maintenant, ce serait très déroutant de ne plus pouvoir recevoir de données en série, non?

Alors vous vous dites peut-être, super, pourquoi voudrais-je jamais utiliser ce genre de choses alors? Voici certains des aspects positifs de l’accès direct au port:

  • Vous devrez peut-être pouvoir activer et désactiver les broches très rapidement, ce qui signifie en quelques fractions de microseconde. Si vous examinez le code source dans lib / target / arduino / câblage.c, vous verrez que digitalRead () et digitalWrite () représentent chacune environ une douzaine de lignes de code, qui sont compilées dans de nombreuses instructions machine. Chaque instruction de la machine nécessite un cycle d'horloge à 16 MHz, ce qui peut s'additionner pour des applications sensibles au facteur temps. L'accès direct au port peut faire le même travail en beaucoup moins de cycles d'horloge.
  • Parfois, vous devrez peut-être configurer plusieurs broches de sortie exactement au même moment. Appelant digitalWrite (10, HAUT); suivi de digitalWrite (11, HIGH); La broche 10 sera mise HAUTE plusieurs microsecondes avant la broche 11, ce qui pourrait perturber certains circuits numériques externes sensibles au facteur temps que vous avez raccordés. Alternativement, vous pouvez définir les deux broches à la hauteur au même moment en utilisant PORTB | = B1100;
  • Si vous manquez de mémoire programme, vous pouvez utiliser ces astuces pour rendre votre code plus petit. Il faut beaucoup moins d'octets de code compilé pour écrire simultanément un ensemble de broches matérielles via les registres de ports plutôt que d'utiliser une boucle for pour définir chaque broche séparément. Dans certains cas, cela peut faire la différence entre l’adaptation de votre programme en mémoire flash ou non!

Voir

Maison de référence

Les corrections, suggestions et nouveaux documents doivent être postés sur le forum .

Le texte de la référence Arduino est protégé par une licence Creative Commons Attribution-ShareAlike 3.0 . Les exemples de code dans la référence sont publiés dans le domaine public.

Portrait de binjch

Tout cela est déjà opérationnel dans le code que j'ai posté, le prob est la gestion des interruptions Comme je l'explique à la fin de mon premier message. Mais merci pour les lignes "machines".(Comme si j'avais pas déjà assez mangé pendant les fêtes...).

Portrait de Spy

pour les timers dans le même style:

es bases

. Par : Christian

Un timer est un registre à l’intérieur du microcontrôleur qui s’incrémente (ou se décrémente) chaque fois qu’il reçoit une impulsion d’un signal d’horloge. Ce signal d’horloge peut être propre au microcontrôleur ou bien extérieur à celui-ci. Un timer est donc un compteur capable de compter le temps qui s’écoule, d’où son nom anglais de timer counter. Dans ce qui suit, le timer est toujours employé dans un mode où il s’incrémente, mais dans d’autres modes, il peut se décrémenter. On peut télécharger la documentation du constructeur sur le lien suivant :

http://www.atmel.com/images/doc8161.pdf

Si le registre du timer comporte 8 bits, il est alors capable de compter de 0 à 255 (en hexadécimal, de 00 à FF). Lorsqu’il arrive à 255 (FF), un coup d’horloge supplémentaire devrait le faire passer à 256 (soit 100 en hexadécimal), ce qui n’est pas possible puisque le registre n’a que 8 bits. Le registre passe donc à 0 ; on dit qu’il subit un débordement (Overflow en anglais) [1], mais ce débordement entraîne la mise à 1 d’un bit bien particulier dans un registre de contrôle associé au timer. Ce bit est appelé un flag (drapeau en anglais) et indique que le timer vient de compter jusqu’à 256, ce qui permet d’attirer l’attention du programmeur, un peu comme ces boîtes aux lettres américaines qui possèdent un petit drapeau qui se dresse chaque fois que le facteur a déposé du courrier à l’intérieur.

L’intérêt d’un timer est qu’il compte sans cesse et que pendant ce temps, le programme peut réaliser autre chose, ce qui n’est pas possible si on utilise la fonction delay() qui est bloquante et qui ne permet pas de faire autre chose pendant ce temps d’attente. Le temps que le timer met pour compter 256 coups dépend bien sûr de la fréquence de l’horloge ; à 16 MHz (fréquence du microcontrôleur utilisé dans les modules Arduino), c’est très rapide, mais il est possible de diviser cette fréquence d’horloge grâce à des circuits internes au microcontrôleur appelés prédiviseur (prescaler en anglais). On peut alors diviser la fréquence de base (16 MHz) par 8, 32, 64, 128, 256 ou 1024 ; pour cela, il faut utiliser intelligemment d’autres registres de contrôle associés au timer. Par exemple, si on règle de prédiviseur pour diviser la fréquence par 1024, le timer comptera donc à une fréquence de 15625 Hz.

Comme pour tout registre, on peut lire la valeur d’un timer ou bien écrire une valeur particulière dans le timer. Mais ce qui est surtout important, ce sont les registres de contrôle associés au timer car ce sont eux qui permettent de modifier le comportement du timer et de contrôler ce qu’il fait. Il faut donc bien les connaître pour bien savoir les utiliser et la lecture de la documentation liée au microcontrôleur est souvent indispensable.

Timers du microcontrôleur de l’Arduino

Le module Arduino Uno est construit autour du microcontrôleur AVR ATmega328P d’Atmel qui possède 3 timers :

  • Le timer0, sur 8 bits, utilisé par les fonctions delay(), millis() et micros(). Il commande également des PWM (Pulse Width Modulation ou Modulation par Largeur d’Impulsion) sur les broches 5 et 6.
  • Le timer1, sur 16 bits, qui compte de 0 à 65535 (0 à FFFF en hexadécimal) et qui est utilisé par la bibliothèque Servo ou bien pour de la PWM sur les broches 9 et 10.
  • Le timer2, sur 8 bits, qui est utilisé par la fonction Tone() ou bien pour de la PWM sur les broches 3 et 11.

Le langage d’Arduino fait donc appel aux timers du microcontrôleur mais ceci reste transparent pour le programmeur. D’ailleurs, dans la majorité des applications, le langage d’Arduino est souvent très suffisant et permet de développer des applications sans avoir besoin de faire appel à l’architecture du microcontrôleur. Néanmoins, dans quelques cas particuliers, il peut être intéressant de savoir programmer les timers ; dans cet article, nous allons évoquer l’utilisation des timers comme compteur de temps, même si ceux-ci sont capables de faire bien d’autres choses.

Les registres de contrôle

Le tableau suivant donne les différents registres de contrôle associés à chaque timer ; nous verrons le rôle de chaque registre tout en nous limitant à ce qui est vraiment à connaître pour réaliser un premier exemple.

Timer 0 Timer 1 Timer 2 Rôle
TCNT0 TCNT1L TCNT2 Timer (bit 0 à 7)
- TCNT1H - Timer (bit 8 à 15)
TCCR0A TCCR1A TCCR2A Registre de contrôle
TCCR0B TCCR1B TCCR2B Registre de contrôle
- TCCR1C - Registre de contrôle
OCR0A OCR1AL OCR2A Output Compare (bit 0 à 7)
- OCR1AH - Output Compare (bit 8 à 15)
OCR0B OCR1BL OCR2B Output Compare (bit 0 à 7)
- OCR1BH - Output Compare (bit 8 à 15)
- ICR1L - Input Capture (bit 0 à 7)
- ICR1H - Input Capture (bit 8 à 15)
TIMSK0 TIMSK1 TIMSK2 Interrupt Mask
TIFR0 TIFR1 TIFR2 Interrupt Flag

ASSR Asynchronous Status Register

GTCCR General Timer/Counter Control Register

Le timer1 est un timer de 16 bits et est donc constitué de deux registres de 8 bits, l’un donnant les bits 0 à 7, l’autre donnant les bits 8 à 15.

Pour se repérer dans ce tableau, il faut savoir que TCNT signifie Timer/Counter (Register), TCCR Timer/Counter Control Register, OCR Output Compare Register, ICR Input Capture Register, TIMSK Timer/Counter Interrupt Mask Register et TIFR Timer/Counter Interrupt Flag Register. OCR et ICR ont des rôles particuliers dont nous ne parlerons pas dans ce premier article. TIMSK et TIFR servent pour que le timer puisse générer des interruptions [2] comme nous le verrons dans un autre article.

Exemple d’utilisation simple

Nous allons maintenant nous intéresser au timer 2 dans son rôle le plus simple, compter le temps, et aux registres de contrôle qui y sont associés. Ce que nous allons dire reste bien entendu valable pour les timers 0 ou 1. Pour mieux comprendre, nous prendrons comme exemple le clignotement d’une DEL à 1 Hz, ce qui signifie que toutes les 500 ms (demi-période), il faut inverser la DEL, c’est-à-dire l’allumer si elle est éteinte et l’éteindre si elle est allumée.

Ralentir ou accélérer le timer

Nous avons vu qu’il était facile de ralentir un timer : il suffit d’utiliser le prédiviseur pour diviser la fréquence du signal d’horloge. Pour faire cela, il suffit de positionner certains bits du registre TCCR2B. À partir de là, notre compteur comptera de 0 à 255 (cela lui prendra un certain temps) puis passera en débordement, c’est-à-dire qu’il repartira de 0 après avoir positionné le flag TOV2 (Timer/Counter 2 Overflow Flag) à 1. Il s’agit là du bit 0 du registre TIFR2. Chaque fois que le bit 0 du registre TIFR2 passe à 1, cela signifie que notre timer a compté un certain laps de temps (connu) ; il suffit alors de repositionner le flag TOV2 à 0 et d’incrémenter un compteur (une variable) pour continuer ce processus. Lorsque ce compteur arrive à une certaine valeur, c’est que le temps à attendre s’est écoulé.

Pour accélérer un timer, il faut le faire déborder avant qu’il ait compté 256 coups : pour cela, il suffit de partir d’une valeur différente de 0 pour effectuer le comptage puisqu’il est possible d’écrire dans le registre du timer.

On voit maintenant comment il faut opérer pour faire clignoter notre DEL. Le timer part de 0 et s’incrémente jusqu’à 255, puis recommence de 0 vers 255 en ayant positionné le flag TOV2 à 1. Il suffit de surveiller ce flag ; chaque fois qu’il est à 1, on repositionne le flag à 0 et on incrémente un compteur. Lorsque ce compteur arrive à une certaine valeur, on a atteint 500 ms et il faut agir sur la DEL. Pour que le timer ait ce comportement, il doit être utilisé en mode normal ; dans ce mode, le timer compte en incrémentant, et le flag est positionné à 1 chaque fois que le timer repasse par 0.

Pour que le timer soit en mode normal, il faut que les trois bits appelés WGM20 , WGM21 et WGM22 soient à 0, ce qui nécessite d’aller écrire dans 2 registres (WGM2 pour Waveform Generation Mode du timer 2). En effet, WGM20 et WGM21 sont les bits 0 et 1 du registre TCCR2A, et WGM22 est le bit 3 du registre TCCR2B. Les bits 0 à 2 de ce même registre sont appelés CS20 , CS21 et CS22 et servent à régler le prédiviseur suivant le facteur de division souhaité (CS2 pour Clock Select du timer 2). Tout cela sera plus clair sur un exemple.

Calcul théorique du comptage

Le timer 2 sera utilisé en le faisant compter à la fréquence de 62500 Hz (fréquence d’horloge divisée par 256). Un cycle d’horloge dure donc 16 µs (l’inverse de la fréquence). Pour avoir 500 ms (500000 µS), il faut compter 500 000 µs / 16 µs = 31250 fois. Cette valeur est décomposable en 125 * 250. Le timer doit compter 250 fois pour déborder ; il suffit de le faire partir de la valeur 6. Chaque fois qu’il déborde, une variable compteur est incrémentée ; quand cette variable atteint 125, on a bien 500 ms qui se sont écoulées et on agit sur la DEL.

Initialisation des registres de contrôle

Pour que le timer soit en mode normal, il faut que WGM20 , WGM21 et WGM22 soient à 0. On positionne ces bits à 0 dans deux registres TCCR2A et TCCR2B par une instruction adéquate (bitClear dans le langage Arduino). Pour que le prédiviseur divise par 256, il faut que le bit CS22 soit égal à 1, le bit CS21 soit égal à 1 et le bit CS20 soit égal à 0 ; il suffit d’écrire la valeur binaire 0b00000110 dans le registre TCCR2B. Heureusement pour nous, le compilateur d’Arduino connaît les noms des registres et les noms des bits.

La dernière chose à savoir, c’est que pour réinitialiser le flag TOV2 par logiciel, il faut écrire à 1 le bit 0 du registre TIFR2 ; l’instruction est bitSet (et non bitClear comme on aurait pu le croire).

Programme

Voici le programme, écrit avec le langage Arduino :


  1. /*
  2.  * Clignotement d'une DEL (LED en anglais) à 1 Hz par timer 2.
  3.  */
  4. const byte Led = 13; // Pour utiliser la LED du module
  5. #define LedToggle digitalWrite (Led, !digitalRead(Led))
  6. void setup ()
  7. {
  8. pinMode (Led, OUTPUT);
  9. bitClear (TCCR2A, WGM20); // WGM20 = 0
  10. bitClear (TCCR2A, WGM21); // WGM21 = 0
  11. TCCR2B = 0b00000110; // Clock / 256 soit 16 micro-s et WGM22 = 0
  12. TIFR2 = 0b00000001; // TOV2
  13. TCNT2 = 256 - 250; // Chargement du timer à 6
  14. }
  15. byte varCompteur = 0; // La variable compteur
  16. void loop () {
  17. if (bitRead (TIFR2, 0) == 1) { // Flag TOV2 mis à 1 ?
  18. TCNT2 = 256 - 250; // Rechargement du timer à 6
  19. bitSet (TIFR2, TOV2); // Remise à zéro du flag TOV2 (voir texte)
  20. if (varCompteur++ > 125) { // Incrémentation et a atteint 125 ?
  21. varCompteur = 0; // On recommence un nouveau cycle
  22. LedToggle; // Inversion de la LED
  23. }
  24. }
  25. }

Télécharger

Conclusion

Nous avons vu qu’un timer est un registre compteur qui s’incrémente (ou se décrémente) à chaque impulsion d’une horloge qui peut être celle du microcontrôleur divisée par un certain nombre pour la ralentir. Lorsque le timer déborde, un flag est positionné à 1 dans un registre de contrôle. En surveillant ce flag, on peut compter le temps qui s’écoule jusqu’à obtenir une durée déterminée. On peut lire la valeur du timer et on peut écrire une valeur dans le timer. Plusieurs registres de contrôle sont associés au timer et permettent de modifier son comportement et de suivre son débordement. La connaissance de ces registres de contrôle et du rôle joué par chacun des bits les composant est indispensable au programmeur qui veut utiliser les timers. Pour cela, une lecture assidue de la documentation du constructeur est indispensable.

L’intérêt d’un timer pour compter le temps qui s’écoule est que le programme peut réaliser autre chose pendant ce temps. Dans l’exemple donné, il suffit de surveiller le flag TOV2 pour connaître l’écoulement du temps ; ceci est possible parce que le programme ne fait rien d’autre que de surveiller. Nous verrons dans un prochain article que le débordement du timer peut engendrer une interruption ; le programme peut alors faire autre chose et être interrompu pour traiter l’événement. Nous verrons aussi que les timers peuvent faire d’autres choses grâce aux autres registres de contrôle comme OCR ou ICR. Mais tout ceci est une autre histoire.

Portrait de Spy

Enfin tu peux retrouver tout ce que tu cherches dans : https://www.locoduino.org/spip.php?rubrique14

Portrait de binjch

J'ai un peu regardé. Ca ressemble à ce qu'on trouve en anglais mais en français ce qui rend la chose un peu plus agréable. Merci ;-) Mais faire clignoter une led est une chose, gérer deux temps d'allumage sur interruption en est une autre :-D

Portrait de binjch

...super bien locoduino

Portrait de Walter

Oups trop tard :)

Je pense que tu confond Binjch, comme Spy, ca m'étonne beaucoup que ton prof te demande de faire ça en langage machine, sans vous avoir fait des cours dessus.
En règle général tu commence à faire de l'assembleur qui est la représentation humainement lisible du langage machine, après pourquoi pas tu fais du langage machine, histoire de bien comprendre ce que fait le processeur.

Je pense que tu doit "simplement" remplacer le delay, par des Timers + interruptions avec du C.
Ce que tu essayé de faire dans ton code à priori, j'ai pas testé ton programme.

Ton soucis est que tu n'a pas d'interruption au relâchement du bouton?
Que veux tu faire avec ton Timer1, tu ajout à counter +1 toutes les X µs et tu le test uniquement dans ta fonction d'interruption du bouton?

Qu'a tu compris des interruptions et des Timers?

Pour les Timers et interruptions je trouve cette documentation est très bien.

https://www.locoduino.org/spip.php?article84

Portrait de binjch

J'ai compris comment gérer les ports en entré ou sortie, j'ai compris que les interruptions servaient à mettre l'exécution du programme en pause lors d'événements spéciaux ce que j'ai réussi à réaliser sur le port int0 avec un bouton poussoir (problème de boucing mais bon). Ce que je ne comprends pas est comment programmer une instruction qui après interruption externe (physique boutton poussoir) fait s'allumer les leds pour un temps donné (AVEC TIMER SANS fonction delay) et exclut toutes autres exécution durant le temps d'allumage des leds. (en fait il y' aun led et un buzzer mais dans un premier temps j'utilise deux leds).

Portrait de Walter

Ok, le mieux reste de suivre les exemples de "locoduino" qui sont très didactiques.

Donc si on résumé on a deux choses:

Une interruption : l'interruption interrompt l'exécution du programme en cours, pour exécuter une routine d'interruption, puis rendre la main au programme principal, une fois la routine terminé.

Un Timer: c'est un compteur de cycle d'horloge,  qui  s'incrémente jusqu'a ce qu'il déborde et provoque une interruption.

En conclusion pour ton cas, on peut dire que l'on peut dans un premier temps faire clignoter une LED avec un corps principal(fct LOOP) vide. Le code qui fait clignoter la LED initialiement dans la fonction "Loop" ce retrouvera donc dans la fonction du Timer et appelée à intervalle régulier(ou non d'ailleurs).

Avec ces deux choses là, il suffit d'appliquer ce que tu décrit en algorithmique.
Sur interruption physique (bouton), tu veux allumer une LED un certain temps, puis au bout d'un temps donnée l'éteindre.

timer.png

Cela te parais t-il claire ?
Vois tu comment faire ?

Portrait de binjch

J'ai compris les principes. D'ailleurs mon code posté plus haut fonctionne plus ou moins suivant les principes de base mis en applications. Mais ce n'est pas parfait ou solide. Par exemple la séquence fonctionne correctement à la première exécution mais s'enlise dès la seconde ( les leds reproduisent plus la même séquence).

Portrait de binjch

Bon j'ai trouvé un code qui fonctionne:

[code]

#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>

// Variable de comptage des dépassements
volatile uint8_t count1;

/** Déclaration des fonctions **/
static void avr_init(void);
void timer1_init(void);

/** Gestion de l'interruption **/

ISR(INT0_vect)    //Ce qui se passe lors de l'interruption en PD2
{
    timer1_init();
    PORTA |= (1 << PA0) | (1 << PA1);

}

ISR(TIMER1_OVF_vect)    //Ce qui se passe lors des dépassements du timer1
{
    count1++;
    if (count1 >= 2)
    {
        PORTA &= ~(1 << PA0);
        PORTA |= (0 << PA0);
        count1 = 0;
    }
    PORTA &= ~(1 << PA1);
    PORTA |= (0 << PA1);
    cli();
}

//MAIN
int main(void)
{
    avr_init();
    //timer1_init();
    while(1)
    {
    }
    return 0;
}

static void avr_init(void)
{
    // Les sorties
    DDRA |= (1 << PA0);        //Les ports D sont conf. comme sorties
    DDRA |= (1 << PA1);

    // Les entrées
    DDRD &= ~(1 << PD2);        // Mise à zéro de PD2
    PORTD |= (1 << PD2);        // configure PORTD bit 2 comme résistance de tirage

    // Active l'interruption externe "INT0" qui allume les leds et active le timer1
    GICR |= (1 << INT0);

    // Définit un déclenchement sur front descendant
    MCUCR |= (1 << ISC01);

    // Active les requètes d'interruptions
    sei();
    return;
}

void timer1_init()
{
    // configure le timer1 avec un pré diviseur = 1024        // 1 cycle = 0,000977 sec
    TCCR1B |= (1 << CS11)|(1 << CS10);
    // initialise le comptage
    TCNT1 = 0;                                    // 1 cycle = 256    // 256 X 0,000977 = 0,250112sec
    // active les interruptions sur dépassements du timer1
    TIMSK |= (1 << TOIE1);
    // Initialise la variable de comptage des dépassements
    count1 = 0;
}

[/code]

Portrait de binjch

Je n'ai plus qu'à intégrer les timings correctes et connecter le buzzer

Portrait de Walter

J'ai du mal a voir ce que tu veux vraiment faire dans la fonction du Timer.
Tu désactives les interruptions à la fin du timer, c'est normal ?