/* ex0400_timer_2_interrupt.ino Exemple d'utilisation du timer 2 pour générer des interruptions. plusieurs registres définissent la manière de générer des imulsions sur les pin 3, 5, 6, 9, 10 et 11. Il y a trois groupes de 2 : Timer pin1 pin2 registres 0 6 5 TCCR0B TCCR0A OCR0A OCR0B TCNT0 1 9 10 TCCR1B TCCR1A OCR1A OCR1B TCNT1 2 11 3 TCCR2B TCCR2A OCR2A OCR2B TCNT2 Ici, on se limitera au Timer 2. "TCCR" signifie Timer Counter Control Register Structure des registres 8-bits de contrôle : TCCR2A - [COM2A1, COM2A0, COM2B1, COM2B0, reserved, reserved, WGM21, WGM20] TCCR2B - [FOC2A, FOC2B, reserved, reserved, WGM22, CS22, CS21, CS20] bit 7 6 5 4 3 2 1 0 COM2Ax bits == Compare Output Mode bits OCR2A == Output Compare Register A WGM2x bits == Waveform Generation Mode bits La valeur du registre se trouve dans TCNT2, en mode R/W (écriture et lecture) Le compteur compte de TCNT2 à 255 ou de TCNT2 à 0, puis génère une interruption. (COMP2A1, COMP2A0) C.f. p. 153 (0, 0) => OC2A est déconnecté, (0, 1) => Toggle OC2A on compare Match (1, 0) => Clear OC2A on Compare Match (Set output to low level) (1, 1) => Clear OC2A on Compare Match (Set output to high level) (COMP2B1, COMP2B0) C.f. p. 153 (0, 0) => OC2B est déconnecté, compte de 0 à 255 ou de 255 à 0 (0, 1) => Toggle OC1B on compare Match (1, 0) => Clear OC2B on Compare Match (Set output to low level) (1, 1) => Clear OC2B on Compare Match (Set output to high level) (WGM22, WGM21, WGM20) C.f. page 146 et page 155 (0, 0, 0) => le compteur TCNT2 est incrémenté chaque T * xxx [us]. Pour xxx c.f. ci-dessous Lorsque le compteur passe de 255 à 0, une interruption est générée. Lors de cette interruption, une nouvelle valeur de départ peut être assignée à TCNT2. À n'importe quelle moment, une nouvelle valeur peut être assignée à TCNT2. (0, 0, 1) => Pour PWM, phase mode. C.f 148 Le compteur compte alternativement en incrémentant, puis décrémentant. (0, 1, 0) => Mode CTC "Clear Timer on Compare". C.f. page 146 Le registre OCR2A est utilisé ici. Lorsque le compteur TCNT2 == OCR2A, le compteur TCNT2 est mis à 0 et une interruption est générée si les interruptions sont autorisée (enabled) (0, 1, 1) => Pour PWM, fast mode, compte de TCNT2 à 255. (1, 0, 0) => reserved (1, 0, 1) => Pour PWM, phase mode. C.f 148 Le compteur compte alternativement en incrémentant, puis décrémentant. (1, 1, 0) => reserved (1, 1, 1) => Pour PWM, fast mode, compte de TCNT2 à OCR2A (FOC2A, FOC2B) C.f. page 156 (Force Output Compare A) et (Force Output Compare B) Ils sont mis à 0 ou 1 selon le mode défini par (WGM22, WGM21, WGM20) et selon la comparaison de TCNT2 avec OCR2A et OCR2B. Gère le temps entre deux incréments du compteur. (CS22, CS21, CS20) C.f. page 156 (0, 0, 0) => No clock source, Timer/Counter stopped (0, 0, 1) => T * 1 = 0,0625 [us] (0, 1, 0) => T * 8 = 0,500 [us], division par 8 de la fréquence (0, 1, 1) => T * 32 = 2,000 [us], division par 32 de la fréquence (1, 0, 0) => T * 64 = 4,000 [us] (1, 0, 1) => T * 128 = 8,000 [us] (1, 1, 0) => T * 256 = 16,000 [us] (1, 1, 1) => T * 1024 = 64,000 [us] = 0,064 [ms] Ce qui suit est utile pour le PWM "Pulse Width Modulated" OCR2A = registre 8 bits Si le compteur atteint la valeur de ce registre, le Flag (bit) OCF2A est mis à 1 au prochain cyle d'horloge. N'est pas utilisé si (COMP2A1, COMP2A0) = (0, 0) OCR2B = registre 8 bits Si le compteur atteint la valeur de ce registre, le Flag (bit) OCF2B est mis à 1 au prochain cyle d'horloge. N'est pas utilisé si (COMP2B1, COMP2B0) = (0, 0) WGM21 = 1 => mode "Fast PWM" ; WGM21 = 0 => mode "Phase-Correct PWM" C'est utile pour le PWM, par pour les interruptions. ==========================================================*/ byte volatile bEtat = 0; byte volatile bCompteur = 0; //Compteur pour interruption void setup() { //============ // Initialise le Timer 2 pour déclencher les interruptions à intervalle régulier cli(); // Désactive l'interruption globale //bitClear (TCCR2A, WGM20); // WGM20 = 0 TCCR2A = 0; //default //bitClear (TCCR2A, WGM21); // WGM21 = 0 TCCR2A = 0b00000000; // TCCR2A = 0; //default TCCR2B = 0b00000110; // Clock * 256 soit 16 micro-s et WGM22 = 0. Clock = T = 0.0625 [us] //TCCR2B = 0b00000011; // Clock * 32 soit 2 micro-s et WGM22 = 0. Clock = T = 0.0625 [us] TIMSK2 = 0b00000001; // Interruption locale autorisée par TOIE2 sei(); // Active l'interruption globale pinMode(13, OUTPUT); } // setup void loop() { //=========== // Boucle principale // Elle ne fait rien, tout se passe dans le timer } // loop // routine d'interruption du timer ISR (TIMER2_OVF_vect) { //===================== // TCNT2 = valeur du compteur, qui est compté de sa valeur initiale à 255. // Lorsqu'on arrive dans cette fonction de gestion d'interruption, TCNT2 = 0 // Le compteur TCNT2 est décrémenté de 1 chaque 0.0625 [us] * facteur défini dans le setup. // Recharge la valeur de départ du compteur pour que la prochaine interruption se déclenche dans ... [ms] TCNT2 = 6; // => 250 X 16 [us] = 4.000 [ms] //TCNT2 = 131; // => 125 x 16 [us] = 2.000 [ms] //TCNT2 = 194; // => 62 x 16 [us] = 0.992 [ms] // Chaque temps ci-dessus est multiplié par 250 if (bCompteur++ == 250) { // Change l'état de la LED bCompteur=0; bEtat = 1 - bEtat; digitalWrite(13, bEtat); } } // ISRex0400_timer_2_interrupt.ino
/* ex0401_timer_2_interrupt-ino Exemple d'utilisation du timer 2 pour changer des fréquences d'interruptions. Cela peut influencer les fonctions "delay()" et "millis()" Plus d'information est donné dans les programmes : "ex045x_....ino" En particulier, à partir de : "ex0451_timer_1_interrupt_OCR1A.ino", le registre OCR1A est utilisé pour mieux contrôler les interruptions. Je recommande cette manière de faire, plutôt que celle utilisée dans cet exemple. Ce programme de tests ne fait rien d'autre que de changer le timer 2. Il est destiné à être utilisé avec un oscilloscope, pour voir les effets. La variable local "int nMode" défini le test que l'on désire exécuter. Les fréquences que l'on peut obtenir varient de 30 Hz à 8'000'000 Hz. plusieurs registres définissent la manière de générer des imulsions sur les pin 3, 5, 6, 9, 10 et 11. Il y a trois groupes de 2 : Timer pin1 pin2 registres 0 6 5 TCCR0B TCCR0A OCR1A OCR0B TCNT0 1 9 10 TCCR1B TCCR1A OCR1A OCR1B TCNT1 2 11 3 TCCR2B TCCR2A OCR2A OCR2B TCNT2 "PWM" signifie Pulse Width Modulated "TCCR" signifie Timer Counter Control Register The TCCR2A and TCCR2B 8-bit registers have the following structures: TCCR2A - [COM2A1, COM2A0, COM2B1, COM2B0, reserved, reserved, WGM21, WGM20] TCCR2B - [FOC2A, FOC2B, reserved, reserved, WGM22, CS22, CS21, CS20] COM2Ax bits == Compare Output Mode bits OCR2A == Output Compare Register A WGM2x bits == Waveform Generation Mode bits COM2A1 == 1 => OCR2A/255 ou OCR2A/256 définit la proportion de temps que passe la pin 3 à l'état haut. => OCR2B/255 ou OCR2B/256 définit la proportion de temps que passe la pin 11 à l'état haut. COM2A1 == 0 => OCR2A définit jusqu'où le compteur compte avant de passer à 0 (Fast PWM) ou redescendre (Phase-Correct PWM) c.f. plus bas. COM2A0 == 0 => comptage jusqu'à 256 (Fast PWM) ou 255 (Phase-Correct PWM). COM2A0 == 1 => comptage jusqu'à OCR2A+1 (Fast PWM) ou OCR2A (Phase-Correct PWM), avant de recompter Un des COM2Ax doit être à 1 pour avoir un signal. WGM21 = 1 => mode "Fast PWM" ; WGM21 = 0 => mode "Phase-Correct PWM" CS22, CS21, CS20 définissent le facteur de division de la fréquence de 16'000'000 Hz de base de l'Arduino. En mode "Fast PWM" : Compte depuis 0 jusqu'à 256 ou ( OCR2A+1 si COM2A0==1) avant de repasser à 0. OCR2A/ 256 défini la proportion de temps à l'état haut de la pin 11 OCR2B/ 256 défini la proportion de temps à l'état haut de la pin 3 En mode "Phase-Correct PWM" : Compte depuis 0 jusqu'à 255 ou ( OCR2A si COM2A0==1) avant de redescendre à 0. OCR2A/ 255 défini la proportion de temps à l'état haut de la pin 11 OCR2B/ 255 défini la proportion de temps à l'état haut de la pin 3 À l'initialisation des timers, tous les clocks sont divisés par 64. Timer 0 est mis en mode "Fast PWM", donc sa fréquence = 16'000'000 Hz / 64 / 256 = 976.5625 Hz. Période = 1.024 ms Timer 1 et 2 sont mis en mode "Phase-Correct PWM", donc sa fréquence = 16'000'000 Hz / 64 / 256 / 2 = 488.2815 Hz. Période = 2.048 ms Références : https://www.arduino.cc/en/Tutorial/SecretsOfArduinoPWM http://www.righto.com/2009/07/secrets-of-arduino-pwm.html http://www.fiz-ix.com/2012/01/how-to-configure-arduino-timer-2-registers-to-drive-an-ultrasonic-transducer-with-a-square-wave/ http://forum.arduino.cc/index.php?topic=16612#msg121031 La documentation de Atmega328 se trouve ici. http://www.sparkfun.com/datasheets/Components/SMD/ATMega328.pdf I est possible de lire la valeur du compteur des timers dans TCNT0 ; TCNT1 et TCNT2, c.f. p95 et 116. */ /* De : // http://forum.arduino.cc/index.php?topic=16612#msg121031 If you change TCCR0B, it affects millis() and delay(). They will count time faster or slower than normal if you change the TCCR0B settings. Below is the adjustment factor to maintain consistent behavior of these functions: Default: delay(1000) or 1000 millis() ~ 1 second 0x01: delay(64000) or 64000 millis() ~ 1 second 0x02: delay(8000) or 8000 millis() ~ 1 second 0x03: is the default 0x04: delay(250) or 250 millis() ~ 1 second 0x05: delay(62) or 62 millis() ~ 1 second (Or 63 if you need to round up. The number is actually 62.5) Also, the default settings for the other timers are: TCCR1B: 0x03 TCCR2B: 0x04 There may be other side effects from changing TCCR0B. For example my project would not properly run with TCCR0B set to 0x02 or 0x01. But it worked fine at 0x03 and higher. YMMV */ void stopTransducer() { //===================== // Arrêt des times. TCCR2A = 0; TCCR2B = 0; } // stopTransducer void setPwmFrequency(int pin, int divisor) { //========================================== // Facteurs de division des compteurs, pour varier la fréquence des pins byte mode; if (pin == 5 || pin == 6 || pin == 9 || pin == 10) { switch(divisor) { case 1: mode = 0x01; break; case 8: mode = 0x02; break; case 64: mode = 0x03; break; case 256: mode = 0x04; break; case 1024: mode = 0x05; break; default: return; } if(pin == 5 || pin == 6) { TCCR0B = TCCR0B & 0b11111000 | mode; } else { TCCR1B = TCCR1B & 0b11111000 | mode; } } else if(pin == 3 || pin == 11) { switch(divisor) { case 1: mode = 0x01; break; case 8: mode = 0x02; break; case 32: mode = 0x03; break; case 64: mode = 0x04; break; case 128: mode = 0x05; break; case 256: mode = 0x06; break; case 1024: mode = 0x07; break; default: return; } TCCR2B = TCCR2B & 0b11111000 | mode; } } // setPwmFrequency void setup() { //============ // Timer 0 pinMode( 5, OUTPUT); analogWrite( 5, 64); pinMode( 6, OUTPUT); analogWrite( 6, 48); // Timer 1 pinMode( 9, OUTPUT); analogWrite( 9, 64); pinMode(11, OUTPUT); analogWrite(10, 48); // Timer 2 pinMode( 3, OUTPUT); analogWrite( 3, 64); pinMode(11, OUTPUT); analogWrite(11, 48); // Pins 3 et 11, car dans TCCR2A il y a un "2", pour Timer 2, correspondant aux pins 3 et 11. //============================================================================================ int nMode = 1; // Impair => "Fast PWM", Pair => "Phase-Correct PWM". if (nMode == 1) { // Le mode "Fast PWM". La fréquence est le double de "Phase-Correct PWM" TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); // _BV(WGM21) = 2 ; _BV(WGM20) = 1 TCCR2B = 7; // = setPwmFrequency(3,1024); OCR2A = 64; // défini la proportion relativement à 256 de l'état haut de la pin 11 OCR2B = 128; // défini la proportion relativement à 256 de l'état haut de la pin 3 //setPwmFrequency(3,1024); // Période = 33 ms ; Fréquence = 30 Hz ; Fréqu = 16 MHz / 1024 / 256 / 2 = 30.517 [Hz] // C'est la plus basse fréquence possible. } else if (nMode == 2) { // Le mode est "Phase-Correct PWM". La fréquence est la moitié de "Fast PWM" TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20); // | 2 = _BV(WGM21) C'est lui qui défini le mode "Fast PWM" TCCR2B = 7; // = setPwmFrequency(3,1024); OCR2A = 64; // défini la proportion relativement à 256 de l'état haut de la pin 11 OCR2B = 128; // défini la proportion relativement à 256 de l'état haut de la pin 3 //setPwmFrequency(3,1024); // Période = 16.5 ms ; Fréquence = 60 Hz ; Fréqu = 16 MHz / 1024 / 256 = 61.035 [Hz] } else if (nMode == 3) { // Contrôle de la fréquence dans OCR2A // Le mode "Fast PWM". La fréquence est le double de "Phase-Correct PWM" TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); TCCR2B = _BV(WGM22) | 7; OCR2A = 180; // Fréqu = 16 MHz / 1024 / 181 = 86.326 [Hz], période = 11.584 ms OCR2B = 50; // défini la proportion relativement à 256 de l'état haut de la pin 3 //setPwmFrequency(3,1024); // La pin 11 est 50% du temps haut et 50% du temps bas. // La fréquence de la pin 11 est la moitié de celle de la pin 3. } else if (nMode == 4) { // Contrôle de la fréquence dans OCR2A // Le mode est "Phase-Correct PWM". La fréquence est la moitié de "Fast PWM" TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM20); // | 2 = _BV(WGM21) C'est lui qui défini le mode "Fast PWM" TCCR2B = _BV(WGM22) | 7; OCR2A = 180; // Fréqu = 16 MHz / 1024 / 180 / 2 = 43.403 [Hz], période = 23.04 ms OCR2B = 50; // défini la proportion relativement à 256 de l'état haut de la pin 3 //setPwmFrequency(3,1024); // La pin 11 est 50% du temps haut et 50% du temps bas. // La fréquence de la pin 11 est la moitié de celle de la pin 3. } else if (nMode == 5) { // Contrôle de la fréquence dans OCR2A // Le mode "Fast PWM". La fréquence est le double de "Phase-Correct PWM" TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); // _BV(WGM21) = 2 ; _BV(WGM20) = 1 TCCR2B = 7; OCR2A = 180; // Fréqu = 16 MHz / 1024 / 181 = 86.326 [Hz], période = 11.584 ms OCR2B = 50; // défini la proportion relativement à 256 de l'état haut de la pin 3 //setPwmFrequency(3,1024); // La pin 11 est 50% du temps haut et 50% du temps bas. // La fréquence de la pin 11 est la moitié de celle de la pin 3. } else if (nMode == 7) { // Les deux signaux sont à 0, _BV(COM2A1) ou _BV(COM2A0) doit être à 1. // Le mode "Fast PWM". La fréquence est le double de "Phase-Correct PWM" TCCR2A = 0 | 0 | _BV(WGM21) | _BV(WGM20); // _BV(WGM21) = 2 ; _BV(WGM20) = 1 TCCR2B = 7; OCR2A = 180; // Fréqu = 16 MHz / 1024 / 181 = 86.326 [Hz], période = 11.584 ms OCR2B = 50; // défini la proportion relativement à 256 de l'état haut de la pin 3 //setPwmFrequency(3,1024); // La pin 11 est 50% du temps haut et 50% du temps bas. // La fréquence de la pin 11 est la moitié de celle de la pin 3. } else if (nMode == 9) { // Comme le nMode == 1 // Le mode "Fast PWM". La fréquence est le double de "Phase-Correct PWM" TCCR2A = 128 | 64 | 32 | 2 | 1; // _BV(WGM21) = 2 ; _BV(WGM20) = 1 TCCR2B = 7; OCR2A = 180; // Fréqu = 16 MHz / 1024 / 181 = 86.326 [Hz], période = 11.584 ms OCR2B = 50; // défini la proportion relativement à 256 de l'état haut de la pin 3 //setPwmFrequency(3,1024); // La pin 11 est 50% du temps haut et 50% du temps bas. // La fréquence de la pin 11 est la moitié de celle de la pin 3. } else if (nMode == 11) { // Contrôle de la fréquence dans OCR2A // Mode haute fréquence // Le mode "Fast PWM". La fréquence est le double de "Phase-Correct PWM" TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); TCCR2B = _BV(WGM22) | 1; OCR2A = 180; // Fréqu = 16 MHz / 1 / 181 = 88.398 [kHz], période = 11.3125 micro-secondes OCR2B = 50; // défini la proportion relativement à 256 de l'état haut de la pin 3 //setPwmFrequency(3,1); // La pin 11 est 50% du temps haut et 50% du temps bas. // La fréquence de la pin 11 est la moitié de celle de la pin 3. } else if (nMode == 13) { // Contrôle de la fréquence dans OCR2A // Mode haute fréquence // Le mode "Fast PWM". La fréquence est le double de "Phase-Correct PWM" TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); TCCR2B = _BV(WGM22) | 1; OCR2A = 1; // Fréqu = 16 MHz / 1 / 2 = 8 [MHz], période = 0.125 micro-secondes OCR2B = 0; // (OCR2B+1) / (OCR2A+1) défini la proportion relativement de l'état haut de la pin 3 //setPwmFrequency(3,1); // La pin 11 est 50% du temps haut et 50% du temps bas. // La fréquence de la pin 11 est la moitié de celle de la pin 3. // C'est la fréquence la plus rapide pour la pin 3. // C'est le double de la fréquence la plus rapide pour la pin 11 } else if (nMode == 15) { // Contrôle de la fréquence dans OCR2A // Mode haute fréquence // Le mode "Fast PWM". La fréquence est le double de "Phase-Correct PWM" TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); TCCR2B = _BV(WGM22) | 1; OCR2A = 0; OCR2B = 0; //setPwmFrequency(3,1); // La pin 11 est 50% du temps haut et 50% du temps bas. // La pin 3 reste à l'état haut // C'est la fréquence la plus rapide pour la pin 11, soit 8 MHz. } // Il reste plusieurs points obscures, sur les combinaisons possibles de bits. } // setup void loop() { //=========== // Boucle principale // Elle ne fait rien, on peut mesurer les fréquences générés // sur les différentes pins avec un oscilloscope. } // loopex0401_timer_2_interrupt.ino
/* ex0405_timer_2_interrupt.ino Exemple d'utilisation du timer 2 pour générer des interruptions. La fréquence de clignotement de la LED pin 13 va augmenter avec le temps. La fréquence varie de 1 [Hz] à 25'000 [Hz], puis redescent à 1 [Hz], pour recommencer à croitre. Un haut-parleur ou un oscilloscope est utile pour percevoir l'effet. c.f. "ex0451.." et suite pour des améliorations imporantes. En particulier, le timer 1 sur 16 bits sera utilisé, ainsi que le registre OCR1A, permettant un meilleur contrôle des temps d'interruptions. Suite de ex0404. plusieurs registres définissent la manière de générer des imulsions sur les pin 3, 5, 6, 9, 10 et 11. Il y a trois groupes de 2 : Timer pin1 pin2 registres 0 6 5 TCCR0B TCCR0A OCR1A OCR0B TCNT0 1 9 10 TCCR1B TCCR1A OCR1A OCR1B TCNT1 2 11 3 TCCR2B TCCR2A OCR2A OCR2B TCNT2 Ici, on se limitera au Timer 2. "TCCR" signifie Timer Counter Control Register Structure des registres 8-bits de contrôle : TCCR2A - [COM2A1, COM2A0, COM2B1, COM2B0, reserved, reserved, WGM21, WGM20] TCCR2B - [FOC2A, FOC2B, reserved, reserved, WGM22, CS22, CS21, CS20] bit 7 6 5 4 3 2 1 0 COM2Ax bits == Compare Output Mode bits OCR2A == Output Compare Register A WGM2x bits == Waveform Generation Mode bits La valeur du registre se trouve dans TCNT2, en mode R/W (écriture et lecture) Le compteur compte de TCNT2 à 255 ou de TCNT2 à 0, puis génère une interruption. (COMP2A1, COMP2A0) C.f. p. 153 (0, 0) => OC2A est déconnecté, (0, 1) => Toggle OC2A on compare Match (1, 0) => Clear OC2A on Compare Match (Set output to low level) (1, 1) => Clear OC2A on Compare Match (Set output to high level) (COMP2B1, COMP2B0) C.f. p. 153 (0, 0) => OC2B est déconnecté, compte de 0 à 255 ou de 255 à 0 (0, 1) => Toggle OC2B on compare Match (1, 0) => Clear OC2B on Compare Match (Set output to low level) (1, 1) => Clear OC2B on Compare Match (Set output to high level) (WGM22, WGM21, WGM20) C.f. page 146 et page 155 (0, 0, 0) => le compteur TCNT2 est incrémenté chaque T * xxx [us]. Pour xxx c.f. ci-dessous Lorsque le compteur passe de 255 à 0, une interruption est générée. Lors de cette interruption, une nouvelle valeur de départ peut être assignée à TCNT2. À n'importe quelle moment, une nouvelle valeur peut être assignée à TCNT2. (0, 0, 1) => Pour PWM, phase mode. C.f 148 Le compteur compte alternativement en incrémentant, puis décrémentant. (0, 1, 0) => Mode CTC "Clear Timer on Compare". C.f. page 146 Le registre OCR2A est utilisé ici. Lorsque le compteur TCNT2 == OCR2A, le compteur TCNT2 est mis à 0 et une interruption est générée si les interruptions sont autorisée (enabled) (0, 1, 1) => Pour PWM, fast mode, compte de TCNT2 à 255. (1, 0, 0) => reserved (1, 0, 1) => Pour PWM, phase mode. C.f 148 Le compteur compte alternativement en incrémentant, puis décrémentant. (1, 1, 0) => reserved (1, 1, 1) => Pour PWM, fast mode, compte de TCNT2 à OCR2A (FOC2A, FOC2B) C.f. page 156 (Force Output Compare A) et (Force Output Compare B) Ils sont mis à 0 ou 1 selon le mode défini par (WGM22, WGM21, WGM20) et selon la comparaison de TCNT2 avec OCR2A et OCR2B. Gère le temps entre deux incréments du compteur. (CS22, CS21, CS20) C.f. page 156 (0, 0, 0) => No clock source, Timer/Counter stopped (0, 0, 1) => T * 1 = 0,0625 [us] (0, 1, 0) => T * 8 = 0,500 [us], division par 8 de la fréquence (0, 1, 1) => T * 32 = 2,000 [us], division par 32 de la fréquence (1, 0, 0) => T * 64 = 4,000 [us] (1, 0, 1) => T * 128 = 8,000 [us] (1, 1, 0) => T * 256 = 16,000 [us] (1, 1, 1) => T * 1024 = 64,000 [us] = 0,064 [ms] Ce qui suit est utile pour le PWM "Pulse Width Modulated" OCR2A = registre 8 bits Si le compteur atteint la valeur de ce registre, le Flag (bit) OCF2A est mis à 1 au prochain cyle d'horloge. N'est pas utilisé si (COMP2A1, COMP2A0) = (0, 0) OCR2B = registre 8 bits Si le compteur atteint la valeur de ce registre, le Flag (bit) OCF2B est mis à 1 au prochain cyle d'horloge. N'est pas utilisé si (COMP2B1, COMP2B0) = (0, 0) WGM21 = 1 => mode "Fast PWM" ; WGM21 = 0 => mode "Phase-Correct PWM" C'est utile pour le PWM, par pour les interruptions. ==========================================================*/ // Pour "volatile", c.f. https://www.arduino.cc/en/pmwiki.php?n=Reference/Volatile // Les variables utilisées dans la fonction de gestion d'interruption doivent être déclarée "volatile". // Cela impose que la variable est sauvegardée en RAM et non dans un registre. // Une variable sauvegardée dans un registre pourrait être modifiée accidentellement entre deux accès à la fonction. byte volatile bEtat = 0; byte volatile bT0H_max = 61; // Temps entre deux transitions, partie haute byte volatile bT0L_max = 9; // Temps entre deux transitions, partie basse byte volatile bT0H = 0; // Compteur de temps entre deux transitions byte volatile bT0L = 0; // == bT0L_max ou bT0L_max - 128 //byte volatile bFac = 1024; // Facteur de multiplication de 0.0625 [us] [us] = micro-seconds float vFreq = 1.0; // [Hz] float vTemps = 0.0; // Temps écoulé depuis le début, en secondes. float vTemps_next_print = 0.0; // Temps auquels on affiche le temps float vTemps_delta = 0.5; // Pour accroissement du temps et décroissement du temps. float vTmp = 1.0; // Pour des calculs temporaires byte volatile bSet = 0; // Est mis à 1 pour mettre à jours les nouvelles valeurs de bT0H_max, bT0L_max et TCCR2B byte volatile bChanged = 0; // Compte le nombre de changements d'état depuis le dernier incrément du compteur. byte volatile bT0H_max_new = 255; // Calculé à partir de vFrequ, puis mis à jours dans la gestion du Timer byte volatile bT0L_max_new = 255; byte volatile bTCCR2B_new = 7; byte volatile TCCR2B_old = 7; byte volatile bFac = 128; byte volatile bChanged_last = 0; // Juste pour informations dans le Serial.print // (256 ) * bFac * 0.0625 [us] = temps entre deux générations d'interruption du timer, sauf le dernier // (bT0L_max) * bFac * 0.0625 [us] = temps entre deux générations d'interruption du timer avant une transition // Si bT0L_max < 128, alors // L'avant dernière interruption avant une transition se fait après // (256 - 128) * bFac * 0.0625 [us] // La dernière interruption avant une transition se fait après // (bT0L_max + 128) ) * bFac * 0.0625 [us] // Ainsi, il n'y a pas un trop petit temps entre deux générations d'interruption. // bFac = 1024 => detla_T_max = 256 * 1024 + 0.0625 [us] = 16.384 [ms] // bFac = 1024 => detla_T_min = 1024 + 0.0625 [us] = 0.064 [ms] // (bT0H * 256 + bT0L) * bFac * 0.0625 [us] = temps entre deux transitions // (bT0H, bT0L, bFac) = // ( 0, 1, 1024) => temps = ( 0 * 256 + 1) * 1024 * 0.0625 = 64.000000 [us] // (255, 255, 1024) => temps = (255 * 256 + 255) * 1024 * 0.0625 = 4.194240 [s] // (122, 18, 1024) => temps = (122 * 256 + 18) * 1024 * 0.0625 = 2.000000 [s] <=> 0.25 Hz // ( 61, 9, 1024) => temps = ( 61 * 256 + 9) * 1024 * 0.0625 = 1.000000 [s] <=> 0.5 Hz // ( 30, 133, 1024) => temps = ( 30 * 256 + 133) * 1024 * 0.0625 = 0.500032 [s] <=> ~1 Hz // ( 0, 1, 32) => temps = ( 0 * 256 + 1) * 32 * 0.0625 = 2.00000 [us] // (255, 255, 32) => temps = (255 * 256 + 255) * 32 * 0.0625 = 0.31250 [s] // (195, 80, 32) => temps = (195 * 256 + 80) * 32 * 0.0625 = 0.10000 [s] <=> 5 Hz // ( 97, 168, 32) => temps = ( 97 * 256 + 168) * 32 * 0.0625 = 0.05000 [s] <=> 10 Hz // ( 1, 244, 32) => temps = ( 1 * 256 + 244) * 32 * 0.0625 = 1.00000 [ms] <=> 2 kHz // ( 0, 250, 32) => temps = ( 0 * 256 + 250) * 32 * 0.0625 = 0.50000 [ms] <=> 1 kHz // ( 0, 1, 8) => temps = ( 0 * 256 + 1) * 8 * 0.0625 = 0.50000 [us] // ( 0, 100, 8) => temps = ( 0 * 256 + 100) * 8 * 0.0625 = 0.05000 [ms] <=> 10 kHz // ( 0, 50, 8) => temps = ( 0 * 256 + 50) * 8 * 0.0625 = 0.02500 [ms] <=> 20 kHz // Formules de conversions //======================== // Selon la valeur de TCCR2B, bFac varie selon le tableau suivant : // TCCR2B 2 3 4 5 6 7 // bFac 1 4 8 16 32 128 // dT [s] = (bT0H * 256 + bT0L) * 2 * 8 * bFac * 62.5 * 10^(-9) // Fréquence = 16'000'000 / ((bT0H * 256 + bT0L) * 2 * 8 * bFac) // Fréquence = 1'000'000 / ((bT0H * 256 + bT0L) * bFac) // // Pour Fréquence = Freq donnée : // Freq [Hz] < 0.5 < 1 < 2 < 4 < 16 >=16 // bFac 128 32 16 8 4 1 // bT0H = Tronc(1000000 / (256 * Freq * bFac) // bT0L = Tronc(1000000 / (256 * Freq * bFac - bT0H * 256) // bT0H et bT0L <= 255 pour Freq < 0.12 [Hz]. void setup() { //============ // Initialise le Timer 2 pour déclencher les interruptions après un certain temps cli(); // Désactive l'interruption globale //bitClear (TCCR2A, WGM20); // WGM20 = 0 TCCR2A = 0; //default //bitClear (TCCR2A, WGM21); // WGM21 = 0 TCCR2A = 0b00000000; // TCCR2A = 0; //default //TCCR2B = 0b00000111; // Clock * 1024 soit 64 micro-s et WGM22 = 0 //TCCR2B = 0b00000110; // Clock * 256 soit 16 micro-s et WGM22 = 0 //TCCR2B = 0b00000011; // Clock * 32 soit 2 micro-s et WGM22 = 0 //TCCR2B = 0b00000010; // Clock * 8 soit 0.5 micro-s et WGM22 = 0 // de Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf // Timer/Counter0 C.f. page 93 // Il gère les fonctions delay(), millis() et micros() // Lorsque le Timer 0 est désactivé, les 3 fonctions ci-dessus ne fonctionnent plus ! TIMSK0 = 0b00000000; // Désactive le Timer/Counter0 C.f. p. 109 //TCCR0B = 0b00000000; // Désactive le Timer/Counter0 // Timer/Counter1 C.f. page 111 // Il est utilisé dans la bibliothèque de commande de servo-moteurs. TIMSK1 = 0b00000000; // Désactive le Timer/Counter1 C.f. p. 135 // Timer/Counter2 C.f. page 141 // Il est utilisé pour générer des tone() //TIMSK2 = 0b00000000; // Désactive le Timer/Counter2 C.f. p. 157 TIMSK2 = 0b00000001; // Active le Timer/Counter 2. // Une interruption est générée lorsque le compteur TCNT2 passe de 255 à 0 (overflow) TCCR2B = 0b00000111; // Clock * 1024 soit 64 micro-s et WGM22 = 0 TCCR2B_old = TCCR2B; vFreq = 1.0; // [Hz] vTemps = 0.0; // [s] vTemps_next_print = vTemps; bSet = 0; // Est mis à 1 pour mettre à jours les nouvelles valeurs de bT0H_max, bT0L_max et TCCR2B // 1 Hz bT0H_max = 122; bT0L_max = 18; bT0H = bT0H_max; bT0L = bT0L_max; sei(); // Active l'interruption globale pinMode(13, OUTPUT); //Serial.begin(9600); // Initialise la communication série à une haute vitesse. //Serial.begin(115200); // Initialise la communication série à une haute vitesse. } // setup void loop() { //=========== // Boucle principale // Augmentation progressive de la fréquence // L'augmentation progressive de la fréquence se fait par à-coups. // Cela s'entend aux hautes fréquences !!! Est-ce vrai ??? if ( (!bSet) && (bChanged) ) { // Il y a eu un changement d'état vTemps += vTemps_delta * float(bChanged) / vFreq; // Mesure du temps en secondes. bChanged_last = bChanged; // Juste pour information dans le Serial.print bChanged = 0; // Attendra le prochaine changement d'état pour changer la valeur du compteur if ( (vFreq > 25000.0) && (vTemps_delta > 0) ) { // Fréquences en descendant. vTemps_delta = -vTemps_delta; } else if ( (vFreq < 1.0) && (vTemps_delta < 0) ) { // Fréquences montantes. vTemps_delta = -vTemps_delta; } vFreq = pow(10.0, vTemps / 10.0); // [Hz] Elle est multipliée par 10 toutes les 10 secondes. // Fixe le diviseur de fréquence d'horloge, qui change la vitesse d'incrémentation du compteur TCNT2 if (vFreq >= 16) { bTCCR2B_new = 2; bFac = 1; } else if (vFreq >= 4) { bTCCR2B_new = 3; bFac = 4; } else if (vFreq >= 2.0) { bTCCR2B_new = 4; bFac = 8; } else if (vFreq >= 1.0) { bTCCR2B_new = 5; bFac = 16; } else if (vFreq >= 0.5) { bTCCR2B_new = 6; bFac = 32; } else { bTCCR2B_new = 7; bFac = 128; } vTmp = 1.0e6 / (vFreq * bFac); bT0H_max_new = byte(vTmp / 256.0); bT0L_max_new = byte(vTmp - 256.0 * bT0H_max_new); bSet = 1; // Indique de mettre à jours les nouvelles valeurs de bT0H_max, bT0L_max et TCCR2B /* if ( ( (vTemps_delta > 0) && (vTemps >= vTemps_next_print) ) || ( (vTemps_delta < 0) && (vTemps <= vTemps_next_print) ) ) { vTemps_next_print += vTemps_delta * 2.0; // Affichage toutes les ... secondes. Serial.print(vTemps); Serial.print(" : "); Serial.print(vFreq); Serial.print(" "); Serial.print(bT0H_max_new); Serial.print(" "); Serial.print(bT0L_max_new); Serial.print(" "); Serial.print(bChanged_last); Serial.println(" "); } /* */ } // if (bSet == 0) && (bChanged) } // loop // routine d'interruption du timer ISR (TIMER2_OVF_vect) { //===================== // TCNT2 = valeur du compteur, qui est compté de sa valeur initiale à 255, puis repasse à 0. // Lorsqu'on arrive dans cette fonction de gestion d'interruption, TCNT2 = 0 // Le compteur TCNT2 est incrémenté de 1 chaque 0.0625 [us] * facteur défini dans TCCR2B. bT0H--; if (bT0H == 255) { // Transition de l'état if (bSet) { // Modifie les paramètres fixant la prochaine interruption bSet = 0; if (TCCR2B_old != bTCCR2B_new) { // Lire la valeur de TCCR2B perturbe. TCCR2B = bTCCR2B_new; TCCR2B_old = bTCCR2B_new; } bT0H_max = bT0H_max_new; bT0L_max = bT0L_max_new; } bChanged += 1; bT0H = bT0H_max; // Recharge la valeur de départ du compteur pour que la prochaine interruption se déclenche dans ... [ms] bT0L = bT0L_max; bEtat = 1 - bEtat; digitalWrite(13, bEtat); // 4 [us], on verra dans d'autres exemples comment accéder aux pins plus rapidement. } if (bT0H == 0) { // La transition d'état se fera à la prochaine interruption TCNT2 -= bT0L; // TCNT2 += 256 - bT0L; } if (bT0H == 1) { // La transition d'état se fera dans deux interruptions if (bT0L_max < 128) { // Pour ne pas avoir deux interruptions trop approchées TCNT2 += 128; // TCNT2 += 256 - 128; bT0L = bT0L_max + 128; } } } // ISR
/* ex0406_timer_2_interrupt.ino Exemple d'utilisation du timer 2 pour générer des interruptions. La fréquence de clignotement de la LED pin 13 va augmenter avec le temps. La fréquence va varier de ~4 kHz à ~25 kHz Elle double en 10 secondes. Suite de ex0405. Autre approche, à chaque passage dans la fonction de gestion d'interruption, on change l'état de la pin 13 plusieurs registres définissent la manière de générer des imulsions sur les pin 3, 5, 6, 9, 10 et 11. Il y a trois groupes de 2 : Timer pin1 pin2 registres 0 6 5 TCCR0B TCCR0A OCR1A OCR0B TCNT0 1 9 10 TCCR1B TCCR1A OCR1A OCR1B TCNT1 2 11 3 TCCR2B TCCR2A OCR2A OCR2B TCNT2 Ici, on se limitera au Timer 2. "TCCR" signifie Timer Counter Control Register Structure des registres 8-bits de contrôle : TCCR2A - [COM2A1, COM2A0, COM2B1, COM2B0, reserved, reserved, WGM21, WGM20] TCCR2B - [FOC2A, FOC2B, reserved, reserved, WGM22, CS22, CS21, CS20] bit 7 6 5 4 3 2 1 0 COM2Ax bits == Compare Output Mode bits OCR2A == Output Compare Register A WGM2x bits == Waveform Generation Mode bits La valeur du registre se trouve dans TCNT2, en mode R/W (écriture et lecture) Le compteur compte de TCNT2 à 255 ou de TCNT2 à 0, puis génère une interruption. (COMP2A1, COMP2A0) C.f. p. 153 (0, 0) => OC2A est déconnecté, (0, 1) => Toggle OC2A on compare Match (1, 0) => Clear OC2A on Compare Match (Set output to low level) (1, 1) => Clear OC2A on Compare Match (Set output to high level) (COMP2B1, COMP2B0) C.f. p. 153 (0, 0) => OC2B est déconnecté, compte de 0 à 255 ou de 255 à 0 (0, 1) => Toggle OC2B on compare Match (1, 0) => Clear OC2B on Compare Match (Set output to low level) (1, 1) => Clear OC2B on Compare Match (Set output to high level) (WGM22, WGM21, WGM20) C.f. page 146 et page 155 (0, 0, 0) => le compteur TCNT2 est incrémenté chaque T * xxx [us]. Pour xxx c.f. ci-dessous Lorsque le compteur passe de 255 à 0, une interruption est générée. Lors de cette interruption, une nouvelle valeur de départ peut être assignée à TCNT2. À n'importe quelle moment, une nouvelle valeur peut être assignée à TCNT2. (0, 0, 1) => Pour PWM, phase mode. C.f 148 Le compteur compte alternativement en incrémentant, puis décrémentant. (0, 1, 0) => Mode CTC "Clear Timer on Compare". C.f. page 146 Le registre OCR2A est utilisé ici. Lorsque le compteur TCNT2 == OCR2A, le compteur TCNT2 est mis à 0 et une interruption est générée si les interruptions sont autorisée (enabled) (0, 1, 1) => Pour PWM, fast mode, compte de TCNT2 à 255. (1, 0, 0) => reserved (1, 0, 1) => Pour PWM, phase mode. C.f 148 Le compteur compte alternativement en incrémentant, puis décrémentant. (1, 1, 0) => reserved (1, 1, 1) => Pour PWM, fast mode, compte de TCNT2 à OCR2A (FOC2A, FOC2B) C.f. page 156 (Force Output Compare A) et (Force Output Compare B) Ils sont mis à 0 ou 1 selon le mode défini par (WGM22, WGM21, WGM20) et selon la comparaison de TCNT2 avec OCR2A et OCR2B. Gère le temps entre deux incréments du compteur. (CS22, CS21, CS20) C.f. page 156 (0, 0, 0) => No clock source, Timer/Counter stopped (0, 0, 1) => T * 1 = 0,0625 [us] (0, 1, 0) => T * 8 = 0,500 [us], division par 8 de la fréquence (0, 1, 1) => T * 32 = 2,000 [us], division par 32 de la fréquence (1, 0, 0) => T * 64 = 4,000 [us] (1, 0, 1) => T * 128 = 8,000 [us] (1, 1, 0) => T * 256 = 16,000 [us] (1, 1, 1) => T * 1024 = 64,000 [us] = 0,064 [ms] Ce qui suit est utile pour le PWM "Pulse Width Modulated" OCR2A = registre 8 bits Si le compteur atteint la valeur de ce registre, le Flag (bit) OCF2A est mis à 1 au prochain cyle d'horloge. N'est pas utilisé si (COMP2A1, COMP2A0) = (0, 0) OCR2B = registre 8 bits Si le compteur atteint la valeur de ce registre, le Flag (bit) OCF2B est mis à 1 au prochain cyle d'horloge. N'est pas utilisé si (COMP2B1, COMP2B0) = (0, 0) WGM21 = 1 => mode "Fast PWM" ; WGM21 = 0 => mode "Phase-Correct PWM" C'est utile pour le PWM, par pour les interruptions. ==========================================================*/ // Nombre de fois qu'il faut répéter la même fréquence word volatile awRepete[] = { 218, 219, 222, 223, 224, 227, 228, 231, 232, 234, 236, 238, 240, 241, 244, 246, 248, 250, 252, 254, 257, 258, 261, 263, 266, 267, 270, 272, 275, 277, 280, 282, 285, 287, 289, 293, 295, 298, 300, 303, 306, 309, 312, 315, 318, 321, 323, 327, 330, 334, 336, 340, 343, 347, 349, 354, 357, 360, 365, 368, 371, 376, 379, 384, 387, 391, 396, 399, 404, 409, 412, 417, 422, 426, 431, 436, 441, 445, 451, 455, 461, 466, 472, 477, 482, 488, 494, 500, 505, 512, 518, 524, 530, 537, 544, 551, 557, 564, 572, 578, 587, 593, 602, 609, 617, 626, 634, 642, 651, 660, 669, 678, 687, 698, 707, 717, 727, 738, 748, 759, 771, 782, 793, 806, 817, 830, 843, 856, 870, 883, 897, 911, 926, 941, 957, 972, 988, 1005, 1023, 1039, 1057, 1076, 1095, 1114, 1134, 1154, 1175, 1197, 1219, 1242, 1265, 1289, 1314, 1339, 1365, 1393, 1420, 1449, 1479, 1508, 1541, 1572, 1606, 1641, 1676, 1713, 1751, 1790, 1830, 1873, 1916, 1961, 2008, 2056, 2106, 2157, 2212, 2267, 2326, 2385, 2449, 2513, 2581, 2651, 2725, 2802, 2881, 2965, 3051, 3142, 3237, 3337, 3440, 3549, 3663, 3783, 3908, 4040, 4179, 4325, 4479, 4641, 4812, 4993, 5184, 5386, 5601, 5828, 6070, 6327, 6601, 6892, 7204, 7536, 7894, 8277, 8687, 9131, 0}; // Le zéro à la fin indique que c'est la fin du tableau word volatile wCompte = 0; // Compte le nombre de répétitions d'une fréquence byte volatile bInd = 0; // Indice de compteur de répétitions d'une fréquence // Pour "volatile", c.f. https://www.arduino.cc/en/pmwiki.php?n=Reference/Volatile // Les variables utilisées dans la fonction de gestion d'interruption doivent être déclarée "volatile". // Cela impose que la variable est sauvegardée en RAM et non dans un registre. // Une variable sauvegardée dans un registre pourrait être modifiée accidentellement entre deux accès à la fonction. byte volatile bEtat = 0; byte volatile bT0L = 255; // Fixe le temps entre deux transitions byte volatile bUp = 1; // Indique si la fréquence va en augmentant ou diminuant // (256 - bT0L) * bFac * 0.0625 [us] = temps entre deux générations d'interruption du timer avant une transition // bFac = 1024 => detla_T_max = 256 * 1024 + 0.0625 [us] = 16.384 [ms] // bFac = 1024 => detla_T_min = 1024 + 0.0625 [us] = 0.064 [ms] // bT0L * bFac * 0.0625 [us] = temps entre deux transitions // (bT0L, bFac) = // ( 0, 8) => temps = (256 - 0) * 8 * 0.0625 = 0.000128 [s] <=> ~ 3'906.25 [Hz] // (127, 8) => temps = (256 - 206) * 8 * 0.0625 = 0.000025 [s] <=> ~ 7'751.94 [Hz] // (206, 8) => temps = (256 - 206) * 8 * 0.0625 = 0.000025 [s] <=> ~20'000.00 [Hz] // (127, 8) => temps = (256 - 216) * 8 * 0.0625 = 0.000020 [s] <=> ~25'000.00 [Hz] // Formules de conversions //======================== // Selon la valeur de TCCR2B, bFac varie selon le tableau suivant : // TCCR2B 2 3 4 5 6 7 // bFac 1 4 8 16 32 128 // dT [s] = bT0L * 2 * 8 * bFac * 62.5 * 10^(-9) // 62.5e-9 = 1 / 16'000'000 // Fréquence = 16'000'000 / (bT0L * 2 * 8 * bFac) // Fréquence = 1'000'000 / (bT0L * bFac) // // Pour Fréquence = Freq donnée : // bFac 128 32 16 8 4 1 // bT0L = Tronc(1000000 / (Freq * bFac) void setup() { //============ // Initialise le Timer 2 pour déclencher les interruptions après un certain temps cli(); // Désactive l'interruption globale //bitClear (TCCR2A, WGM20); // WGM20 = 0 TCCR2A = 0; //default //bitClear (TCCR2A, WGM21); // WGM21 = 0 TCCR2A = 0b00000000; // TCCR2A = 0; //default //TCCR2B = 0b00000111; // Clock * 1024 soit 64 micro-s et WGM22 = 0 //TCCR2B = 0b00000110; // Clock * 256 soit 16 micro-s et WGM22 = 0 //TCCR2B = 0b00000011; // Clock * 32 soit 2 micro-s et WGM22 = 0 //TCCR2B = 0b00000010; // Clock * 8 soit 0.5 micro-s et WGM22 = 0 // de Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf // Timer/Counter0 C.f. page 93 // Il gère les fonctions delay(), millis() et micros() // Lorsque le Timer 0 est désactivé, les 3 fonctions ci-dessus ne fonctionnent plus ! TIMSK0 = 0b00000000; // Désactive le Timer/Counter0 C.f. p. 109 //TCCR0B = 0b00000000; // Désactive le Timer/Counter0 // Timer/Counter1 C.f. page 111 // Il est utilisé dans la bibliothèque de commande de servo-moteurs. TIMSK1 = 0b00000000; // Désactive le Timer/Counter1 C.f. p. 135 // Timer/Counter2 C.f. page 141 // Il est utilisé pour générer des tone() //TIMSK2 = 0b00000000; // Désactive le Timer/Counter2 C.f. p. 157 TIMSK2 = 0b00000001; // Active le Timer/Counter 2. // Une interruption est générée lorsque le compteur TCNT2 passe de 255 à 0 (overflow) TCCR2B = 0b00000010; // Clock * 8 soit 0.5 micro-s et WGM22 = 0 bT0L = 0; // Freq = Tronc(1000000 / Freq bInd = 0; wCompte = awRepete[bInd]; // La fréquence va varier de ~4 kHz à ~25 kHz // Elle double en 10 secondes. sei(); // Active l'interruption globale pinMode(13, OUTPUT); //Serial.begin(9600); // Initialise la communication série à une haute vitesse. //Serial.begin(115200); // Initialise la communication série à une haute vitesse. } // setup void loop() { //=========== // Boucle principale // Augmentation progressive de la fréquence /* if ( ( (vTemps_delta > 0) && (vTemps >= vTemps_next_print) ) || ( (vTemps_delta < 0) && (vTemps <= vTemps_next_print) ) ) { vTemps_next_print += vTemps_delta * 2.0; // Affichage toutes les ... secondes. Serial.print(vTemps); Serial.print(" : "); Serial.print(vFreq); Serial.print(" "); Serial.print(bT0H_max_new); Serial.print(" "); Serial.print(bT0L_max_new); Serial.print(" "); Serial.print(bChanged_last); Serial.println(" "); } /* */ } // loop // routine d'interruption du timer ISR (TIMER2_OVF_vect) { //===================== // TCNT2 = valeur du compteur, qui est compté de sa valeur initiale à 255, puis repasse à 0. // Lorsqu'on arrive dans cette fonction de gestion d'interruption, TCNT2 = 0 // Le compteur TCNT2 est incrémenté de 1 chaque 0.0625 [us] * facteur défini dans TCCR2B. // Transition de l'état // Étrangement, TCNT2 n'est pas toujours nul lorsqu'on entre dans cette fonction. TCNT2 += bT0L; // Fréquence = 1'000'000 / ( (256 - bT0L)) bEtat = 1 - bEtat; digitalWrite(13, bEtat); // 4 [us] if (bUp) { // Fréquence montante wCompte -= 1; if (wCompte == 0) { // Augmentation de la fréquence. bT0L++; // Passage à la fréquence suivante. bInd++; wCompte = awRepete[bInd]; if (wCompte == 0) { // Passe en fréquence descendante bUp = 0; bT0L--; bInd--; wCompte = awRepete[bInd]; } } } else { // Fréquence descendante wCompte -= 1; if (wCompte == 0) { // Diminution de la fréquence. bT0L--; // Passage à la fréquence précédente. bInd--; wCompte = awRepete[bInd]; if (bT0L == 0) { // Passe en fréquence montante bUp = 1; bT0L = 0; bInd = 0; wCompte = awRepete[bInd]; } } } } // ISR
/* ex0440_Menu_LiquidCrystal_vitesses_tests.ino Pour faire des mesures de temps de diverses instructions, telles que : ° l'écriture sur l'afficheur, c'est long, environ 5 [ms] pour afficher une ligne. ° la lecture des boutons ° la conversion d'un float en string Ces mesures utilisent un oscilloscope. La pin 2 sera la référence pour le trigger la pin 3 changera d'état après chaque boucle d'instruction À partir d'un menu, permet aussi d'afficher le nom du bouton pressé et la valeur de l'ADC 0 correspondant. C.f. ex0448_Menu_LCD_vitesses_tests.ino qui fait des mesures de temps beaucoup plus précis. Connections : LCD Arduino (pin) ou tension 1 - Vss = 0 V 2 - Vcc = 5 V 3 - V0 = Tension variable réglée par une résistance variable, pour la lumineusité de l'affichage 4 - RS = pin 8 (Register Select) 5 - R/W = 0 V donc uniquement en Write 6 - E = pin 9 Enable, mise à haut pour accepter les données. 7 .. 10 = connecté à la commande de rétro-éclairage "back light" 11 - D4 = pin 4, données sur 4 bits 12 - D5 = pin 5, données sur 4 bits 13 - D6 = pin 6, données sur 4 bits 14 - D7 = pin 7, données sur 4 bits 15 - LED + = 220 Ohm - +5V 16 - LED - = 0V Autres connexions : pin 2 et 3 pour le signal pin 13 pour indiquer si le HP est on ou off pin 0, pin 1, utilisées pour la transmission par USB, ADC 0 pour les lecture les boutons. c.f. : http://www.arduino.cc/en/Tutorial/LiquidCrystal c.f. : https://arduino-info.wikispaces.com/LCD-Pushbuttons Quelques constantes : ===================== Elles sont définies dans le fichier : Arduino.h Sous Linux KUbuntu, ce fichier se trouve dans le dossier : /usr/share/arduino/hardware/arduino/core/arduino #define HIGH 0x1 #define LOW 0x0 #define INPUT 0x0 #define OUTPUT 0x1 #define INPUT_PULLUP 0x2 #define true 0x1 #define false 0x0 #define PI 3.1415926535897932384626433832795 #define HALF_PI 1.5707963267948966192313216916398 #define TWO_PI 6.283185307179586476925286766559 #define DEG_TO_RAD 0.017453292519943295769236907684886 #define RAD_TO_DEG 57.295779513082320876798154814105 #define SERIAL 0x0 #define DISPLAY 0x1 #define LSBFIRST 0 #define MSBFIRST 1 ======================================================== */ // include the library code: #include <LiquidCrystal.h> // Déclaration de Constantes #define btnNONE 0 #define btnSELECT 1 #define btnLEFT 2 #define btnDOWN 3 #define btnUP 4 #define btnRIGHT 5 // Dernier numéro de menu. Non utilisé #define MENUMAX 8 #define pinTrigger 2 // pour le signal du trigger #define pinSignal 3 // pour le signal de mesure de temps de l'instruction // initialize the library with the numbers of the interface pins //LiquidCrystal lcd(RS, E, D4, D5, D6, D7); LiquidCrystal lcd(8, 9, 4, 5, 6, 7); String strS = ""; // c.f. : https://www.arduino.cc/en/Reference/StringObject char acStr[20]; // Pour conversion de nombre en un string. int nButtonPush = btnNONE; // Bouton pressé byte bMenu1 = 5; // indique le menu sélectionné byte bMenu1_Last = 0; // indique le dernier menu sélectionné // que lorsque adc_key_in a suffisemment changé. String strCode = ""; // pour indiquer l'état du code byte bEtat1 = 0; // Etat du signal 1, LOW=0 ou HIGH=1 unsigned long lwTemps1 = 0; // temps en micro secondes unsigned long lwTemps2 = 0; // temps en micro secondes unsigned long lwTempsActionLast = 0; // temps en micro secondes de la dernière commande byte bNbCycle = 0; // Nombre de boucle dans loop() avant la lecture de boutons void setup() { //============ // set up the LCD's number of columns and rows: lcd.begin(16, 2); // Affichage du type de programme en cours. lcd.print("Oscillo, mes. T "); lcd.setCursor(0, 1); lcd.print("ex0440 20200626 "); delay(1200); // Attente pour afficher le programme en cours // efface le texte lcd.setCursor(0, 0); lcd.print(" "); lcd.setCursor(0, 1); lcd.print(" "); //lcd.autoscroll(); lwTemps1 = micros(); lwTemps2 = lwTemps1; lwTempsActionLast = lwTemps1; pinMode(13, OUTPUT); pinMode( pinSignal, OUTPUT); pinMode( pinTrigger, OUTPUT); digitalWrite( pinSignal, LOW); digitalWrite( pinTrigger, LOW); bNbCycle = 0; AfficheEtat(); } // setup int read_LCD_buttons() { // ====================== // Lecture du bouton appuié // Il faut détecter 3 fois de suite le même bouton pour qu'on estime détecté le bon bouton pressé. // L'avantage de cette manière de faire est que la lecture des boutons ne prend que // le temps d'une lecture analogique, soit environ 100 micro secondes. int nAdc_key_in = 0; byte bButton = btnNONE; // Button pressed according to the value of adc_key_in static byte bButtonLast = btnNONE; // Last button static byte bButtonCount = 0; // compte le nombre de fois que le même boutton est détecté à la suite. nAdc_key_in = analogRead(0); // Lecture de la valeur du bouton pressé. // La lecture des boutons sont centrée autour des valeurs 0, 144, 329, 504, 741 // J'ai ajouté environ 50 à ces valeurs pour la détection des boutons if (nAdc_key_in > 790) { // Aucun bouton pressé. bButtonLast = btnNONE; bButtonCount = 0; return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result } else if (nAdc_key_in > 555) bButton = btnSELECT; else if (nAdc_key_in > 380) bButton = btnLEFT; else if (nAdc_key_in > 195) bButton = btnDOWN; else if (nAdc_key_in > 55) bButton = btnUP; else bButton = btnRIGHT; if (bButton == bButtonLast) { // Le même bouton a été détecté bButtonCount++; // compte combien de fois de suite le même bouton est détecté. if (bButtonCount == 3) { bButtonCount = 0; return bButton; // Le même bouton a été détecté .. fois de suite, donc c'est le bon } } else { // Un nouveau bouton est détecté. bButtonLast = bButton; bButtonCount = 1; } return btnNONE; // On est pas encore sûr que le bon bouton est bButton. } // read_LCD_buttons void AfficheEtat() { //================== // Affichage du menu et d'information if (bMenu1 == 1) { lwTemps1 = micros(); lcd.setCursor(0, 0); lcd.print("1) affich. LCD "); lwTemps2 = micros(); // Temps d'affichage d'une ligne sur le LCD : 4850 us ~= 5 [ms] !!! sprintf(acStr, "T=%9d [us]", lwTemps2 - lwTemps1); lcd.setCursor(0, 1); lcd.print(acStr); } if (bMenu1 == 2) { // Temps entre deux passages ~= 10 [ms] // C'est le temps d'affichage sur l'écran LCD qui est long lwTemps1 = lwTemps2; lwTemps2 = micros(); lcd.setCursor(0, 0); lcd.print("2. T une boucle "); sprintf(acStr, "T= %8d [us]", lwTemps2 - lwTemps1); lcd.setCursor(0, 1); lcd.print(acStr); } if (bMenu1 == 3) { // Mesure le temps du "sprintf". T ≃ 160 [us] lcd.setCursor(0, 0); lcd.print("3. T sprintf "); lwTemps1 = micros(); sprintf(acStr, "T= %8d [us]", lwTemps2 - lwTemps1); lwTemps2 = micros(); sprintf(acStr, "T= %8d [us]", lwTemps2 - lwTemps1); lcd.setCursor(0, 1); lcd.print(acStr); } if (bMenu1 == 4) { // Mesure le temps du "sprintf" avec conversion en float. T ≃ 270 [us] lcd.setCursor(0, 0); lcd.print("4. T sprintf + f"); lwTemps1 = micros(); float f = (lwTemps2 - lwTemps1) / 1000.0; sprintf(acStr, "dT = %3d.%03d :", (int)f, (int)(f*1000)%1000); lwTemps2 = micros(); f = (lwTemps2 - lwTemps1) / 1000.0; sprintf(acStr, "dT= %3d.%03d [ms]", (int)f, (int)(f*1000)%1000); lcd.setCursor(0, 1); lcd.print(acStr); } if (bMenu1 == 5) { // Mesure le temps du "digitalWrite". T ≃ 4 [us] // Temps de lecture de micros() : T = 4 [us]. lcd.setCursor(0, 0); lcd.print("5. T digitalWrit"); lwTemps1 = micros(); bEtat1 = 1 - bEtat1; //pinMode(pinSignal, OUTPUT); // 4 [us] digitalWrite( pinSignal, bEtat1); // 4 [us] //lwTemps2 = micros(); // 4 [us] lwTemps2 = micros(); // 4 [us] sprintf(acStr, "T= %8d [us]", lwTemps2 - lwTemps1); lcd.setCursor(0, 1); lcd.print(acStr); } if (bMenu1 == 6) { // Mesure le temps du "dtostrf. T ≃ 160 [us] lcd.setCursor(0, 0); lcd.print("6. T dtostrf "); lwTemps1 = micros(); dtostrf( (lwTemps2 - lwTemps1) / 1000.0, 8, 3, acStr); // dtostrf fonctionne bien, alors qu'il a un bug dans tinkercad lwTemps2 = micros(); float f = (lwTemps2 - lwTemps1) / 1000.0; sprintf(acStr, "dT= %3d.%03d [ms]", (int)f, (int)(f*1000)%1000); lcd.setCursor(0, 1); lcd.print(acStr); } if (bMenu1 == 7) { // Vérifie qu'un delay(10) dure bien 10 [ms] lcd.setCursor(0, 0); lcd.print("7. T delay(10) "); lwTemps1 = micros(); delay(10); lwTemps2 = micros(); sprintf(acStr, "T= %8d [us]", lwTemps2 - lwTemps1); lcd.setCursor(0, 1); lcd.print(acStr); } if (bMenu1 == 8) { // Boucle sans affichage de menu // Temps d'une boucle est d'environ 16 [us] lwTemps2 = micros(); if (bMenu1_Last != bMenu1) { lcd.setCursor(0, 0); lcd.print("8. T une boucle "); sprintf(acStr, "T= %8d [us]", lwTemps2 - lwTemps1); lcd.setCursor(0, 1); lcd.print(acStr); bMenu1_Last = bMenu1; } else { bMenu1_Last = bMenu1 - 1; // différent de bMenu1 } lwTemps1 = micros(); } if (bMenu1 > 8) { // Pour afficher des informations sur le bouton pressé // Pour afficher des informations sur le bouton pressé // Indique le bouton pressé String astrS[6]; // Indices vont de astrS[0] à astrS[5], l'indice 6 n'est pas valide ! // Texte qui peut etre affiché astrS[btnNONE] = "None "; astrS[btnSELECT] = "Select"; astrS[btnLEFT] = "Left "; astrS[btnDOWN] = "Down "; astrS[btnUP] = "Up "; astrS[btnRIGHT] = "Right "; int nAdc_key_in = analogRead(0); sprintf(acStr, "Menu=%2d ", bMenu1); lcd.setCursor(0, 0); lcd.print(acStr); sprintf(acStr, " adc=%4d", nAdc_key_in); String strS = astrS[nButtonPush] + acStr; lcd.setCursor(0, 1); lcd.print(strS); } } // AfficheEtat void MenuTreat() { //================ // Pour afficher des informations sur le bouton pressé AfficheEtat(); } // MenuTreat void Menu1_Change() { //=================== // Changement du Menu1 if (micros() - lwTempsActionLast < 200000ul) return; // Pas deux changements de menu en moins de 0,2 [s] lwTempsActionLast = micros(); // Mémorise quand le dernier changement de menu a eu lieu. if (nButtonPush == btnLEFT) { bMenu1 -= 1; if (bMenu1 == 0) bMenu1 = MENUMAX; // boucle } if (nButtonPush == btnRIGHT) { bMenu1 += 1; // if (bMenu1 > MENUMAX) bMenu1 = 1; // boucle } AfficheEtat(); bMenu1_Last = bMenu1; // Mémorise le dernier menu } // Menu1_Change void loop() { //=========== // Boucle principale, pour tester le temps d'exécution d'instructions. // Ne lit l'état des boutons que tous les ... cycles bNbCycle++; if (bNbCycle == 4) { // Lecture de l'état des boutons bNbCycle = 0; digitalWrite( pinTrigger, 1); // 4.6 à 5.0 us de décalage entre les deux "digitalWrite" nButtonPush = read_LCD_buttons(); if ( (nButtonPush == btnLEFT) || (nButtonPush == btnRIGHT) ) Menu1_Change(); // Changement de menu if ( (nButtonPush == btnUP) || (nButtonPush == btnDOWN) || (nButtonPush == btnSELECT) ) MenuTreat(); // Un bouton pressé, traitement digitalWrite( pinTrigger, 0); } if (bMenu1 <= 9) AfficheEtat(); // Mesure de certain temps d'affichage bEtat1 = 1 - bEtat1; digitalWrite( pinSignal, bEtat1); } // loop //FIN
/* ex0441_digitalWrite_speed.ino Test la vitesse d'écriture sur un port digital c.f. https://arduino.stackexchange.com/questions/36871/is-the-digitalwritefast-library-still-needed Un cycle = 0.0625 micro-seconde. On note [us] l'unité des micro-secondes. un cycle = 1 / 16'000'000 [Hz] La fréquence de base d'un Arduino est de 16'000'000 [Hz]. Ce test montre qu'un accès direct à un port pour changer son état est 47/2 = 23 fois plus rapide que l'utilisation de la fonction "digitalWrite(byte, byte)". Voir aussi : ex0448_Menu_LCD_vitesses_tests.ino Certaines mesures diffèrent dans cet autre programme. Pourquoi ??? Une différence est que "ex0448_Menu_LCD_vitesses_tests.ino" utilise la librairie : <iquidCrystal.h> ***********************************************************************/ void setup() { //============ uint16_t overhead, start, end1; pinMode( 2, OUTPUT); pinMode(13, OUTPUT); Serial.begin(9600); TCCR1A = 0; TCCR1B = 1; overhead = 4; // Nombre de cycles utilisés pour la lecture du compteur TCNT1 // Time digitalWrite(). cli(); start = TCNT1; digitalWrite(2, HIGH); // 47 cycles end1 = TCNT1; sei(); Serial.print("digitalWrite(): "); Serial.print(end1 - start - overhead); Serial.println(" cycles"); // Time digitalWrite(). cli(); start = TCNT1; digitalWrite(2, HIGH); digitalWrite(2, HIGH); // Deux fois 47 cycles. end1 = TCNT1; sei(); Serial.print("digitalWrite(): "); Serial.print(end1 - start - overhead); Serial.println(" cycles"); // Time direct port access. cli(); start = TCNT1; //PORTD &= ~_BV(PD2); // _BV(PD2) = 0b00000100 ~_BV(PD2) = 0b11111011 2 cycles. //PORTD &= 0b11111011; // mise à 0 équivalent à : PORTD = PORTD & 0b11111011; 2 cycles PORTD |= 0b00000100; // mise à 1 équivalent à : PORTD = PORTD | 0b00000100; 2 cycles end1 = TCNT1; sei(); Serial.print("direct port access: "); Serial.print(end1 - start - overhead); Serial.println(" cycles"); } // setup() void loop() { //=========== // Juste comme exemple de manipulation de port. // Fait clignoter une LED branchée sur la pin n° 2 et la LED de la pin 13. PORTD &= 0b11111011; // mise à 0 PORTB &= 0b11011111; // mise à 0 delay(500); PORTD |= 0b00000100; // mise à 1 PORTB |= 0b00100000; // mise à 1 delay(500); } // loop /* PORTD bit 0 => pin 0 bit 1 => pin 1 bit 2 => pin 2 ... bit 7 => pin 7 PORTB bit 0 => pin 8 bit 1 => pin 9 bit 2 => pin 10 bit 3 => pin 11 bit 4 => pin 12 bit 5 => pin 13 bit 6 => non utilisable bit 7 => non utilisable PORTC Analog input pins c.f. https://www.arduino.cc/en/Reference/PortManipulation */ex0441_DigitalWrite_speed.ino
/* ex0448_Menu_LCD_vitesses_tests.ino Pour faire des mesures de temps de diverses instructions, telles que : ° l'écriture sur l'afficheur, c'est long, environ 5 [ms] pour afficher une ligne. ° la lecture des boutons ° la conversion d'un float en string Ces mesures utilisent un oscilloscope. Elles affichent également le résultat des mesures sur l'afficheur LCD 1602 avec les 5 boutons et utilisent une menu pour sélectionner le test à faire. Indique également la valeur de l'ADC0 associé à chaque bouton. La pin 2 sera la référence pour le trigger la pin 3 changera d'état après chaque boucle d'instruction À partir d'un menu, permet aussi d'afficher le nom du bouton pressé et la valeur de l'ADC 0 correspondant. C.f. ex0149_Menu_LiquidCrystal_vitesses_tests.ino qui fait aussi des mesures de temps, mais moins précisément. C.f. ex0440_DigitalWrite_speed.ino Fait aussi des mesures, avec la mesure du "DigitalWrite" différente. Une différence entre ces programmes est que "ex0440_DigitalWrite_speed.ino" utilise l'affichage dans le moniteur série et pas la librairie <LiquidCrystal.h> qui est utilisée dans ce programme. Connections : LCD Arduino (pin) ou tension 1 - Vss = 0 V 2 - Vcc = 5 V 3 - V0 = Tension variable réglée par une résistance variable, pour la lumineusité de l'affichage 4 - RS = pin 8 (Register Select) 5 - R/W = 0 V donc uniquement en Write 6 - E = pin 9 Enable, mise à haut pour accepter les données. 7 .. 10 = connecté à la commande de rétro-éclairage "back light" 11 - D4 = pin 4, données sur 4 bits 12 - D5 = pin 5, données sur 4 bits 13 - D6 = pin 6, données sur 4 bits 14 - D7 = pin 7, données sur 4 bits 15 - LED + = 220 Ohm - +5V 16 - LED - = 0V Autres connexions : pin 2 et 3 pour le signal pin 13 pour indiquer si le HP est on ou off pin 0, pin 1, utilisées pour la transmission par USB, ADC 0 pour les lecture les boutons. c.f. : http://www.arduino.cc/en/Tutorial/LiquidCrystal c.f. : https://arduino-info.wikispaces.com/LCD-Pushbuttons Quelques constantes : ===================== Elles sont définies dans le fichier : Arduino.h Sous Linux KUbuntu, ce fichier se trouve dans le dossier : /usr/share/arduino/hardware/arduino/core/arduino #define HIGH 0x1 #define LOW 0x0 #define INPUT 0x0 #define OUTPUT 0x1 #define INPUT_PULLUP 0x2 #define true 0x1 #define false 0x0 #define PI 3.1415926535897932384626433832795 #define HALF_PI 1.5707963267948966192313216916398 #define TWO_PI 6.283185307179586476925286766559 #define DEG_TO_RAD 0.017453292519943295769236907684886 #define RAD_TO_DEG 57.295779513082320876798154814105 #define SERIAL 0x0 #define DISPLAY 0x1 #define LSBFIRST 0 #define MSBFIRST 1 ======================================================== */ // include the library code: #include <LiquidCrystal.h> // Déclaration de Constantes #define btnNONE 0 #define btnSELECT 1 #define btnLEFT 2 #define btnDOWN 3 #define btnUP 4 #define btnRIGHT 5 // Dernier numéro de menu. Non utilisé #define MENUMAX 8 #define pinTrigger 2 // pour le signal du trigger #define pinSignal 3 // pour le signal de mesure de temps de l'instruction // initialize the library with the numbers of the interface pins //LiquidCrystal lcd(RS, E, D4, D5, D6, D7); LiquidCrystal lcd(8, 9, 4, 5, 6, 7); String strS = ""; // c.f. : https://www.arduino.cc/en/Reference/StringObject char acStr[20]; // Pour conversion de nombre en un string. int nButtonPush = btnNONE; // Bouton pressé byte bMenu1 = 5; // indique le menu sélectionné byte bEtat1 = 0; // Etat du signal 1, LOW=0 ou HIGH=1 unsigned long lwTemps1 = 0; // temps en micro secondes unsigned long lwTemps2 = 0; // temps en micro secondes unsigned long lwTempsActionLast = 0; // temps en micro secondes de la dernière commande byte bNbCycle = 0; // Nombre de boucle dans loop() avant la lecture de boutons // Pour des mesures de temps // Utilisation du compteur TCNT1, qui est incrémenté de 1 16'000'000 de fois par seconde, donc toutes les 0.0625 [us]. uint16_t overhead = 0; // Nombre de cycles pour lire la valeur du compteur TCNT1. Un cycle = 0.0626 [us]. uint16_t start = 0; // valeur du compteur TCNT1 avant l'instruction uint16_t end1 = 0; // valeur du compteur TCNT1 après l'instruction uint16_t wTmp = 0; // Un word pour un traitement temporaire. byte bOverflow = 0; // Mémorise s'il y a eu un overfloe void setup() { //============ // set up the LCD's number of columns and rows: lcd.begin(16, 2); // Affichage du type de programme en cours. lcd.print("Oscillo, mes. T "); lcd.setCursor(0, 1); lcd.print("ex0448 20200626 "); delay(1200); // Attente pour afficher le programme en cours // efface le texte lcd.setCursor(0, 0); lcd.print(" "); lcd.setCursor(0, 1); lcd.print(" "); //lcd.autoscroll(); lwTemps1 = micros(); lwTemps2 = lwTemps1; lwTempsActionLast = lwTemps1; pinMode(13, OUTPUT); pinMode( pinSignal, OUTPUT); pinMode( pinTrigger, OUTPUT); digitalWrite( pinSignal, LOW); digitalWrite( pinTrigger, LOW); bNbCycle = 0; // Définit la vitesse de comptage du timer 1. TCCR1A = 0; // Mode normal d'opération TCCR1B = 1; // Mode normal et incrémente le compteur TCNT1 toutes les 0.0625 [us], soit 16'000'000 de fois par seconde. TIMSK1 = 0; // Désactive toutes les interruptions du timer 1. // Le timer 0 continue d'être utilisé, pour les fonctions : "delay", "delayMicroseconds", "millis" et "micros". overhead = 8; // Nombre de cycles utilisés pour les deux lectures du compteur TCNT1 AfficheEtat(); } // setup int read_LCD_buttons() { // ====================== // Lecture du bouton appuié // Il faut détecter 3 fois de suite le même bouton pour qu'on estime détecté le bon bouton pressé. // L'avantage de cette manière de faire est que la lecture des boutons ne prend que // le temps d'une lecture analogique, soit environ 100 micro secondes. int nAdc_key_in = 0; byte bButton = btnNONE; // Button pressed according to the value of adc_key_in static byte bButtonLast = btnNONE; // Last button static byte bButtonCount = 0; // compte le nombre de fois que le même boutton est détecté à la suite. nAdc_key_in = analogRead(0); // Lecture de la valeur du bouton pressé. // La lecture des boutons sont centrée autour des valeurs 0, 144, 329, 504, 741 // J'ai ajouté environ 50 à ces valeurs pour la détection des boutons if (nAdc_key_in > 790) { // Aucun bouton pressé. bButtonLast = btnNONE; bButtonCount = 0; return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result } else if (nAdc_key_in > 555) bButton = btnSELECT; else if (nAdc_key_in > 380) bButton = btnLEFT; else if (nAdc_key_in > 195) bButton = btnDOWN; else if (nAdc_key_in > 55) bButton = btnUP; else bButton = btnRIGHT; if (bButton == bButtonLast) { // Le même bouton a été détecté bButtonCount++; // compte combien de fois de suite le même bouton est détecté. if (bButtonCount == 3) { bButtonCount = 0; return bButton; // Le même bouton a été détecté .. fois de suite, donc c'est le bon } } else { // Un nouveau bouton est détecté. bButtonLast = bButton; bButtonCount = 1; } return btnNONE; // On est pas encore sûr que le bon bouton est bButton. } // read_LCD_buttons void AfficheEtat() { //================== // Affichage du menu et d'information // Temps de mesure maximale = 65531 Cycles = 4.0956875 [ms] byte bMenucheck = 0; bMenucheck++; if (bMenu1 == bMenucheck) { // Nombre de cycles pour ne rien faire = 0 cycles cli(); start = TCNT1; // 4 cycles, même si on ne fait rien. end1 = TCNT1; sei(); sprintf(acStr, "%1d) rien", bMenucheck); lcd.setCursor(0, 0); lcd.print(acStr); sprintf(acStr, "T=%5d [cycles]", end1 - start - overhead); lcd.setCursor(0, 1); lcd.print(acStr); } bMenucheck++; if (bMenu1 == bMenucheck) { // Nombre de cycles pour lire la valeur du registre 16 bits TCNT1 = 4 cycles <=> 0.25 [us] cli(); start = TCNT1; wTmp = TCNT1; // 4 cycles end1 = TCNT1; sei(); sprintf(acStr, "%1d) lect de TCNT1", bMenucheck); lcd.setCursor(0, 0); lcd.print(acStr); sprintf(acStr, "T=%5d [cycles]", end1 - start - overhead); lcd.setCursor(0, 1); lcd.print(acStr); } bMenucheck++; if (bMenu1 == bMenucheck) { // Nombre de cycles pour lire deux fois la valeur du registre 16 bits TCNT1 = 8 cycles <=> 0.5 [us] cli(); start = TCNT1; wTmp = TCNT1; // 4 cycles wTmp = TCNT1; // + 4 cylces end1 = TCNT1; sei(); sprintf(acStr, "%1d) lect 2x TCNT1", bMenucheck); lcd.setCursor(0, 0); lcd.print(acStr); sprintf(acStr, "T=%5d [cycles]", end1 - start - overhead); lcd.setCursor(0, 1); lcd.print(acStr); } bMenucheck++; if (bMenu1 == bMenucheck) { // Nombre de cycles pour lire trois fois la valeur du registre 16 bits TCNT1 = 12 cycles <=> 0.75[us] cli(); start = TCNT1; wTmp = TCNT1; // 4 cycles wTmp = TCNT1; // + 4 cylces wTmp = TCNT1; // + 4 cylces end1 = TCNT1; sei(); sprintf(acStr, "%1d) lect 3x TCNT1", bMenucheck); lcd.setCursor(0, 0); lcd.print(acStr); sprintf(acStr, "T=%5d [cycles]", end1 - start - overhead); lcd.setCursor(0, 1); lcd.print(acStr); } bMenucheck++; if (bMenu1 == bMenucheck) { // Nombre de cycles d'exéction d'un "lcd.print(acStr)" = 65536 + 7096 = 72'632 cycles <=> 4.5395 [ms] // Ici on dépasse le temps de mesure maximale des 65535 cycles. cli(); TCNT1 = 0; TIFR1 = 1; // Efface les flags (bits) du registre "Timer/Counter1", qui détecte les "overflow" start = TCNT1; lcd.print("2) affich. LCD "); end1 = TCNT1; sei(); bOverflow = TIFR1; // Mémorise le overflow flag sprintf(acStr, "%1d) affich. LCD ", bMenucheck); lcd.setCursor(0, 0); lcd.print(acStr); if (bOverflow & 1) { // Il y a eu un overflow, le compteur TCNT1 a dépassé 65535. lcd.setCursor(0, 1); lcd.print("Overflow : TCNT1"); delay(1000); } sprintf(acStr, "T=%5d [cy] + >", end1 - start - overhead); lcd.setCursor(0, 1); lcd.print(acStr); } bMenucheck++; if (bMenu1 == bMenucheck) { // Nombre de cycles d'exéction d'un "lcd.setCursor(0, 0);" = 4515 cycles <=> 282.1875 [us] lcd.setCursor(0, 0); cli(); TCNT1 = 0; TIFR1 = 1; // Efface les flags (bits) du registre "Timer/Counter1", qui détecte les "overflow" start = TCNT1; lcd.setCursor(0, 0); end1 = TCNT1; sei(); bOverflow = TIFR1; // Mémorise le overflow flag sprintf(acStr, "%1d) lcd.setcursor", bMenucheck); lcd.setCursor(0, 0); lcd.print(acStr); sprintf(acStr, "T= %4d [cycles]", end1 - start - overhead); lcd.setCursor(0, 1); lcd.print(acStr); if (bOverflow & 1) { // Il y a eu un overflow, le compteur TCNT1 a dépassé 65535. lcd.setCursor(0, 1); lcd.print("Overflow : TCNT1"); } } bMenucheck++; if (bMenu1 == bMenucheck) { // Nombre de cycles d'exéction d'un "sprintf" = 2495 cycles <=> 155.9375 [us] lcd.setCursor(0, 0); cli(); TCNT1 = 0; TIFR1 = 1; // Efface les flags (bits) du registre "Timer/Counter1", qui détecte les "overflow" start = TCNT1; sprintf(acStr, "T= %4d [cycles]", end1 - start - overhead); end1 = TCNT1; sei(); bOverflow = TIFR1; // Mémorise le overflow flag sprintf(acStr, "%1d) sprintf(...);", bMenucheck); lcd.setCursor(0, 0); lcd.print(acStr); sprintf(acStr, "T= %4d [cycles]", end1 - start - overhead); lcd.setCursor(0, 1); lcd.print(acStr); if (bOverflow & 1) { // Il y a eu un overflow, le compteur TCNT1 a dépassé 65535. lcd.setCursor(0, 1); lcd.print("Overflow : TCNT1"); } } bMenucheck++; if (bMenu1 == bMenucheck) { // Mesure le temps du "sprintf". T ≃ 156 [us], varie légèrement dans cette mesure. Les interruptions ne sont pas stoppée ! lwTemps1 = micros(); sprintf(acStr, "T= %8d [us]", lwTemps2 - lwTemps1); lwTemps2 = micros(); sprintf(acStr, "%2d) T sprintf ", bMenucheck); lcd.setCursor(0, 0); lcd.print(acStr); sprintf(acStr, "T= %8d [us]", lwTemps2 - lwTemps1); lcd.setCursor(0, 1); lcd.print(acStr); } bMenucheck++; if (bMenu1 == bMenucheck) { // Mesure le temps du "sprintf" avec conversion en float. T ≃ 270 [us] lwTemps1 = micros(); float f = (lwTemps2 - lwTemps1) / 1000.0; sprintf(acStr, "dT = %3d.%03d :", (int)f, (int)(f*1000)%1000); lwTemps2 = micros(); sprintf(acStr, "%2d) T sprintf +f", bMenucheck); lcd.setCursor(0, 0); lcd.print(acStr); f = (lwTemps2 - lwTemps1) / 1000.0; sprintf(acStr, "dT= %3d.%03d [ms]", (int)f, (int)(f*1000)%1000); lcd.setCursor(0, 1); lcd.print(acStr); } bMenucheck++; if (bMenu1 == bMenucheck) { // Nombre de cycles d'exéction d'un "digitalWrite(2, HIGH);" = 58 cycles <=> 3.625 [us] // Valeur différente de celle obtenue avec ex0440_DigitalWrite_speed !?! cli(); TCNT1 = 0; TIFR1 = 1; // Efface les flags (bits) du registre "Timer/Counter1", qui détecte les "overflow" start = TCNT1; digitalWrite(2, HIGH); // 58 cycles end1 = TCNT1; sei(); sprintf(acStr, "%2d) digitalWrite", bMenucheck); lcd.setCursor(0, 0); lcd.print(acStr); sprintf(acStr, "T= %4d [cycles]", end1 - start - overhead); lcd.setCursor(0, 1); lcd.print(acStr); } bMenucheck++; if (bMenu1 == bMenucheck) { // Nombre de cycles d'exéction d'un "digitalWrite(2, HIGH);" = 58 cycles <=> 3.625 [us] // Valeur différente de celle obtenue avec ex0440_DigitalWrite_speed !?! cli(); TCNT1 = 0; TIFR1 = 1; // Efface les flags (bits) du registre "Timer/Counter1", qui détecte les "overflow" start = TCNT1; digitalWrite(2, HIGH); // 58 cycles digitalWrite(2, HIGH); // 58 cycles end1 = TCNT1; sei(); sprintf(acStr, "%2d) digitalW 2 x", bMenucheck); lcd.setCursor(0, 0); lcd.print(acStr); sprintf(acStr, "T= %4d [cycles]", end1 - start - overhead); lcd.setCursor(0, 1); lcd.print(acStr); } bMenucheck++; if (bMenu1 == bMenucheck) { // Nombre de cycles d'exéction d'un "PORTD = ...;" = 2 cycles <=> 0.125 [us], Accès direct au port D. cli(); TCNT1 = 0; TIFR1 = 1; // Efface les flags (bits) du registre "Timer/Counter1", qui détecte les "overflow" start = TCNT1; //PORTD &= ~_BV(PD2); // _BV(PD2) = 0b00000100 ~_BV(PD2) = 0b11111011 2 cycles. //PORTD &= 0b11111011; // mise à 0 équivalent à : PORTD = PORTD & 0b11111011; 2 cycles PORTD |= 0b00000100; // mise à 1 équivalent à : PORTD = PORTD | 0b00000100; 2 cycles end1 = TCNT1; sei(); sprintf(acStr, "%2d) PORTD |= 0b.", bMenucheck); lcd.setCursor(0, 0); lcd.print(acStr); sprintf(acStr, "T= %4d [cycles]", end1 - start - overhead); lcd.setCursor(0, 1); lcd.print(acStr); } bMenucheck++; if (bMenu1 == bMenucheck) { // Nombre de cycles d'exéction d'un "delay(2);". // Variable entre 32126 cycles <=> 2'007.875 [us] et 32143 cycles <=> 2'008.9375 [us]. //cli(); // Il faut accepter les interruptions, car la fonction "delay" utilise l'interruption du timer 0. TCNT1 = 0; TIFR1 = 1; // Efface les flags (bits) du registre "Timer/Counter1", qui détecte les "overflow" start = TCNT1; delay(2); // attente de 2 [ms] end1 = TCNT1; //sei(); sprintf(acStr, "%2d) delay(2) ", bMenucheck); lcd.setCursor(0, 0); lcd.print(acStr); sprintf(acStr, "T=%5d [cycles]", end1 - start - overhead); lcd.setCursor(0, 1); lcd.print(acStr); } bMenucheck++; if (bMenu1 == bMenucheck) { // Nombre de cycles d'exéction d'un "dtostrf" = 1770 cycles <=> 110.625 [us] cli(); TCNT1 = 0; TIFR1 = 1; // Efface les flags (bits) du registre "Timer/Counter1", qui détecte les "overflow" start = TCNT1; dtostrf( (lwTemps2 - lwTemps1) / 1000.0, 8, 3, acStr); end1 = TCNT1; sei(); sprintf(acStr, "%1d) dtostrf(...) ", bMenucheck); lcd.setCursor(0, 0); lcd.print(acStr); sprintf(acStr, "T= %4d [cycles]", end1 - start - overhead); lcd.setCursor(0, 1); lcd.print(acStr); } bMenucheck++; if (bMenu1 == bMenucheck) { // Mesure le temps du "dtostrf". T ≃ 160 [us] lwTemps1 = micros(); dtostrf( (lwTemps2 - lwTemps1) / 1000.0, 8, 3, acStr); // dtostrf fonctionne bien, alors qu'il a un bug dans tinkercad lwTemps2 = micros(); sprintf(acStr, "%1d) dtostrf(...) ", bMenucheck); lcd.setCursor(0, 0); lcd.print(acStr); float f = (lwTemps2 - lwTemps1) / 1000.0; sprintf(acStr, "dT= %3d.%03d [ms]", (int)f, (int)(f*1000)%1000); lcd.setCursor(0, 1); lcd.print(acStr); } bMenucheck++; if (bMenu1 >= bMenucheck) { // Pour afficher des informations sur le bouton pressé // Indique le bouton pressé String astrS[6]; // Indices vont de astrS[0] à astrS[5], l'indice 6 n'est pas valide ! // Texte qui peut etre affiché astrS[btnNONE] = "None "; astrS[btnSELECT] = "Select"; astrS[btnLEFT] = "Left "; astrS[btnDOWN] = "Down "; astrS[btnUP] = "Up "; astrS[btnRIGHT] = "Right "; int nAdc_key_in = analogRead(0); sprintf(acStr, "Menu=%2d ", bMenu1); lcd.setCursor(0, 0); lcd.print(acStr); sprintf(acStr, " adc=%4d ", nAdc_key_in); String strS = astrS[nButtonPush] + acStr; lcd.setCursor(0, 1); lcd.print(strS); } } // AfficheEtat void MenuTreat() { //================ // Pour afficher des informations sur le bouton pressé if (micros() - lwTempsActionLast < 200000ul) return; // Pas deux changements de menu en moins de 0,2 [s] lwTempsActionLast = micros(); // Mémorise quand le dernier changement de menu a eu lieu. AfficheEtat(); } // MenuTreat void Menu1_Change() { //=================== // Changement du Menu1 if (micros() - lwTempsActionLast < 200000ul) return; // Pas deux changements de menu en moins de 0,2 [s] lwTempsActionLast = micros(); // Mémorise quand le dernier changement de menu a eu lieu. if (nButtonPush == btnLEFT) { bMenu1 -= 1; if (bMenu1 == 0) bMenu1 = 1; } if (nButtonPush == btnRIGHT) { bMenu1 += 1; } AfficheEtat(); } // Menu1_Change void loop() { //=========== // Boucle principale, pour tester le temps d'exécution d'instructions. // Ne lit l'état des boutons que tous les ... cycles bNbCycle++; if (bNbCycle == 4) { // Lecture de l'état des boutons bNbCycle = 0; digitalWrite( pinTrigger, 1); // 4.6 à 5.0 us de décalage entre les deux "digitalWrite" nButtonPush = read_LCD_buttons(); if ( (nButtonPush == btnLEFT) || (nButtonPush == btnRIGHT) ) Menu1_Change(); // Changement de menu if ( (nButtonPush == btnUP) || (nButtonPush == btnDOWN) || (nButtonPush == btnSELECT) ) MenuTreat(); // Un bouton pressé, traitement digitalWrite( pinTrigger, 0); } bEtat1 = 1 - bEtat1; digitalWrite( pinSignal, bEtat1); } // loop //FIN
/* Plusieurs registres définissent la manière de générer des imulsions sur les pin 3, 5, 6, 9, 10 et 11. Il y a trois groupes de 2 : Timer pin1 pin2 registres 0 6 5 TCCR0B TCCR0A OCR1A OCR0B TCNT0 1 9 10 TCCR1B TCCR1A OCR1A OCR1B TCNT1 2 11 3 TCCR2B TCCR2A OCR2A OCR2B TCNT2 Ce qui suit n'utilisera pas cette manière de faire qui correspond au PMW. Ici, on se limitera au Timer 1. Référence = Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf "TCCR" signifie Timer Counter Control Register Structure des registres 8-bits de contrôle : TCCR1A - [COM1A1, COM1A0, COM1B1, COM1B0, reserved, reserved, WGM11, WGM10] TCCR1B - [ICNC1, ICES1, reserved, WGM13, WGM12, CS12, CS11, CS10] TCCR1C - [F0C1A, F0C1B, reserved, reserved, reserved, reserved, reserved, reserved] TIMSK1 - [ - , - , ICE1, - , - , OCIE1B, OCIE1A, TOIE1] c.f. page 135 bit 7 6 5 4 3 2 1 0 ICE1 = 1 --- Interruption lorsque le Flag "ICF1" est mis à 1 OCIE1B = 1 --- Interruption lorsque le Flag "OCF1B" est mis à 1. Ce Flag est mis à 1 lorsque le compteur TCNT1 == OCR1B OCIE1A = 1 --- Interruption lorsque le Flag "OCF1A" est mis à 1. Ce Flag est mis à 1 lorsque le compteur TCNT1 == OCR1A TOIE1 = 1 --- Interruption lorsque le Flag "TOV1" est mis à 1. Ce Flag est mis à 1 lorsque le compteur TCNT1 passe de 65535 à 0 (overflow) OCR1A et OCR1B sont deux registres 16 bits qui permettent le contrôle précis de générations d'interruptions, lorsque le compteur 16 bits TCNT1 atteint un de ces registres. Status Register ( Timer/Counter1 Interrupt Flag Register) : c.f. page 136 TIFR1 - [ - , - , ICF1, - , - , OCF1B, OCF1A, TOV1] bit 7 6 5 4 3 2 1 0 OCF1B est mis à 1 lorsque : TCNT1 == OCR1B. Indépendemment du fait qu'une interruption est exécutée ou non. OCF1A est mis à 1 lorsque : TCNT1 == OCR1A. Indépendemment du fait qu'une interruption est exécutée ou non. TOV1 est mis à 1 lorsque : TCNT1 passe de 65535 à 0 (overflow). Indépendemment du fait qu'une interruption est exécutée ou non. Dans chaque cas, le Flag est mis à zéro lorsque l'on écrit un 1 à la place de ce bit dans le registre TIFR1. Il est aussi mis à zéro, si le bit de gestion d'interruptions est activé (mis à 1) et qu'une interruption a lieu. ICF1 est mis à 1 lorsqu'une "capture event" à lieu sur le pin ICP1. pin ICP1 = digital I/O 8 = "Input Capture Pin". Ainsi, il est possible de générer une interruption lorsqu'il se passe quelque chose sur la pin 8. C.f. p. 117 et 133 Le bit ICES1 du registre TCCR1B indique si l'interruption à lieu lors d'une transistion descendante (bit = 0) ou montante (bit = 1). Lors d'un "event" sur la pin ICP1, le registre 16 bits ICR1 mémorise la valeur du compteur TCNT1 lors de l'interruption, pour mesurer très précisément quand l'interruption a eu lieu. C.f. p. 135. ICF1 n'est pas utilisé dans ce programme ! COM1Ax bits == Compare Output Mode bits == 0 dans ce prog. OCR1A == Output Compare Register A utilisé à partir de ex0451. Registre de 16 bits, pouvant aller de 0 à 65535. WGM1(0:3) 4 bits == Waveform Generation Mode bits WGM1(0:3) = 0b0100 == 4 à partir de ex0451. Les registres TCNT1, OCR1A et OCR1B sont en mode R/W (écriture et lecture). Ce sont des registres de 16 bits, allant de 0 à 65535. Le compteur TCNT1 compte de 0 à OCR1A, puis revient à 0. Une interruption est générée si le Flag OCIE1A ou OCIE1B du registre TIMSK1 est mis à 1. Utile pour la génération de PMW (Pulse Width Modulated). (COM1A1, COM1A0) C.f. p. 131 Permet d'activer ou non le bit OC1A c.f. "Port A pins" OC1A == pin 9 (0, 0) => OC1A est déconnecté, (0, 1) => Toggle OC1A on compare Match (1, 0) => Clear OC1A on Compare Match (Set output to low level) (1, 1) => Clear OC1A on Compare Match (Set output to high level) (COM1B1, COM1B0) C.f. p. 131 Permet d'activer ou non le bit OC1B c.f. "Port B pins" OC1B == pin 10 (0, 0) => OC1B est déconnecté, (0, 1) => Toggle OC1B on compare Match (1, 0) => Clear OC1B on Compare Match (Set output to low level) (1, 1) => Clear OC1B on Compare Match (Set output to high level) (WGM13, WGM12, WGM11, WGM10) C.f. page 132 (0, 0, 0, 0) => le compteur TCNT1 est incrémenté chaque T * xxx [us]. Pour xxx c.f. bits CS1(0:2) Lorsque le compteur passe à 0, une interruption est générée si la valeur du masque : TIMSK1 = 0bxxxxxxx1 Lors de cette interruption, une nouvelle valeur de départ peut être assignée à TCNT1. À n'importe quelle moment, une nouvelle valeur peut être assignée à TCNT1. (0, 0, 0, 1) => Pour PWM, phase mode. C.f. p. 125 Le compteur compte alternativement en incrémentant, puis décrémentant. (0, 1, 0, 0) => Mode CTC "Clear Timer on Compare". C.f. page 122. C'est le mode que je conseille. Le registre OCR1A est utilisé ici. Lorsque le compteur TCNT1 == OCR1A, le compteur TCNT1 est mis à 0 et une interruption est générée si la valeur du masque : TIMSK1 = 0bxxxxxx1x ... Gère le temps entre deux incréments du compteur TCNT1 (CS12, CS11, CS10) C.f. page 134 (0, 0, 0) => No clock source, Timer/Counter stopped (0, 0, 1) => T * 1 = 0.0625 [us] (0, 1, 0) => T * 8 = 0.500 [us], division par 8 de la fréquence (0, 1, 1) => T * 64 = 4.000 [us], division par 64 de la fréquence (1, 0, 0) => T * 256 = 16.000 [us], division par 256 de la fréquence (1, 1, 1) => T * 1024 = 64.000 [us], division par 1024 de la fréquence Pour d'autres modes de fonctionnement. -------------------------------------- C.f. page 131 TCCR1A = [COM1A1 COM1A0 COM1B1 COM1B0 0 0 WGM11 WGM10 TCCR1A = 0b01000000; // Pour activer le bit OC1A ( == pin 9), // Bits : WGM11 = 0 et WGM10 = 0 TCCR1B = [ICNC1 ICES1 0 WGM13 WGM12 CS12 CS11 CS10] TCCR1B = 0b00001001; // wGM13 = 0 WGM12 = 1 TCCR1C = [F0C1A F0C1B 0 0 0 0 0 0] TCCR = Timer Counter Control Register CTC mode = Clear Timer on Compare WGM mode = Wave Generation mode COM mode = Compare Output mode N'affecte pas la séquence du compteur TCNT1 TIMSK = Timer Interrupt Mask Register Mode Normal, TCNT1 compte de 0 à 65535, WGM = 0b0000 = 0 Mode CTC, TCNT1 compte de 0 à OCR1A, WGM = 0b0100 = 4 Mode utilisé dans ce programme. C.f. p. 122 ( Mode CTC, TCNT1 compte de 0 à ICR1, WGM = 0b1100 = 12 ) ( Fast PWM Mode, WGM = 5, 6, 7, 14 ou 15 ) ( Phase PWM Mode, WGM = 1, 2, 3, 10 ou 11 ) ( Phase and Frequency Correct PWM Mode, WGM = 8 ou 9 ) TIMSK = [ 0 0 ICIE1 - - OCIE1B OCIE1A TOIE1 ] TOIE1 = 1 --- génération d'interruption lors d'un overflow OCIE1A = 1 --- génération d'interruption lorsque TCNT1 == OCR1A Ce qui est utilisé dans ce prog. OCIE1B = 1 --- génération d'interruption lorsque TCNT1 == OCR1B ICIE1 = 1 --- génération d'interruption lorsque TCNT1 == ICR1 ====================================================================*/ex045x_comments
/* ex0450_timer_1_interrupt.ino Exemple d'utilisation du timer 1 pour générer des interruptions. POUR ADRUINO MEGA car beaucoup de mémoire utilisée ! L'utilisation du timer 1 va simplifier la gestion, car ce timer a un compteur sur 16 bits contrairement aux timers 0 et 2 qui ont des compteurs sur 8 bits. La fréquence de clignotement de la LED pin 13 va augmenter avec le temps. Suite de ex040x_timer_2_interrupt.ino. Autre approche, à chaque passage dans la fonction de gestion d'interruption, on change l'état de la pin 13 C.f. ex0451_timer_1_interrupt_OCR1A.ino pour une meilleure approche. ====================================================================*/ // Nombre de fois qu'il faut répéter la même fréquence word volatile awRepete[] = { 9, 7, 7, 7, 8, 7, 7, 7, 8, 7, 7, 7, 8, 7, 7, 7, 8, 7, 7, 7, 8, 7, 7, 8, 7, 7, 8, 7, 7, 8, 7, 7, 7, 8, 7, 8, 7, 7, 8, 7, 7, 8, 7, 7, 8, 7, 8, 7, 7, 8, 7, 7, 8, 7, 8, 7, 8, 7, 7, 8, 7, 8, 7, 8, 7, 8, 7, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 8, 7, 8, 7, 8, 7, 8, 7, 8, 8, 7, 8, 7, 8, 8, 7, 8, 7, 8, 8, 7, 8, 8, 7, 8, 7, 8, 8, 7, 8, 8, 7, 8, 8, 7, 8, 8, 8, 7, 8, 8, 7, 8, 8, 7, 8, 8, 8, 7, 8, 8, 8, 7, 8, 8, 8, 7, 8, 8, 8, 8, 7, 8, 8, 8, 7, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 8, 8, 8, 8, 8, 8, 9, 8, 8, 8, 8, 8, 8, 9, 8, 8, 8, 8, 9, 8, 8, 8, 8, 9, 8, 8, 8, 9, 8, 8, 8, 9, 8, 8, 8, 9, 8, 8, 9, 8, 8, 8, 9, 8, 8, 9, 8, 8, 9, 8, 8, 9, 8, 8, 9, 8, 8, 9, 8, 9, 8, 8, 9, 8, 9, 8, 8, 9, 8, 9, 8, 9, 8, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 9, 8, 9, 8, 9, 9, 8, 9, 8, 9, 9, 8, 9, 8, 9, 9, 8, 9, 9, 8, 9, 9, 8, 9, 9, 8, 9, 9, 8, 9, 9, 8, 9, 9, 9, 8, 9, 9, 9, 8, 9, 9, 9, 8, 9, 9, 9, 9, 8, 9, 9, 9, 9, 8, 9, 9, 9, 9, 9, 8, 9, 9, 9, 9, 9, 9, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 9, 9, 9, 9, 9, 9, 9, 10, 9, 9, 9, 9, 10, 9, 9, 9, 9, 10, 9, 9, 9, 9, 10, 9, 9, 9, 10, 9, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 9, 10, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 10, 9, 9, 10, 9, 10, 9, 10, 9, 9, 10, 9, 10, 9, 10, 9, 10, 9, 10, 9, 10, 9, 10, 9, 10, 9, 10, 10, 9, 10, 9, 10, 9, 10, 10, 9, 10, 9, 10, 10, 9, 10, 9, 10, 10, 9, 10, 10, 9, 10, 10, 9, 10, 10, 9, 10, 10, 10, 9, 10, 10, 9, 10, 10, 10, 9, 10, 10, 10, 10, 9, 10, 10, 10, 10, 9, 10, 10, 10, 10, 10, 9, 10, 10, 10, 10, 10, 10, 10, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 10, 10, 10, 10, 10, 10, 10, 11, 10, 10, 10, 10, 10, 11, 10, 10, 10, 10, 11, 10, 10, 10, 11, 10, 10, 10, 11, 10, 10, 10, 11, 10, 10, 11, 10, 10, 11, 10, 10, 11, 10, 10, 11, 10, 10, 11, 10, 11, 10, 10, 11, 10, 11, 10, 11, 10, 11, 10, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 11, 10, 11, 10, 11, 10, 11, 10, 11, 11, 10, 11, 11, 10, 11, 10, 11, 11, 10, 11, 11, 10, 11, 11, 10, 11, 11, 11, 10, 11, 11, 11, 10, 11, 11, 11, 10, 11, 11, 11, 10, 11, 11, 11, 11, 11, 10, 11, 11, 11, 11, 11, 11, 11, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 11, 11, 11, 11, 11, 11, 11, 12, 11, 11, 11, 11, 12, 11, 11, 11, 11, 12, 11, 11, 11, 12, 11, 11, 11, 12, 11, 11, 12, 11, 11, 12, 11, 11, 12, 11, 11, 12, 11, 11, 12, 11, 12, 11, 11, 12, 11, 12, 11, 12, 11, 12, 11, 12, 11, 12, 11, 12, 11, 12, 11, 12, 11, 12, 11, 12, 12, 11, 12, 11, 12, 12, 11, 12, 12, 11, 12, 11, 12, 12, 12, 11, 12, 12, 11, 12, 12, 12, 11, 12, 12, 12, 11, 12, 12, 12, 12, 11, 12, 12, 12, 12, 11, 12, 12, 12, 12, 12, 12, 12, 12, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 12, 12, 12, 12, 12, 12, 12, 13, 12, 12, 12, 12, 12, 13, 12, 12, 12, 12, 13, 12, 12, 12, 13, 12, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 13, 12, 12, 13, 12, 13, 12, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 13, 12, 13, 12, 13, 12, 13, 13, 12, 13, 13, 12, 13, 13, 12, 13, 13, 12, 13, 13, 12, 13, 13, 13, 12, 13, 13, 13, 12, 13, 13, 13, 13, 13, 12, 13, 13, 13, 13, 13, 13, 13, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 13, 13, 13, 13, 13, 13, 13, 14, 13, 13, 13, 13, 13, 14, 13, 13, 13, 14, 13, 13, 13, 14, 13, 13, 14, 13, 13, 14, 13, 13, 14, 13, 13, 14, 13, 14, 13, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 13, 14, 14, 13, 14, 13, 14, 14, 13, 14, 13, 14, 14, 13, 14, 14, 14, 13, 14, 14, 13, 14, 14, 14, 14, 13, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 14, 14, 14, 14, 14, 14, 14, 14, 15, 14, 14, 14, 14, 15, 14, 14, 14, 15, 14, 14, 14, 15, 14, 14, 15, 14, 14, 15, 14, 14, 15, 14, 15, 14, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 15, 14, 15, 14, 15, 15, 14, 15, 15, 14, 15, 15, 14, 15, 15, 15, 14, 15, 15, 15, 14, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 15, 15, 15, 15, 15, 16, 15, 15, 15, 15, 16, 15, 15, 16, 15, 15, 15, 16, 15, 15, 16, 15, 16, 15, 15, 16, 15, 16, 15, 16, 15, 16, 15, 16, 15, 16, 15, 16, 15, 16, 16, 15, 16, 15, 16, 16, 15, 16, 16, 15, 16, 16, 16, 15, 16, 16, 16, 15, 16, 16, 16, 16, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 16, 16, 16, 16, 17, 16, 16, 16, 17, 16, 16, 16, 17, 16, 16, 17, 16, 17, 16, 16, 17, 16, 17, 16, 16, 17, 16, 17, 16, 17, 17, 16, 17, 16, 17, 16, 17, 17, 16, 17, 17, 16, 17, 17, 16, 17, 17, 17, 16, 17, 17, 17, 17, 16, 17, 17, 17, 17, 17, 17, 17, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 17, 17, 17, 17, 17, 17, 17, 18, 17, 17, 17, 17, 18, 17, 17, 17, 18, 17, 17, 18, 17, 17, 18, 17, 17, 18, 17, 18, 17, 18, 17, 18, 17, 18, 17, 18, 17, 18, 17, 18, 18, 17, 18, 18, 17, 18, 18, 17, 18, 18, 18, 17, 18, 18, 18, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 17, 18, 18, 18, 18, 19, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 18, 18, 18, 19, 18, 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 19, 18, 19, 18, 19, 18, 19, 18, 19, 19, 18, 19, 18, 19, 19, 18, 19, 19, 19, 18, 19, 19, 19, 18, 19, 19, 19, 19, 19, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 19, 19, 19, 19, 19, 20, 19, 19, 19, 19, 20, 19, 19, 20, 19, 19, 20, 19, 20, 19, 19, 20, 19, 20, 19, 20, 19, 20, 20, 19, 20, 19, 20, 20, 19, 20, 20, 19, 20, 20, 20, 19, 20, 20, 20, 20, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 20, 20, 20, 20, 21, 20, 20, 20, 21, 20, 20, 21, 20, 20, 21, 20, 21, 20, 21, 20, 21, 20, 21, 20, 21, 20, 21, 21, 20, 21, 20, 21, 21, 21, 20, 21, 21, 21, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 21, 21, 21, 21, 22, 21, 21, 22, 21, 21, 22, 21, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 22, 21, 22, 21, 22, 22, 22, 21, 22, 22, 22, 21, 22, 22, 22, 22, 22, 22, 22, 21, 22, 22, 22, 22, 23, 22, 22, 22, 22, 22, 22, 22, 23, 22, 22, 22, 23, 22, 22, 23, 22, 22, 23, 22, 23, 22, 23, 22, 23, 22, 23, 22, 23, 23, 22, 23, 23, 22, 23, 23, 22, 23, 23, 23, 23, 23, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 23, 23, 23, 23, 23, 24, 23, 23, 23, 24, 23, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 23, 24, 24, 23, 24, 24, 24, 23, 24, 24, 24, 24, 24, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 24, 24, 24, 24, 24, 25, 24, 24, 25, 24, 24, 25, 24, 24, 25, 24, 25, 24, 25, 24, 25, 25, 24, 25, 25, 24, 25, 25, 25, 24, 25, 25, 25, 25, 25, 25, 25, 24, 25, 26, 25, 25, 25, 25, 25, 25, 25, 26, 25, 25, 25, 26, 25, 25, 26, 25, 25, 26, 25, 26, 25, 26, 25, 26, 26, 25, 26, 26, 25, 26, 26, 26, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 26, 26, 26, 26, 27, 26, 26, 27, 26, 27, 26, 27, 26, 27, 26, 27, 26, 27, 27, 26, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 27, 27, 28, 27, 27, 28, 27, 27, 28, 27, 28, 28, 27, 28, 27, 28, 28, 27, 28, 28, 28, 28, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29, 28, 28, 28, 28, 29, 28, 28, 29, 28, 29, 28, 29, 28, 29, 28, 29, 28, 29, 29, 28, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 30, 29, 29, 29, 29, 30, 29, 29, 30, 29, 29, 30, 29, 30, 29, 30, 30, 29, 30, 29, 30, 30, 30, 30, 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 31, 30, 30, 30, 31, 30, 30, 31, 30, 31, 30, 31, 30, 31, 30, 31, 31, 30, 31, 31, 31, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 32, 31, 31, 31, 31, 32, 31, 32, 31, 31, 32, 31, 32, 32, 31, 32, 31, 32, 32, 32, 32, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33, 32, 32, 32, 33, 32, 32, 33, 32, 33, 32, 33, 33, 32, 33, 32, 33, 33, 33, 33, 33, 32, 33, 33, 33, 33, 33, 34, 33, 33, 33, 33, 34, 33, 33, 34, 33, 34, 33, 34, 33, 34, 34, 33, 34, 34, 34, 34, 33, 34, 34, 34, 34, 34, 34, 35, 34, 34, 34, 35, 34, 34, 35, 34, 35, 34, 35, 34, 35, 34, 35, 35, 35, 34, 35, 35, 35, 35, 35, 35, 35, 35, 36, 35, 35, 35, 36, 35, 35, 36, 35, 36, 35, 36, 36, 35, 36, 36, 35, 36, 36, 36, 36, 36, 36, 36, 36, 36, 37, 36, 36, 36, 37, 36, 37, 36, 37, 36, 37, 36, 37, 37, 37, 36, 37, 37, 37, 37, 37, 37, 37, 37, 37, 38, 37, 37, 38, 37, 37, 38, 38, 37, 38, 37, 38, 38, 38, 37, 38, 38, 38, 38, 38, 38, 38, 39, 38, 38, 38, 39, 38, 39, 38, 39, 38, 39, 38, 39, 39, 39, 39, 38, 39, 39, 39, 39, 40, 39, 39, 39, 39, 40, 39, 40, 39, 40, 39, 40, 39, 40, 40, 40, 40, 40, 39, 40, 41, 40, 40, 40, 40, 40, 41, 40, 41, 40, 41, 40, 41, 40, 41, 41, 41, 41, 40, 41, 41, 41, 42, 41, 41, 41, 41, 42, 41, 42, 41, 42, 41, 42, 42, 41, 42, 42, 42, 42, 42, 42, 42, 42, 42, 43, 42, 42, 43, 42, 43, 42, 43, 42, 43, 43, 43, 43, 43, 42, 44, 43, 43, 43, 43, 43, 44, 43, 44, 43, 44, 43, 44, 44, 44, 43, 44, 44, 44, 44, 44, 44, 45, 44, 44, 45, 44, 45, 44, 45, 44, 45, 45, 45, 44, 45, 45, 45, 46, 45, 45, 45, 45, 46, 45, 46, 45, 46, 46, 45, 46, 46, 46, 46, 46, 46, 46, 46, 46, 47, 46, 47, 46, 47, 46, 47, 47, 46, 47, 47, 47, 47, 47, 47, 47, 48, 47, 47, 48, 47, 48, 48, 47, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 49, 48, 49, 48, 49, 49, 48, 49, 49, 49, 49, 49, 49, 49, 50, 49, 49, 50, 49, 50, 50, 49, 50, 50, 50, 50, 50, 50, 50, 50, 51, 50, 50, 51, 50, 51, 51, 51, 50, 51, 51, 51, 51, 52, 51, 51, 52, 51, 51, 52, 52, 51, 52, 52, 52, 52, 52, 52, 52, 53, 52, 53, 52, 53, 52, 53, 53, 53, 53, 53, 53, 53, 53, 53, 54, 53, 53, 54, 54, 53, 54, 54, 54, 54, 54, 54, 55, 54, 54, 55, 54, 55, 55, 54, 55, 55, 55, 55, 55, 55, 56, 55, 56, 55, 56, 55, 56, 56, 56, 56, 56, 56, 56, 57, 56, 56, 57, 57, 56, 57, 57, 57, 57, 57, 57, 57, 58, 57, 58, 57, 58, 58, 58, 57, 58, 58, 59, 58, 58, 59, 58, 59, 58, 59, 59, 59, 59, 59, 59, 59, 60, 59, 59, 60, 60, 60, 59, 60, 60, 60, 61, 60, 60, 61, 60, 61, 61, 60, 61, 61, 61, 61, 62, 61, 61, 62, 62, 61, 62, 62, 62, 62, 62, 62, 63, 62, 63, 62, 63, 63, 63, 63, 63, 63, 63, 63, 64, 63, 64, 64, 64, 64, 64, 64, 64, 64, 65, 64, 65, 64, 65, 65, 65, 65, 65, 65, 66, 65, 66, 66, 65, 66, 66, 66, 66, 67, 66, 66, 67, 67, 66, 67, 67, 67, 68, 67, 67, 68, 67, 68, 68, 68, 68, 68, 68, 68, 69, 68, 69, 69, 68, 69, 70, 69, 69, 69, 70, 69, 70, 70, 70, 70, 70, 70, 71, 70, 71, 70, 71, 71, 71, 71, 72, 71, 71, 72, 72, 71, 72, 72, 73, 72, 72, 73, 72, 73, 73, 73, 73, 73, 73, 74, 73, 74, 74, 74, 74, 74, 74, 74, 75, 75, 74, 75, 75, 75, 75, 76, 75, 76, 76, 75, 76, 76, 77, 76, 76, 77, 77, 76, 77, 77, 78, 77, 77, 78, 78, 77, 78, 79, 78, 78, 79, 78, 79, 79, 79, 79, 79, 80, 79, 80, 79, 80, 80, 81, 80, 80, 81, 81, 81, 81, 81, 81, 81, 82, 82, 81, 82, 82, 83, 82, 82, 83, 83, 83, 83, 83, 83, 84, 84, 83, 84, 84, 85, 84, 84, 85, 85, 85, 85, 85, 85, 86, 86, 85, 86, 86, 87, 86, 87, 86, 87, 87, 87, 88, 87, 88, 88, 87, 88, 89, 88, 89, 88, 89, 89, 89, 90, 89, 90, 90, 90, 90, 90, 90, 91, 91, 91, 91, 91, 91, 92, 92, 91, 92, 93, 92, 93, 92, 93, 93, 93, 94, 93, 94, 94, 94, 94, 95, 94, 95, 95, 95, 95, 96, 95, 96, 96, 96, 96, 97, 97, 96, 97, 98, 97, 97, 98, 98, 98, 98, 99, 98, 99, 99, 99, 100, 99, 100, 100, 100, 100, 101, 101, 100, 101, 102, 101, 102, 102, 102, 102, 102, 103, 102, 103, 104, 103, 103, 104, 104, 104, 105, 104, 105, 105, 105, 105, 106, 106, 106, 106, 106, 107, 106, 107, 107, 108, 107, 108, 108, 108, 109, 109, 108, 109, 110, 109, 110, 110, 110, 110, 111, 111, 111, 111, 111, 112, 112, 112, 112, 113, 112, 113, 114, 113, 114, 113, 115, 114, 114, 115, 115, 115, 116, 116, 116, 116, 116, 117, 117, 117, 117, 118, 118, 118, 118, 118, 119, 119, 119, 120, 120, 120, 120, 120, 121, 121, 121, 122, 122, 121, 123, 122, 123, 123, 123, 124, 123, 124, 125, 124, 125, 125, 125, 126, 126, 126, 126, 127, 127, 127, 127, 128, 128, 128, 128, 129, 129, 130, 129, 130, 130, 131, 130, 131, 131, 132, 132, 132, 132, 133, 133, 133, 134, 133, 135, 134, 135, 135, 135, 135, 136, 136, 137, 136, 138, 137, 137, 138, 139, 138, 139, 139, 140, 139, 140, 141, 140, 141, 142, 141, 142, 143, 142, 143, 143, 144, 144, 144, 144, 145, 145, 146, 145, 147, 146, 147, 147, 147, 148, 148, 149, 148, 150, 149, 150, 150, 151, 150, 152, 151, 152, 152, 153, 153, 153, 154, 154, 154, 155, 155, 155, 156, 156, 156, 157, 158, 157, 158, 158, 159, 159, 160, 160, 160, 160, 161, 162, 161, 163, 162, 163, 163, 164, 164, 164, 165, 166, 165, 166, 167, 166, 168, 167, 168, 169, 169, 169, 170, 170, 170, 171, 171, 172, 172, 173, 173, 173, 174, 175, 174, 175, 176, 176, 177, 176, 178, 178, 178, 178, 179, 180, 180, 180, 181, 182, 182, 182, 183, 183, 183, 184, 185, 185, 186, 186, 186, 187, 187, 188, 189, 189, 189, 190, 190, 191, 191, 192, 192, 193, 193, 194, 194, 195, 195, 196, 197, 196, 198, 198, 198, 199, 199, 200, 201, 201, 201, 202, 203, 203, 204, 204, 205, 205, 206, 206, 207, 208, 208, 209, 209, 210, 210, 211, 211, 212, 213, 213, 214, 214, 215, 216, 216, 217, 217, 218, 218, 220, 219, 221, 221, 221, 222, 223, 224, 224, 224, 226, 225, 227, 227, 228, 228, 230, 229, 231, 231, 232, 232, 233, 234, 234, 235, 236, 236, 237, 238, 238, 240, 239, 241, 241, 242, 243, 243, 244, 245, 245, 246, 247, 248, 248, 249, 250, 250, 252, 252, 252, 254, 254, 255, 256, 256, 258, 258, 259, 259, 261, 261, 262, 262, 264, 264, 266, 266, 266, 268, 268, 269, 271, 270, 272, 273, 273, 274, 275, 276, 277, 278, 278, 280, 280, 281, 282, 283, 284, 284, 286, 286, 288, 288, 289, 290, 291, 292, 293, 294, 295, 295, 297, 298, 298, 300, 300, 302, 302, 303, 305, 305, 307, 307, 308, 310, 310, 312, 312, 314, 314, 316, 316, 318, 319, 320, 321, 321, 323, 325, 325, 326, 327, 329, 330, 330, 332, 333, 334, 335, 337, 337, 339, 340, 341, 342, 343, 345, 346, 347, 348, 349, 351, 352, 353, 354, 356, 356, 359, 359, 360, 362, 363, 365, 366, 367, 368, 370, 371, 372, 374, 375, 376, 378, 379, 381, 381, 384, 384, 387, 387, 389, 390, 392, 393, 395, 396, 398, 399, 401, 402, 403, 405, 407, 408, 410, 411, 413, 414, 416, 418, 419, 421, 422, 424, 426, 427, 429, 430, 433, 434, 435, 437, 439, 441, 443, 444, 446, 447, 450, 451, 453, 455, 456, 459, 460, 462, 464, 466, 468, 469, 472, 473, 476, 477, 479, 481, 484, 485, 487, 489, 491, 493, 496, 497, 499, 502, 503, 506, 508, 510, 512, 514, 516, 519, 521, 523, 525, 528, 529, 533, 534, 537, 538, 542, 543, 547, 548, 551, 553, 556, 558, 560, 563, 566, 568, 571, 573, 575, 579, 581, 583, 586, 589, 591, 594, 597, 600, 602, 605, 608, 611, 613, 616, 620, 622, 625, 627, 631, 634, 637, 639, 643, 646, 649, 652, 655, 658, 661, 665, 668, 671, 674, 677, 681, 684, 688, 690, 695, 697, 701, 705, 708, 711, 715, 719, 722, 726, 730, 733, 737, 740, 745, 748, 752, 756, 760, 764, 767, 772, 775, 780, 784, 788, 792, 796, 800, 805, 808, 814, 817, 822, 826, 831, 835, 840, 844, 849, 853, 858, 863, 867, 873, 877, 882, 887, 891, 897, 902, 907, 912, 917, 922, 928, 932, 939, 943, 949, 954, 960, 966, 971, 977, 982, 988, 994, 1000, 1006, 1012, 1017, 1024, 1030, 1036, 1043, 1048, 1056, 1061, 1068, 1074, 1081, 1088, 1094, 1102, 1107, 1115, 1122, 0}; // Le zéro à la fin indique que c'est la fin du tableau word volatile wCompte = 0; // Compte le nombre de répétitions d'une fréquence word volatile wInd = 0; // Indice de compteur de répétitions d'une fréquence // Pour "volatile", c.f. https://www.arduino.cc/en/pmwiki.php?n=Reference/Volatile // Les variables utilisées dans la fonction de gestion d'interruptions doivent être déclarées "volatile". // Cela impose que la variable est sauvegardée en RAM et non dans un registre. // Une variable sauvegardée dans un registre pourrait être modifiée accidentellement entre deux accès à la fonction. word volatile wT0 = 255; // Fixe le temps entre deux transitions byte volatile bEtat = 0; byte volatile bUp = 1; // Indique si la fréquence va en augmentant ou diminuant // (256 - wT0) * bFac * 0.0625 [us] = temps entre deux générations d'interruption du timer avant une transition // bFac = 1024 => detla_T_max = 256 * 1024 + 0.0625 [us] = 16.384 [ms] // bFac = 1024 => detla_T_min = 1024 + 0.0625 [us] = 0.064 [ms] // bT0L * bFac * 0.0625 [us] = temps entre deux transitions // (bT0L, bFac) = // ( 0, 8) => temps = (256 - 0) * 8 * 0.0625 = 0.000128 [s] --- ~ 3'906.25 [Hz] // (127, 8) => temps = (256 - 206) * 8 * 0.0625 = 0.000025 [s] --- ~ 7'751.94 [Hz] // (206, 8) => temps = (256 - 206) * 8 * 0.0625 = 0.000025 [s] --- ~20'000.00 [Hz] // (127, 8) => temps = (256 - 216) * 8 * 0.0625 = 0.000020 [s] --- ~25'000.00 [Hz] // Formules de conversions //======================== // Selon la valeur de TCCR2B, bFac varie selon le tableau suivant : // TCCR2B 2 3 4 5 6 7 // bFac 1 4 8 16 32 128 // dT [s] = bT0L * 2 * 8 * bFac * 62.5 * 10^(-9) // 62.5e-9 = 1 / 16'000'000 // Fréquence = 16'000'000 / (bT0L * 2 * 8 * bFac) // Fréquence = 1'000'000 / (bT0L * bFac) // // Pour Fréquence = Freq donnée : // bFac 128 32 16 8 4 1 // bT0L = Tronc(1000000 / (Freq * bFac) void setup() { //============ // Initialise le Timer 1 pour déclencher les interruptions après un certain temps cli(); // Désactive l'interruption globale // de Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf // Timer/Counter0 C.f. page 93 // Il gère les fonctions delay(), millis() et micros() // Lorsque le Timer 0 est désactivé, les 3 fonctions ci-dessus ne fonctionnent plus ! TIMSK0 = 0b00000000; // Désactive les interruptions dues au Timer/Counter0 C.f. p. 109 //TCCR0B = 0b00000000; // Désactive le Timer/Counter0 // Timer/Counter1 C.f. page 111 // Il est utilisé dans la bibliothèque de commande de servo-moteurs. TIMSK1 = 0b00000001; // Active les les interruptions dues au Timer/Counter1 C.f. p. 135 // Une interruption est générée lorsque le compteur TCNT1 passe de 65535 à 0 (overflow) TCCR1A = 0b00000000; // default. Bits : WGM11 = 0 et WGM10 = 0 TCCR1B = 0b00000001; // Clock soit 0.125 micro-s et WGM12 = 0 // Timer/Counter2 C.f. page 141 // Il est utilisé pour générer des tone() TIMSK2 = 0b00000000; // Désactive les les interruptions dues au Timer/Counter2 C.f. p. 157 //TIMSK2 = 0b00000001; // Active les les interruptions dues au Timer/Counter 2. // Une interruption est générée lorsque le compteur TCNT2 passe de 255 à 0 (overflow) // Fréquence = 8'000 [kHz] / (65536 - wT0 + 9) // wT0 = 65536 + 9 - 8'000 / Fréquence [kHz] // Fréquence min. = 8'000'000 / 65536 = 122.07 [Hz] wT0 = 0; // Frequ = 122.05 Hz. //wT0 = -7991; // Frequ = 1 kHz. //wT0 = -791; // Frequ = 10 kHz. //wT0 = -400; // Frequ = 20 kHz. Limite. La période est légèrement trop grande //wT0 = -391; // Frequ = 40 kHz. Avec la précision de l'oscilloscope. //wT0 = -200; // Frequ = 40 kHz. Limite. La période est légèrement trop grande //wT0 = -191; // Frequ = 40 kHz. Avec la précision de l'oscilloscope. //wT0 = -160; // Frequ = 50 kHz. Limite. La période est légèrement trop grande //wT0 = -151; // Frequ = 50 kHz. Avec la précision de l'oscilloscope. //wT0 = -121; // Frequ = 61.538 kHz // Proche de la fréquence maximale. //wT0 = -71; // Frequ = 100 kHz // Trop élevé, ne fonctionne pas //wT0 = -27; // Frequ = 1 MHz. // Trop élevé, ne fonctionne pas wT0 = 61547; wInd = 0; wCompte = awRepete[wInd]; // La fréquence va varier de ~2 kHz à ~25 kHz // Elle double en 10 secondes. sei(); // Active l'interruption globale pinMode(13, OUTPUT); //Serial.begin(9600); // Initialise la communication série à une haute vitesse. Cette vitesse est celle standard //Serial.begin(115200); // Initialise la communication série à une haute vitesse. Vitesse de communication plus élevée. } // setup void loop() { //=========== // Boucle principale // Augmentation progressive de la fréquence // L'augmentation progressive de la fréquence se fait par à-coups. // Cela s'entend aux hautes fréquences !!! Est-ce vrai ??? /* if ( ( (vTemps_delta > 0) && (vTemps >= vTemps_next_print) ) || ( (vTemps_delta < 0) && (vTemps <= vTemps_next_print) ) ) { vTemps_next_print += vTemps_delta * 2.0; // Affichage toutes les ... secondes. Serial.print(vTemps); Serial.print(" : "); Serial.print(vFreq); Serial.print(" "); Serial.print(bT0H_max_new); Serial.print(" "); Serial.print(bT0L_max_new); Serial.print(" "); Serial.print(bChanged_last); Serial.println(" "); } /* */ } // loop // routine d'interruption du timer 1 ISR (TIMER1_OVF_vect) { //===================== // TCNT1 = valeur du compteur, qui est compté de sa valeur initiale à 65535, puis repasse à 0. // Lorsqu'on arrive dans cette fonction de gestion d'interruption, TCNT1 = 0 // Le compteur TCNT1 est incrémenté de 1 chaque 0.0625 [us] * facteur défini dans TCCR1B. // Transition de l'état TCNT1 += wT0; // Fréquence = 8'000'000 / ( (65536+9 - wT0)) bEtat = 1 - bEtat; digitalWrite(13, bEtat); if (bUp) { // Fréquence montante wCompte -= 1; if (wCompte == 0) { // Augmentation de la fréquence. wT0++; // Passage à la fréquence suivante. wInd++; wCompte = awRepete[wInd]; if (wCompte == 0) { // Passe en fréquence descendante bUp = 0; wT0--; wInd--; wCompte = awRepete[wInd]; } } } else { // Fréquence descendante wCompte -= 1; if (wCompte == 0) { // Diminution de la fréquence. wT0--; // Passage à la fréquence précédente. wInd--; wCompte = awRepete[wInd]; if (wInd == 0) { // Passe en fréquence montante bUp = 1; wT0 = 61547; wInd = 0; wCompte = awRepete[wInd]; } } } } // ISRex0450_timer_1_interrupt.ino
/* ex0451_timer_1_interrupt_OCR1A.ino Exemple d'utilisation du timer 1 pour générer des interruptions. Utiliser le registre OCR1A pour générer les interruptions. L'utilisation du timer 1 va simplifier la gestion, car ce timer a un compteur sur 16 bits contrairement au timer 0 et 2 qui ont des compteurs sur 8 bits. La fréquence de clignotement de la LED pin 13 va augmenter avec le temps, puis diminuer et recommencer. La fréquence va varier de ~2 kHz à ~25 kHz. Elle double en 10 secondes. Suite de ex0450_timer_1_interrupt.ino. Autre approche, à chaque passage dans la fonction de gestion d'interruption, on change l'état de la pin 13 C'est une meilleure approche, qui utilise le registre OCR1A. ==========================================================*/ // Nombre de fois qu'il faut répéter la même fréquence // c.f. https://www.carnetdumaker.net/articles/reduire-lempreinte-memoire-dun-programme-arduino-avec-progmem/#la-solution-progmem //const word PROGMEM awRepete[] = { word volatile awRepete[] = { 14, 14, 14, 15, 14, 15, 15, 14, 15, 15, 14, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 15, 15, 15, 16, 15, 15, 16, 15, 16, 15, 16, 15, 16, 15, 16, 16, 15, 16, 16, 16, 15, 16, 16, 16, 16, 16, 16, 16, 16, 17, 16, 16, 16, 16, 17, 16, 17, 16, 16, 17, 16, 17, 17, 16, 17, 17, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 17, 17, 18, 17, 17, 18, 18, 17, 18, 17, 18, 18, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 18, 18, 19, 18, 19, 18, 19, 18, 19, 18, 19, 19, 19, 19, 18, 19, 19, 19, 19, 19, 20, 19, 19, 19, 20, 19, 19, 20, 19, 20, 19, 20, 20, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 20, 20, 21, 20, 21, 21, 20, 21, 21, 20, 21, 21, 21, 21, 21, 21, 21, 21, 22, 21, 21, 22, 21, 22, 21, 22, 21, 22, 22, 22, 21, 22, 22, 22, 23, 22, 22, 22, 22, 23, 22, 23, 22, 23, 22, 23, 23, 23, 23, 22, 23, 23, 24, 23, 23, 23, 24, 23, 23, 24, 24, 23, 24, 24, 23, 24, 24, 24, 24, 24, 25, 24, 24, 25, 24, 25, 24, 25, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 25, 25, 26, 26, 25, 26, 26, 25, 26, 26, 26, 26, 27, 26, 26, 27, 26, 27, 26, 27, 27, 27, 26, 27, 28, 27, 27, 27, 27, 28, 27, 28, 28, 27, 28, 28, 28, 28, 28, 28, 29, 28, 28, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 30, 29, 30, 29, 30, 30, 29, 30, 30, 30, 31, 30, 30, 31, 30, 31, 31, 30, 31, 31, 31, 32, 31, 31, 32, 31, 32, 32, 31, 32, 32, 33, 32, 32, 32, 33, 33, 32, 33, 33, 33, 33, 33, 34, 33, 33, 34, 34, 34, 33, 34, 35, 34, 34, 35, 34, 35, 35, 34, 35, 35, 36, 35, 35, 36, 36, 35, 36, 36, 36, 36, 37, 36, 37, 36, 37, 37, 37, 37, 37, 38, 37, 38, 38, 38, 38, 38, 38, 38, 39, 38, 39, 39, 39, 39, 39, 40, 39, 40, 40, 40, 40, 40, 40, 41, 40, 41, 41, 41, 41, 41, 42, 41, 42, 42, 42, 42, 42, 42, 43, 43, 42, 43, 44, 43, 43, 44, 44, 44, 44, 44, 44, 45, 44, 45, 45, 45, 46, 45, 46, 46, 46, 46, 46, 46, 47, 47, 47, 47, 47, 48, 47, 48, 48, 48, 49, 48, 49, 49, 49, 49, 50, 49, 50, 50, 50, 50, 51, 51, 51, 51, 51, 52, 51, 52, 52, 53, 52, 53, 53, 53, 53, 54, 53, 54, 54, 55, 54, 55, 55, 55, 56, 55, 56, 56, 56, 57, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59, 60, 59, 60, 61, 60, 61, 61, 61, 62, 62, 62, 62, 62, 63, 63, 64, 63, 64, 64, 65, 64, 65, 66, 65, 66, 66, 66, 67, 67, 67, 68, 67, 69, 68, 69, 69, 69, 70, 69, 71, 70, 71, 71, 72, 72, 72, 72, 73, 73, 73, 74, 74, 75, 74, 76, 75, 76, 76, 76, 77, 78, 77, 78, 78, 79, 79, 79, 80, 80, 81, 81, 81, 82, 82, 82, 83, 84, 83, 84, 85, 85, 85, 86, 86, 86, 87, 88, 88, 88, 89, 89, 89, 90, 91, 91, 91, 92, 93, 92, 94, 93, 95, 94, 96, 95, 96, 97, 97, 98, 98, 99, 99, 100, 100, 101, 102, 102, 102, 103, 104, 104, 104, 106, 105, 107, 107, 107, 108, 109, 109, 110, 111, 111, 112, 112, 113, 114, 114, 115, 116, 116, 117, 118, 118, 119, 120, 120, 121, 122, 122, 123, 124, 125, 125, 126, 127, 128, 128, 129, 130, 131, 131, 133, 133, 134, 134, 136, 136, 137, 138, 139, 140, 141, 141, 143, 143, 144, 146, 146, 147, 148, 149, 149, 151, 152, 153, 154, 155, 155, 157, 158, 159, 160, 161, 163, 163, 164, 166, 166, 168, 169, 170, 171, 173, 173, 175, 176, 178, 178, 180, 181, 182, 184, 185, 187, 187, 189, 191, 191, 194, 194, 196, 198, 199, 200, 202, 204, 205, 207, 208, 210, 211, 213, 215, 216, 218, 220, 221, 223, 225, 227, 228, 231, 232, 234, 236, 238, 240, 242, 244, 245, 248, 250, 252, 254, 257, 258, 261, 263, 265, 267, 270, 272, 275, 277, 279, 282, 284, 287, 290, 292, 294, 298, 300, 303, 305, 309, 311, 315, 317, 320, 323, 327, 329, 333, 336, 339, 342, 346, 349, 353, 356, 360, 364, 367, 370, 375, 378, 383, 386, 390, 395, 398, 403, 407, 412, 416, 420, 425, 430, 434, 439, 444, 449, 454, 460, 464, 470, 475, 480, 487, 492, 497, 504, 510, 515, 522, 529, 534, 542, 548, 555, 561, 569, 576, 584, 591, 598, 607, 614, 622, 631, 639, 648, 656, 666, 674, 684, 693, 703, 713, 723, 734, 744, 755, 766, 777, 788, 801, 812, 825, 838, 850, 864, 877, 891, 905, 920, 934, 950, 965, 981, 998, 1015, 1031, 1050, 1067, 1087, 1105, 1125, 1145, 1165, 1187, 1209, 1231, 1255, 1278, 1302, 1327, 1354, 1380, 1407, 1435, 1465, 1495, 1525, 1557, 1591, 1624, 1659, 1695, 1733, 1771, 1811, 1853, 1895, 1939, 1985, 2033, 2082, 2132, 2186, 2240, 2297, 2356, 2418, 2481, 2548, 2617, 2689, 2764, 2842, 2923, 3009, 3097, 3190, 3287, 3389, 3495, 3606, 3723, 3846, 3974, 4110, 4252, 4402, 4560, 4726, 4903, 5088, 5285, 5493, 5713, 5949, 6197, 6462, 6745, 7047, 7368, 7713, 8083, 8480, 0 }; // Le zéro à la fin indique que c'est la fin du tableau word volatile wCompte = 0; // Compte le nombre de répétitions d'une fréquence word volatile wInd = 0; // Indice de compteur de répétitions d'une fréquence // Pour "volatile", c.f. https://www.arduino.cc/en/pmwiki.php?n=Reference/Volatile // Les variables utilisées dans la fonction de gestion d'interruption doivent être déclarée "volatile". // Cela impose que la variable est sauvegardée en RAM et non dans un registre. // Une variable sauvegardée dans un registre pourrait être modifiée accidentellement entre deux accès à la fonction. word volatile wT0 = 255; // Fixe le temps entre deux transitions byte volatile bEtat = 0; byte volatile bUp = 1; // Indique si la fréquence va en augmentant ou diminuant // (256 - wT0) * bFac * 0.0625 [us] = temps entre deux générations d'interruption du timer avant une transition // bFac = 1024 => detla_T_max = 256 * 1024 + 0.0625 [us] = 16.384 [ms] // bFac = 1024 => detla_T_min = 1024 + 0.0625 [us] = 0.064 [ms] // bT0L * bFac * 0.0625 [us] = temps entre deux transitions // (bT0L, bFac) = // ( 0, 8) => temps = (256 - 0) * 8 * 0.0625 = 0.000128 [s] --- ~ 3'906.25 [Hz] // (127, 8) => temps = (256 - 206) * 8 * 0.0625 = 0.000025 [s] --- ~ 7'751.94 [Hz] // (206, 8) => temps = (256 - 206) * 8 * 0.0625 = 0.000025 [s] --- ~20'000.00 [Hz] // (127, 8) => temps = (256 - 216) * 8 * 0.0625 = 0.000020 [s] --- ~25'000.00 [Hz] // Formules de conversions //======================== // Selon la valeur de TCCR2B, bFac varie selon le tableau suivant : // TCCR2B 2 3 4 5 6 7 // bFac 1 4 8 16 32 128 // dT [s] = bT0L * 2 * 8 * bFac * 62.5 * 10^(-9) // 62.5e-9 = 1 / 16'000'000 // Fréquence = 16'000'000 / (bT0L * 2 * 8 * bFac) // Fréquence = 1'000'000 / (bT0L * bFac) // // Pour Fréquence = Freq donnée : // bFac 128 32 16 8 4 1 // bT0L = Tronc(1000000 / (Freq * bFac) void setup() { //============ // Initialise le Timer 2 pour déclencher les interruptions après un certain temps cli(); // Désactive l'interruption globale TCCR1A = 0b11000000; // default. Bits : (WGM11 = 0 ; WGM10 = 0) // de Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf // Timer/Counter0 C.f. page 93 // Il gère les fonctions delay(), millis() et micros() // Lorsque le Timer 0 est désactivé, les 3 fonctions ci-dessus ne fonctionnent plus ! TIMSK0 = 0b00000000; // Désactive les interruptions dues au Timer/Counter0 C.f. p. 109 //TCCR0B = 0b00000000; // Désactive le Timer/Counter0 // Timer/Counter1 C.f. page 111 // Il est utilisé dans la bibliothèque de commande de servo-moteurs. //TIMSK1 = 0b00000001; // Active les les interruptions dues au Timer/Counter1 C.f. p. 135 // Une interruption est générée lorsque le compteur TCNT1 passe de 65535 à 0 (overflow) TIMSK1 = 0b00000010; // Active les les interruptions dues au Timer/Counter1 C.f. p. 135 // Une interruption est générée lorsque le compteur TCNT1 égale OCR1A. // Timer/Counter2 C.f. page 141 // Il est utilisé pour générer des tone() TIMSK2 = 0b00000000; // Désactive les les interruptions dues au Timer/Counter2 C.f. p. 157 //TIMSK2 = 0b00000001; // Active les les interruptions dues au Timer/Counter 2. // Une interruption est générée lorsque le compteur TCNT2 passe de 255 à 0 (overflow) //TCCR1B = 0b00001001; // Clock soit fact * 0.125 micro-s et (WGM13 = 0 ; WGM12 = 1) fact = 1 TCCR1B = 0b00001010; // Clock soit fact * 0.125 micro-s et (WGM13 = 0 ; WGM12 = 1) fact = 8 //TCCR1B = 0b00001011; // Clock soit fact * 0.125 micro-s et (WGM13 = 0 ; WGM12 = 1) fact = 64 //TCCR1B = 0b00001100; // Clock soit fact * 0.125 micro-s et (WGM13 = 0 ; WGM12 = 1) fact = 256 //TCCR1B = 0b00001101; // Clock soit fact * 0.125 micro-s et (WGM13 = 0 ; WGM12 = 1) fact = 1024 // Fréquence = 8'000 [kHz] / (wT0 * fact) // Période = wT0 * fact * 0.125 [us] // wT0 = 8'000 / (fact * Fréquence [kHz]) // wT0 = Période / (fact * 0.125 [us]) // Une fréquence supérieure à ~60 [kHz] est non atteignable. wT0 = 4000; wInd = 0; wCompte = 1; OCR1A = wT0-1; // Limite TOP du compteur TCNT1 avant son retour à 0. // La fréquence va varier de ~2 kHz à ~25 kHz // Elle double en 10 secondes. sei(); // Active l'interruption globale pinMode(13, OUTPUT); //Serial.begin(9600); // Initialise la communication série à une haute vitesse. Cette vitesse est celle standard //Serial.begin(115200); // Initialise la communication série à une haute vitesse. Vitesse de communication plus élevée. } // setup void loop() { //=========== // Boucle principale /* if ( ( (vTemps_delta > 0) && (vTemps >= vTemps_next_print) ) || ( (vTemps_delta < 0) && (vTemps <= vTemps_next_print) ) ) { vTemps_next_print += vTemps_delta * 2.0; // Affichage toutes les ... secondes. Serial.print(vTemps); Serial.print(" : "); Serial.print(vFreq); Serial.print(" "); Serial.print(bT0H_max_new); Serial.print(" "); Serial.print(bT0L_max_new); Serial.print(" "); Serial.print(bChanged_last); Serial.println(" "); } /* */ } // loop // routine d'interruption du timer 1, lorsque le registre OCR1A est utilisé. ISR (TIMER1_COMPA_vect) { //======================= // TCNT1 = valeur du compteur, qui est compté de 0 à OCR1A, puis repasse à 0. // Il est délicat d'utiliser la valeur du registre TCNT1, car elle augmente rapidement sans arrêt. // Le compteur TCNT1 est incrémenté de 1 chaque 0.0625 [us] * facteur défini dans TCCR1B. // Transition de l'état bEtat = 1 - bEtat; digitalWrite(13, bEtat); wCompte -= 1; if (bUp) { // Fréquence montante if (wCompte == 0) { // Augmentation de la fréquence. OCR1A--; // Passage à la fréquence suivante. if (OCR1A > 3000) wCompte = 1; else if (OCR1A > 2400) wCompte = 2; else if (OCR1A > 2000) wCompte = 3; else if (OCR1A > 1800) wCompte = 4; else if (OCR1A > 1600) wCompte = 5; else if (OCR1A > 1500) wCompte = 6; else if (OCR1A > 1400) wCompte = 7; else if (OCR1A > 1000) wCompte = 10; // Féqu < 1000 [Hz] else { wInd++; wCompte = awRepete[wInd]; } //Serial.println(OCR1A); if (wCompte == 0) { // Passe en fréquence descendante bUp = 0; OCR1A++; wInd--; wCompte = awRepete[wInd]; } } } else { // Fréquence descendante if (wCompte == 0) { // Diminution de la fréquence. OCR1A++; // Passage à la fréquence précédente. if (wInd > 0) { wInd--; wCompte = awRepete[wInd]; } else if (OCR1A <= 1400) wCompte = 10; else if (OCR1A <= 1500) wCompte = 7; else if (OCR1A <= 1600) wCompte = 6; else if (OCR1A <= 1800) wCompte = 5; else if (OCR1A <= 2000) wCompte = 4; else if (OCR1A <= 2400) wCompte = 3; else if (OCR1A <= 3000) wCompte = 2; else wCompte = 1; if (OCR1A == wT0) { // Passe en fréquence montante bUp = 1; OCR1A = wT0-1; wInd = 0; wCompte = 1; } } } } // ISR
/* ex0452_timer_1_interrupt_OCR1A_PROGMEM.ino Exemple d'utilisation du timer 1 pour générer des interruption. Utilisation de "PROGMEM" pour stocker des données dans la mémoire du programme Utiliser le registre OCR1A pour générer les interruptions. L'utilisation du timer 1 va simplifier la gestion, car ce timer a un compteur sur 16 bits contrairement au timer 0 et 2 qui ont des compteurs sur 8 bits. La fréquence de clignotement de la LED pin 13 va augmenter avec le temps. Avec affichage dans le terminal série de la fréquence, qui augmente de 250 [Hz] à 25'000 [Hz], puis diminue. Brancher la pin 13 sur un haut-parleur, pour entendre la fréquence générée et la limite de l'inaudible. Cette limite est plus basse pour des personnes âgées. Cette affichage génère des "tac" sur le haut-parleur, ce qui perturbe le son généré. Suite de ex040x et ex045x À chaque passage dans la fonction de gestion d'interruption, on change l'état de la pin 13 ==========================================================*/ #include <avr/pgmspace.h> // Pour stocker des tableaux non modifiables dans l'espace mémoire du programme // c.f. https://www.carnetdumaker.net/articles/reduire-lempreinte-memoire-dun-programme-arduino-avec-progmem/#la-solution-progmem // C.f. page 589 du livre : "Arduino Cookbook" // PROGMEM indique de stocker le tableau en mémoire du programme // wCompte = pgm_read_word(&awRepete[wInd]); permet de lire le contenu du tableau. // Nombre de fois qu'il faut répéter la même fréquence const word awRepete[] PROGMEM = { 4, 5, 4, 5, 4, 5, 4, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 5, 4, 5, 4, 5, 5, 4, 5, 5, 4, 5, 4, 5, 5, 4, 5, 5, 4, 5, 5, 4, 5, 5, 5, 4, 5, 5, 4, 5, 5, 5, 4, 5, 5, 5, 4, 5, 5, 5, 4, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 6, 5, 5, 5, 6, 5, 5, 5, 6, 5, 5, 5, 6, 5, 5, 6, 5, 5, 6, 5, 5, 6, 5, 5, 6, 5, 5, 6, 5, 6, 5, 5, 6, 5, 6, 5, 6, 5, 6, 5, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 6, 5, 6, 5, 6, 5, 6, 6, 5, 6, 5, 6, 6, 5, 6, 6, 5, 6, 6, 5, 6, 6, 5, 6, 6, 5, 6, 6, 6, 5, 6, 6, 6, 5, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 7, 6, 6, 6, 7, 6, 6, 7, 6, 6, 7, 6, 6, 7, 6, 6, 7, 6, 6, 7, 6, 7, 6, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 7, 6, 7, 6, 7, 7, 6, 7, 7, 6, 7, 7, 6, 7, 7, 6, 7, 7, 7, 6, 7, 7, 7, 6, 7, 7, 7, 7, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 7, 7, 7, 7, 7, 8, 7, 7, 7, 8, 7, 7, 7, 8, 7, 7, 8, 7, 7, 8, 7, 8, 7, 7, 8, 7, 8, 7, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 8, 7, 8, 7, 8, 8, 7, 8, 7, 8, 8, 7, 8, 8, 8, 7, 8, 8, 8, 7, 8, 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 8, 8, 8, 8, 8, 8, 8, 9, 8, 8, 8, 8, 9, 8, 8, 9, 8, 8, 8, 9, 8, 8, 9, 8, 9, 8, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 9, 8, 9, 8, 9, 9, 8, 9, 9, 9, 8, 9, 9, 9, 8, 9, 9, 9, 9, 9, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 9, 9, 9, 9, 10, 9, 9, 9, 10, 9, 9, 9, 10, 9, 9, 10, 9, 10, 9, 10, 9, 9, 10, 9, 10, 9, 10, 10, 9, 10, 9, 10, 10, 9, 10, 10, 9, 10, 10, 10, 9, 10, 10, 10, 10, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 10, 10, 10, 10, 11, 10, 10, 10, 11, 10, 10, 11, 10, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 11, 10, 11, 10, 11, 11, 11, 10, 11, 11, 11, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 11, 11, 11, 12, 11, 11, 12, 11, 11, 12, 11, 11, 12, 11, 12, 11, 12, 12, 11, 12, 11, 12, 12, 11, 12, 12, 11, 12, 12, 12, 12, 12, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 12, 12, 12, 12, 13, 12, 12, 12, 13, 12, 13, 12, 12, 13, 12, 13, 12, 13, 13, 12, 13, 12, 13, 13, 12, 13, 13, 13, 13, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 13, 13, 13, 14, 13, 13, 14, 13, 13, 14, 13, 14, 13, 14, 13, 14, 14, 13, 14, 14, 13, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 14, 14, 14, 15, 14, 14, 15, 14, 14, 15, 14, 15, 14, 15, 14, 15, 15, 14, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 15, 15, 15, 16, 15, 15, 16, 15, 16, 15, 16, 15, 16, 16, 15, 16, 16, 16, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 16, 16, 16, 17, 16, 16, 17, 16, 17, 16, 17, 16, 17, 17, 16, 17, 17, 17, 17, 17, 16, 17, 17, 17, 18, 17, 17, 17, 17, 17, 18, 17, 17, 18, 17, 18, 17, 18, 18, 17, 18, 18, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 19, 19, 18, 19, 19, 19, 18, 19, 19, 19, 19, 19, 19, 19, 20, 19, 19, 19, 20, 19, 20, 19, 20, 19, 20, 20, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 20, 20, 21, 20, 21, 20, 21, 21, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 21, 21, 22, 21, 22, 22, 21, 22, 22, 21, 22, 22, 22, 22, 22, 22, 23, 22, 22, 23, 22, 22, 23, 23, 22, 23, 23, 22, 23, 23, 23, 23, 23, 24, 23, 23, 23, 24, 23, 24, 23, 24, 24, 23, 24, 24, 24, 24, 24, 24, 24, 25, 24, 24, 25, 24, 25, 25, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 25, 25, 26, 25, 26, 26, 25, 26, 26, 26, 26, 26, 27, 26, 26, 27, 26, 27, 26, 27, 27, 26, 27, 27, 27, 27, 28, 27, 27, 28, 27, 28, 27, 28, 28, 28, 28, 28, 28, 28, 29, 28, 28, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 30, 29, 30, 30, 29, 30, 30, 30, 30, 31, 30, 30, 31, 30, 31, 31, 30, 31, 31, 31, 32, 31, 31, 32, 31, 32, 32, 31, 32, 32, 32, 33, 32, 32, 33, 32, 33, 33, 33, 33, 33, 33, 33, 34, 33, 34, 34, 33, 34, 34, 34, 35, 34, 34, 35, 35, 34, 35, 35, 35, 35, 36, 35, 36, 35, 36, 36, 36, 36, 36, 37, 36, 37, 36, 37, 37, 37, 37, 37, 38, 37, 38, 38, 37, 38, 39, 38, 38, 39, 38, 39, 39, 39, 39, 39, 40, 39, 40, 40, 39, 40, 41, 40, 40, 41, 41, 41, 41, 41, 41, 41, 42, 42, 41, 42, 43, 42, 42, 43, 42, 43, 43, 43, 44, 43, 44, 44, 43, 44, 45, 44, 45, 44, 45, 45, 45, 46, 45, 46, 45, 46, 47, 46, 46, 47, 47, 47, 47, 47, 48, 47, 48, 48, 48, 49, 48, 49, 49, 49, 49, 49, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 53, 53, 54, 54, 54, 55, 54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59, 59, 60, 60, 60, 61, 61, 61, 61, 62, 61, 62, 63, 62, 63, 63, 63, 64, 64, 64, 64, 65, 65, 65, 66, 66, 66, 66, 67, 67, 67, 67, 68, 68, 69, 68, 70, 69, 69, 70, 71, 70, 71, 71, 72, 71, 73, 72, 73, 73, 73, 74, 74, 75, 74, 75, 76, 76, 76, 76, 77, 77, 78, 78, 78, 79, 79, 79, 80, 80, 81, 81, 81, 82, 82, 82, 83, 83, 84, 84, 85, 84, 86, 85, 87, 86, 87, 88, 87, 89, 88, 90, 89, 90, 91, 91, 91, 92, 92, 93, 93, 94, 95, 94, 95, 96, 96, 97, 97, 98, 98, 99, 99, 100, 100, 101, 101, 102, 103, 103, 103, 105, 104, 105, 106, 107, 107, 107, 108, 109, 109, 110, 111, 111, 112, 112, 113, 114, 114, 115, 116, 116, 117, 118, 118, 119, 119, 121, 121, 122, 122, 123, 124, 125, 125, 126, 127, 128, 128, 129, 130, 131, 131, 132, 134, 133, 135, 136, 136, 137, 138, 139, 140, 141, 141, 143, 143, 144, 145, 147, 147, 148, 148, 150, 151, 152, 153, 154, 154, 156, 157, 158, 159, 160, 161, 162, 164, 164, 166, 166, 168, 169, 170, 171, 173, 173, 175, 176, 177, 179, 180, 181, 182, 184, 185, 186, 188, 189, 191, 191, 194, 194, 196, 198, 199, 200, 202, 204, 205, 206, 209, 209, 212, 213, 214, 217, 218, 219, 222, 223, 225, 227, 228, 231, 232, 234, 236, 238, 240, 242, 243, 246, 248, 250, 252, 254, 257, 258, 261, 263, 265, 267, 270, 272, 275, 277, 279, 282, 284, 287, 289, 293, 294, 298, 300, 303, 305, 309, 311, 314, 318, 320, 323, 327, 329, 333, 336, 339, 342, 346, 349, 353, 356, 360, 363, 367, 371, 375, 378, 383, 386, 390, 395, 398, 403, 407, 412, 416, 420, 425, 430, 434, 439, 444, 449, 454, 459, 465, 470, 475, 480, 487, 491, 498, 504, 509, 516, 522, 529, 534, 542, 548, 555, 561, 569, 576, 584, 591, 598, 607, 614, 622, 631, 639, 648, 656, 665, 675, 684, 693, 703, 713, 723, 734, 744, 754, 766, 777, 789, 801, 812, 825, 838, 850, 864, 877, 891, 905, 919, 935, 950, 965, 981, 998, 1015, 1031, 1050, 1067, 1086, 1106, 1125, 1145, 1165, 1187, 1209, 1231, 1254, 1278, 1303, 1327, 1353, 1380, 1408, 1435, 1465, 1494, 1526, 1557, 1590, 1624, 1660, 1695, 1733, 1771, 1811, 1853, 1895, 1939, 1985, 2033, 2081, 2133, 2186, 2240, 2297, 2356, 2418, 2481, 2548, 2617, 2689, 2764, 2842, 2923, 3008, 3098, 3190, 3287, 3389, 3495, 3606, 3723, 3846, 3974, 4110, 4252, 4402, 4560, 4726, 4902, 5089, 5284, 5493, 5714, 5948, 6198, 6462, 6745, 7047, 7368, 7713, 8083, 8479, 0 }; // Le zéro à la fin indique que c'est la fin du tableau word volatile wCompte = 0; // Compte le nombre de répétitions d'une fréquence word volatile wInd = 0; // Indice de compteur de répétitions d'une fréquence // Pour "volatile", c.f. https://www.arduino.cc/en/pmwiki.php?n=Reference/Volatile // Les variables utilisées dans la fonction de gestion d'interruption doivent être déclarée "volatile". // Cela impose que la variable est sauvegardée en RAM et non dans un registre. // Une variable sauvegardée dans un registre pourrait être modifiée accidentellement entre deux accès à la fonction. word volatile wT0 = 4000; // Fixe le temps entre deux transitions, au départ. byte volatile bEtat = 0; // État de la pin 13 byte volatile bUp = 1; // Indique si la fréquence va en augmentant (1) ou diminuant (0) byte bEtat0 = 0; // Pour détecter les transitions d'état dans la boucle principale. word wTempsL = 0; // Temps modulo 65536 en fact * 0.0625 [us] word wTempsH = 0; // Temps / 65536 en fact * 0.0625 [us] // = temps en multiple de 32.767 [ms], pour fact == 8. word wTmp = 0; // Temporaire // Formules de conversions //======================== // Selon la valeur de TCCR1B, bFac varie selon le tableau suivant : // TCCR1B 1 2 3 4 5 // bFac 1 8 64 256 1024 // Temps entre deux transistions = (OCR1A+1) * bFac * 62.5 * 10^(-9) [s] // 62.5e-9 = 1 / 16'000'000 // La période est le double du temps entre deux transistions // Fréquence = 8'000'000 [Hz] / ((OCR1A+1) * bFac) // // Pour Fréquence = Freq donnée : // OCR1A = -1 + Tronc(16'000'000 / (Freq * bFac) // Une fréquence supérieure à ~60 [kHz] est non atteignable. void setup() { //============ // Initialise le Timer 1 pour déclencher les interruptions après temps défini dans OCR1A cli(); // Désactive l'interruption globale // de Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf // Timer/Counter0 C.f. page 93 // Il gère les fonctions delay(), millis() et micros() // Lorsque le Timer 0 est désactivé, les 3 fonctions ci-dessus ne fonctionnent plus ! TIMSK0 = 0b00000000; // Désactive les interruptions dues au Timer/Counter0 C.f. p. 109 //TCCR0B = 0b00000000; // Désactive le Timer/Counter0 // Timer/Counter2 C.f. page 141 // Il est utilisé pour générer des tone() TIMSK2 = 0b00000000; // Désactive les les interruptions dues au Timer/Counter2 C.f. p. 157 //TIMSK2 = 0b00000001; // Active les les interruptions dues au Timer/Counter 2. // Désavticé : une interruption est générée lorsque le compteur TCNT2 passe de 255 à 0 (overflow) // Timer/Counter1 C.f. page 111 // Il est utilisé dans la bibliothèque de commande de servo-moteurs. //TIMSK1 = 0b00000001; // Active les les interruptions dues au Timer/Counter1 C.f. p. 135 // Une interruption est générée lorsque le compteur TCNT1 passe de 65535 à 0 (overflow) TIMSK1 = 0b00000010; // Active les les interruptions dues au Timer/Counter1 C.f. p. 135 // Lorsque le compteur TCNT1 égale OCR1A, ( car WGM1(3:0) = 0b0100 == 4 ) // TCNT1 passe à 0. // et une interruption est générée. TCCR1A = 0b00000000; // default. Bits : COM1A1=0 ; COM1A0=0 ; COM1B1=0 ; COM1B0=0 ; / ; / ; WGM11=0 ; WGM10=0, donc pas de PWM. //TCCR1B = 0b00001001; // (WGM13 = 0 ; WGM12 = 1) fact = 1 TCCR1B = 0b00001010; // (WGM13 = 0 ; WGM12 = 1) fact = 8 //TCCR1B = 0b00001011; // (WGM13 = 0 ; WGM12 = 1) fact = 64 //TCCR1B = 0b00001100; // (WGM13 = 0 ; WGM12 = 1) fact = 256 //TCCR1B = 0b00001101; // (WGM13 = 0 ; WGM12 = 1) fact = 1024 wT0 = 4000; wInd = 0; wCompte = 1; OCR1A = wT0-1; // Limite TOP du compteur TCNT1 avant son retour à 0. // La fréquence va varier de 250 [Hz] à ~25 kHz // Elle double en 10 secondes. sei(); // Active l'interruption globale pinMode(13, OUTPUT); //Serial.begin(9600); // Initialise la communication série vitesse par défaut. Serial.begin(115200); // Initialise la communication série à une haute vitesse. } // setup void loop() { //=========== // Boucle principale. Tout se passe dans la routine de gestion d'interruption. if (bEtat != bEtat0) { // Il y a eu une transistion d'état, incrémente le temps en conséquence. bEtat0 = bEtat; wTmp = wTempsL; wTempsL = wTempsL + OCR1A+1; // Temps en fact * 0.0625 [us] == 0.5 [us] if (wTempsL < wTmp) { // Une boucle de temps a été faite wTempsH++; if (wTempsH % 16 == 0) { // Affichage d'information // F("texte") fait en sorte que le texte soit stocké dans la page programme et non en mémoire des variables. Serial.print(F("Temps = ")); Serial.print(wTempsH * 0.032768); Serial.print(F(" : Frequ = ")); Serial.print(1000.0 / (OCR1A+1)); Serial.println(" "); } } } } // loop // routine d'interruption du timer 1, lorsque le registre OCR1A est utilisé. ISR (TIMER1_COMPA_vect) { //======================= // TCNT1 = valeur du compteur, qui est compté de 0 à OCR1A, puis repasse à 0. // Lorsqu'on arrive dans cette fonction de gestion d'interruption, TCNT1 = 0 // Le compteur TCNT1 est incrémenté de 1 chaque 0.0625 [us] * facteur défini dans TCCR1B. // Transition de l'état bEtat = 1 - bEtat; digitalWrite(13, bEtat); wCompte -= 1; if (bUp) { // Fréquence montante if (wCompte == 0) { // Augmentation de la fréquence. OCR1A--; // Passage à la fréquence suivante. if (OCR1A > 3000) wCompte = 1; else if (OCR1A > 2400) wCompte = 2; else if (OCR1A > 2000) wCompte = 3; else if (OCR1A > 1800) wCompte = 4; else { // OCR1A <= 1800 fréque ~= 555 [Hz] wCompte = 2*pgm_read_word(&awRepete[wInd]); wInd++; } //Serial.println(OCR1A); if (wCompte == 0) { // Passe en fréquence descendante bUp = 0; OCR1A++; wInd--; wCompte = pgm_read_word(&awRepete[wInd]); } } } else { // Fréquence descendante if (wCompte == 0) { // Diminution de la fréquence. OCR1A++; // Passage à la fréquence précédente. if (wInd > 0) { wInd--; wCompte = 2*pgm_read_word(&awRepete[wInd]); } else if (OCR1A <= 1800) wCompte = 5; else if (OCR1A <= 2000) wCompte = 4; else if (OCR1A <= 2400) wCompte = 3; else if (OCR1A <= 3000) wCompte = 2; else wCompte = 1; if (OCR1A == wT0) { // Passe en fréquence montante bUp = 1; OCR1A = wT0-1; wInd = 0; wCompte = 1; } } } } // ISRex0452_timer_1_interrupt_OCR1A_PROGMEM.ino
/* ex0453_timer_1_interrupt_OCR1A_PROGMEM_LED.ino Exemple d'utilisation du timer 1 pour générer des interruption. Utilisation de "PROGMEM" pour stocker des données dans la mémoire du programme Utiliser le registre OCR1A pour générer les interruptions. L'utilisation du timer 1 va simplifier la gestion, car ce timer a un compteur sur 16 bits contrairement au timer 0 et 2 qui ont des compteurs sur 8 bits. La fréquence de clignotement de la LED pin 13 va augmenter avec le temps. Avec affichage dans l'afficheur LED de la fréquence, qui augmente de 250 [Hz] à 25'000 [Hz], puis diminue. Brancher la pin 13 sur un haut-parleur, pour entendre la fréquence générée et la limite de l'inaudible. Cette limite est plus basse pour des personnes âgées. Cette affichage génère des légers "tac" sur le haut-parleur, ce qui perturbe un peu le son généré. Suite de ex0452 À chaque passage dans la fonction de gestion d'interruption, on change l'état de la pin 13 ==========================================================*/ #include <avr/pgmspace.h> // Pour stocker des tableaux non modifiables dans l'espace mémoire du programme // c.f. https://www.carnetdumaker.net/articles/reduire-lempreinte-memoire-dun-programme-arduino-avec-progmem/#la-solution-progmem // C.f. page 589 du livre : "Arduino Cookbook" // PROGMEM indique de stocker le tableau en mémoire du programme // wCompte = pgm_read_word(&awRepete[wInd]); permet de lire le contenu du tableau. // Nombre de fois qu'il faut répéter la même fréquence const word awRepete[] PROGMEM = { 4, 5, 4, 5, 4, 5, 4, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 5, 4, 5, 4, 5, 5, 4, 5, 5, 4, 5, 4, 5, 5, 4, 5, 5, 4, 5, 5, 4, 5, 5, 5, 4, 5, 5, 4, 5, 5, 5, 4, 5, 5, 5, 4, 5, 5, 5, 4, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 6, 5, 5, 5, 6, 5, 5, 5, 6, 5, 5, 5, 6, 5, 5, 6, 5, 5, 6, 5, 5, 6, 5, 5, 6, 5, 5, 6, 5, 6, 5, 5, 6, 5, 6, 5, 6, 5, 6, 5, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 6, 5, 6, 5, 6, 5, 6, 6, 5, 6, 5, 6, 6, 5, 6, 6, 5, 6, 6, 5, 6, 6, 5, 6, 6, 5, 6, 6, 6, 5, 6, 6, 6, 5, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 7, 6, 6, 6, 7, 6, 6, 7, 6, 6, 7, 6, 6, 7, 6, 6, 7, 6, 6, 7, 6, 7, 6, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 7, 6, 7, 6, 7, 7, 6, 7, 7, 6, 7, 7, 6, 7, 7, 6, 7, 7, 7, 6, 7, 7, 7, 6, 7, 7, 7, 7, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 7, 7, 7, 7, 7, 8, 7, 7, 7, 8, 7, 7, 7, 8, 7, 7, 8, 7, 7, 8, 7, 8, 7, 7, 8, 7, 8, 7, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 8, 7, 8, 7, 8, 8, 7, 8, 7, 8, 8, 7, 8, 8, 8, 7, 8, 8, 8, 7, 8, 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 8, 8, 8, 8, 8, 8, 8, 9, 8, 8, 8, 8, 9, 8, 8, 9, 8, 8, 8, 9, 8, 8, 9, 8, 9, 8, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 9, 8, 9, 8, 9, 9, 8, 9, 9, 9, 8, 9, 9, 9, 8, 9, 9, 9, 9, 9, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 9, 9, 9, 9, 10, 9, 9, 9, 10, 9, 9, 9, 10, 9, 9, 10, 9, 10, 9, 10, 9, 9, 10, 9, 10, 9, 10, 10, 9, 10, 9, 10, 10, 9, 10, 10, 9, 10, 10, 10, 9, 10, 10, 10, 10, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 10, 10, 10, 10, 11, 10, 10, 10, 11, 10, 10, 11, 10, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 11, 10, 11, 10, 11, 11, 11, 10, 11, 11, 11, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 11, 11, 11, 12, 11, 11, 12, 11, 11, 12, 11, 11, 12, 11, 12, 11, 12, 12, 11, 12, 11, 12, 12, 11, 12, 12, 11, 12, 12, 12, 12, 12, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 12, 12, 12, 12, 13, 12, 12, 12, 13, 12, 13, 12, 12, 13, 12, 13, 12, 13, 13, 12, 13, 12, 13, 13, 12, 13, 13, 13, 13, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 13, 13, 13, 14, 13, 13, 14, 13, 13, 14, 13, 14, 13, 14, 13, 14, 14, 13, 14, 14, 13, 14, 14, 14, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 14, 14, 14, 15, 14, 14, 15, 14, 14, 15, 14, 15, 14, 15, 14, 15, 15, 14, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 15, 15, 15, 16, 15, 15, 16, 15, 16, 15, 16, 15, 16, 16, 15, 16, 16, 16, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 16, 16, 16, 17, 16, 16, 17, 16, 17, 16, 17, 16, 17, 17, 16, 17, 17, 17, 17, 17, 16, 17, 17, 17, 18, 17, 17, 17, 17, 17, 18, 17, 17, 18, 17, 18, 17, 18, 18, 17, 18, 18, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 19, 19, 18, 19, 19, 19, 18, 19, 19, 19, 19, 19, 19, 19, 20, 19, 19, 19, 20, 19, 20, 19, 20, 19, 20, 20, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 20, 20, 21, 20, 21, 20, 21, 21, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 21, 21, 22, 21, 22, 22, 21, 22, 22, 21, 22, 22, 22, 22, 22, 22, 23, 22, 22, 23, 22, 22, 23, 23, 22, 23, 23, 22, 23, 23, 23, 23, 23, 24, 23, 23, 23, 24, 23, 24, 23, 24, 24, 23, 24, 24, 24, 24, 24, 24, 24, 25, 24, 24, 25, 24, 25, 25, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 25, 25, 26, 25, 26, 26, 25, 26, 26, 26, 26, 26, 27, 26, 26, 27, 26, 27, 26, 27, 27, 26, 27, 27, 27, 27, 28, 27, 27, 28, 27, 28, 27, 28, 28, 28, 28, 28, 28, 28, 29, 28, 28, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 30, 29, 30, 30, 29, 30, 30, 30, 30, 31, 30, 30, 31, 30, 31, 31, 30, 31, 31, 31, 32, 31, 31, 32, 31, 32, 32, 31, 32, 32, 32, 33, 32, 32, 33, 32, 33, 33, 33, 33, 33, 33, 33, 34, 33, 34, 34, 33, 34, 34, 34, 35, 34, 34, 35, 35, 34, 35, 35, 35, 35, 36, 35, 36, 35, 36, 36, 36, 36, 36, 37, 36, 37, 36, 37, 37, 37, 37, 37, 38, 37, 38, 38, 37, 38, 39, 38, 38, 39, 38, 39, 39, 39, 39, 39, 40, 39, 40, 40, 39, 40, 41, 40, 40, 41, 41, 41, 41, 41, 41, 41, 42, 42, 41, 42, 43, 42, 42, 43, 42, 43, 43, 43, 44, 43, 44, 44, 43, 44, 45, 44, 45, 44, 45, 45, 45, 46, 45, 46, 45, 46, 47, 46, 46, 47, 47, 47, 47, 47, 48, 47, 48, 48, 48, 49, 48, 49, 49, 49, 49, 49, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 53, 53, 54, 54, 54, 55, 54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59, 59, 60, 60, 60, 61, 61, 61, 61, 62, 61, 62, 63, 62, 63, 63, 63, 64, 64, 64, 64, 65, 65, 65, 66, 66, 66, 66, 67, 67, 67, 67, 68, 68, 69, 68, 70, 69, 69, 70, 71, 70, 71, 71, 72, 71, 73, 72, 73, 73, 73, 74, 74, 75, 74, 75, 76, 76, 76, 76, 77, 77, 78, 78, 78, 79, 79, 79, 80, 80, 81, 81, 81, 82, 82, 82, 83, 83, 84, 84, 85, 84, 86, 85, 87, 86, 87, 88, 87, 89, 88, 90, 89, 90, 91, 91, 91, 92, 92, 93, 93, 94, 95, 94, 95, 96, 96, 97, 97, 98, 98, 99, 99, 100, 100, 101, 101, 102, 103, 103, 103, 105, 104, 105, 106, 107, 107, 107, 108, 109, 109, 110, 111, 111, 112, 112, 113, 114, 114, 115, 116, 116, 117, 118, 118, 119, 119, 121, 121, 122, 122, 123, 124, 125, 125, 126, 127, 128, 128, 129, 130, 131, 131, 132, 134, 133, 135, 136, 136, 137, 138, 139, 140, 141, 141, 143, 143, 144, 145, 147, 147, 148, 148, 150, 151, 152, 153, 154, 154, 156, 157, 158, 159, 160, 161, 162, 164, 164, 166, 166, 168, 169, 170, 171, 173, 173, 175, 176, 177, 179, 180, 181, 182, 184, 185, 186, 188, 189, 191, 191, 194, 194, 196, 198, 199, 200, 202, 204, 205, 206, 209, 209, 212, 213, 214, 217, 218, 219, 222, 223, 225, 227, 228, 231, 232, 234, 236, 238, 240, 242, 243, 246, 248, 250, 252, 254, 257, 258, 261, 263, 265, 267, 270, 272, 275, 277, 279, 282, 284, 287, 289, 293, 294, 298, 300, 303, 305, 309, 311, 314, 318, 320, 323, 327, 329, 333, 336, 339, 342, 346, 349, 353, 356, 360, 363, 367, 371, 375, 378, 383, 386, 390, 395, 398, 403, 407, 412, 416, 420, 425, 430, 434, 439, 444, 449, 454, 459, 465, 470, 475, 480, 487, 491, 498, 504, 509, 516, 522, 529, 534, 542, 548, 555, 561, 569, 576, 584, 591, 598, 607, 614, 622, 631, 639, 648, 656, 665, 675, 684, 693, 703, 713, 723, 734, 744, 754, 766, 777, 789, 801, 812, 825, 838, 850, 864, 877, 891, 905, 919, 935, 950, 965, 981, 998, 1015, 1031, 1050, 1067, 1086, 1106, 1125, 1145, 1165, 1187, 1209, 1231, 1254, 1278, 1303, 1327, 1353, 1380, 1408, 1435, 1465, 1494, 1526, 1557, 1590, 1624, 1660, 1695, 1733, 1771, 1811, 1853, 1895, 1939, 1985, 2033, 2081, 2133, 2186, 2240, 2297, 2356, 2418, 2481, 2548, 2617, 2689, 2764, 2842, 2923, 3008, 3098, 3190, 3287, 3389, 3495, 3606, 3723, 3846, 3974, 4110, 4252, 4402, 4560, 4726, 4902, 5089, 5284, 5493, 5714, 5948, 6198, 6462, 6745, 7047, 7368, 7713, 8083, 8479, 0 }; // Le zéro à la fin indique que c'est la fin du tableau word volatile wCompte = 0; // Compte le nombre de répétitions d'une fréquence word volatile wInd = 0; // Indice de compteur de répétitions d'une fréquence // Pour "volatile", c.f. https://www.arduino.cc/en/pmwiki.php?n=Reference/Volatile // Les variables utilisées dans la fonction de gestion d'interruption doivent être déclarée "volatile". // Cela impose que la variable est sauvegardée en RAM et non dans un registre. // Une variable sauvegardée dans un registre pourrait être modifiée accidentellement entre deux accès à la fonction. word volatile wT0 = 4000; // Fixe le temps entre deux transitions, au départ. byte volatile bEtat = 0; // État de la pin 13 byte volatile bUp = 1; // Indique si la fréquence va en augmentant (1) ou diminuant (0) byte bEtat0 = 0; // Pour détecter les transitions d'état dans la boucle principale. word wTempsL = 0; // Temps modulo 65536 en fact * 0.0625 [us] word wTempsH = 0; // Temps / 65536 en fact * 0.0625 [us] // = temps en multiple de 32,767 [ms], pour fact == 8. word wTmp = 0; // Temporaire // Inclus la librairie qui gère l'affichage #include <LiquidCrystal.h> // initialize the library with the numbers of the interface pins //LiquidCrystal lcd(RS, E, D4, D5, D6, D7); LiquidCrystal lcd(8, 9, 4, 5, 6, 7); char acStr[20]; // Un "array" de caractères, pour conversion de nombre en un String. String strS = ""; // Une chaine de caractères. float vTmp = 0.0; // Temporaire // Formules de conversions //======================== // Selon la valeur de TCCR1B, bFac varie selon le tableau suivant : // TCCR1B 1 2 3 4 5 // bFac 1 8 64 256 1024 // Temps entre deux transistions = (OCR1A+1) * bFac * 62.5 * 10^(-9) [s] // 62.5e-9 = 1 / 16'000'000 // La période est le double du temps entre deux transistions // Fréquence = 8'000'000 [Hz] / ((OCR1A+1) * bFac) // // Pour Fréquence = Freq donnée : // OCR1A = -1 + Tronc(16'000'000 / (Freq * bFac) // Une fréquence supérieure à ~60 [kHz] est non atteignable. void setup() { //============ // set up the LCD's number of columns and rows: lcd.begin(16, 2); // Print a message to the LCD. lcd.setCursor(0, 0); lcd.print("Timer freq. gen."); lcd.setCursor(0, 1); lcd.print("ex0453 20200611 "); delay(1200); lcd.setCursor(0, 0); lcd.print(" "); // efface le texte lcd.setCursor(0, 1); lcd.print(" "); // Initialise le Timer 1 pour déclencher les interruptions après temps défini dans OCR1A cli(); // Désactive l'interruption globale TCCR1A = 0b00000000; // default. Bits : COM1A1=0 ; COM1A0=0 ; COM1B1=0 ; COM1B0=0 ; / ; / ; WGM11=0 ; WGM10=0, donc pas de PWM. // de Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf // Timer/Counter0 C.f. page 93 // Il gère les fonctions delay(), millis() et micros() // Lorsque le Timer 0 est désactivé, les 3 fonctions ci-dessus ne fonctionnent plus ! TIMSK0 = 0b00000000; // Désactive les interruptions dues au Timer/Counter0 C.f. p. 109 //TCCR0B = 0b00000000; // Désactive le Timer/Counter0 // Timer/Counter1 C.f. page 111 // Il est utilisé dans la bibliothèque de commande de servo-moteurs. //TIMSK1 = 0b00000001; // Active les les interruptions dues au Timer/Counter1 C.f. p. 135 // Une interruption est générée lorsque le compteur TCNT1 passe de 65535 à 0 (overflow) TIMSK1 = 0b00000010; // Active les les interruptions dues au Timer/Counter1 C.f. p. 135 // Lorsque le compteur TCNT1 égale OCR1A, ( car WGM1(3:0) = 0b0100 == 4 ) // TCNT1 passe à 0. // et une interruption est générée. // Timer/Counter2 C.f. page 141 // Il est utilisé pour générer des tone() TIMSK2 = 0b00000000; // Désactive les les interruptions dues au Timer/Counter2 C.f. p. 157 //TIMSK2 = 0b00000001; // Active les les interruptions dues au Timer/Counter 2. // Désavticé : une interruption est générée lorsque le compteur TCNT2 passe de 255 à 0 (overflow) //TCCR1B = 0b00001001; // (WGM13 = 0 ; WGM12 = 1) fact = 1 TCCR1B = 0b00001010; // (WGM13 = 0 ; WGM12 = 1) fact = 8 //TCCR1B = 0b00001011; // (WGM13 = 0 ; WGM12 = 1) fact = 64 //TCCR1B = 0b00001100; // (WGM13 = 0 ; WGM12 = 1) fact = 256 //TCCR1B = 0b00001101; // (WGM13 = 0 ; WGM12 = 1) fact = 1024 wT0 = 4000; wInd = 0; wCompte = 1; OCR1A = wT0-1; // Limite TOP du compteur TCNT1 avant son retour à 0. // La fréquence va varier de 250 [Hz] à ~25 kHz // Elle double en 10 secondes. sei(); // Active l'interruption globale pinMode(13, OUTPUT); } // setup void loop() { //=========== // Boucle principale. Tout se passe dans la routine de gestion d'interruption. if (bEtat != bEtat0) { // Il y a eu une transistion d'état, incrémente le temps en conséquence. bEtat0 = bEtat; wTmp = wTempsL; wTempsL = wTempsL + OCR1A+1; // Temps en fact * 0.0625 [us] == 0.5 [us] if (wTempsL < wTmp) { // Une boucle de temps a été faite wTempsH++; if (wTempsH % 16 == 0) { // Affichage d'information // F("texte") fait en sorte que le texte soit stocké dans la page programme et non en mémoire des variables. vTmp = wTempsH * 0.032768; sprintf(acStr, "T=%4d.%01d [ms]", (int)vTmp, (int)(vTmp*10)%10); lcd.setCursor(0, 0); lcd.print(acStr); vTmp = 1000.0 / (OCR1A+1); sprintf(acStr, "F=%4d.%01d [kHz]", (int)vTmp, (int)(vTmp*10)%10); lcd.setCursor(0, 1); lcd.print(acStr); } } } } // loop // routine d'interruption du timer 1, lorsque le registre OCR1A est utilisé. ISR (TIMER1_COMPA_vect) { //======================= // TCNT1 = valeur du compteur, qui est compté de 0 à OCR1A, puis repasse à 0. // Lorsqu'on arrive dans cette fonction de gestion d'interruption, TCNT1 = 0 // Le compteur TCNT1 est incrémenté de 1 chaque 0.0625 [us] * facteur défini dans TCCR1B. // Transition de l'état bEtat = 1 - bEtat; digitalWrite(13, bEtat); wCompte -= 1; if (bUp) { // Fréquence montante if (wCompte == 0) { // Augmentation de la fréquence. OCR1A--; // Passage à la fréquence suivante. if (OCR1A > 3000) wCompte = 1; else if (OCR1A > 2400) wCompte = 2; else if (OCR1A > 2000) wCompte = 3; else if (OCR1A > 1800) wCompte = 4; else { // OCR1A <= 1800 fréque ~= 555 [Hz] wCompte = 2*pgm_read_word(&awRepete[wInd]); wInd++; } //Serial.println(OCR1A); if (wCompte == 0) { // Passe en fréquence descendante bUp = 0; OCR1A++; wInd--; wCompte = pgm_read_word(&awRepete[wInd]); } } } else { // Fréquence descendante if (wCompte == 0) { // Diminution de la fréquence. OCR1A++; // Passage à la fréquence précédente. if (wInd > 0) { wInd--; wCompte = 2*pgm_read_word(&awRepete[wInd]); } else if (OCR1A <= 1800) wCompte = 5; else if (OCR1A <= 2000) wCompte = 4; else if (OCR1A <= 2400) wCompte = 3; else if (OCR1A <= 3000) wCompte = 2; else wCompte = 1; if (OCR1A == wT0) { // Passe en fréquence montante bUp = 1; OCR1A = wT0-1; wInd = 0; wCompte = 1; } } } } // ISR
/* ex0460_timer_1_interrupt_OCR1A_LCD.ino Exemple d'utilisation du timer 1 pour générer des interruptions. Utilise le registre OCR1A pour générer les interruptions. Le timer 0 est utilisé pour une mesure du temps, sans génération d'interruptions. Les fonctions "delay", "delayMicroseconds", "millis" et "micros" ne sont pas utilisables. Elles sont désactivées, pour augmenter la précision de la période de génération d'interruptions. L'affichage LCD, avec les 5 boutons de commande est utilisé. Les pins 2 et 3 sont utilisées pour le signal, en opposition. Avec la touche "Select", on peut activer ou désactiver le signal. La pin 13 indique si le signal est activé (1) ou désactivé (0). Accès beaucoup plus rapide aux changement d'état des pins qu'avec la fonction "digitalWrite". C.f. "Arduino Cookbook" de Michael Margolis, édition O'reilly, page 628 et "AVR_Assembler_Lecture6.pdf", page 53 Permet de générer une fréquence de 0.12 [Hz] à 120'000 [Hz]. La lecture de l'ADC 0, pour connaître le bouton pressé est fait de manière beaucoup plus rapide qu'en utilisant la fonction analogRead. Le temps de conversion est toujours d'environ 100 [us], mais aucune attente n'est faite en l'initialisation du début de conversion et le résultat de la mesure, 100 [us] plus tard. [us] = micro-seconde. ==========================================================*/ // Inclus la bibliothèque qui gère l'affichage. #include <LiquidCrystal.h> // Initialise la bibliothèque avec les pins utilisés par l'afficheur LCD 2x16 muni de 5 boutons. //LiquidCrystal lcd(RS, E, D4, D5, D6, D7); LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // Déclaration de Constantes #define btnNONE 0 #define btnSELECT 1 #define btnLEFT 2 #define btnDOWN 3 #define btnUP 4 #define btnRIGHT 5 // Nombre de menus #define MENU_MAX 8 #define pinFreqP 2 // pour générer une fréquence #define pinFreqN 3 // = état opposé à celui de pintFreqP lors du fonctionnement. char acStr[20]; // Un "array" de caractères, pour conversion de nombre en un String. String strS = ""; // Une chaine de caractères. int nButtonPush = btnNONE; // Indique le bouton pressé byte bMenu1 = 4; // indique le menu sélectionné byte bMenu1_Last = 0; // indique le dernier menu sélectionné word wTime0 = 61; // Compteur décrémenté de 1 toutes les 0.016384 [s] ~= 1/61 [s] // Utilisé pour ne pas lire l'état des boutons trop souvent. // Pour "volatile", c.f. https://www.arduino.cc/en/pmwiki.php?n=Reference/Volatile // Les variables utilisées dans la fonction de gestion d'interruption doivent être déclarée "volatile". // Cela impose que la variable est sauvegardée en RAM et non dans un registre. // Une variable sauvegardée dans un registre pourrait être modifiée accidentellement entre deux accès à la fonction. byte volatile bEtat = 0; // État des pins : pinFreqP et pinFreqN byte volatile bEtat_enable = 1; // Active ou désactive le signal. 1 = Actif word volatile OCR1A_new = 4000-1; // Fixe le temps entre deux transitions word volatile OCR1A_old = 4000-1; // Pour déterminer la variation de la variable OCR1A_new, qu'elle soit d'au moins +-1 float vOCR1A_calc_new = 0.0; // Nouvelle valeur de OCR1A pour générer la fréquence désirée. word wFact = 8; // Facteur de division de la fréquence d'incrémentation de TCNT1. Est modifiable suivant la fréquence générée. float vFreq = 250.0; // [Hz] Fréquence générée. float vFreq_base = 1000000.0; // [Hz] fréquence à laquelle le compteur TCNT1 est incrémenté. = 8000000.0 / vFreq float vFreq_delta = 0.0; // Variation de fréquence demandée // Pour la conversion ADC uint8_t bLow, bHigh; #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) // Formules de conversions //======================== // Selon la valeur de TCCR1B, bFac varie selon le tableau suivant : // TCCR1B 1 2 3 4 5 // bFac 1 8 64 256 1024 // Temps entre deux transistions = (OCR1A+1) * bFac * 62.5 * 10^(-9) [s] // 62.5e-9 = 1 / 16'000'000 // La période est le double du temps entre deux transistions // Fréquence = 8'000'000 [Hz] / ((OCR1A+1) * bFac) // // Pour Fréquence = Freq donnée : // OCR1A = -1 + Tronc(8'000'000 / (Freq * bFac) // Une fréquence supérieure à ~120 [kHz] est non atteignable. void setup() { //============ // Indique le nombre de lignes et de colonnes de l'afficheur LCD 16x2. lcd.begin(16, 2); // Affiche un message au départ, indiquant le progr. et la date lcd.setCursor(0, 0); lcd.print("Timer freq. LCD."); lcd.setCursor(0, 1); lcd.print("ex0460 20200617 "); delay(1200); lcd.setCursor(0, 0); lcd.print(" "); // efface le texte lcd.setCursor(0, 1); lcd.print(" "); cli(); // Désactive l'interruption globale // de Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf // Timer/Counter0 C.f. page 93 // Il gère les fonctions delay(), delayMicroseconds, millis() et micros(), qui sont désactivées. // Lorsque le Timer 0 est désactivé, les 4 fonctions ci-dessus ne fonctionnent plus ! TCCR0A = 0b00000000; // default. Bits : COM0A1=0 ; COM0A0=0 ; COM0B1=0 ; COM0B0=0 ; / ; / ; WGM01=0 ; WGM00=0, donc pas de PWM lié au timer 0 TCCR0B = 0b00000101; // Clock * 1024 soit 64 micro-s et WGM02 = 0 TIMSK0 = 0b00000000; // Désactive les interruptions dues au Timer/Counter0 C.f. p. 109 // Ne pas utiliser TCNT0, mais le bit 0 de TIFR0 pour compter le nombre de boucles du compteur liées au timer 0. // Timer/Counter2 C.f. page 141 // Il est utilisé pour générer des tone() TIMSK2 = 0b00000000; // Désactive les les interruptions dues au Timer/Counter2 C.f. p. 157 // Initialise le Timer 1 pour déclencher les interruptions après temps défini dans OCR1A // Timer/Counter1 C.f. page 111 // Il est utilisé dans la bibliothèque de commande de servo-moteurs. //TIMSK1 = 0b00000001; // Active les les interruptions dues au Timer/Counter1 C.f. p. 135 // Une interruption est générée lorsque le compteur TCNT1 passe de 65535 à 0 (overflow) TIMSK1 = 0b00000010; // Active les les interruptions dues au Timer/Counter1 C.f. p. 135 // Lorsque le compteur TCNT1 égale OCR1A, ( car WGM1(3:0) = 0b0100 == 4 ) // TCNT1 passe à 0. // et une interruption est générée. TCCR1A = 0b00000000; // default. Bits : COM1A1=0 ; COM1A0=0 ; COM1B1=0 ; COM1B0=0 ; / ; / ; WGM11=0 ; WGM10=0, donc pas de PWM. //TCCR1B = 0b00001001; // (WGM13 = 0 ; WGM12 = 1) fact = 1 TCCR1B = 0b00001010; // (WGM13 = 0 ; WGM12 = 1) fact = 8 //TCCR1B = 0b00001011; // (WGM13 = 0 ; WGM12 = 1) fact = 64 //TCCR1B = 0b00001100; // (WGM13 = 0 ; WGM12 = 1) fact = 256 //TCCR1B = 0b00001101; // (WGM13 = 0 ; WGM12 = 1) fact = 1024 OCR1A_new = 4000-1; // Fréquence de départ : Fréquence = 8'000'000 [Hz] / ((OCR1A+1) * fact) = 1'000'000 / 4000 = 250 [Hz]. T = 4 [ms]. OCR1A = OCR1A_new; // Limite TOP du compteur TCNT1 avant son retour à 0. sei(); // Active l'interruption globale pinMode(13, OUTPUT); // La pin 13 indique si un signal est généré ou si les sorties des pins 2 et 3 sont à 0 [V]. pinMode( pinFreqN, OUTPUT); // controle une borne de l'haut-parleur pinMode( pinFreqP, OUTPUT); // controle l'autre borne de l'haut-parleur = le niveau opposé à la pinFreqN digitalWrite( 13, bEtat_enable); digitalWrite( pinFreqN, LOW); // borne 1 à 0 V digitalWrite( pinFreqP, LOW); // borne 2 à 0 V AfficheEtat(); // Pour la lecture de l'ADC // c.f. https://garretlab.web.fc2.com/en/arduino/inside/arduino/wiring_analog.c/analogRead.html ADMUX = 0b01000000; // 01 => Default reference voltage bit_5 = ADLAR = 0 => right adjusted bits_3210 = 0 => port 0 // start the conversion sbi(ADCSRA, ADSC); } // setup int read_LCD_buttons() { // ====================== // Lecture du bouton appuié // Il faut détecter 3 fois de suite le même bouton pour qu'on estime détecté le bon bouton pressé. // L'avantage de cette manière de faire est que la lecture des boutons ne prend que // le temps d'une lecture analogique, soit environ 100 micro secondes. // Une lecture toutes les 1/61 seconde. int nAdc_key_in = 0; byte bButton = btnNONE; // Bouton pressé selon la lecture de l'ADC. static byte bButtonLast = btnNONE; // Dernier bouton pressé. static byte bButtonCount = 0; // compte le nombre de fois que le même boutton est détecté à la suite. //nAdc_key_in = analogRead(0); // Lecture de la valeur du bouton pressé. // c.f. https://garretlab.web.fc2.com/en/arduino/inside/arduino/wiring_analog.c/analogRead.html //ADMUX = 0b01000000; // 01 => Default reference voltage bit_5 = ADLAR = 0 => right adjusted bits_3210 = 0 => port 0 // ADSC is cleared when the conversion finishes // while (bit_is_set(ADCSRA, ADSC)); // Plus besoin d'attendre, car cela est fait grace à : lwTempsLastReadBtn // Lecture de ADCL en premier. Il est mis dans un buffer et non modifié tant que ADCH n'a pas été lu. // La lecture de ADCL en deuxième bloquerait la lecture de l'ADC. // Il faut donc lire ADCH en deuxième. bLow = ADCL; bHigh = ADCH; // Démarre une conversion, qui sera terminée dans environ 100 [us]. // La fin de conversion peut se détecter en lisant ADSC, qui sera mis à 0 une fois la conversion terminée. // ADSC n'est pas utilisé ici, car on laisse amplement le temps de terminer la conversion en n'en faisant que 61 par seconde. sbi(ADCSRA, ADSC); if (bHigh >= 3) { // Aucun bouton pressé. bButtonLast = btnNONE; bButtonCount = 0; return btnNONE; } // Ici, on sait qu'un bouton a été pressé. nAdc_key_in = word(bHigh, bLow); // Conversion en un word. // Les lectures des boutons sont centrées autour des valeurs 0, 144, 329, 504, 741 // J'ai ajouté environ 50 à ces valeurs pour la détection des boutons if (nAdc_key_in > 555) bButton = btnSELECT; else if (nAdc_key_in > 380) bButton = btnLEFT; else if (nAdc_key_in > 195) bButton = btnDOWN; else if (nAdc_key_in > 55) bButton = btnUP; else bButton = btnRIGHT; if (bButton == bButtonLast) { // Le même bouton a été détecté bButtonCount++; // compte combien de fois de suite le même bouton est détecté. if (bButtonCount == 3) { bButtonCount = 0; wTime0 = 20; // Pour indiquer qu'un bouton a été traité, il faudra attendre avant de traiter à nouveau un bouton. return bButton; // Le même bouton a été détecté .. fois de suite, donc c'est le bon } } else { // Un nouveau bouton est détecté. bButtonLast = bButton; bButtonCount = 0; } return btnNONE; // On est pas encore sûr que le bon bouton est bButton. } // read_LCD_buttons void AfficheEtat() { //================== // Affichage de la fréquence ou de la variation de fréquence, suivant le menu if (bMenu1 <= MENU_MAX) { // Affiche Fréquence du signal 1 if (bMenu1 != bMenu1_Last) { lcd.setCursor(0, 0); if (bMenu1 == 1) lcd.print("Frequ +-0.001 Hz"); else if (bMenu1 == 2) lcd.print("Frequ +- 0.01 Hz"); else if (bMenu1 == 3) lcd.print("Frequ +- 0.10 Hz"); else if (bMenu1 == 4) lcd.print("Frequ +- 1 Hz "); else if (bMenu1 == 5) lcd.print("Frequ +- 10 Hz "); else if (bMenu1 == 6) lcd.print("Frequ +- 100 Hz "); else if (bMenu1 == 7) lcd.print("Frequ +- 1000 Hz"); else if (bMenu1 == 8) lcd.print("+- octave / 24 "); // Ainsi, 24 changements doublent ou divisent la fréquence (change d'une octave). } dtostrf(vFreq, 8, 3, acStr); // Conversion d'un nombre à virgule en un string prenant 8 caractères, avec 3 chiffres après la virgule. strS = acStr; strS = "Frequ.= " + strS; lcd.setCursor(0, 1); // Affichage en début de 2e ligne de l'afficheur LCD. lcd.print(strS); } } // AfficheEtat void MenuTreat() { //================ // Un bouton UP, DOWN ou SELECT a été pressé, traite l'action adéquat. vFreq_delta = 0.0; if (bMenu1 == 1) { // Changement de fréquence du signal 1 de +-1 milli-Hz if (nButtonPush == btnUP) vFreq_delta = 0.001; if (nButtonPush == btnDOWN) vFreq_delta = -0.001; } if (bMenu1 == 2) { // Changement de fréquence du signal 1 de +-10 milli-Hz if (nButtonPush == btnUP) vFreq_delta = 0.01; if (nButtonPush == btnDOWN) vFreq_delta = -0.01; } if (bMenu1 == 3) { // Changement de fréquence du signal 1 de +-100 milli-Hz if (nButtonPush == btnUP) vFreq_delta = 0.1; if (nButtonPush == btnDOWN) vFreq_delta = -0.1; } if (bMenu1 == 4) { // Changement de fréquence du signal 1 de +-1 Hz if (nButtonPush == btnUP) vFreq_delta = 1.0; if (nButtonPush == btnDOWN) vFreq_delta = -1.0; } if (bMenu1 == 5) { // Changement de fréquence du signal 1 de +-10 Hz if (nButtonPush == btnUP) vFreq_delta = 10.0; if (nButtonPush == btnDOWN) vFreq_delta = -10.0; } if (bMenu1 == 6) { // Changement de fréquence du signal 1 de +-100 Hz if (nButtonPush == btnUP) vFreq_delta = 100.0; if (nButtonPush == btnDOWN) vFreq_delta = -100.0; } if (bMenu1 == 7) { // Changement de fréquence du signal 1 de +-1000 Hz if (nButtonPush == btnUP) vFreq_delta = 1000.0; if (nButtonPush == btnDOWN) vFreq_delta = -1000.0; } if (bMenu1 == 8) { // Changement de fréquence du signal pour que 24 changement passe à l'octave suivante. // if (nButtonPush == btnUP) vFreq_delta = 0.059463094 * vFreq; // pour que 12 changement passe à l'octave suivante. racine_12e(2) = 1.059463094 // if (nButtonPush == btnDOWN) vFreq_delta = -0.056125687 * vFreq; // pour que 12 changement passe à l'octave précédente. 1 - 1/racine_12e(2) if (nButtonPush == btnUP) vFreq_delta = 0.029302237 * vFreq; // pour que 24 changement passe à l'octave suivante. racine_24e(2) = 1.029302237 if (nButtonPush == btnDOWN) vFreq_delta = -0.028468059 * vFreq; // pour que 24 changement passe à l'octave précédente. 1 - 1/racine_24e(2) } if (vFreq_delta != 0) { // Il y a une demande de changement de fréquence vFreq = vFreq + vFreq_delta; OCR1A_old = OCR1A_new; vOCR1A_calc_new = -1 + vFreq_base / vFreq; // Teste que les capacités du compteur TCNT1 lié au timer 1 ne soient pas dépassées if (vOCR1A_calc_new > 65535.0) { // Passe à un diviseur de fréquence (wFact) plus grand, pour générer une plus basse fréquence, une plus haute période. OCR1A_old = 0; // Assure que : OCR1A_old != OCR1A_new if (wFact == 1024) { // Assure de ne pas descendre en-dessous d'une fréquence trop basse, non atteignable par le Timer 1 de l'Arduino. vFreq = 0.1193; } else if (wFact == 256) { wFact = 1024; TCCR1B = 0b00001101; // (WGM13 = 0 ; WGM12 = 1) fact = 1024 } else if (wFact == 64) { wFact = 256; TCCR1B = 0b00001100; // (WGM13 = 0 ; WGM12 = 1) fact = 256 } else if (wFact == 8) { wFact = 64; TCCR1B = 0b00001011; // (WGM13 = 0 ; WGM12 = 1) fact = 64 } else if (wFact == 1) { wFact = 8; TCCR1B = 0b00001010; // (WGM13 = 0 ; WGM12 = 1) fact = 8 } vFreq_base = 8000000.0 / wFact; // Changement de la fréquence de base vOCR1A_calc_new = -1 + vFreq_base / vFreq; } // if (vOCR1A_calc_new > 65535.0) // Teste si l'on peut être plus précis dans la fréquence générée. else if (vOCR1A_calc_new < 7500.0) { // Passe à un diviseur de fréquence plus petit, pour être plus précis dans la fréquence générée. if (wFact == 1) { // Teste que la fréquence ne soit pas trop grande, sinon dépasse les capacités de génération de fréquences de l'Arduino. if (vFreq > 120000.0) { // Fréquence max. = 120 [kHz]. vFreq = 120000.0; OCR1A_old = 0; // Assure que : OCR1A_old != OCR1A_new } } else { OCR1A_old = 0; // Assure que : OCR1A_old != OCR1A_new if (wFact == 8) { wFact = 1; TCCR1B = 0b00001001; // (WGM13 = 0 ; WGM12 = 1) fact = 1 } else if (wFact == 64) { wFact = 8; TCCR1B = 0b00001010; // (WGM13 = 0 ; WGM12 = 1) fact = 8 } else if (wFact == 256) { wFact = 64; TCCR1B = 0b00001011; // (WGM13 = 0 ; WGM12 = 1) fact = 64 } else if (wFact == 1024) { wFact = 256; TCCR1B = 0b00001100; // (WGM13 = 0 ; WGM12 = 1) fact = 256 } } vFreq_base = 8000000.0 / wFact; // Changement de la fréquence de base vOCR1A_calc_new = -1 + vFreq_base / vFreq; } // else if (vOCR1A_calc_new < 7500.0) OCR1A_new = word(vOCR1A_calc_new); if (OCR1A_new == OCR1A_old) { // Force le changement de fréquence minimal. if (vFreq_delta > 0) OCR1A_new--; else OCR1A_new++; } vFreq = vFreq_base / (OCR1A_new + 1.0); // Fréquence réelle, légèrement différente de la fréquence désirée. } // if (vFreq_delta != 0) // Le bouton "Select" active ou désactive le signal if (nButtonPush == btnSELECT) { bEtat_enable = 1 - bEtat_enable; digitalWrite(13, bEtat_enable); // Indique sur l'état de la pin 13 si le signal est arrêté ou actif. digitalWrite( pinFreqP, bEtat & bEtat_enable); // off si signal désactivé digitalWrite( pinFreqN, (1 - bEtat) & bEtat_enable); // off si signal désactivé } AfficheEtat(); } // MenuTreat void Menu1_Change() { //=================== // Changement du Menu1 // Un bouton LEFT ou RIGHT a été pressé bMenu1_Last = bMenu1; // Mémorise le dernier menu if (nButtonPush == btnLEFT) { bMenu1 -= 1; if (bMenu1 == 0) bMenu1 = MENU_MAX; // boucle } if (nButtonPush == btnRIGHT) { bMenu1 += 1; if (bMenu1 > MENU_MAX) bMenu1 = 1; // boucle } AfficheEtat(); } // Menu1_Change void loop() { //=========== // Boucle principale. Voir également la routine de gestion d'interruption du timer 1 if (TIFR0 & 1) { // Le compteur du timer 0 a fait une boucle complète // Une boucle se fait en environ 1/61 [s]. Il y a donc 61 boucles par seconde. // Ceci laisse le temps au convertisseur ADC de terminer sa conversion TIFR0 = 1; // Efface les flags du registre TIFR0 // Ne lit l'état des boutons que si un bouton a été traité il y a plus de 0,33 seconde. if (wTime0 == 0) { // Lecture de l'état des boutons, pour changer la fréquence ou la phase du signal nButtonPush = read_LCD_buttons(); if ( (nButtonPush == btnLEFT) || (nButtonPush == btnRIGHT) ) Menu1_Change(); // Un bouton pressé, changement de menu if ( (nButtonPush == btnUP) || (nButtonPush == btnDOWN) || (nButtonPush == btnSELECT) ) MenuTreat(); // Un bouton pressé, traitement } if (wTime0 > 0) wTime0--; // Pour assurer qu'un bouton pressé ne soit pas traité trop rapidement plusieurs fois à la suite. } // if (TIFR0 & 1) { } // loop // routine d'interruption du timer 1, lorsque le registre OCR1A est utilisé, ce qui est le cas dans ce programme. ISR (TIMER1_COMPA_vect) { //======================= // Transition de l'état // TCNT1 = valeur du compteur, qui est compté de 0 à OCR1A, puis repasse à 0. // Lorsqu'on arrive dans cette fonction de gestion d'interruption, TCNT1 = 0. // Il est préférable de ne pas utiliser directement le registre TCNT1, car sa valeur peut varier très rapidement. // Le compteur TCNT1 est incrémenté de 1 chaque 0.0625 [us] * facteur défini dans TCCR1B. OCR1A = OCR1A_new; // Définit la durée de la prochaine période de transition. if (bEtat_enable) { // Manière beaucoup plus rapide de changer l'état des pins, permet de générer de plus hautes fréquences. // C.f. AVR_Assembler_Lecture6.pdf bEtat = 1 - bEtat; if (bEtat) PORTD = (PORTD | 0b00001000) & 0b11111011; // Pour accéder beaucoup plus rapidement aux pins de l'Arduino. else PORTD = (PORTD | 0b00000100) & 0b11110111; // PORTD contrôle les pins de 0 à 7. bit des unités = port 0 // PORTB contrôle les pins de 8 à 13 <=> bits 0 à 5 // PORTC contrôle les pins de 14 à 19 <=> bits 0 à 5. Correspond aux pins analogiques, pouvant aussi être utilisés en digital. } /* // Autre manière possible de faire. if (bEtat_enable) { // Manière beaucoup plus rapide de changer l'état des pins, permet de générer de plus hautes fréquences. // C.f. AVR_Assembler_Lecture6.pdf bEtat = 1 - bEtat; if (bEtat) { bitSet(PORTD, 3); bitClear(PORTD, 2); } // Pour accéder beaucoup plus rapidement aux pins de l'Arduino. else { bitSet(PORTD, 2); bitClear(PORTD, 3); } // PORTD contrôle les pins de 0 à 7. bit des unités = port 0 // PORTB contrôle les pins de 8 à 13 <=> bits 0 à 5 // PORTC contrôle les pins de 14 à 19 <=> bits 0 à 5. Correspond aux pins analogiques, pouvant aussi être utilisés en digital. } */ /* // Manière plus claire de faire, mais plus de 10 fois plus lente. if (bEtat_enable) { digitalWrite(pinFreqP, bEtat); // prend environ 4 [us] = 4 micro-secondes bEtat = 1 - bEtat; digitalWrite(pinFreqN, bEtat); // prend environ 4 [us] = 4 micro-secondes } */ } // ISR // FIN.
/* ex0462_timer_1_interrupt_LCD_sinus.ino Suite de ex0461... Le but est de générer un signal qui se rapproche d'une sinusoïdale de fréquence entre 300 et 500 [Hz]. Utilisation de la pin 3 de l'Arduino pour charger, maintenir constant ou décharger un condensateur. Les pins 0 à 19 de l'Arduino ont 3 états possibles : 1) 0 [V], du courant entre dans la pin 2) 5 [V], du courant sort de la pin 3) haute impédance, c'est comme si elle était débranchée. Un condensateur branché ainsi reste dans son état de charge. Les pin 14 à 19, correspondants à la lecture de tensions analogiques (ADC), peuvent aussi être utilisée comme des entrées et sorties digitales. Lors de la montée du sinus, on alterne entre la charge du condensateur et le maintien constant. En chargent par petites durées, aux bons instants, une tension sinusoïdal peut être générée. Lors de la descente du sinus, on alterne entre la décharge du condensateur et le maintient constant. Les instant de charges et décharges sont stockés dans un tableau, soit de 100 valeurs soit de 200 valeurs. Ces valeurs ont été calculées dans un programme Python. Le programme est copié à la fin de ce commentaire. Elles se basent sur un modèle de charge - décharge d'un condensateur à travers une résistance. La fréquence utilisée dans le modèle est de 400 [Hz]. R = 2200 Ohms C = 220 [nF]. La tension oscille entre 1 [V] et 4 [V]. On peut faire varier légèrement la fréquence, mais cela éloigne les données de ce qu'elles devraient être, la courbe s'éloigne d'une sinusoïdale. Exemple d'utilisation du timer 1 pour générer des interruptions. Utilise le registre OCR1A pour générer les interruptions. Le timer 0 est utilisé pour une mesure du temps, sans génération d'interruption. Les fonctions "delay", "delayMicroseconds", "millis" et "micros" ne sont pas utilisables. Elles sont désactivées, pour augmenter la précision de la période de génération d'interruptions. L'affichage LCD, avec les 5 boutons de commande est utilisé. Les pins 2 et 3 sont utilisés pour le signal, en opposition. Avec la touche "Select", on peut activer ou désactiver le signal. La pin 13 indique si le signal est activé (1) ou désactivé (0). Accès beaucoup plus rapide aux changement d'état des pins qu'avec la fonction "digitalWrite". C.f. "Arduino Cookbook" de Michael Margolis, édition O'reilly, page 628 et "AVR_Assembler_Lecture6.pdf", page 53 La lecture de l'ADC 0, pour connaître le bouton pressé est fait de manière beaucoup plus rapide qu'en utilisant la fonction analogRead. Le temps de conversion est toujours d'environ 100 [us], mais aucune attente n'est faite en l'initialisation du début de conversion et le résultat de la mesure, 100 [us] plus tard. [us] = micro-seconde. ************** Programme en langage Python servant à calculer le tableau des instants de charges du condensateur. ************ # Cacluls_durees_sinus.py ''' 2020 06 18 Pour Arduino Le but est de générer un signal quazi sinusoïdal avec un Arduino, en utilisant une résistance 2200 [Ohms] et un condensateur de 220 nF. Théorie : Le signal désiré est Us(t) = 2.5 - 1.5 * cos(2 * pi * 400 * t) La charge du condensateur est : Uc( n ) = 5 [V] * (1 - exp( -(t_0 + n * dt) / RC) ) t_0 est choisi pour que Uc(0) = 1 [V] n = 0 au départ. Chaque fois que la tension du signal dépasse celle de ( Uc(n) + Uc(n+1) ) / 2, on charge le condensateur durant un temps dt et on incrémente n de 1. Les différents instants (nombre * dt) durant lesquels il faut charger le condensateur sont mémorisés dans un tableau. (On ne mémorise que "nombre"). La décharge est symétrique. RC = R * C est choisi pour que la tension aux bornes du condensateur lorsque U = 2.5 [V] croit légèrement plus rapidement que la tension du signal désiré. R est telle que le courant de l'Arduino est entre 1 et 5 mA. R = 2200 Ohms est un choix raisonnable. Une version plus évoluée, ferait ces calculs dans l'Arduino, pour pouvoir changer la fréquence et garder un signal sinusoïdal variant entre 1 et 4 [V]. ''' from math import * # Données : R = 2200.0 # Ohms C = 220.0e-9 # F Freq = 400.0 # Hz nbSlots = 200 # nombre d'interruptions durant une demi période. # Constantes déduites des données Periode = 1.0 / Freq dt = Periode / (2.0 * nbSlots) print("dt [us] = ", dt * 1e6) dt_RC = dt / (R * C) print("dt_RC = ", dt_RC) TC0 = log(1 - 1/5) print("TC0_RC = ", 5 * (1 - exp(TC0)) ) # donne 1 [V] au départ. nC = 0 # Compte le nombre de fois que le condensateur est chargé nTS = 0 # nTS * dt = temps écoulé UC = 1.0 # Tension en Volts aux bornes du condensateur US = 1.0 # Tension du signal au temps nTS * dt compte = 0 # Pour une impression utilisable en copier-coller directement dans le programme print('--------------- Départ ------------------') print('=========================================') #print('{:6d}'.format(floor(temps*1000)), '{:5d}'.format(OCR1A), '{:5d}'.format(compte), '{:8.3f}'.format(1/T)) # while ( nTS <= nbSlots/10): while ( nTS < nbSlots + 10 ): nC += 1 UC_last = UC UC = 5 * (1 - exp(TC0 - dt_RC * nC)) # Détermine quand il faut charger le condensateur durant un "slot de temps", pour atteindre la tension désirée du signal while (US < (UC_last + UC) / 2) and (nTS < nbSlots + 10): nTS += 1 US = 2.5 - 1.5 * cos(2 * pi * Freq * dt * nTS) #print('{:4d}'.format(nC), '{:4d}'.format(nTS), '{:8.3f}'.format(UC), '{:8.3f}'.format(US)) print('{:3d}'.format(nTS), end=',') compte += 1 if (compte == 20): compte = 0; print() ==========================================================*/ // Inclus la bibliothèque qui gère l'affichage. #include <LiquidCrystal.h> // Initialise la bibliothèque avec les pins utilisés par l'afficheur LCD 2x16 muni de 5 boutons. //LiquidCrystal lcd(RS, E, D4, D5, D6, D7); LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // Valeurs calculées pour une fréquence de 400 [Hz]. // R = 2200 [Ohms] // C = 220 [nF] /* // 100 gestions d'interruption par demi-période. #define CENT 100 const byte volatile abT[] = { 9, 15, 19, 23, 25, 28, 30, 33, 35, 37, 39, 40, 42, 44, 45, 47, 48, 50, 51, 52, 54, 55, 56, 57, 59, 60, 61, 62, 63, 64, 65, 67, 68, 69, 70, 71, 72, 73, 74, 75, 77, 78, 79, 80, 81, 82, 84, 85, 86, 88, 90, 92, 94, 98 }; */ // 200 gestions d'interruption par demi-période. #define CENT 200 const byte volatile abT[] = { 12, 21, 27, 32, 36, 40, 43, 46, 49, 52, 54, 57, 59, 62, 64, 66, 68, 70, 72, 74, 76, 77, 79, 81, 83, 84, 86, 87, 89, 91, 92, 94, 95, 96, 98, 99,101,102,103,105, 106,107,109,110,111,112,114,115,116,117,118,120,121,122,123,124,125,127,128,129, 130,131,132,133,134,135,137,138,139,140,141,142,143,144,145,146,148,149,150,151, 152,153,154,155,157,158,159,160,161,163,164,165,166,168,169,170,172,173,175,176, 178,180,182,184,186,189,193 }; // Déclaration de Constantes #define btnNONE 0 #define btnSELECT 1 #define btnLEFT 2 #define btnDOWN 3 #define btnUP 4 #define btnRIGHT 5 // Nombre de menus #define MENU_MAX 6 #define pinFreqN 3 // = état opposé à celui de pintFreqP lors du fonctionnement. #define pinFreqP 2 // pour générer une fréquence char acStr[20]; // Un "array" de caractères, pour conversion de nombre en un String. String strS = ""; // Une chaine de caractères. int nButtonPush = btnNONE; // Indique le bouton pressé byte bMenu1 = 5; // indique le menu sélectionné byte bMenu1_Last = 0; // indique le dernier menu sélectionné word wTime0 = 61; // Compteur décrémenté de 1 toutes les 0.016384 [s] ~= 1/61 [s] // Utilisé pour ne pas lire l'état des boutons trop souvent. // Pour "volatile", c.f. https://www.arduino.cc/en/pmwiki.php?n=Reference/Volatile // Les variables utilisées dans la fonction de gestion d'interruption doivent être déclarée "volatile". // Cela impose que la variable est sauvegardée en RAM et non dans un registre. // Une variable sauvegardée dans un registre pourrait être modifiée accidentellement entre deux accès à la fonction. byte volatile bEtat = 0; // État des pins : pinFreqP et pinFreqN byte volatile bEtat_enable = 1; // Active ou désactive le signal. 1 = Actif word volatile OCR1A_new = 4000-1; // Fixe le temps entre deux transitions word volatile OCR1A_old = 4000-1; // Pour déterminer la variation de la variable OCR1A_new, qu'elle soit d'au moins +-1 float vOCR1A_calc_new = 0.0; // Nouvelle valeur de OCR1A pour générer la fréquence désirée. word wFact = 1; // Facteur de division de la fréquence d'incrémentation de TCNT1. Est modifiable suivant la fréquence générée. float vFreq = 400.0; // [Hz] Fréquence générée. float vFreq_base = 8000000.0; // [Hz] fréquence à laquelle le compteur TCNT1 est incrémenté. = 8000000.0 / vFreq float vFreq_delta = 0.0; // Variation de fréquence demandée // Pour la conversion ADC uint8_t bLow, bHigh; #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) // Formules de conversions //======================== // Selon la valeur de TCCR1B, bFac varie selon le tableau suivant : // TCCR1B 1 2 3 4 5 // bFac 1 8 64 256 1024 // Temps entre deux transistions = (OCR1A+1) * bFac * 62.5 * 10^(-9) [s] // 62.5e-9 = 1 / 16'000'000 // La période est le double du temps entre deux transistions // Fréquence = 8'000'000 [Hz] / ((OCR1A+1) * bFac) // // Pour Fréquence = Freq donnée : // OCR1A = -1 + Tronc(8'000'000 / (Freq * bFac) // Une fréquence supérieure à ~120 [kHz] est non atteignable. void setup() { //============ // Indique le nombre de lignes et de colonnes de l'afficheur LCD 16x2. lcd.begin(16, 2); // Affiche un message au départ, indiquant le progr. et la date lcd.setCursor(0, 0); lcd.print("Timer freq. LCD."); lcd.setCursor(0, 1); lcd.print("ex0460 20200617 "); delay(1200); lcd.setCursor(0, 0); lcd.print(" "); // efface le texte lcd.setCursor(0, 1); lcd.print(" "); // Initialise le Timer 1 pour déclencher les interruptions après temps défini dans OCR1A cli(); // Désactive l'interruption globale // de Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf // Timer/Counter0 C.f. page 93 // Il gère les fonctions delay(), delayMicroseconds, millis() et micros(), qui sont désactivées. // Lorsque le Timer 0 est désactivé, les 4 fonctions ci-dessus ne fonctionnent plus ! TCCR0A = 0b00000000; // default. Bits : COM0A1=0 ; COM0A0=0 ; COM0B1=0 ; COM0B0=0 ; / ; / ; WGM01=0 ; WGM00=0, donc pas de PWM lié au timer 0 TCCR0B = 0b00000101; // Clock * 1024 soit 64 micro-s et WGM02 = 0 TIMSK0 = 0b00000000; // Désactive les interruptions dues au Timer/Counter0 C.f. p. 109 // Ne pas utiliser TCNT0, mais le bit 0 de TIFR0 pour compter le nombre de boucles du compteur liées au timer 0. // Timer/Counter1 C.f. page 111 // Il est utilisé dans la bibliothèque de commande de servo-moteurs. //TIMSK1 = 0b00000001; // Active les les interruptions dues au Timer/Counter1 C.f. p. 135 // Une interruption est générée lorsque le compteur TCNT1 passe de 65535 à 0 (overflow) TIMSK1 = 0b00000010; // Active les les interruptions dues au Timer/Counter1 C.f. p. 135 // Lorsque le compteur TCNT1 égale OCR1A, ( car WGM1(3:0) = 0b0100 == 4 ) // TCNT1 passe à 0. // et une interruption est générée. TCCR1A = 0b00000000; // default. Bits : COM1A1=0 ; COM1A0=0 ; COM1B1=0 ; COM1B0=0 ; / ; / ; WGM11=0 ; WGM10=0, donc pas de PWM. TCCR1B = 0b00001001; // (WGM13 = 0 ; WGM12 = 1) fact = 1 //TCCR1B = 0b00001010; // (WGM13 = 0 ; WGM12 = 1) fact = 8 //TCCR1B = 0b00001011; // (WGM13 = 0 ; WGM12 = 1) fact = 64 //TCCR1B = 0b00001100; // (WGM13 = 0 ; WGM12 = 1) fact = 256 //TCCR1B = 0b00001101; // (WGM13 = 0 ; WGM12 = 1) fact = 1024 // Timer/Counter2 C.f. page 141 // Il est utilisé pour générer des tone() TIMSK2 = 0b00000000; // Désactive les les interruptions dues au Timer/Counter2 C.f. p. 157 sei(); // Active l'interruption globale pinMode(13, OUTPUT); // La pin 13 indique si un signal est généré ou si les sorties des pins 2 et 3 sont à 0 [V]. pinMode( pinFreqN, OUTPUT); // controle une borne de l'haut-parleur pinMode( pinFreqP, OUTPUT); // controle l'autre borne de l'haut-parleur = le niveau opposé à la pinFreqN digitalWrite( 13, bEtat_enable); digitalWrite( pinFreqN, LOW); // borne 1 à 0 V digitalWrite( pinFreqP, LOW); // borne 2 à 0 V vOCR1A_calc_new = -1 + vFreq_base / (CENT * vFreq); OCR1A_new = word(vOCR1A_calc_new); OCR1A = OCR1A_new; // Limite TOP du compteur TCNT1 avant son retour à 0. vFreq = (vFreq_base / CENT) / (OCR1A_new + 1.0); // Fréquence réelle, légèrement différente de la fréquence désirée. AfficheEtat(); // Pour la lecture de l'ADC // c.f. https://garretlab.web.fc2.com/en/arduino/inside/arduino/wiring_analog.c/analogRead.html ADMUX = 0b01000000; // 01 => Default reference voltage bit_5 = ADLAR = 0 => right adjusted bits_3210 = 0 => port 0 // start the conversion sbi(ADCSRA, ADSC); } // setup int read_LCD_buttons() { // ====================== // Lecture du bouton appuié // Il faut détecter 3 fois de suite le même bouton pour qu'on estime détecté le bon bouton pressé. // L'avantage de cette manière de faire est que la lecture des boutons ne prend que // le temps d'une lecture analogique, soit environ 100 micro secondes. // Une lecture toutes les 1/61 seconde. int nAdc_key_in = 0; byte bButton = btnNONE; // Bouton pressé selon la lecture de l'ADC. static byte bButtonLast = btnNONE; // Dernier bouton pressé. static byte bButtonCount = 0; // compte le nombre de fois que le même boutton est détecté à la suite. //nAdc_key_in = analogRead(0); // Lecture de la valeur du bouton pressé. // c.f. https://garretlab.web.fc2.com/en/arduino/inside/arduino/wiring_analog.c/analogRead.html //ADMUX = 0b01000000; // 01 => Default reference voltage bit_5 = ADLAR = 0 => right adjusted bits_3210 = 0 => port 0 // ADSC is cleared when the conversion finishes // while (bit_is_set(ADCSRA, ADSC)); // Plus besoin d'attendre, car cela est fait grace à : lwTempsLastReadBtn // Lecture de ADCL en premier. Il est mis dans un buffer et non modifié tant que ADCH n'a pas été lu. // La lecture de ADCL en deuxième bloquerait la lecture de l'ADC. // Il faut donc lire ADCH en deuxième. bLow = ADCL; bHigh = ADCH; // Démarre une conversion, qui sera terminée dans environ 100 [us]. // La fin de conversion peut se détecter en lisant ADSC, qui sera mis à 0 une fois la conversion terminée. // ADSC n'est pas utilisé ici, car on laisse amplement le temps de terminer la conversion en n'en faisant que 61 par seconde. sbi(ADCSRA, ADSC); if (bHigh >= 3) { // Aucun bouton pressé. bButtonLast = btnNONE; bButtonCount = 0; return btnNONE; } // Ici, on sait qu'un bouton a été pressé. nAdc_key_in = word(bHigh, bLow); // Conversion en un word. // Les lectures des boutons sont centrées autour des valeurs 0, 144, 329, 504, 741 // J'ai ajouté environ 50 à ces valeurs pour la détection des boutons if (nAdc_key_in > 555) bButton = btnSELECT; else if (nAdc_key_in > 380) bButton = btnLEFT; else if (nAdc_key_in > 195) bButton = btnDOWN; else if (nAdc_key_in > 55) bButton = btnUP; else bButton = btnRIGHT; if (bButton == bButtonLast) { // Le même bouton a été détecté bButtonCount++; // compte combien de fois de suite le même bouton est détecté. if (bButtonCount == 3) { bButtonCount = 0; wTime0 = 20; // Pour indiquer qu'un bouton a été traité, il faudra attendre avant de traiter à nouveau un bouton. return bButton; // Le même bouton a été détecté .. fois de suite, donc c'est le bon } } else { // Un nouveau bouton est détecté. bButtonLast = bButton; bButtonCount = 0; } return btnNONE; // On est pas encore sûr que le bon bouton est bButton. } // read_LCD_buttons void AfficheEtat() { //================== // Affichage de la fréquence ou de la variation de fréquence, suivant le menu if (bMenu1 <= MENU_MAX) { // Affiche Fréquence du signal 1 if (bMenu1 != bMenu1_Last) { lcd.setCursor(0, 0); if (bMenu1 == 1) lcd.print("Frequ +-0.001 Hz"); else if (bMenu1 == 2) lcd.print("Frequ +- 0.01 Hz"); else if (bMenu1 == 3) lcd.print("Frequ +- 0.10 Hz"); else if (bMenu1 == 4) lcd.print("Frequ +- 1 Hz "); else if (bMenu1 == 5) lcd.print("Frequ +- 10 Hz "); else if (bMenu1 == 6) lcd.print("Frequ +- 100 Hz "); } dtostrf(vFreq, 8, 3, acStr); // Conversion d'un nombre à virgule en un string prenant 8 caractères, avec 3 chiffres après la virgule. strS = acStr; strS = "Frequ.= " + strS; lcd.setCursor(0, 1); // Affichage en début de 2e ligne de l'afficheur LCD. lcd.print(strS); } } // AfficheEtat void MenuTreat() { //================ // Un bouton UP, DOWN ou SELECT a été pressé, traite l'action adéquat. vFreq_delta = 0.0; if (bMenu1 == 1) { // Changement de fréquence du signal 1 de +-1 milli-Hz if (nButtonPush == btnUP) vFreq_delta = 0.001; if (nButtonPush == btnDOWN) vFreq_delta = -0.001; } if (bMenu1 == 2) { // Changement de fréquence du signal 1 de +-10 milli-Hz if (nButtonPush == btnUP) vFreq_delta = 0.01; if (nButtonPush == btnDOWN) vFreq_delta = -0.01; } if (bMenu1 == 3) { // Changement de fréquence du signal 1 de +-100 milli-Hz if (nButtonPush == btnUP) vFreq_delta = 0.1; if (nButtonPush == btnDOWN) vFreq_delta = -0.1; } if (bMenu1 == 4) { // Changement de fréquence du signal 1 de +-1 Hz if (nButtonPush == btnUP) vFreq_delta = 1.0; if (nButtonPush == btnDOWN) vFreq_delta = -1.0; } if (bMenu1 == 5) { // Changement de fréquence du signal 1 de +-10 Hz if (nButtonPush == btnUP) vFreq_delta = 10.0; if (nButtonPush == btnDOWN) vFreq_delta = -10.0; } if (bMenu1 == 6) { // Changement de fréquence du signal 1 de +-100 Hz if (nButtonPush == btnUP) vFreq_delta = 100.0; if (nButtonPush == btnDOWN) vFreq_delta = -100.0; } if (vFreq_delta != 0) { // Il y a une demande de changement de fréquence vFreq = vFreq + vFreq_delta; OCR1A_old = OCR1A_new; vOCR1A_calc_new = -1 + vFreq_base / (CENT * vFreq); OCR1A_new = word(vOCR1A_calc_new); if (OCR1A_new == OCR1A_old) { // Force le changement de fréquence minimal. if (vFreq_delta > 0) OCR1A_new--; else OCR1A_new++; } vFreq = (vFreq_base / CENT) / (OCR1A_new + 1.0); // Fréquence réelle, légèrement différente de la fréquence désirée. } // if (vFreq_delta != 0) // Le bouton "Select" active ou désactive le signal if (nButtonPush == btnSELECT) { bEtat_enable = 1 - bEtat_enable; digitalWrite(13, bEtat_enable); // Indique sur l'état de la pin 13 si le signal est arrêté ou actif. digitalWrite( pinFreqP, bEtat & bEtat_enable); // off si signal désactivé digitalWrite( pinFreqN, (1 - bEtat) & bEtat_enable); // off si signal désactivé } AfficheEtat(); } // MenuTreat void Menu1_Change() { //=================== // Changement du Menu1 // Un bouton LEFT ou RIGHT a été pressé bMenu1_Last = bMenu1; // Mémorise le dernier menu if (nButtonPush == btnLEFT) { bMenu1 -= 1; if (bMenu1 == 0) bMenu1 = MENU_MAX; // boucle } if (nButtonPush == btnRIGHT) { bMenu1 += 1; if (bMenu1 > MENU_MAX) bMenu1 = 1; // boucle } AfficheEtat(); } // Menu1_Change void loop() { //=========== // Boucle principale. Voir également la routine de gestion d'interruption du timer 1 if (TIFR0 & 1) { // Le compteur du timer 0 a fait une boucle complète // Une boucle se fait en environ 1/61 [s]. Il y a donc 61 boucles par seconde. // Ceci laisse le temps au convertisseur ADC de terminer sa conversion TIFR0 = 1; // Efface les flags du registre TIFR0 // Ne lit l'état des boutons que si un bouton a été traité il y a plus de 0,33 seconde. if (wTime0 == 0) { // Lecture de l'état des boutons, pour changer la fréquence ou la phase du signal nButtonPush = read_LCD_buttons(); if ( (nButtonPush == btnLEFT) || (nButtonPush == btnRIGHT) ) Menu1_Change(); // Un bouton pressé, changement de menu if ( (nButtonPush == btnUP) || (nButtonPush == btnDOWN) || (nButtonPush == btnSELECT) ) MenuTreat(); // Un bouton pressé, traitement } if (wTime0 > 0) wTime0--; // Pour assurer qu'un bouton pressé ne soit pas traité trop rapidement plusieurs fois à la suite. } // if (TIFR0 & 1) { } // loop byte volatile bTS = 0; // Compte combien de fois la routine d'interruption a été appelée. Correspond au temps écoulé. byte volatile bTC = 0; // Compte le nombre de boucles durant lesquelles le condensateur a été chargé. // routine d'interruption du timer 1, lorsque le registre OCR1A est utilisé, ce qui est le cas dans ce programme. ISR (TIMER1_COMPA_vect) { //======================= // Transition de l'état // TCNT1 = valeur du compteur, qui est compté de 0 à OCR1A, puis repasse à 0. // Lorsqu'on arrive dans cette fonction de gestion d'interruption, TCNT1 = 0. // Il est préférable de ne pas utiliser directement le registre TCNT1, car sa valeur peut varier très rapidement. // Le compteur TCNT1 est incrémenté de 1 chaque 0.0625 [us] * facteur défini dans TCCR1B. // C.f. : https://www.arduino.cc/en/Reference/PortManipulation pour la manipulation de PORT, // pour accéder beaucoup plus rapidement aux ports de l'Arduino. bTS++; if ( bTS == abT[bTC] ) { // Charge le condensateur //pinMode( pinFreqN, OUTPUT); // controle une borne de l'haut-parleur DDRD = DDRD | 0b00001000; // pinFreqN = 3, donc le bit 3 est mis à : OUTPUT == 1 if (bEtat) PORTD = PORTD | 0b00001000; // Pour accéder beaucoup plus rapidement aux pins de l'Arduino. else PORTD = PORTD & 0b11110111; bTC++; // On a chargé le condensateur durant une boucle supplémentaire. } else { // Met en mode input, avec haute impédence, le condensateur reste à tension constante. //pinMode( pinFreqN, INPUT); // Met la pin à haute impédence DDRD = DDRD & 0b11110111; // pinFreqN = 3, donc le bit 3 est mis à : INPUT == 0 PORTD = PORTD & 0b11110111; // Pour ne pas avoir de "PULL-UP", pin 3 mise dans un état de haute impédence. } if (bTS < CENT) return; // On passe de la phase montante à la phase descendante ou de la descendante à la montante. bTS = 0; bTC = 0; OCR1A = OCR1A_new; // Définit la durée de la prochaine période de transition. Elle reste généralement la même if (bEtat_enable) { // Manière beaucoup plus rapide de changer l'état des pins, permet de générer de plus hautes fréquences. // C.f. AVR_Assembler_Lecture6.pdf bEtat = 1 - bEtat; if (bEtat) PORTD = PORTD & 0b11111011; // Pour accéder beaucoup plus rapidement aux pins de l'Arduino. else PORTD = PORTD | 0b00000100; // PORTD contrôle les pins de 0 à 7. bit des unités = port 0 // PORTB contrôle les pins de 8 à 13 --- bits 0 à 5 // PORTC contrôle les pins de 14 à 19 --- bits 0 à 5. Correspond aux pins analogiques, pouvant aussi être utilisés en digital. } } // ISR // FIN.
/* ex0463_timer_1_interrupt_digital_oscillo_LCD.ino Génère deux signaux de fréquences ajustables. Un sur la pin 11 et l'autre sur la pin 12. Permet d'étudier des déphasages, des battements et des figures de lissajous sur un OSCILLOSCOPE. Il est également possible de gérer précisément le déphasage entre les deux signaux. C.f. ex1150_digital_oscillo_LCD,ino En parcourant le menu, on peut choisir 4 paires de fréquences mystères, à déterminer à l'aide d'un oscilloscope, comme exercice. Le code "uududd" permet d'afficher les fréquences générées, par défaut elles ne sont pas générées. En pressant sur la touche "Select" au démarrage, le code est mis à "uupudd". C'est une version plus sophistiquée que celle de ex1150_digital_oscillo_LCD.ino, car elle utilise le timer 1 pour générer les interruptions et le timer 0 pour le temps. Utilisation du timer 1 pour générer des interruptions. Utilise le registre OCR1A pour générer les interruptions. Le timer 0 est utilisé pour une mesure du temps, sans génération d'interruption. Les fonctions "delay", "delayMicroseconds", "millis" et "micros" ne sont pas utilisables. Elles sont désactivées, pour augmenter la précision de la période de génération d'interruptions. L'affichage LCD, avec les 5 boutons de commande est utilisé. La pin 13 indique si le signal est activé (1) ou désactivé (0). Accès beaucoup plus rapide aux changement d'état des pins qu'avec la fonction "digitalWrite". C.f. "Arduino Cookbook" de Michael Margolis, édition O'reilly, page 628 et "AVR_Assembler_Lecture6.pdf", page 53 Permet de générer une fréquence de 0.12 [Hz] à 120'000 [Hz]. La lecture de l'ADC 0, pour connaître le bouton pressé est fait de manière beaucoup plus rapide qu'en utilisant la fonction analogRead. Le temps de conversion est toujours d'environ 100 [us], mais aucune attente n'est faite en l'initialisation du début de conversion et le résultat de la mesure, 100 [us] plus tard. [us] = micro-seconde. ==========================================================*/ // Inclus la bibliothèque qui gère l'affichage. #include <LiquidCrystal.h> // Initialise la bibliothèque avec les pins utilisés par l'afficheur LCD 2x16 muni de 5 boutons. //LiquidCrystal lcd(RS, E, D4, D5, D6, D7); LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // Déclaration de Constantes #define btnNONE 0 #define btnSELECT 1 #define btnLEFT 2 #define btnDOWN 3 #define btnUP 4 #define btnRIGHT 5 // Nombre de menus #define MENU_MAX 12 // Les deux pins sur lesquelles les signaux sont générés. #define PIN11 11 #define PIN12 12 char acStr[20]; // Un "array" de caractères, pour conversion de nombre en un String. String strS = ""; // Une chaine de caractères. int nButtonPush = btnNONE; // Indique le bouton pressé byte bMenu1 = 4; // indique le menu sélectionné byte bMenu1_Last = 0; // indique le dernier menu sélectionné word wTime0 = 61; // Compteur décrémenté de 1 toutes les 0.016384 [s] ~= 1/61 [s] // Utilisé pour ne pas lire l'état des boutons trop souvent. byte bMenu_mytere = 0; // Définit la fréquence mystère à déterminer byte bAfficheF = 1; // indique si la fréquence est affichée ou non. Est initialisée dans la fonction setup(). String strCode = "uududd"; // Code pour demander d'afficher les fréquences. // Pour "volatile", c.f. https://www.arduino.cc/en/pmwiki.php?n=Reference/Volatile // Les variables utilisées dans la fonction de gestion d'interruption doivent être déclarée "volatile". // Cela impose que la variable est sauvegardée en RAM et non dans un registre. // Une variable sauvegardée dans un registre pourrait être modifiée accidentellement entre deux accès à la fonction. byte volatile bEtat11 = 0; // État de la pin 11 byte volatile bEtat12 = 0; // État de la pin 12 byte volatile bEtat_enable = 1; // Active ou désactive le signal. 1 = Actif word volatile wOCR11_new = 4000-1; // Fixe le temps entre deux transitions du signal sur la pin 11 word volatile wOCR12_new = 4000-1; // Fixe le temps entre deux transitions du signal sur la pin 12 word volatile wOCR11_old = 4000-1; word volatile wOCR12_old = 4000-1; word volatile wOCR11 = 4000-1; word volatile wOCR12 = 4000-1; byte volatile bPinNext = 12; // Indique quelle pin changera d'état à la prochaine interruption word wFact = 1; // Facteur de division de la fréquence d'incrémentation de TCNT1. Est modifiable suivant la fréquence générée. float vFreq11 = 400.0; // [Hz] Fréquence générée. float vFreq12 = 400.0; // [Hz] Fréquence générée. float vFreq_base = 8000000.0; // [Hz] fréquence à laquelle le compteur TCNT1 est incrémenté. = 8000000.0 / vFreq float vFreq_delta11 = 0.0; // Variation de fréquence 1 demandée float vFreq_delta12 = 0.0; // Variation de fréquence 2 demandée float vOCR11_calc_new = 0.0; // Pour le calcule de wOCR11_new en fonction de la fréquence désirée. float vOCR12_calc_new = 0.0; // Pour le calcule de wOCR12_new en fonction de la fréquence désirée. // Pour la conversion ADC uint8_t bLow, bHigh; #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) // Formules de conversions //======================== // Selon la valeur de TCCR1B, bFac varie selon le tableau suivant : // TCCR1B 1 2 3 4 5 // wFact 1 8 64 256 1024 // Temps entre deux transistions = (OCR1A+1) * wFact * 62.5 * 10^(-9) [s] // 62.5e-9 = 1 / 16'000'000 // La période est le double du temps entre deux transistions // Fréquence = 8'000'000 [Hz] / ((OCR1A+1) * wFact) // // Pour Fréquence = Freq donnée : // OCR1A = -1 + Tronc(8'000'000 / (Freq * wFact) // Une fréquence supérieure à ~120 [kHz] est non atteignable. void setup() { //============ // Indique le nombre de lignes et de colonnes de l'afficheur LCD 16x2. lcd.begin(16, 2); // Affiche un message au départ, indiquant le progr. et la date lcd.setCursor(0, 0); lcd.print("Timer freq. LCD."); lcd.setCursor(0, 1); lcd.print("ex0463 20200620 "); delay(1200); lcd.setCursor(0, 0); lcd.print(" "); // efface le texte lcd.setCursor(0, 1); lcd.print(" "); // Pour afficher les fréquences, si au départ on presse sur le bouton "Select". if ( (analogRead(0) > 555) && (analogRead(0) < 1000) ) { bAfficheF = 1; strCode = "uududd"; } else { bAfficheF = 0; strCode = ""; } // Initialise le Timer 1 pour déclencher les interruptions après temps défini dans OCR1A cli(); // Désactive l'interruption globale // de Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf // Timer/Counter0 C.f. page 93 // Il gère les fonctions delay(), delayMicroseconds, millis() et micros(), qui sont désactivées. // Lorsque le Timer 0 est désactivé, les 4 fonctions ci-dessus ne fonctionnent plus ! TCCR0A = 0b00000000; // default. Bits : COM0A1=0 ; COM0A0=0 ; COM0B1=0 ; COM0B0=0 ; / ; / ; WGM01=0 ; WGM00=0, donc pas de PWM lié au timer 0 TCCR0B = 0b00000101; // Clock * 1024 soit 64 micro-s et WGM02 = 0 TIMSK0 = 0b00000000; // Désactive les interruptions dues au Timer/Counter0 C.f. p. 109 // Ne pas utiliser TCNT0, mais le bit 0 de TIFR0 pour compter le nombre de boucles du compteur liées au timer 0. // Timer/Counter2 C.f. page 141 // Il est utilisé pour générer des tone() TIMSK2 = 0b00000000; // Désactive les les interruptions dues au Timer/Counter2 C.f. p. 157 // Timer/Counter1 C.f. page 111 // Il est utilisé dans la bibliothèque de commande de servo-moteurs. //TIMSK1 = 0b00000001; // Active les les interruptions dues au Timer/Counter1 C.f. p. 135 // Une interruption est générée lorsque le compteur TCNT1 passe de 65535 à 0 (overflow) TIMSK1 = 0b00000010; // Active les les interruptions dues au Timer/Counter1 C.f. p. 135 // Lorsque le compteur TCNT1 égale OCR1A, ( car WGM1(3:0) = 0b0100 == 4 ) // TCNT1 passe à 0. // et une interruption est générée. TCCR1A = 0b00000000; // default. Bits : COM1A1=0 ; COM1A0=0 ; COM1B1=0 ; COM1B0=0 ; / ; / ; WGM11=0 ; WGM10=0, donc pas de PWM. TCCR1B = 0b00001001; // (WGM13 = 0 ; WGM12 = 1) fact = 1 //TCCR1B = 0b00001010; // (WGM13 = 0 ; WGM12 = 1) fact = 8 //TCCR1B = 0b00001011; // (WGM13 = 0 ; WGM12 = 1) fact = 64 //TCCR1B = 0b00001100; // (WGM13 = 0 ; WGM12 = 1) fact = 256 //TCCR1B = 0b00001101; // (WGM13 = 0 ; WGM12 = 1) fact = 1024 vOCR11_calc_new = -1 + vFreq_base / vFreq11; wOCR11_new = round(vOCR11_calc_new); wOCR11 = wOCR11_new + wOCR11_new / 3; // "+ wOCR11_new / 3" crée un déphasage au départ vFreq11 = vFreq_base / (wOCR11_new + 1.0); // Fréquence réelle, légèrement différente de la fréquence désirée. vOCR12_calc_new = -1 + vFreq_base / vFreq12; wOCR12_new = round(vOCR12_calc_new); wOCR12 = wOCR12_new; vFreq12 = vFreq_base / (wOCR12_new + 1.0); // Fréquence réelle, légèrement différente de la fréquence désirée. OCR1A = wOCR12_new; sei(); // Active l'interruption globale pinMode(13, OUTPUT); // La pin 13 indique si un signal est généré ou si les sorties des pins 2 et 3 sont à 0 [V]. pinMode( PIN11, OUTPUT); pinMode( PIN12, OUTPUT); digitalWrite( 13, bEtat_enable); digitalWrite( PIN11, bEtat11); digitalWrite( PIN12, bEtat12); AfficheEtat(); // Pour la lecture de l'ADC // c.f. https://garretlab.web.fc2.com/en/arduino/inside/arduino/wiring_analog.c/analogRead.html ADMUX = 0b01000000; // 01 => Default reference voltage bit_5 = ADLAR = 0 => right adjusted bits_3210 = 0 => port 0 // start the conversion sbi(ADCSRA, ADSC); } // setup int read_LCD_buttons() { // ====================== // Lecture du bouton appuié // Il faut détecter 3 fois de suite le même bouton pour qu'on estime détecté le bon bouton pressé. // L'avantage de cette manière de faire est que la lecture des boutons ne prend que // le temps d'une lecture analogique, soit environ 100 micro secondes. // Une lecture toutes les 1/61 seconde. int nAdc_key_in = 0; byte bButton = btnNONE; // Bouton pressé selon la lecture de l'ADC. static byte bButtonLast = btnNONE; // Dernier bouton pressé. static byte bButtonCount = 0; // compte le nombre de fois que le même boutton est détecté à la suite. //nAdc_key_in = analogRead(0); // Lecture de la valeur du bouton pressé. // c.f. https://garretlab.web.fc2.com/en/arduino/inside/arduino/wiring_analog.c/analogRead.html //ADMUX = 0b01000000; // 01 => Default reference voltage bit_5 = ADLAR = 0 => right adjusted bits_3210 = 0 => port 0 // ADSC is cleared when the conversion finishes // while (bit_is_set(ADCSRA, ADSC)); // Plus besoin d'attendre, car cela est fait grace à : lwTempsLastReadBtn // Lecture de ADCL en premier. Il est mis dans un buffer et non modifié tant que ADCH n'a pas été lu. // La lecture de ADCL en deuxième bloquerait la lecture de l'ADC. // Il faut donc lire ADCH en deuxième. bLow = ADCL; bHigh = ADCH; // Démarre une conversion, qui sera terminée dans environ 100 [us]. // La fin de conversion peut se détecter en lisant ADSC, qui sera mis à 0 une fois la conversion terminée. // ADSC n'est pas utilisé ici, car on laisse amplement le temps de terminer la conversion en n'en faisant que 61 par seconde. sbi(ADCSRA, ADSC); if (bHigh >= 3) { // Aucun bouton pressé. bButtonLast = btnNONE; bButtonCount = 0; return btnNONE; } // Ici, on sait qu'un bouton a été pressé. nAdc_key_in = word(bHigh, bLow); // Conversion en un word. // Les lectures des boutons sont centrées autour des valeurs 0, 144, 329, 504, 741 // J'ai ajouté environ 50 à ces valeurs pour la détection des boutons if (nAdc_key_in > 555) bButton = btnSELECT; else if (nAdc_key_in > 380) bButton = btnLEFT; else if (nAdc_key_in > 195) bButton = btnDOWN; else if (nAdc_key_in > 55) bButton = btnUP; else bButton = btnRIGHT; if (bButton == bButtonLast) { // Le même bouton a été détecté bButtonCount++; // compte combien de fois de suite le même bouton est détecté. if (bButtonCount == 3) { bButtonCount = 0; wTime0 = 20; // Pour indiquer qu'un bouton a été traité, il faudra attendre avant de traiter à nouveau un bouton. return bButton; // Le même bouton a été détecté .. fois de suite, donc c'est le bon } } else { // Un nouveau bouton est détecté. bButtonLast = bButton; bButtonCount = 0; } return btnNONE; // On est pas encore sûr que le bon bouton est bButton. } // read_LCD_buttons void AfficheEtat() { //================== // Affichage de la fréquence ou de la variation de fréquence, suivant le menu if (bMenu1 <= MENU_MAX) { // Affiche Fréquence du signal 1 if (bMenu1 != bMenu1_Last) { lcd.setCursor(0, 0); if (bMenu1 == 1) lcd.print("Freq 1 +-0.01 Hz"); else if (bMenu1 == 2) lcd.print("Freq 1 +-0.1 Hz"); else if (bMenu1 == 3) lcd.print("Freq 1 +-1 Hz"); else if (bMenu1 == 4) lcd.print("Freq 1 +-10 Hz"); else if (bMenu1 == 5) lcd.print("Freq 1 +-100 Hz"); else if (bMenu1 == 6) lcd.print("Freq 2 +-0.01 Hz"); else if (bMenu1 == 7) lcd.print("Freq 2 +-0.1 Hz"); else if (bMenu1 == 8) lcd.print("Freq 2 +-1 Hz"); else if (bMenu1 == 9) lcd.print("Freq 2 +-10 Hz"); else if (bMenu1 ==10) lcd.print("Freq 2 +-100 Hz"); else if (bMenu1 ==11) lcd.print("Freq mystere "); else if (bMenu1 ==12) lcd.print("pass "); } if (bMenu1 == 12) { // Menu de la demande d'un mot de passe (Code) pour afficher les fréquences // Le mot de passe est : up up down up down down // "Select" efface le mot tapé. lcd.setCursor(0, 0); lcd.print("C : "); lcd.setCursor(0, 1); lcd.print(" "); lcd.setCursor(0, 1); // lcd.print(strCode); // strCode = "uududd" est le bon Code. strS = ""; for(int nn = 1; nn<=strCode.length(); nn++) strS += "*"; // Les lettres sont remplacées par des * lcd.print(strS); } else { if (bMenu1 <= 5) { dtostrf(vFreq11, 8, 3, acStr); // Conversion d'un nombre à virgule en un string prenant 8 caractères, avec 3 chiffres après la virgule. strS = acStr; strS = "Freq 1 =" + strS; //sprintf(acStr, "%8d", wOCR11_new); strS = strS + acStr; // Pour des tests } else if (bMenu1 <= 10) { dtostrf(vFreq12, 8, 3, acStr); // Conversion d'un nombre à virgule en un string prenant 8 caractères, avec 3 chiffres après la virgule. strS = acStr; strS = "Freq 2 =" + strS; //sprintf(acStr, "%8d", wOCR12_new); strS = strS + acStr; // Pour des tests } else { // bMenu1 == 11 sprintf(acStr, "Freq Nr : %3d ", bMenu_mytere); strS = acStr; } if ( (!bAfficheF) && (bMenu1 <= 10) ) strS = " "; // Efface la deuxième ligne lcd.setCursor(0, 1); // Affichage en début de 2e ligne de l'afficheur LCD. lcd.print(strS); } } } // AfficheEtat void MenuTreat() { //================ // Un bouton UP, DOWN ou SELECT a été pressé, traite l'action adéquat. vFreq_delta11 = 0.0; vFreq_delta12 = 0.0; if (bMenu1 == 1) { // Changement de fréquence du signal 1 de +-10 milli-Hz if (nButtonPush == btnUP) vFreq_delta11 = 0.01; if (nButtonPush == btnDOWN) vFreq_delta11 = -0.01; } if (bMenu1 == 2) { // Changement de fréquence du signal 1 de +-100 milli-Hz if (nButtonPush == btnUP) vFreq_delta11 = 0.1; if (nButtonPush == btnDOWN) vFreq_delta11 = -0.1; } if (bMenu1 == 3) { // Changement de fréquence du signal 1 de +-1 Hz if (nButtonPush == btnUP) vFreq_delta11 = 1.0; if (nButtonPush == btnDOWN) vFreq_delta11 = -1.0; } if (bMenu1 == 4) { // Changement de fréquence du signal 1 de +-10 Hz if (nButtonPush == btnUP) vFreq_delta11 = 10.0; if (nButtonPush == btnDOWN) vFreq_delta11 = -10.0; } if (bMenu1 == 5) { // Changement de fréquence du signal 1 de +-100 Hz if (nButtonPush == btnUP) vFreq_delta11 = 100.0; if (nButtonPush == btnDOWN) vFreq_delta11 = -100.0; } if (bMenu1 == 6) { // Changement de fréquence du signal 2 de +-10 milli-Hz if (nButtonPush == btnUP) vFreq_delta12 = 0.01; if (nButtonPush == btnDOWN) vFreq_delta12 = -0.01; } if (bMenu1 == 7) { // Changement de fréquence du signal 2 de +-100 milli-Hz if (nButtonPush == btnUP) vFreq_delta12 = 0.1; if (nButtonPush == btnDOWN) vFreq_delta12 = -0.1; } if (bMenu1 == 8) { // Changement de fréquence du signal 2 de +-1 Hz if (nButtonPush == btnUP) vFreq_delta12 = 1.0; if (nButtonPush == btnDOWN) vFreq_delta12 = -1.0; } if (bMenu1 == 9) { // Changement de fréquence du signal 2 de +-10 Hz if (nButtonPush == btnUP) vFreq_delta12 = 10.0; if (nButtonPush == btnDOWN) vFreq_delta12 = -10.0; } if (bMenu1 == 10) { // Changement de fréquence du signal 2 de +-100 Hz if (nButtonPush == btnUP) vFreq_delta12 = 100.0; if (nButtonPush == btnDOWN) vFreq_delta12 = -100.0; } if (bMenu1 == 11) { // Fréquence mystère, à déterminer avec l'oscilloscope // Elle n'est déterminée que si un bouton UP ou DOWN est pressé depuis ce menu if ( (nButtonPush == btnUP) || (nButtonPush == btnDOWN) ) { if (nButtonPush == btnUP) { bMenu_mytere++; if (bMenu_mytere >= 4) bMenu_mytere = 0; } if (nButtonPush == btnDOWN) { if (bMenu_mytere == 0) bMenu_mytere = 4; bMenu_mytere--; } if (bMenu_mytere == 0) { vFreq11 = 500.0; vFreq12 = 1000.0; } else if (bMenu_mytere == 1) { vFreq11 = 800.0; vFreq12 = 800.08; } else if (bMenu_mytere == 2) { vFreq11 = 200.0; vFreq12 = 800.08; } else if (bMenu_mytere == 3) { vFreq11 = 2000.0; vFreq12 = 8000.0; } vOCR11_calc_new = -1 + vFreq_base / vFreq11; wOCR11_new = word(vOCR11_calc_new); vFreq11 = vFreq_base / (wOCR11_new + 1.0); // Fréquence réelle, légèrement différente de la fréquence désirée. vOCR12_calc_new = -1 + vFreq_base / vFreq12; wOCR12_new = word(vOCR12_calc_new); vFreq12 = vFreq_base / (wOCR12_new + 1.0); // Fréquence réelle, légèrement différente de la fréquence désirée. TCNT1 = 0; // Pour redémarrer le compteur depuis 0. wOCR11 = wOCR11_new + wOCR11_new / 4; // Pour définir la phase wOCR12 = wOCR12_new; // Si on veu définir la phase OCR1A = wOCR12; // = min(wOCR11, wOCR12) bPinNext = 12; } } if (bMenu1 == 12) { // Code pour affichage des fréquences if (nButtonPush == btnSELECT) strCode = ""; // Efface le code tapé if (nButtonPush == btnUP) strCode += "u"; if (nButtonPush == btnDOWN) strCode += "d"; if (strCode == "uududd") bAfficheF = 1; // Bon mot de passe else bAfficheF = 0; // Mauvais mot de passe, on affiche pas les fréquences générées. // Efface la deuxième ligne if (!bAfficheF) { strS = " "; // N'affiche pas la fréquence lcd.setCursor(0, 1); lcd.print(strS); } } if (vFreq_delta11 != 0) { // Il y a une demande de changement de fréquence sur la pin 11 vFreq11 = vFreq11 + vFreq_delta11; wOCR11_old = wOCR11_new; vOCR11_calc_new = -1 + vFreq_base / vFreq11; // Teste que les capacités du compteur TCNT1 liée au timer 1 ne soient pas dépassées if (vOCR11_calc_new > 65535.0) { // Passe à un diviseur de fréquence (wFact) plus grand, pour générer une plus basse fréquence, une plus haute période. wOCR11_old = 0; // Assure que : wOCR11_old != wOCR11_new if (wFact == 1024) { // Assure de ne pas descendre en-dessous d'une fréquence trop basse, non atteignable par le Timer 1 de l'Arduino. vFreq11 = 0.1193; } else if (wFact == 256) { wFact = 1024; TCCR1B = 0b00001101; // (WGM13 = 0 ; WGM12 = 1) fact = 1024 } else if (wFact == 64) { wFact = 256; TCCR1B = 0b00001100; // (WGM13 = 0 ; WGM12 = 1) fact = 256 } else if (wFact == 8) { wFact = 64; TCCR1B = 0b00001011; // (WGM13 = 0 ; WGM12 = 1) fact = 64 } else if (wFact == 1) { wFact = 8; TCCR1B = 0b00001010; // (WGM13 = 0 ; WGM12 = 1) fact = 8 } vFreq_base = 8000000.0 / wFact; // Changement de la fréquence de base vOCR11_calc_new = -1 + vFreq_base / vFreq11; } // if (vOCR11_calc_new > 65535.0) // Teste si l'on peut être plus précis dans la fréquence générée. else if (vOCR11_calc_new < 7500.0) { // Passe à un diviseur de fréquence plus petit, pour être plus précis dans la fréquence générée. // Si c'est compatible avec la deuxième fréquence et la fréquence max. if (wFact == 1) { // Teste que la fréquence ne soit pas trop grande, sinon dépasse les capacités de génération de fréquences de l'Arduino. if (vFreq11 > 120000.0) { // Fréquence max. = 120 [kHz]. vFreq11 = 120000.0; //wOCR11_old = 0; // Assure que : wOCR11_old != wOCR11_new } } else if (wOCR12_new < 7500) { // Il faut que la deuxième fréquence permette également le changement du facteur wOCR11_old = 0; // Assure que : wOCR11_old != wOCR11_new if (wFact == 8) { wFact = 1; TCCR1B = 0b00001001; // (WGM13 = 0 ; WGM12 = 1) fact = 1 } else if (wFact == 64) { wFact = 8; TCCR1B = 0b00001010; // (WGM13 = 0 ; WGM12 = 1) fact = 8 } else if (wFact == 256) { wFact = 64; TCCR1B = 0b00001011; // (WGM13 = 0 ; WGM12 = 1) fact = 64 } else if (wFact == 1024) { wFact = 256; TCCR1B = 0b00001100; // (WGM13 = 0 ; WGM12 = 1) fact = 256 } } vFreq_base = 8000000.0 / wFact; // Changement de la fréquence de base vOCR11_calc_new = -1 + vFreq_base / vFreq11; } // else if (vOCR11_calc_new < 7500.0) wOCR11_new = round(vOCR11_calc_new); if (wOCR11_new == wOCR11_old) { // Force le changement de fréquence minimal. if (vFreq_delta11 > 0) wOCR11_new--; else wOCR11_new++; } vFreq11 = vFreq_base / (wOCR11_new + 1.0); // Fréquence réelle, légèrement différente de la fréquence désirée. vOCR12_calc_new = -1 + vFreq_base / vFreq12; // Utile si : vFreq_base a changé wOCR12_new = round(vOCR12_calc_new); } // if (vFreq_delta11 != 0) if (vFreq_delta12 != 0) { // Il y a une demande de changement de fréquence sur la pin 12 vFreq12 = vFreq12 + vFreq_delta12; wOCR12_old = wOCR12_new; vOCR12_calc_new = -1 + vFreq_base / vFreq12; // Teste que les capacités du compteur TCNT1 lié au timer 1 ne soient pas dépassées if (vOCR12_calc_new > 65535.0) { // Passe à un diviseur de fréquence (wFact) plus grand, pour générer une plus basse fréquence, une plus haute période. wOCR12_old = 0; // Assure que : wOCR12_old != wOCR12_new if (wFact == 1024) { // Assure de ne pas descendre en-dessous d'une fréquence trop basse, non atteignable par le Timer 1 de l'Arduino. vFreq12 = 0.1193; } else if (wFact == 256) { wFact = 1024; TCCR1B = 0b00001101; // (WGM13 = 0 ; WGM12 = 1) fact = 1024 } else if (wFact == 64) { wFact = 256; TCCR1B = 0b00001100; // (WGM13 = 0 ; WGM12 = 1) fact = 256 } else if (wFact == 8) { wFact = 64; TCCR1B = 0b00001011; // (WGM13 = 0 ; WGM12 = 1) fact = 64 } else if (wFact == 1) { wFact = 8; TCCR1B = 0b00001010; // (WGM13 = 0 ; WGM12 = 1) fact = 8 } vFreq_base = 8000000.0 / wFact; // Changement de la fréquence de base vOCR12_calc_new = -1 + vFreq_base / vFreq12; } // if (vOCR12_calc_new > 65535.0) // Teste si l'on peut être plus précis dans la fréquence générée. else if (vOCR12_calc_new < 7500.0) { // Passe à un diviseur de fréquence plus petit, pour être plus précis dans la fréquence générée. if (wFact == 1) { // Teste que la fréquence ne soit pas trop grande, sinon dépasse les capacités de génération de fréquences de l'Arduino. if (vFreq12 > 120000.0) { // Fréquence max. = 120 [kHz]. vFreq12 = 120000.0; //wOCR12_old = 0; // Assure que : wOCR12_old != wOCR12_new } } else if (wOCR11_new < 7500) { // Il faut que la deuxième fréquence permette également le changement du facteur wOCR12_old = 0; // Assure que : wOCR12_old != wOCR12_new if (wFact == 8) { wFact = 1; TCCR1B = 0b00001001; // (WGM13 = 0 ; WGM12 = 1) fact = 1 } else if (wFact == 64) { wFact = 8; TCCR1B = 0b00001010; // (WGM13 = 0 ; WGM12 = 1) fact = 8 } else if (wFact == 256) { wFact = 64; TCCR1B = 0b00001011; // (WGM13 = 0 ; WGM12 = 1) fact = 64 } else if (wFact == 1024) { wFact = 256; TCCR1B = 0b00001100; // (WGM13 = 0 ; WGM12 = 1) fact = 256 } } vFreq_base = 8000000.0 / wFact; // Changement de la fréquence de base vOCR12_calc_new = -1 + vFreq_base / vFreq12; } // else if (vOCR12_calc_new < 7500.0) wOCR12_new = round(vOCR12_calc_new); if (wOCR12_new == wOCR12_old) { // Force le changement de fréquence minimal. if (vFreq_delta12 > 0) wOCR12_new--; else wOCR12_new++; } vFreq12 = vFreq_base / (wOCR12_new + 1.0); // Fréquence réelle, légèrement différente de la fréquence désirée. vOCR11_calc_new = -1 + vFreq_base / vFreq11; // Utile si : vFreq_base a changé wOCR11_new = round(vOCR11_calc_new); } // if (vFreq_delta12 != 0) // Le bouton "Select" active ou désactive le signal if ( (nButtonPush == btnSELECT) && (bMenu1 != 12) ) { bEtat_enable = 1 - bEtat_enable; digitalWrite(13, bEtat_enable); // Indique sur l'état de la pin 13 si le signal est arrêté ou actif. digitalWrite( PIN11, bEtat11 & bEtat_enable); // off si signal désactivé digitalWrite( PIN12, bEtat12 & bEtat_enable); // off si signal désactivé } AfficheEtat(); } // MenuTreat void Menu1_Change() { //=================== // Changement du Menu1 // Un bouton LEFT ou RIGHT a été pressé bMenu1_Last = bMenu1; // Mémorise le dernier menu if (nButtonPush == btnLEFT) { bMenu1 -= 1; if (bMenu1 == 0) bMenu1 = MENU_MAX; // boucle } if (nButtonPush == btnRIGHT) { bMenu1 += 1; if (bMenu1 > MENU_MAX) bMenu1 = 1; // boucle } AfficheEtat(); } // Menu1_Change void loop() { //=========== // Boucle principale. Voir également la routine de gestion d'interruption du timer 1 if (TIFR0 & 1) { // Le compteur du timer 0 a fait une boucle complète // Une boucle se fait en environ 1/61 [s]. Il y a donc 61 boucles par seconde. // Ceci laisse le temps au convertisseur ADC de terminer sa conversion TIFR0 = 1; // Efface les flags du registre TIFR0 // Ne lit l'état des boutons que si un bouton a été traité il y a plus de 0,33 seconde. if (wTime0 == 0) { // Lecture de l'état des boutons, pour changer la fréquence ou la phase du signal nButtonPush = read_LCD_buttons(); if ( (nButtonPush == btnLEFT) || (nButtonPush == btnRIGHT) ) Menu1_Change(); // Un bouton pressé, changement de menu if ( (nButtonPush == btnUP) || (nButtonPush == btnDOWN) || (nButtonPush == btnSELECT) ) MenuTreat(); // Un bouton pressé, traitement } if (wTime0 > 0) wTime0--; // Pour assurer qu'un bouton pressé ne soit pas traité trop rapidement plusieurs fois à la suite. } // if (TIFR0 & 1) { } // loop // routine d'interruption du timer 1, lorsque le registre OCR1A est utilisé, ce qui est le cas dans ce programme. ISR (TIMER1_COMPA_vect) { //======================= // Transition de l'état // TCNT1 = valeur du compteur, qui est compté de 0 à OCR1A, puis repasse à 0. // Une boucle complète fait OCR1A+1 (cycles d'horloge / wFact) // Lorsqu'on arrive dans cette fonction de gestion d'interruption, TCNT1 = 0. // Il est préférable de ne pas utiliser directement le registre TCNT1, car sa valeur peut varier très rapidement. // Le compteur TCNT1 est incrémenté de 1 chaque 0.0625 [us] * facteur défini dans TCCR1B. (facteur = wFact) if (!bEtat_enable) return; // changements d'états désactivés. if (bPinNext == 11) { // Manière beaucoup plus rapide de changer l'état des pins, permet de générer de plus hautes fréquences. // C.f. AVR_Assembler_Lecture6.pdf // Transition d'état de la pin 11 bEtat11 = 1 - bEtat11; if (bEtat11) PORTB = PORTB | 0b00001000; else PORTB = PORTB & 0b11110111; // PORTD contrôle les pins de 0 à 7. bit des unités = port 0 // PORTB contrôle les pins de 8 à 13 --- bits 0 à 5 // PORTC contrôle les pins de 14 à 19 --- bits 0 à 5. Correspond aux pins analogiques, pouvant aussi être utilisés en digital. wOCR11 = wOCR11_new; wOCR12 -= OCR1A+1; // ici, on sait que : wOCR12 >= OCR1A+1 if (wOCR12 < 128) { // 128 --- 8 [us] si fact = 1 ( 112 est trop petit ! ) // Transition d'état de la pin 12, faite un peu trop tôt, pour éviter de la rater. bEtat12 = 1 - bEtat12; if (bEtat12) PORTB = PORTB | 0b00010000; else PORTB = PORTB & 0b11101111; wOCR12 += wOCR12_new+1; } } else { // (bPinNext == 12) // Manière beaucoup plus rapide de changer l'état des pins, permet de générer de plus hautes fréquences. // C.f. AVR_Assembler_Lecture6.pdf // Transition d'état de la pin 12 bEtat12 = 1 - bEtat12; if (bEtat12) PORTB = PORTB | 0b00010000; else PORTB = PORTB & 0b11101111; wOCR12 = wOCR12_new; wOCR11 -= OCR1A; // ici, on sait que : wOCR11 >= OCR1A if (wOCR11 < 128) { // 128 --- 8 [us] si fact = 1 // Transition d'état de la pin 11, faite un peu trop tôt, pour éviter de la rater. bEtat11 = 1 - bEtat11; if (bEtat11) PORTB = PORTB | 0b00001000; else PORTB = PORTB & 0b11110111; wOCR11 += wOCR11_new+1; } wOCR11--; } // Définition de quand aura lieu la prochaine transistion d'état d'une pin et de laquelle. if (wOCR11 < wOCR12) { OCR1A = wOCR11; bPinNext = 11; } else { // wOCR11 >= wOCR12 OCR1A = wOCR12; bPinNext = 12; } // Pour des tests, voir si on rate une interruption //if (TCNT1 >= OCR1A) { bEtat_enable = 0; OCR1A = wOCR11_new; wOCR11 = wOCR11_new; wOCR12 = wOCR12_new + 200;} } // ISR // FIN.
/* ex0464_timer_1_interrupt_digital_oscillo_LCD.ino Suite de ex0463_timer_1_interrupt_digital_oscillo_LCD.ino Fait de légères variations sur OCR1A de périodes d'interruption, pour obtenir plus précisément la fréquence désirée. La fréquence est définie au millième de Hz près. Génère deux signaux d'une fréquence ajustables. Un sur la pin 11 et l'autre sur la pin 12. Permet d'étudier des déphasages, des battements et des figures de lissajous sur un OSCILLOSCOPE. Il est également possible de gérer précisément le déphasage entre les deux signaux. C.f. ex1150_digital_oscillo_LCD,ino En parcourant le menu, on peut choisir 4 paires de fréquences mystères, à déterminer à l'aide d'un oscilloscope, comme exercice. Le code "uududd" permet d'afficher les fréquences générées, par défaut elles ne sont pas générées. En pressant sur la touche "Select" au démarrage, le code est mis à "uupudd". Adjonction du cas où : code = "uududdd", qui impose que le signal 2 soit de même fréquence que le signal 1, en opposition. C'est pratique pour faire vibrer un haut-parleur au maximum. C'est une version plus sophistiquée que celle de ex1150_digital_oscillo_LCD.ino, car elle utilise le timer 1 pour générer les interruptions et le timer 0 pour le temps. Utilisation du timer 1 pour générer des interruptions. Utilise le registre OCR1A pour générer les interruptions. Le timer 0 est utilisé pour une mesure du temps, sans génération d'interruption. Les fonctions "delay", "delayMicroseconds", "millis" et "micros" ne sont pas utilisables. Elles sont désactivées, pour augmenter la précision de la période de génération d'interruptions. L'affichage LCD, avec les 5 boutons de commande est utilisé. La pin 13 indique si le signal est activé (1) ou désactivé (0). Accès beaucoup plus rapide aux changement d'état des pins qu'avec la fonction "digitalWrite". C.f. "Arduino Cookbook" de Michael Margolis, édition O'reilly, page 628 et "AVR_Assembler_Lecture6.pdf", page 53 Permet de générer une fréquence de 0.12 [Hz] à 45'000 [Hz]. La lecture de l'ADC 0, pour connaître le bouton pressé est fait de manière beaucoup plus rapide qu'en utilisant la fonction analogRead. Le temps de conversion est toujours d'environ 100 [us], mais aucune attente n'est faite en l'initialisation du début de conversion et le résultat de la mesure, 100 [us] plus tard. [us] = micro-seconde. ==========================================================*/ // Inclus la bibliothèque qui gère l'affichage. #include <LiquidCrystal.h> // Initialise la bibliothèque avec les pins utilisés par l'afficheur LCD 2x16 muni de 5 boutons. //LiquidCrystal lcd(RS, E, D4, D5, D6, D7); LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // Déclaration de Constantes #define btnNONE 0 #define btnSELECT 1 #define btnLEFT 2 #define btnDOWN 3 #define btnUP 4 #define btnRIGHT 5 // Nombre de menus #define MENU_MAX 14 // Les deux pins sur lesquelles les signaux sont générés. #define PIN11 11 #define PIN12 12 char acStr[20]; // Un "array" de caractères, pour conversion de nombre en un String. String strS = ""; // Une chaine de caractères. int nButtonPush = btnNONE; // Indique le bouton pressé byte bMenu1 = 4; // indique le menu sélectionné byte bMenu1_Last = 0; // indique le dernier menu sélectionné word wTime0 = 61; // Compteur décrémenté de 1 toutes les 0.016384 [s] ~= 1/61 [s] // Utilisé pour ne pas lire l'état des boutons trop souvent. byte bMenu_mytere = 0; // Définit la fréquence mystère à déterminer byte bAfficheF = 1; // indique si la fréquence est affichée ou non. Est initialisée dans la fonction setup(). String strCode = "uududd"; // Code pour demander d'afficher les fréquences. // Pour "volatile", c.f. https://www.arduino.cc/en/pmwiki.php?n=Reference/Volatile // Les variables utilisées dans la fonction de gestion d'interruption doivent être déclarée "volatile". // Cela impose que la variable est sauvegardée en RAM et non dans un registre. // Une variable sauvegardée dans un registre pourrait être modifiée accidentellement entre deux accès à la fonction. byte volatile bEtat11 = 0; // État de la pin 11 byte volatile bEtat12 = 0; // État de la pin 12 byte volatile bEtat_enable = 1; // Active ou désactive le signal. 1 = Actif word volatile wOCR11_new = 4000-1; // Fixe le temps entre deux transitions du signal sur la pin 11 word volatile wOCR12_new = 4000-1; // Fixe le temps entre deux transitions du signal sur la pin 12 word volatile wOCR11 = 4000-1; word volatile wOCR12 = 4000-1; byte volatile bPinNext = 12; // Indique quelle pin changera d'état à la prochaine interruption word volatile wOCR11_delta = 0; // Pour déterminer la différence entre la période désirée et celle obtenue word volatile wOCR11_gap = 0; // Somme des différences entre les périodes désirées et celle appliquées // Lorsque wOCR11_gap > 30000, on ajoute un cycle à la période, pour se rapprocher de la période désirée. word volatile wOCR12_delta = 0; // Pour déterminer la différence entre la période désirée et celle obtenue word volatile wOCR12_gap = 0; // Somme des différences entre les périodes désirées et celle appliquées // Lorsque wOCR12_gap > 30000, on ajoute un cycle à la période, pour se rapprocher de la période désirée. byte volatile bTmp = 0; // Pour des calculs temporaires word wFact = 1; // Facteur de division de la fréquence d'incrémentation de TCNT1. Est modifiable suivant la fréquence générée. float vFreq11 = 400.0; // [Hz] Fréquence générée. float vFreq12 = 400.0; // [Hz] Fréquence générée. float vFreq_base = 8000000.0; // [Hz] fréquence à laquelle le compteur TCNT1 est incrémenté. = 8000000.0 / vFreq float vFreq_delta11 = 0.0; // Variation de fréquence 1 demandée float vFreq_delta12 = 0.0; // Variation de fréquence 2 demandée float vOCR11_calc_new = 0.0; // Pour le calcule de wOCR11_new en fonction de la fréquence désirée. float vOCR12_calc_new = 0.0; // Pour le calcule de wOCR12_new en fonction de la fréquence désirée. // Pour la conversion ADC uint8_t bLow, bHigh; #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) // Formules de conversions //======================== // Selon la valeur de TCCR1B, bFac varie selon le tableau suivant : // TCCR1B 1 2 3 4 5 // wFact 1 8 64 256 1024 // Temps entre deux transistions = (OCR1A+1) * wFact * 62.5 * 10^(-9) [s] // 62.5e-9 = 1 / 16'000'000 // La période est le double du temps entre deux transistions // Fréquence = 8'000'000 [Hz] / ((OCR1A+1) * wFact) // // Pour Fréquence = Freq donnée : // OCR1A = -1 + Tronc(8'000'000 / (Freq * wFact) // Une fréquence supérieure à ~120 [kHz] est non atteignable. void setup() { //============ // Indique le nombre de lignes et de colonnes de l'afficheur LCD 16x2. lcd.begin(16, 2); // Affiche un message au départ, indiquant le progr. et la date lcd.setCursor(0, 0); lcd.print("Timer freq. LCD."); lcd.setCursor(0, 1); lcd.print("ex0464 20200621 "); delay(1200); lcd.setCursor(0, 0); lcd.print(" "); // efface le texte lcd.setCursor(0, 1); lcd.print(" "); // Pour afficher les fréquences, si au départ on presse sur le bouton "Select". if ( (analogRead(0) > 555) && (analogRead(0) < 1000) ) { bAfficheF = 1; strCode = "uududd"; } else { bAfficheF = 0; strCode = ""; } // Initialise le Timer 1 pour déclencher les interruptions après temps défini dans OCR1A cli(); // Désactive l'interruption globale // de Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf // Timer/Counter0 C.f. page 93 // Il gère les fonctions delay(), delayMicroseconds, millis() et micros(), qui sont désactivées. // Lorsque le Timer 0 est désactivé, les 4 fonctions ci-dessus ne fonctionnent plus ! TCCR0A = 0b00000000; // default. Bits : COM0A1=0 ; COM0A0=0 ; COM0B1=0 ; COM0B0=0 ; / ; / ; WGM01=0 ; WGM00=0, donc pas de PWM lié au timer 0 TCCR0B = 0b00000101; // Clock * 1024 soit 64 micro-s et WGM02 = 0 TIMSK0 = 0b00000000; // Désactive les interruptions dues au Timer/Counter0 C.f. p. 109 // Ne pas utiliser TCNT0, mais le bit 0 de TIFR0 pour compter le nombre de boucles du compteur liées au timer 0. // Timer/Counter2 C.f. page 141 // Il est utilisé pour générer des tone() TIMSK2 = 0b00000000; // Désactive les les interruptions dues au Timer/Counter2 C.f. p. 157 // Timer/Counter1 C.f. page 111 // Il est utilisé dans la bibliothèque de commande de servo-moteurs. //TIMSK1 = 0b00000001; // Active les les interruptions dues au Timer/Counter1 C.f. p. 135 // Une interruption est générée lorsque le compteur TCNT1 passe de 65535 à 0 (overflow) TIMSK1 = 0b00000010; // Active les les interruptions dues au Timer/Counter1 C.f. p. 135 // Lorsque le compteur TCNT1 égale OCR1A, ( car WGM1(3:0) = 0b0100 == 4 ) // TCNT1 passe à 0. // et une interruption est générée. TCCR1A = 0b00000000; // default. Bits : COM1A1=0 ; COM1A0=0 ; COM1B1=0 ; COM1B0=0 ; / ; / ; WGM11=0 ; WGM10=0, donc pas de PWM. TCCR1B = 0b00001001; // (WGM13 = 0 ; WGM12 = 1) fact = 1 //TCCR1B = 0b00001010; // (WGM13 = 0 ; WGM12 = 1) fact = 8 //TCCR1B = 0b00001011; // (WGM13 = 0 ; WGM12 = 1) fact = 64 //TCCR1B = 0b00001100; // (WGM13 = 0 ; WGM12 = 1) fact = 256 //TCCR1B = 0b00001101; // (WGM13 = 0 ; WGM12 = 1) fact = 1024 vOCR11_calc_new = -1 + vFreq_base / vFreq11; wOCR11_new = trunc(vOCR11_calc_new); wOCR11 = wOCR11_new + wOCR11_new / 3; // "+ wOCR11_new / 3" crée un déphasage au départ wOCR11_delta = trunc(30000 * (vOCR11_calc_new - wOCR11_new)); // Ecart entre la fréquence désirée et celle obtenue wOCR11_gap = 0; vOCR12_calc_new = -1 + vFreq_base / vFreq12; wOCR12_new = trunc(vOCR12_calc_new); wOCR12 = wOCR12_new; wOCR12_delta = trunc(30000 * (vOCR12_calc_new - wOCR12_new)); // Ecart entre la fréquence désirée et celle obtenue wOCR12_gap = 0; OCR1A = wOCR12; sei(); // Active l'interruption globale pinMode(13, OUTPUT); // La pin 13 indique si un signal est généré ou si les sorties des pins 2 et 3 sont à 0 [V]. pinMode( PIN11, OUTPUT); pinMode( PIN12, OUTPUT); digitalWrite( 13, bEtat_enable); digitalWrite( PIN11, bEtat11); digitalWrite( PIN12, bEtat12); AfficheEtat(); // Pour la lecture de l'ADC // c.f. https://garretlab.web.fc2.com/en/arduino/inside/arduino/wiring_analog.c/analogRead.html ADMUX = 0b01000000; // 01 => Default reference voltage bit_5 = ADLAR = 0 => right adjusted bits_3210 = 0 => port 0 // start the conversion sbi(ADCSRA, ADSC); } // setup int read_LCD_buttons() { // ====================== // Lecture du bouton appuié // Il faut détecter 3 fois de suite le même bouton pour qu'on estime détecté le bon bouton pressé. // L'avantage de cette manière de faire est que la lecture des boutons ne prend que // le temps d'une lecture analogique, soit environ 100 micro secondes. // Une lecture toutes les 1/61 seconde. int nAdc_key_in = 0; byte bButton = btnNONE; // Bouton pressé selon la lecture de l'ADC. static byte bButtonLast = btnNONE; // Dernier bouton pressé. static byte bButtonCount = 0; // compte le nombre de fois que le même boutton est détecté à la suite. //nAdc_key_in = analogRead(0); // Lecture de la valeur du bouton pressé. // c.f. https://garretlab.web.fc2.com/en/arduino/inside/arduino/wiring_analog.c/analogRead.html //ADMUX = 0b01000000; // 01 => Default reference voltage bit_5 = ADLAR = 0 => right adjusted bits_3210 = 0 => port 0 // ADSC is cleared when the conversion finishes // while (bit_is_set(ADCSRA, ADSC)); // Plus besoin d'attendre, car cela est fait grace à : lwTempsLastReadBtn // Lecture de ADCL en premier. Il est mis dans un buffer et non modifié tant que ADCH n'a pas été lu. // La lecture de ADCL en deuxième bloquerait la lecture de l'ADC. // Il faut donc lire ADCH en deuxième. bLow = ADCL; bHigh = ADCH; // Démarre une conversion, qui sera terminée dans environ 100 [us]. // La fin de conversion peut se détecter en lisant ADSC, qui sera mis à 0 une fois la conversion terminée. // ADSC n'est pas utilisé ici, car on laisse amplement le temps de terminer la conversion en n'en faisant que 61 par seconde. sbi(ADCSRA, ADSC); if (bHigh >= 3) { // Aucun bouton pressé. bButtonLast = btnNONE; bButtonCount = 0; return btnNONE; } // Ici, on sait qu'un bouton a été pressé. nAdc_key_in = word(bHigh, bLow); // Conversion en un word. // Les lectures des boutons sont centrées autour des valeurs 0, 144, 329, 504, 741 // J'ai ajouté environ 50 à ces valeurs pour la détection des boutons if (nAdc_key_in > 555) bButton = btnSELECT; else if (nAdc_key_in > 380) bButton = btnLEFT; else if (nAdc_key_in > 195) bButton = btnDOWN; else if (nAdc_key_in > 55) bButton = btnUP; else bButton = btnRIGHT; if (bButton == bButtonLast) { // Le même bouton a été détecté bButtonCount++; // compte combien de fois de suite le même bouton est détecté. if (bButtonCount == 3) { bButtonCount = 0; wTime0 = 20; // Pour indiquer qu'un bouton a été traité, il faudra attendre avant de traiter à nouveau un bouton. return bButton; // Le même bouton a été détecté .. fois de suite, donc c'est le bon } } else { // Un nouveau bouton est détecté. bButtonLast = bButton; bButtonCount = 0; } return btnNONE; // On est pas encore sûr que le bon bouton est bButton. } // read_LCD_buttons void AfficheEtat() { //================== // Affichage de la fréquence ou de la variation de fréquence, suivant le menu if (bMenu1 <= MENU_MAX) { // Affiche Fréquence du signal 1 if (bMenu1 != bMenu1_Last) { lcd.setCursor(0, 0); if (bMenu1 == 1) lcd.print("Freq 1 +-0.001Hz"); else if (bMenu1 == 2) lcd.print("Freq 1 +-0.01 Hz"); else if (bMenu1 == 3) lcd.print("Freq 1 +-0.1 Hz"); else if (bMenu1 == 4) lcd.print("Freq 1 +-1 Hz"); else if (bMenu1 == 5) lcd.print("Freq 1 +-10 Hz"); else if (bMenu1 == 6) lcd.print("Freq 1 +-100 Hz"); else if (bMenu1 == 7) lcd.print("Freq 2 +-0.001Hz"); else if (bMenu1 == 8) lcd.print("Freq 2 +-0.01 Hz"); else if (bMenu1 == 9) lcd.print("Freq 2 +-0.1 Hz"); else if (bMenu1 ==10 ) lcd.print("Freq 2 +-1 Hz"); else if (bMenu1 ==11) lcd.print("Freq 2 +-10 Hz"); else if (bMenu1 ==12) lcd.print("Freq 2 +-100 Hz"); else if (bMenu1 ==MENU_MAX) lcd.print("pass "); else if (bMenu1 ==13) { if (strCode != "uududdd") { // Cas normal, pour l'étude du signal sur un oscilloscope lcd.print("Freq mystere "); } else { // Cas particulier pour faire osciller un haut-parleur lcd.print("+- octave / 24 "); // Ainsi, 24 changements doublent ou divise la fréquence (change d'une octave). dtostrf(vFreq11, 8, 3, acStr); // Conversion d'un nombre à virgule en un string prenant 8 caractères, avec 3 chiffres après la virgule. strS = acStr; strS = "Freq 1 =" + strS; //lcd.setCursor(0, 1); // Affichage en début de 2e ligne de l'afficheur LCD. //lcd.print(strS); } } } if (bMenu1 == MENU_MAX) { // Menu de la demande d'un mot de passe (Code) pour afficher les fréquences // Le mot de passe est : up up down up down down // "Select" efface le mot tapé. lcd.setCursor(0, 0); lcd.print("C : "); lcd.setCursor(0, 1); lcd.print(" "); lcd.setCursor(0, 1); // lcd.print(strCode); // strCode = "uududd" est le bon Code. strS = ""; for(int nn = 1; nn<=strCode.length(); nn++) strS += "*"; // Les lettres sont remplacées par des * lcd.print(strS); } else { if (bMenu1 <= 6) { dtostrf(vFreq11, 8, 3, acStr); // Conversion d'un nombre à virgule en un string prenant 8 caractères, avec 3 chiffres après la virgule. strS = acStr; strS = "Freq 1 =" + strS; //sprintf(acStr, "%8d", wOCR11_new); strS = strS + acStr; // Pour des tests } else if (bMenu1 <= 12) { dtostrf(vFreq12, 8, 3, acStr); // Conversion d'un nombre à virgule en un string prenant 8 caractères, avec 3 chiffres après la virgule. strS = acStr; strS = "Freq 2 =" + strS; //sprintf(acStr, "%8d", wOCR12_new); strS = strS + acStr; // Pour des tests } else if (strCode != "uududdd") { // bMenu1 == 13 = MENU_MAX -1 sprintf(acStr, "Freq Nr : %3d ", bMenu_mytere); strS = acStr; } if ( (!bAfficheF) && (bMenu1 <= 12) ) strS = " "; // Efface la deuxième ligne lcd.setCursor(0, 1); // Affichage en début de 2e ligne de l'afficheur LCD. lcd.print(strS); } } } // AfficheEtat void MenuTreat() { //================ // Un bouton UP, DOWN ou SELECT a été pressé, traite l'action adéquat. vFreq_delta11 = 0.0; vFreq_delta12 = 0.0; if (bMenu1 == 1) { // Changement de fréquence du signal 1 de +-1 milli-Hz if (nButtonPush == btnUP) vFreq_delta11 = 0.001; if (nButtonPush == btnDOWN) vFreq_delta11 = -0.001; } if (bMenu1 == 2) { // Changement de fréquence du signal 1 de +-10 milli-Hz if (nButtonPush == btnUP) vFreq_delta11 = 0.01; if (nButtonPush == btnDOWN) vFreq_delta11 = -0.01; } if (bMenu1 == 3) { // Changement de fréquence du signal 1 de +-100 milli-Hz if (nButtonPush == btnUP) vFreq_delta11 = 0.1; if (nButtonPush == btnDOWN) vFreq_delta11 = -0.1; } if (bMenu1 == 4) { // Changement de fréquence du signal 1 de +-1 Hz if (nButtonPush == btnUP) vFreq_delta11 = 1.0; if (nButtonPush == btnDOWN) vFreq_delta11 = -1.0; } if (bMenu1 == 5) { // Changement de fréquence du signal 1 de +-10 Hz if (nButtonPush == btnUP) vFreq_delta11 = 10.0; if (nButtonPush == btnDOWN) vFreq_delta11 = -10.0; } if (bMenu1 == 6) { // Changement de fréquence du signal 1 de +-100 Hz if (nButtonPush == btnUP) vFreq_delta11 = 100.0; if (nButtonPush == btnDOWN) vFreq_delta11 = -100.0; } if (bMenu1 == 7) { // Changement de fréquence du signal 2 de +-1 milli-Hz if (nButtonPush == btnUP) vFreq_delta12 = 0.001; if (nButtonPush == btnDOWN) vFreq_delta12 = -0.001; } if (bMenu1 == 8) { // Changement de fréquence du signal 2 de +-10 milli-Hz if (nButtonPush == btnUP) vFreq_delta12 = 0.01; if (nButtonPush == btnDOWN) vFreq_delta12 = -0.01; } if (bMenu1 == 9) { // Changement de fréquence du signal 2 de +-100 milli-Hz if (nButtonPush == btnUP) vFreq_delta12 = 0.1; if (nButtonPush == btnDOWN) vFreq_delta12 = -0.1; } if (bMenu1 == 10) { // Changement de fréquence du signal 2 de +-1 Hz if (nButtonPush == btnUP) vFreq_delta12 = 1.0; if (nButtonPush == btnDOWN) vFreq_delta12 = -1.0; } if (bMenu1 == 11) { // Changement de fréquence du signal 2 de +-10 Hz if (nButtonPush == btnUP) vFreq_delta12 = 10.0; if (nButtonPush == btnDOWN) vFreq_delta12 = -10.0; } if (bMenu1 == 12) { // Changement de fréquence du signal 2 de +-100 Hz if (nButtonPush == btnUP) vFreq_delta12 = 100.0; if (nButtonPush == btnDOWN) vFreq_delta12 = -100.0; } if (bMenu1 == 13) { if (strCode != "uududdd") { // Fréquence mystère, à déterminer avec l'oscilloscope // Elle n'est déterminée que si un bouton UP ou DOWN est pressé depuis ce menu if ( (nButtonPush == btnUP) || (nButtonPush == btnDOWN) ) { if (nButtonPush == btnUP) { bMenu_mytere++; if (bMenu_mytere >= 4) bMenu_mytere = 0; } if (nButtonPush == btnDOWN) { if (bMenu_mytere == 0) bMenu_mytere = 4; bMenu_mytere--; } if (bMenu_mytere == 0) { vFreq11 = 500.0; vFreq12 = 1000.0; } else if (bMenu_mytere == 1) { vFreq11 = 800.0; vFreq12 = 800.08; } else if (bMenu_mytere == 2) { vFreq11 = 200.0; vFreq12 = 800.08; } else if (bMenu_mytere == 3) { vFreq11 = 2000.0; vFreq12 = 8000.0; } vOCR11_calc_new = -1 + vFreq_base / vFreq11; wOCR11_new = trunc(vOCR11_calc_new); wOCR11_delta = trunc(30000 * (vOCR11_calc_new - wOCR11_new)); // Ecart entre la fréquence désirée et celle obtenue vOCR12_calc_new = -1 + vFreq_base / vFreq12; wOCR12_new = trunc(vOCR12_calc_new); wOCR12_delta = trunc(30000 * (vOCR12_calc_new - wOCR12_new)); // Ecart entre la fréquence désirée et celle obtenue TCNT1 = 0; // Pour redémarrer le compteur depuis 0. wOCR11 = wOCR11_new + wOCR11_new / 4; // Pour définir la phase wOCR12 = wOCR12_new; // Si on veu définir la phase OCR1A = wOCR12; // = min(wOCR11, wOCR12) bPinNext = 12; } } // if (strCode != "uududdd") else { // Cas particulier, on augmente ou diminue d'un 24e d'octave les deux fréquences. if (nButtonPush == btnUP) { vFreq_delta11 = 0.029302237 * vFreq11; // pour que 24 changement passe à l'octave suivante. racine_24e(2) = 1.029302237 } if (nButtonPush == btnDOWN) { vFreq_delta11 = -0.028468059 * vFreq11; // pour que 24 changement passe à l'octave précédente. 1 - 1/racine_24e(2) } } } // if (bMenu1 == 13) if (bMenu1 == MENU_MAX) { // Code pour affichage des fréquences if (nButtonPush == btnSELECT) strCode = ""; // Efface le code tapé if (nButtonPush == btnUP) strCode += "u"; if (nButtonPush == btnDOWN) strCode += "d"; if ( (strCode == "uududd") || (strCode == "uududdd") ) bAfficheF = 1; // Bon mot de passe else bAfficheF = 0; // Mauvais mot de passe, on affiche pas les fréquences générées. // Efface la deuxième ligne if (!bAfficheF) { strS = " "; // N'affiche pas la fréquence lcd.setCursor(0, 1); lcd.print(strS); } } if (vFreq_delta11 != 0) { // Il y a une demande de changement de fréquence sur la pin 11 if (vFreq11 + vFreq_delta11 > 0.1193) vFreq11 = vFreq11 + vFreq_delta11; vOCR11_calc_new = -1 + vFreq_base / vFreq11; // Teste que les capacités du compteur TCNT1 liée au timer 1 ne soient pas dépassées if (vOCR11_calc_new > 65535.0) { // Passe à un diviseur de fréquence (wFact) plus grand, pour générer une plus basse fréquence, une plus haute période. if (wFact == 1024) { // Assure de ne pas descendre en-dessous d'une fréquence trop basse, non atteignable par le Timer 1 de l'Arduino. vFreq11 = 0.1193; } else if (wFact == 256) { wFact = 1024; TCCR1B = 0b00001101; // (WGM13 = 0 ; WGM12 = 1) fact = 1024 } else if (wFact == 64) { wFact = 256; TCCR1B = 0b00001100; // (WGM13 = 0 ; WGM12 = 1) fact = 256 } else if (wFact == 8) { wFact = 64; TCCR1B = 0b00001011; // (WGM13 = 0 ; WGM12 = 1) fact = 64 } else if (wFact == 1) { wFact = 8; TCCR1B = 0b00001010; // (WGM13 = 0 ; WGM12 = 1) fact = 8 } vFreq_base = 8000000.0 / wFact; // Changement de la fréquence de base vOCR11_calc_new = -1 + vFreq_base / vFreq11; } // if (vOCR11_calc_new > 65535.0) // Teste si l'on peut être plus précis dans la fréquence générée. else if (vOCR11_calc_new < 7500.0) { // Passe à un diviseur de fréquence plus petit, pour être plus précis dans la fréquence générée. // Si c'est compatible avec la deuxième fréquence et la fréquence max. if (wFact == 1) { // Teste que la fréquence ne soit pas trop grande, sinon dépasse les capacités de génération de fréquences de l'Arduino. if (vFreq11 > 45000.0) { // Fréquence max. = 45 [kHz]. vFreq11 = 45000.0; } } else if (wOCR12_new < 7500) { // Il faut que la deuxième fréquence permette également le changement du facteur if (wFact == 8) { wFact = 1; TCCR1B = 0b00001001; // (WGM13 = 0 ; WGM12 = 1) fact = 1 } else if (wFact == 64) { wFact = 8; TCCR1B = 0b00001010; // (WGM13 = 0 ; WGM12 = 1) fact = 8 } else if (wFact == 256) { wFact = 64; TCCR1B = 0b00001011; // (WGM13 = 0 ; WGM12 = 1) fact = 64 } else if (wFact == 1024) { wFact = 256; TCCR1B = 0b00001100; // (WGM13 = 0 ; WGM12 = 1) fact = 256 } } vFreq_base = 8000000.0 / wFact; // Changement de la fréquence de base vOCR11_calc_new = -1 + vFreq_base / vFreq11; } // else if (vOCR11_calc_new < 7500.0) wOCR11_new = trunc(vOCR11_calc_new); wOCR11_delta = trunc(30000 * (vOCR11_calc_new - wOCR11_new)); // Ecart entre la fréquence désirée et celle obtenue if (strCode == "uududdd") { // Impose que la fréquence 12 soit la même que la fréquence 11 // et que les signaux soient en opposition. vFreq12 = vFreq11; wOCR12_new = wOCR11_new; wOCR12_delta = wOCR11_delta; cli(); // Désactive l'interruption globale if (bPinNext == 11) { wOCR12 = OCR1A + 1; wOCR12_gap = wOCR11_gap; bEtat12 = 1 - bEtat11; // Signaux en opposition à la prochaine transition } else { wOCR11 = OCR1A + 1; wOCR11_gap = wOCR12_gap; bEtat11 = 1 - bEtat12; // Signaux en opposition à la prochaine transition } sei(); // Active l'interruption globale } // Cas où les deux signaux ont la même fréquence et sont en opposition. vOCR12_calc_new = -1 + vFreq_base / vFreq12; // Utile si : vFreq_base a changé wOCR12_new = trunc(vOCR12_calc_new); wOCR12_delta = trunc(30000 * (vOCR12_calc_new - wOCR12_new)); // Ecart entre la fréquence désirée et celle obtenue } // if (vFreq_delta11 != 0) if (vFreq_delta12 != 0) { // Il y a une demande de changement de fréquence sur la pin 12 if (vFreq12 + vFreq_delta12 > 0.1193) vFreq12 = vFreq12 + vFreq_delta12; vOCR12_calc_new = -1 + vFreq_base / vFreq12; // Teste que les capacités du compteur TCNT1 lié au timer 1 ne soient pas dépassées if (vOCR12_calc_new > 65535.0) { // Passe à un diviseur de fréquence (wFact) plus grand, pour générer une plus basse fréquence, une plus haute période. if (wFact == 1024) { // Assure de ne pas descendre en-dessous d'une fréquence trop basse, non atteignable par le Timer 1 de l'Arduino. vFreq12 = 0.1193; } else if (wFact == 256) { wFact = 1024; TCCR1B = 0b00001101; // (WGM13 = 0 ; WGM12 = 1) fact = 1024 } else if (wFact == 64) { wFact = 256; TCCR1B = 0b00001100; // (WGM13 = 0 ; WGM12 = 1) fact = 256 } else if (wFact == 8) { wFact = 64; TCCR1B = 0b00001011; // (WGM13 = 0 ; WGM12 = 1) fact = 64 } else if (wFact == 1) { wFact = 8; TCCR1B = 0b00001010; // (WGM13 = 0 ; WGM12 = 1) fact = 8 } vFreq_base = 8000000.0 / wFact; // Changement de la fréquence de base vOCR12_calc_new = -1 + vFreq_base / vFreq12; } // if (vOCR12_calc_new > 65535.0) // Teste si l'on peut être plus précis dans la fréquence générée. else if (vOCR12_calc_new < 7500.0) { // Passe à un diviseur de fréquence plus petit, pour être plus précis dans la fréquence générée. if (wFact == 1) { // Teste que la fréquence ne soit pas trop grande, sinon dépasse les capacités de génération de fréquences de l'Arduino. if (vFreq12 > 45000.0) { // Fréquence max. = 45 [kHz]. vFreq12 = 45000.0; } } else if (wOCR11_new < 7500) { // Il faut que la deuxième fréquence permette également le changement du facteur if (wFact == 8) { wFact = 1; TCCR1B = 0b00001001; // (WGM13 = 0 ; WGM12 = 1) fact = 1 } else if (wFact == 64) { wFact = 8; TCCR1B = 0b00001010; // (WGM13 = 0 ; WGM12 = 1) fact = 8 } else if (wFact == 256) { wFact = 64; TCCR1B = 0b00001011; // (WGM13 = 0 ; WGM12 = 1) fact = 64 } else if (wFact == 1024) { wFact = 256; TCCR1B = 0b00001100; // (WGM13 = 0 ; WGM12 = 1) fact = 256 } } vFreq_base = 8000000.0 / wFact; // Changement de la fréquence de base vOCR12_calc_new = -1 + vFreq_base / vFreq12; } // else if (vOCR12_calc_new < 7500.0) wOCR12_new = trunc(vOCR12_calc_new); wOCR12_delta = trunc(30000 * (vOCR12_calc_new - wOCR12_new)); // Ecart entre la fréquence désirée et celle obtenue vOCR11_calc_new = -1 + vFreq_base / vFreq11; // Utile si : vFreq_base a changé wOCR11_new = trunc(vOCR11_calc_new); wOCR11_delta = trunc(30000 * (vOCR11_calc_new - wOCR11_new)); // Ecart entre la fréquence désirée et celle obtenue } // if (vFreq_delta12 != 0) // Le bouton "Select" active ou désactive le signal if ( (nButtonPush == btnSELECT) && (bMenu1 != MENU_MAX) ) { bEtat_enable = 1 - bEtat_enable; digitalWrite(13, bEtat_enable); // Indique sur l'état de la pin 13 si le signal est arrêté ou actif. digitalWrite( PIN11, bEtat11 & bEtat_enable); // off si signal désactivé digitalWrite( PIN12, bEtat12 & bEtat_enable); // off si signal désactivé } AfficheEtat(); } // MenuTreat void Menu1_Change() { //=================== // Changement du Menu1 // Un bouton LEFT ou RIGHT a été pressé bMenu1_Last = bMenu1; // Mémorise le dernier menu if (nButtonPush == btnLEFT) { bMenu1 -= 1; if (bMenu1 == 0) bMenu1 = MENU_MAX; // boucle } if (nButtonPush == btnRIGHT) { bMenu1 += 1; if (bMenu1 > MENU_MAX) bMenu1 = 1; // boucle } AfficheEtat(); } // Menu1_Change void loop() { //=========== // Boucle principale. Voir également la routine de gestion d'interruption du timer 1 if (TIFR0 & 1) { // Le compteur du timer 0 a fait une boucle complète // Une boucle se fait en environ 1/61 [s]. Il y a donc 61 boucles par seconde. // Ceci laisse le temps au convertisseur ADC de terminer sa conversion TIFR0 = 1; // Efface les flags du registre TIFR0 // Ne lit l'état des boutons que si un bouton a été traité il y a plus de 0,33 seconde. if (wTime0 == 0) { // Lecture de l'état des boutons, pour changer la fréquence ou la phase du signal nButtonPush = read_LCD_buttons(); if ( (nButtonPush == btnLEFT) || (nButtonPush == btnRIGHT) ) Menu1_Change(); // Un bouton pressé, changement de menu if ( (nButtonPush == btnUP) || (nButtonPush == btnDOWN) || (nButtonPush == btnSELECT) ) MenuTreat(); // Un bouton pressé, traitement } if (wTime0 > 0) wTime0--; // Pour assurer qu'un bouton pressé ne soit pas traité trop rapidement plusieurs fois à la suite. } // if (TIFR0 & 1) { } // loop // routine d'interruption du timer 1, lorsque le registre OCR1A est utilisé, ce qui est le cas dans ce programme. ISR (TIMER1_COMPA_vect) { //======================= // Transition de l'état // TCNT1 = valeur du compteur, qui est compté de 0 à OCR1A, puis repasse à 0. // Une boucle complète fait OCR1A+1 (cycles d'horloge / wFact) // Lorsqu'on arrive dans cette fonction de gestion d'interruption, TCNT1 = 0. // Il est préférable de ne pas utiliser directement le registre TCNT1, car sa valeur peut varier très rapidement. // Le compteur TCNT1 est incrémenté de 1 chaque 0.0625 [us] * facteur défini dans TCCR1B. (facteur = wFact) if (!bEtat_enable) return; // changements d'états désactivés. if (bPinNext == 11) { // Transition d'état de la pin 11 bEtat11 = 1 - bEtat11; wOCR11 = wOCR11_new; wOCR11_gap += wOCR11_delta; if (wOCR11_gap > 30000) { // Il faut ajouter un cycle à wOCR11, pour se rapprocher de la fréquence désirée wOCR11++; wOCR11_gap -= 30000; } wOCR12 -= OCR1A; // ici, on sait que : wOCR12 > OCR1A if (wOCR12 < 160) { // 160 --- 10 [us] si fact = 1 ( 112 est trop petit ! ) // Transition d'état de la pin 12, faite un peu trop tôt, pour éviter de la rater. if (wOCR12 < 20) { // 16 --- 1 [us] si fact = 1 // Les deux transtions se font en même temps. bTmp = PORTB; if (bEtat11) bTmp |= 0b00001000; else bTmp &= 0b11110111; if (bEtat12) bTmp &= 0b11101111; else bTmp |= 0b00010000; PORTB = bTmp; // Les deux transitions en même temps } else { // La transition 11 se fait quelques micro-secondes avant la transition 12. if (bEtat11) PORTB = PORTB | 0b00001000; // Transition d'état de la pin 11 else PORTB = PORTB & 0b11110111; for (bTmp=0; bTmp<lowByte(wOCR12); bTmp+=24) {} // Pour attendre un peu if (bEtat12) PORTB = PORTB & 0b11101111; // Transition d'état de la pin 12 else PORTB = PORTB | 0b00010000; } // Changement de l'état 12 bEtat12 = 1 - bEtat12; wOCR12 += wOCR12_new+1; wOCR12_gap += wOCR12_delta; if (wOCR12_gap > 30000) { // Il faut ajouter un cycle à wOCR12, pour se rapprocher de la fréquence désirée wOCR12++; wOCR12_gap -= 30000; } } // if (wOCR12 < 160) else { // Les deux transitions ne sont pas trop rapprochées, wOCR12 >= 160 // Manière beaucoup plus rapide de changer l'état des pins, permet de générer de plus hautes fréquences. // C.f. AVR_Assembler_Lecture6.pdf if (bEtat11) PORTB = PORTB | 0b00001000; else PORTB = PORTB & 0b11110111; // PORTD contrôle les pins de 0 à 7. bit des unités = port 0 // PORTB contrôle les pins de 8 à 13 --- bits 0 à 5 // PORTC contrôle les pins de 14 à 19 --- bits 0 à 5. Correspond aux pins analogiques, pouvant aussi être utilisés en digital. } wOCR12--; } else { // (bPinNext == 12) // Transition d'état de la pin 12 bEtat12 = 1 - bEtat12; wOCR12 = wOCR12_new; wOCR12_gap += wOCR12_delta; if (wOCR12_gap > 30000) { // Il faut ajouter un cycle à wOCR12, pour se rapprocher de la fréquence désirée wOCR12++; wOCR12_gap -= 30000; } wOCR11 -= OCR1A; // ici, on sait que : wOCR11 >= OCR1A if (wOCR11 < 160) { // 160 --- 10 [us] si fact = 1 // Transition d'état de la pin 11, faite un peu trop tôt, pour éviter de la rater. if (wOCR11 < 20) { // 16 --- 1 [us] si fact = 1 // Les deux transtions se font en même temps bTmp = PORTB; if (bEtat12) bTmp |= 0b00010000; else bTmp &= 0b11101111; if (bEtat11) bTmp &= 0b11110111; else bTmp |= 0b00001000; PORTB = bTmp; // Les deux transitions en même temps } else { // La transition 12 se fait quelques micro-secondes avant la transition 11. if (bEtat12) PORTB |= 0b00010000; // Transition d'état de la pin 12 else PORTB &= 0b11101111; for (bTmp=0; bTmp<lowByte(wOCR11); bTmp+=24) {} // Pour attendre un peu if (bEtat11) PORTB = PORTB & 0b11110111; // Transition d'état de la pin 11 else PORTB = PORTB | 0b00001000; } // Changement de l'état 11 bEtat11 = 1 - bEtat11; wOCR11 += wOCR11_new+1; wOCR11_gap += wOCR11_delta; if (wOCR11_gap > 30000) { // Il faut ajouter un cycle à wOCR11, pour se rapprocher de la fréquence désirée wOCR11++; wOCR11_gap -= 30000; } } // if (wOCR11 < 160) else { // Les deux transitions ne sont pas trop rapprochées, wOCR11 >= 160 if (bEtat12) PORTB = PORTB | 0b00010000; // Transition d'état de la pin 12 else PORTB = PORTB & 0b11101111; } wOCR11--; } // Définition de quand aura lieu la prochaine transistion d'état d'une pin et de laquelle. if (wOCR11 < wOCR12) { OCR1A = wOCR11; bPinNext = 11; } else { // wOCR11 >= wOCR12 OCR1A = wOCR12; bPinNext = 12; } // Pour des tests, voir si on rate une interruption //if (TCNT1 >= OCR1A) { bEtat_enable = 0; OCR1A = wOCR11_new; wOCR11 = wOCR11_new; wOCR12 = wOCR12_new + 200;} } // ISR // FIN.
Plan du Site :
Home
Microcontroleur
code_exemples.html
code_exemples2.html
( = http://www.juggling.ch/gisin/microcontroleur/code_exemples2.html )
Page mise à jour le 28 juin 2020 par Bernard Gisin
( Envoyer un e-mail )
Hébergement par : www.infomaniak.ch