Operácie

Meranie dĺžky impulzu 2: Rozdiel medzi revíziami

Z SensorWiki

(Úloha:)
(29 medziľahlých úprav od rovnakého používateľa nie je zobrazených.)
Riadok 1: Riadok 1:
 +
 +
 +
<div style='text-align: center;'>
 +
[[Súbor:MIPS_pulseDemoScope01.png]]<BR>
 +
''Meranie impulzov na osciloskope.''
 +
</div>
  
 
V tomto návode vytvoríme program na meranie krok za krokom.
 
V tomto návode vytvoríme program na meranie krok za krokom.
 +
 +
 +
== 1. Neznámy signál a základný program ==
  
  
Riadok 91: Riadok 100:
 
ISR (TIMER0_OVF_vect)     
 
ISR (TIMER0_OVF_vect)     
 
{   
 
{   
   timer0ext++;       // pomocne pocitadlo
+
   timer0ext++;               // pomocne pocitadlo
  
   if (timer0ext>20)
+
   if (timer0ext>15)         // 15x bude Log. 1
 
       set_bit(PORTB,LED1);
 
       set_bit(PORTB,LED1);
 
 
   if (timer0ext>64) // po 61 preruseniach zmeni stav
+
   if (timer0ext>74)         // 59x (=74-15) bude log.0
 
     {
 
     {
  timer0ext = 0;
+
  timer0ext = 0;     // a zaroven zacne cely cyklus znova
       clear_bit(PORTB,LED1); //predtym len toggle a zmenilo to 50:50
+
       clear_bit(PORTB,LED1);  
 
  }  
 
  }  
 
}
 
}
Riadok 142: Riadok 151:
  
 
Tento program preložte, nahrajte do procesora a pripojte sa cez sériový terminál. LED dióda na doske by mala začať blikať
 
Tento program preložte, nahrajte do procesora a pripojte sa cez sériový terminál. LED dióda na doske by mala začať blikať
cca 1x za sekundu a vo výpise by ste mali vidieť, že na vstupe je stále log. 0.  
+
cca 1x za sekundu a vo výpise by ste mali vidieť, že na vstupe je stále log. 0.
 +
 
 +
== 2. Meranie frekvencie / periódy ==
 +
  
 
Chceme najskôr zmerať, s akou frekvenciou sa mení výstup s LED diódou (teda PB5, na doske D13). Na to použijeme to najpresnejšie počítadlo,  
 
Chceme najskôr zmerať, s akou frekvenciou sa mení výstup s LED diódou (teda PB5, na doske D13). Na to použijeme to najpresnejšie počítadlo,  
Riadok 148: Riadok 160:
 
nábežná hrana, odchytíme aktuálny stav počítadla TCNT1 do záchytného (capture) registra ICR1 (Input Capture Register). Toto odchytenie by sme
 
nábežná hrana, odchytíme aktuálny stav počítadla TCNT1 do záchytného (capture) registra ICR1 (Input Capture Register). Toto odchytenie by sme
 
síce mohli spraviť aj softvérovo, teda kontrolovať stav na vstupe PB5 a pri zmene sa pozrieť do TCNT1 a odpamätať si aktuálny stav. Ale to je
 
síce mohli spraviť aj softvérovo, teda kontrolovať stav na vstupe PB5 a pri zmene sa pozrieť do TCNT1 a odpamätať si aktuálny stav. Ale to je
nepohodlné a nepresné, preto na to využijeme možnosť spraviť to automaticky - počítadlo T1 to umožňuje spraviť signálom na vstupe PB0. Preto musíte  
+
nepohodlné a nepresné, preto na to využijeme možnosť spraviť to automaticky - počítadlo T1 to umožňuje spraviť signálom na vstupe PB0. Preto  
prepojiť káblikom výstup D13 (PB5) so vstupom D8 (PB0).  
+
'''musíte prepojiť káblikom výstup D13 (PB5) so vstupom D8 (PB0)'''.  
  
 
V demonštračnom programe by ste už mali vidieť meniaci sa stav na vstupe.  
 
V demonštračnom programe by ste už mali vidieť meniaci sa stav na vstupe.  
Riadok 163: Riadok 175:
 
Ďalším krokom bude nakonfigurovanie počítadla T1 tak, aby jednak samostatne počítalo od 0 po 65 535 a zároveň aby sa aktuálny stav počítadla
 
Ďalším krokom bude nakonfigurovanie počítadla T1 tak, aby jednak samostatne počítalo od 0 po 65 535 a zároveň aby sa aktuálny stav počítadla
 
odchytil do registra ICR1. Pri konfigurácii sa dá vybrať, či sa odchytávanie uskutoční pri nábežnej, alebo dobežnej hrane.  
 
odchytil do registra ICR1. Pri konfigurácii sa dá vybrať, či sa odchytávanie uskutoční pri nábežnej, alebo dobežnej hrane.  
 +
 +
 +
<div style='text-align: center;'>
 +
[[Súbor:MIPS_pulseDemoTimer01.png|1200px]]<BR>
 +
''Bloková schéma počítadla T1 v režime zachytávania impulzov.''
 +
</div>
  
 
Zvolíme spúšťanie ICR nábežnou hranou, počítadlo bude počítať s frekvenciou 16 MHz:1024 (prescaler 1024), a do výpisu si pridáme aj stavy  
 
Zvolíme spúšťanie ICR nábežnou hranou, počítadlo bude počítať s frekvenciou 16 MHz:1024 (prescaler 1024), a do výpisu si pridáme aj stavy  
 
všetkých zúčastnených registrov
 
všetkých zúčastnených registrov
 +
 +
<source lang="c++" style="background: LightYellow;">
 +
 +
...
 +
 +
/* Doplnime definiciu funkcie, ktora inicializuje T1 */
 +
 +
void timer1_init(void)
 +
{
 +
    TCCR1A  = 0x00;                // Mode 0: normal
 +
    TCCR1B  = (1<<CS02)|(1<<CS00);  // Clock :1024 
 +
    TCCR1B |= (1<<ICES1);          // Capture on rising edge
 +
    TCNT1  = 0x0000;              // 16-bit Counter reset
 +
 +
}
 +
 +
...
 +
 +
/* ktoru potom pridame na koniec inicializacie, teda
 +
  niekam pred while(1)                              */
 +
 +
  timer1_init();
 +
 +
...
 +
 +
/* a napokon rozsirime funkciu na vypis hodnot  */
 +
 +
printf("Input D8: %u TCNT1: %u ICR: %u\r",inputValue,TCNT1,ICR1);
 +
 +
...
 +
</source>
 +
 +
Mali by ste dostať výstup nejako podobný tomuto
  
 
<source linenumbers lang="c">
 
<source linenumbers lang="c">
Input D8 = 1  TCNT1 =  353  ICR =    0
+
Input D8: 0 TCNT1: 35579 ICR: 23040
Input D8 = 1  TCNT1 =  1543  ICR =    0
+
Input D8: 0 TCNT1: 37238 ICR: 23040
Input D8 = 1  TCNT1 =  2749  ICR =    0
+
Input D8: 0 TCNT1: 38897 ICR: 23040 // tuto niekde nastala zmena a zachyti sa novy stav do ICR
Input D8 = 1  TCNT1 =  3956  ICR =    0
+
Input D8: 1 TCNT1: 40556 ICR: 39680
Input D8 = 1  TCNT1 =  5163  ICR =    0
+
Input D8: 1 TCNT1: 42215 ICR: 39680
Input D8 = 0  TCNT1 =  6369  ICR =  6070  D = 6070  T = 388.4800 ms
+
Input D8: 1 TCNT1: 43874 ICR: 39680
Input D8 = 0 TCNT1 =  7706  ICR =  6070 
+
Input D8: 1 TCNT1: 45533 ICR: 39680
Input D8 = 0 TCNT1 =  8961  ICR =  6070 
+
Input D8: 1 TCNT1: 47192 ICR: 39680
Input D8 = 0 TCNT1 = 10216  ICR =  6070 
+
Input D8: 1 TCNT1: 48851 ICR: 39680
Input D8 = 0  TCNT1 = 11488  ICR =  6070 
+
Input D8: 1 TCNT1: 50510 ICR: 39680
Input D8 = 0  TCNT1 = 12759 ICR =  6070 
+
Input D8: 0 TCNT1: 52169 ICR: 39680
Input D8 = 1 TCNT1 = 14031  ICR =  6070 
+
Input D8: 0 TCNT1: 53828 ICR: 39680
Input D8 = 1 TCNT1 = 15303  ICR =  6070 
+
Input D8: 0 TCNT1: 55487 ICR: 39680 // a tuto je dalsia zmena, znova sa zachyti do ICR
Input D8 = 1 TCNT1 = 16574  ICR =  6070 
+
Input D8: 1 TCNT1: 57145 ICR: 56320
Input D8 = 1 TCNT1 = 17846  ICR =  6070 
+
Input D8: 1 TCNT1: 58804 ICR: 56320
Input D8 = 1 TCNT1 = 19117  ICR =  6070 
+
Input D8: 1 TCNT1: 60463 ICR: 56320
Input D8 = 1 TCNT1 = 20389  ICR =  6070 
+
 
Input D8 = 1 TCNT1 = 21661  ICR = 21591  D = 15521  T = 993.3441 ms
+
</source>
Input D8 = 0 TCNT1 = 23046  ICR = 21591 
+
 
Input D8 = 0 TCNT1 = 24334  ICR = 21591 
+
 
Input D8 = 0 TCNT1 = 25622  ICR = 21591 
+
Vo výpise vidíme, že keď sa zmení stav na vstupe D8 tak sa aktuálny stav počítadla TCNT1 prepíše do ICR. Nám teraz stačí odpočítať dve po sebe idúce hodnoty ICR a získame tak časový rozdiel medzi dvoma nábežnými hranami, čo je vlastne perióda signálu na vstupe.  
Input D8 = 0  TCNT1 = 26910 ICR = 21591 
+
 
Input D8 = 0  TCNT1 = 28197  ICR = 21591 
+
V našej ukážke sme namerali zmenu dvakrát, rozdiel medzi stavom počítadla bol v oboch prípadoch rovnaký:
Input D8 = 1 TCNT1 = 29485  ICR = 21591 
+
 
Input D8 = 1 TCNT1 = 30773  ICR = 21591 
+
<math> 56320 - 39680 = 16640 </math>
Input D8 = 1  TCNT1 = 32061  ICR = 21591 
+
 
Input D8 = 1  TCNT1 = 33349  ICR = 21591 
+
alebo
Input D8 = 1  TCNT1 = 34636  ICR = 21591 
+
 
Input D8 = 1  TCNT1 = 35924  ICR = 21591 
+
<math> 39680 - 23040 = 16640 </math>
Input D8 = 1  TCNT1 = 37212  ICR = 37110  D = 15519  T = 993.2161 ms
+
 
Input D8 = 0  TCNT1 = 38597  ICR = 37110 
+
Ak by počítadlo T1 inkrementovalo svoj stav vždy presne po 1ms, tak by sme mali po odčítaní priamo čas v milisekundách. Lenže počítadlo T1 inkrementuje svoj stav vždy po čase
Input D8 = 0 TCNT1 = 39885  ICR = 37110 
+
 
Input D8 = 0  TCNT1 = 41173  ICR = 37110 
+
<math> \frac{1}{16\,000\,000:1\,024} = 64 \; [\mu s] </math>
Input D8 = 0  TCNT1 = 42461  ICR = 37110 
+
 
Input D8 = 0  TCNT1 = 43749  ICR = 37110 
+
Preto nami nameraná dĺžka impulzu je teda
Input D8 = 1 TCNT1 = 45037  ICR = 37110 
+
 
Input D8 = 1 TCNT1 = 46324  ICR = 37110 
+
<math>16\,640\cdot 64 = 1\,064\,960\;[\mu{}s] = 1\,064,960\; [ms] = 1,06496\;[s] </math>
Input D8 = 1  TCNT1 = 47612  ICR = 37110 
+
 
Input D8 = 1  TCNT1 = 48900  ICR = 37110 
+
Porovnajte výsledok s obrázkom z osciloskopu v úvode stránky.
Input D8 = 1  TCNT1 = 50188  ICR = 37110 
+
 
Input D8 = 1  TCNT1 = 51476  ICR = 37110 
+
Tento výpočet by už mohol robiť aj mikroprocesor, ale nejdeme si teraz komplikovať život počítaním v plávajúcej desatinnej čiarke. Kto si život komplikovať chce, tak si prečíta
Input D8 = 1  TCNT1 = 52763  ICR = 52630  D = 15520  T = 993.2800
+
[[Typy premenných v avr-gcc]].
Input D8 = 0  TCNT1 = 54149  ICR = 52630 
+
 
Input D8 = 0  TCNT1 = 55437  ICR = 52630 
+
Zamyslieť by sme sa však mali ešte nad situáciou, keď počítadlo TCNT1 pretečie. Teda ak jedna nábežná hrana príde napríklad v čase, keď hodnota TCNT1 = 55296 a pri druhej nábežnej hrane
Input D8 = 0  TCNT1 = 56724  ICR = 52630 
+
už počítadlo začalo dávno počítať znova od nuly, takže nájdeme v ICR odchytenú hodnotu 6400.
Input D8 = 0  TCNT1 = 58012  ICR = 52630 
+
 
Input D8 = 0  TCNT1 = 59300  ICR = 52630  
+
== 3. Meranie frekvencie s prerušením ==
Input D8 = 1 TCNT1 = 60588  ICR = 52630 
+
 
Input D8 = 1  TCNT1 = 61876  ICR = 52630 
+
Ostaňme ešte pri meraní frekvencie, resp. periódy a vylepšime program aspoň o automatické vypočítavanie rozdielu, pričom by sme boli radi, aby sme už nevypisovali všetky možné stavy, ale len ten
Input D8 = 1  TCNT1 = 63163  ICR = 52630 
+
jeden, kedy sa zmení ICR a zaktualizujeme zmeranú periódu. Nie je to nič komplikované, stačí, ak zároveň s odchytením registra vyvoláme aj prerušenie a v jeho obsluhe porovnáme aktuálnu hodnotu s predošlou.
Input D8 = 1  TCNT1 = 64451  ICR = 52630 
+
 
Input D8 = 1  TCNT1 =  203  ICR = 52630 
+
Na to musíme spraviť niekoľko krokov. Prerušenie samotné musíme najprv povoliť. To znamená, že do funkcie <code>timer1_init(void)</code> pridáme nasledovný riadok
Input D8 = 1  TCNT1 =  1458  ICR = 52630 
+
 
Input D8 = 1  TCNT1 =  2730  ICR = 2619  D = 15525  T = 993.6000
+
<source lang="c++" style="background: LightYellow;">  
Input D8 = 0  TCNT1 =  4083  ICR =  2619 
+
  TIMSK1 = (1<<ICIE1);          // Enable Input Capture Interrupt
 +
</source>
 +
 
 +
a nesmieme zabudnúť ani na povolenie na globálnej úrovni príkazom
 +
 
 +
<source lang="c++" style="background: LightYellow;">
 +
  sei();
 
</source>
 
</source>
  
Ak chceme merať aj striedu, musíme meranie realizovať s prerušením a v obsluhe prerušenia preklopiť konfiguračný bit, ktorý rozhoduje o prepise
+
ktorý zaradíme na koniec inicializácie, napr. pred while(1) cyklus.
TCNT do ICR registra. Je to bit ICES1, ktorý je v TCCR1B.6.
+
 
 +
No a samozrejme potrebujeme samotnú obsluhu prerušenia, v ktorej vypočítame rozdiel aktuálnej hodnoty ICR od predošlej a vypíšeme ho na terminál. Hneď potom si aktuálnu hodnotu ICR odložíme do premennnej <code>oldICR</code>, pretože pri ďalšom vyvolaní prerušenia bude ICR obsahovať už nový stav. Premenná <code>oldICR</code> musí byť definovaná ako globálna a typu volatile, aby sme k nej vedeli pristúpiť aj z prerušenia a aby sa nemenila medzi prerušeniami. Príznak prerušenia ICF1 nulovať nemusíme, to nastane automaticky vyvolaním obslužnej rutiny.
  
<source lang="c">
+
<source lang="c++" style="background: LightYellow;">
#include <avr/interrupt.h> 
 
  
volatile int newTick = 0;  // The variable for interrupt should be declared as a volatile one!
+
...
                 
 
ISR(TIMER1_CAPT_vect)      // Timer 1 Capture Interrupt Service Routine
 
{
 
  TCCR1B = ??              // toggle Edge Select bit
 
  newTick = ICR1;
 
};
 
  
main()
+
volatile unsigned int oldICR = 0;    // premenna, kam si odlozime staru hodnotu ICR
{
 
  
  DDRB = ??                // Set ICR - Port B, pin0  as INPUT
+
...
TCCR1B = ??                // T1 clk = F_CPU : 1024, falling edge pin ICP1,
 
TCCR1A = ??                // T1 in timer mode !! Note: if You omit this, TCNT1 will be only 8-bit !!
 
  TCNT1 = 0x0000;          // initialize the counter (16-bit! Low+High bytes)
 
  TIFR1 = ??                // (1<<ICF1);  if a 1 is written to a ICF1 bit
 
                            //              - the ICF1 bit will be cleared
 
                     
 
TIMSK1 = ??                // Enable ICR interrupt
 
  sei();                    // Enable ALL interrupts                     
 
  
  ....                      // TODO: display measured value somewhere
+
ISR(TIMER1_CAPT_vect)              // Timer 1 Capture Interrupt Service Routine
 +
{
 
    
 
    
 +
  printf("* Pulse %u \n",ICR1-oldICR);
 +
  oldICR = ICR1;
 +
 
}
 
}
 +
 +
...
 +
</source>
 +
 +
 +
A potom dostaneme napríklad takýto výstup
 +
 +
<source lang="c++" style="background: LightYellow;">
 +
 +
Input D8: 0 TCNT1: 57617 ICR: 41728
 +
* Pulse 16640
 +
Input D8: 1 TCNT1: 59315 ICR: 58368
 +
Input D8: 1 TCNT1: 60974 ICR: 58368
 +
Input D8: 1 TCNT1: 62633 ICR: 58368
 +
 
</source>
 
</source>
  
 +
Vidíme, že pomedzi pravidelné výpisy (ktoré už teraz ani nepotrebujeme) sa občas prepašuje výpis nameranej hodnoty. Ten sa tam dostal v obsluhe prerušenia. Nie je to síce dobrá stratégia, robiť výpisy v prerušení, ale opäť to pre zjednodušenie necháme tak. Správne by sa v obsluhe mal nastaviť len príznak, že hodnota je pripravená na výpis a ten by sme spravili potom v hlavnej slučke.
  
Ukážka pre Arduino využíva zabudovaný príkaz [http://arduino.cc/en/Reference/PulseIn PulseIn]:
 
<source lang="c">
 
#define SWITCH 8                // select the pin for Switch
 
unsigned long duration;
 
  
void setup()
+
== 4. Zobrazenie v správnych jednotkách ==
 +
 
 +
 
 +
Ešte by sme mohli upraviť výpočet v prerušovacej rutine tak, aby sa nám vypisoval priamo čas, teda dĺžka impulzu v sekundách, resp. iných vhodných jednotkách. Vyššie sme si ukázali, že ak vynásobíme rozdiel impulzov v ICR registri hodnotou 64, dostaneme výsledok v mikrosekundách. To je trocha nepohodlné a pre naše účely by stačilo mať výsledok v milisekundách s presnosťou na 1ms aby sme sa vyhli potrebe desatinného čísla.
 +
Upravený kód pre obsluhu prerušenia môže vyzerať napríklad takto
 +
 
 +
<source lang="c++" style="background: LightYellow;">
 +
 
 +
ISR(TIMER1_CAPT_vect)               // Timer 1 Capture Interrupt Service Routine
 
{
 
{
pinMode(SWITCH, INPUT);       // this pin is an INPUT
+
  unsigned long pulsePeriod;
  Serial.begin(9600);
+
 
Serial.println("PulsIn test:");
+
  pulsePeriod = (ICR1-oldICR);       // 16640
 +
  pulsePeriod = pulsePeriod*64;      //  1064960 vysledok v [us]
 +
  pulsePeriod = pulsePeriod/1000;   //  1064    vysledok v [ms]
 +
 
 +
  printf("* Pulse %lu [ms]\n",pulsePeriod); //aj tu musime zmenit %u na %lu
 +
  oldICR = ICR1;
 +
 
 
}
 
}
 +
</source>
 +
  
void loop()                      // endless loop
 
{
 
duration = pulseIn(SWITCH, HIGH);
 
Serial.print(" T1 = ");
 
Serial.print(duration,DEC);
 
Serial.print(" [us]");
 
  
duration = pulseIn(SWITCH, LOW);
+
Keďže rozdiel ICR môže byť aj dosť veľké číslo, ľahko by sa nám mohlo stať, že násobením hodnotou 64 by sme sa už nezmestili do rozsahu premennej typu int. Preto premennú pulsePeriod definujeme ako typ long. Mohli by sme síce prehodiť poradie operácií a najprv deliť 1000 a až potom násobiť, ale tým by sme zasa zbytočne strácali presnosť merania (stále pracujeme s celými číslami, desatiny sa stratia).
Serial.print(" T0 = ");
+
 
Serial.print(duration,DEC);
+
== 4. Meranie plnenia (striedy) ==
Serial.println(" [us]");
+
 
}
+
 
 +
 
 +
Ak chceme merať aj striedu, alebo plnenie, ktoré je definované ako pomer času log.1 k celej perióde, potrebujeme zmerať len šírku impulzu log. 1 a nie celú periódu. Preto musíme najprv zachytiť stav TCNT1 pri nábežnej hrane a potom druhý raz pri dobežnej hrane. Meranie budeme opäť realizovať s prerušením a v obsluhe prerušenia preklopíme konfiguračný bit, ktorý rozhoduje o polarite aktivačného signálu. Je to bit ICES1, ktorý je v TCCR1B.6.
 +
 
 +
<source lang="c">
 +
 
 +
  toggle_bit(TCCR1B,ICES1);   // toggle Edge Select bit
 +
 
 
</source>
 
</source>
  
 +
Pripomíname, že plnenie (duty cycle) je pre obdĺžnikový signál definované ako
 +
 +
 +
<math>
 +
D = \frac{T_{on}}{T_{total}} = \frac{T_{on}}{T_{on} + T_{off}}
 +
</math>
 +
 +
 +
 +
== '''Úloha:''' ==
 +
 +
 +
Zdrojové kódy na tejto stránke majú nastavené trocha inú periódu aj plnenie ako je uvedené v texte. Vaša úloha je zmerať
 +
* Periódu signálu v [ms]
 +
* Plnenie signálu [-] (pozor na definíciu)
  
=== Literatúra ===
+
== Literatúra ==
  
 
* [http://www.atmel.com/dyn/resources/prod_documents/doc2505.pdf AVR130: Setup and Use the AVR® Timers.]  Aplication Note, Atmel Corporation 2002.<BR> + [http://www.atmel.com/dyn/resources/prod_documents/AVR130.zip software download]
 
* [http://www.atmel.com/dyn/resources/prod_documents/doc2505.pdf AVR130: Setup and Use the AVR® Timers.]  Aplication Note, Atmel Corporation 2002.<BR> + [http://www.atmel.com/dyn/resources/prod_documents/AVR130.zip software download]
Riadok 291: Riadok 381:
  
  
[[Category:AVR]][[Category:MMP]][[Category:DVPS]]
+
[[Category:AVR]][[Category:MIPS]]

Verzia zo dňa a času 05:11, 18. máj 2021


MIPS pulseDemoScope01.png
Meranie impulzov na osciloskope.

V tomto návode vytvoríme program na meranie krok za krokom.


1. Neznámy signál a základný program

Najprv si vytvorte nový projekt, ktorý bude pozostávať z nasledovných súborov.

#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#include "hardware.h"
#include "uart.h"

FILE uart_stream = FDEV_SETUP_STREAM(uart_putc, uart_getc, _FDEV_SETUP_RW);


int main(void)
{   
   hw_init();              // tu je skryte nastavenie vystupu D13 (PD5) na nejaku frekvenciu a plnenie
   
   uart_init(57600);       // konfiguracia seriovej linky na rychlost 57600 Bd

   stdout = stdin = &uart_stream;
   
   printf("Ready to start...\n\n");   
 
    /* 
     * v nekonecnej slucke budeme do terminalu vypisovat
     * hodnotu na vstupe PB0 (na doske oznaceny ako D8)
     * vypis bude 10x za sekundu (preto 100ms) 
     */

   while(1)  
    {
     unsigned char inputValue =0;
	 
     if bit_is_set(PINB,PB0)
       inputValue = 1;
     else
       inputValue = 0;
	 
     printf("Input D8: %u\r",inputValue); 
     _delay_ms(100);

    }

 return(0); 
}
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define LED1  PB5          // Arduino D13 - zabudovana dioda 
#define RCinput PB3        // Arduino D8

#define set_bit(ADDRESS,BIT) (ADDRESS |= (1<<BIT))
#define clear_bit(ADDRESS,BIT) (ADDRESS &= ~(1<<BIT))
#define toggle_bit(ADDRESS,BIT) (ADDRESS ^= (1<<BIT))

void hw_init(void);
void timer0_init(void);
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define LED1  PB5             // Arduino D13 - zabudovana dioda 
#define inputPulse PB0        // Arduino D8

#define set_bit(ADDRESS,BIT) (ADDRESS |= (1<<BIT))
#define clear_bit(ADDRESS,BIT) (ADDRESS &= ~(1<<BIT))
#define toggle_bit(ADDRESS,BIT) (ADDRESS ^= (1<<BIT))

unsigned volatile static int timer0ext = 0;


ISR (TIMER0_OVF_vect)     
{  
   timer0ext++;	              // pomocne pocitadlo

   if (timer0ext>15)          // 15x bude Log. 1
       set_bit(PORTB,LED1);
	
   if (timer0ext>74)          // 59x (=74-15) bude log.0 
     {
	   timer0ext = 0;     // a zaroven zacne cely cyklus znova
       clear_bit(PORTB,LED1); 
	  }	 
}

void timer0_init(void)  /* CTC match mode */
{
    /* Timer 0 Normal mode with clock = I/O clock / 1024 */    
	TCCR0A = 0x00;                 // Mode 0: normal
	TCCR0B = (1<<CS02)|(1<<CS00);  // Clock :1024  
	 TCNT0 = 0x00;                 // 8-bit Counter reset
	 TIFR0 = (1 << TOV0);          // Clear interrupt flag 
	TIMSK0 = (1 << TOIE0);         // Enable Overflow Interrupt 
	  sei();					   // Enable interrupts 
	
}

void hw_init(void)
{
   cli();                     // zakaz vsetky prerusenia

   set_bit(DDRB,LED1);        // set pin LED1 as output
   clear_bit(DDRB,inputPulse);     // set RC input as input
   timer0_init();
}
 /* použite vlastný z predošlého cvičenia */
 /* použite vlastný z predošlého cvičenia */



Tento program preložte, nahrajte do procesora a pripojte sa cez sériový terminál. LED dióda na doske by mala začať blikať cca 1x za sekundu a vo výpise by ste mali vidieť, že na vstupe je stále log. 0.

2. Meranie frekvencie / periódy

Chceme najskôr zmerať, s akou frekvenciou sa mení výstup s LED diódou (teda PB5, na doske D13). Na to použijeme to najpresnejšie počítadlo, ktoré na procesore máme k dispozícii, t.j. 16-bitové počítadlo T1. Necháme ho len samovoľne počítať s nejakou frekvenciou a vždy, keď príde nábežná hrana, odchytíme aktuálny stav počítadla TCNT1 do záchytného (capture) registra ICR1 (Input Capture Register). Toto odchytenie by sme síce mohli spraviť aj softvérovo, teda kontrolovať stav na vstupe PB5 a pri zmene sa pozrieť do TCNT1 a odpamätať si aktuálny stav. Ale to je nepohodlné a nepresné, preto na to využijeme možnosť spraviť to automaticky - počítadlo T1 to umožňuje spraviť signálom na vstupe PB0. Preto musíte prepojiť káblikom výstup D13 (PB5) so vstupom D8 (PB0).

V demonštračnom programe by ste už mali vidieť meniaci sa stav na vstupe.

Ready to measure...

Input D8: 0 
Input D8: 0 
Input D8: 1 
Input D8: 1 


Ďalším krokom bude nakonfigurovanie počítadla T1 tak, aby jednak samostatne počítalo od 0 po 65 535 a zároveň aby sa aktuálny stav počítadla odchytil do registra ICR1. Pri konfigurácii sa dá vybrať, či sa odchytávanie uskutoční pri nábežnej, alebo dobežnej hrane.


MIPS pulseDemoTimer01.png
Bloková schéma počítadla T1 v režime zachytávania impulzov.

Zvolíme spúšťanie ICR nábežnou hranou, počítadlo bude počítať s frekvenciou 16 MHz:1024 (prescaler 1024), a do výpisu si pridáme aj stavy všetkých zúčastnených registrov

...

/* Doplnime definiciu funkcie, ktora inicializuje T1 */

void timer1_init(void)
{
    TCCR1A  = 0x00;                 // Mode 0: normal
    TCCR1B  = (1<<CS02)|(1<<CS00);  // Clock :1024  
    TCCR1B |= (1<<ICES1);           // Capture on rising edge
    TCNT1   = 0x0000;               // 16-bit Counter reset
	
}

...

/* ktoru potom pridame na koniec inicializacie, teda
   niekam pred while(1)                               */

  timer1_init();

...

/* a napokon rozsirime funkciu na vypis hodnot  */

printf("Input D8: %u TCNT1: %u ICR: %u\r",inputValue,TCNT1,ICR1); 

...

Mali by ste dostať výstup nejako podobný tomuto

Input D8: 0 TCNT1: 35579 ICR: 23040
Input D8: 0 TCNT1: 37238 ICR: 23040
Input D8: 0 TCNT1: 38897 ICR: 23040  // tuto niekde nastala zmena a zachyti sa novy stav do ICR
Input D8: 1 TCNT1: 40556 ICR: 39680
Input D8: 1 TCNT1: 42215 ICR: 39680
Input D8: 1 TCNT1: 43874 ICR: 39680
Input D8: 1 TCNT1: 45533 ICR: 39680
Input D8: 1 TCNT1: 47192 ICR: 39680
Input D8: 1 TCNT1: 48851 ICR: 39680
Input D8: 1 TCNT1: 50510 ICR: 39680
Input D8: 0 TCNT1: 52169 ICR: 39680
Input D8: 0 TCNT1: 53828 ICR: 39680
Input D8: 0 TCNT1: 55487 ICR: 39680  // a tuto je dalsia zmena, znova sa zachyti do ICR
Input D8: 1 TCNT1: 57145 ICR: 56320
Input D8: 1 TCNT1: 58804 ICR: 56320
Input D8: 1 TCNT1: 60463 ICR: 56320


Vo výpise vidíme, že keď sa zmení stav na vstupe D8 tak sa aktuálny stav počítadla TCNT1 prepíše do ICR. Nám teraz stačí odpočítať dve po sebe idúce hodnoty ICR a získame tak časový rozdiel medzi dvoma nábežnými hranami, čo je vlastne perióda signálu na vstupe.

V našej ukážke sme namerali zmenu dvakrát, rozdiel medzi stavom počítadla bol v oboch prípadoch rovnaký:

 56320 - 39680 = 16640

alebo

  39680 - 23040 = 16640

Ak by počítadlo T1 inkrementovalo svoj stav vždy presne po 1ms, tak by sme mali po odčítaní priamo čas v milisekundách. Lenže počítadlo T1 inkrementuje svoj stav vždy po čase

 \frac{1}{16\,000\,000:1\,024} = 64 \; [\mu s]

Preto nami nameraná dĺžka impulzu je teda

16\,640\cdot 64 = 1\,064\,960\;[\mu{}s] = 1\,064,960\; [ms] = 1,06496\;[s]

Porovnajte výsledok s obrázkom z osciloskopu v úvode stránky.

Tento výpočet by už mohol robiť aj mikroprocesor, ale nejdeme si teraz komplikovať život počítaním v plávajúcej desatinnej čiarke. Kto si život komplikovať chce, tak si prečíta Typy premenných v avr-gcc.

Zamyslieť by sme sa však mali ešte nad situáciou, keď počítadlo TCNT1 pretečie. Teda ak jedna nábežná hrana príde napríklad v čase, keď hodnota TCNT1 = 55296 a pri druhej nábežnej hrane už počítadlo začalo dávno počítať znova od nuly, takže nájdeme v ICR odchytenú hodnotu 6400.

3. Meranie frekvencie s prerušením

Ostaňme ešte pri meraní frekvencie, resp. periódy a vylepšime program aspoň o automatické vypočítavanie rozdielu, pričom by sme boli radi, aby sme už nevypisovali všetky možné stavy, ale len ten jeden, kedy sa zmení ICR a zaktualizujeme zmeranú periódu. Nie je to nič komplikované, stačí, ak zároveň s odchytením registra vyvoláme aj prerušenie a v jeho obsluhe porovnáme aktuálnu hodnotu s predošlou.

Na to musíme spraviť niekoľko krokov. Prerušenie samotné musíme najprv povoliť. To znamená, že do funkcie timer1_init(void) pridáme nasledovný riadok

	 
   TIMSK1 = (1<<ICIE1);          // Enable Input Capture Interrupt

a nesmieme zabudnúť ani na povolenie na globálnej úrovni príkazom

   sei();

ktorý zaradíme na koniec inicializácie, napr. pred while(1) cyklus.

No a samozrejme potrebujeme samotnú obsluhu prerušenia, v ktorej vypočítame rozdiel aktuálnej hodnoty ICR od predošlej a vypíšeme ho na terminál. Hneď potom si aktuálnu hodnotu ICR odložíme do premennnej oldICR, pretože pri ďalšom vyvolaní prerušenia bude ICR obsahovať už nový stav. Premenná oldICR musí byť definovaná ako globálna a typu volatile, aby sme k nej vedeli pristúpiť aj z prerušenia a aby sa nemenila medzi prerušeniami. Príznak prerušenia ICF1 nulovať nemusíme, to nastane automaticky vyvolaním obslužnej rutiny.

...

volatile unsigned int oldICR = 0;    // premenna, kam si odlozime staru hodnotu ICR

...

ISR(TIMER1_CAPT_vect)               // Timer 1 Capture Interrupt Service Routine
{
  
  printf("* Pulse %u \n",ICR1-oldICR); 
  oldICR = ICR1;

}

...


A potom dostaneme napríklad takýto výstup

Input D8: 0 TCNT1: 57617 ICR: 41728
* Pulse 16640
Input D8: 1 TCNT1: 59315 ICR: 58368
Input D8: 1 TCNT1: 60974 ICR: 58368
Input D8: 1 TCNT1: 62633 ICR: 58368

Vidíme, že pomedzi pravidelné výpisy (ktoré už teraz ani nepotrebujeme) sa občas prepašuje výpis nameranej hodnoty. Ten sa tam dostal v obsluhe prerušenia. Nie je to síce dobrá stratégia, robiť výpisy v prerušení, ale opäť to pre zjednodušenie necháme tak. Správne by sa v obsluhe mal nastaviť len príznak, že hodnota je pripravená na výpis a ten by sme spravili potom v hlavnej slučke.


4. Zobrazenie v správnych jednotkách

Ešte by sme mohli upraviť výpočet v prerušovacej rutine tak, aby sa nám vypisoval priamo čas, teda dĺžka impulzu v sekundách, resp. iných vhodných jednotkách. Vyššie sme si ukázali, že ak vynásobíme rozdiel impulzov v ICR registri hodnotou 64, dostaneme výsledok v mikrosekundách. To je trocha nepohodlné a pre naše účely by stačilo mať výsledok v milisekundách s presnosťou na 1ms aby sme sa vyhli potrebe desatinného čísla. Upravený kód pre obsluhu prerušenia môže vyzerať napríklad takto

ISR(TIMER1_CAPT_vect)                // Timer 1 Capture Interrupt Service Routine
{
  unsigned long pulsePeriod;
  
  pulsePeriod = (ICR1-oldICR);       //  16640 
  pulsePeriod = pulsePeriod*64;      //  1064960  vysledok v [us]
  pulsePeriod = pulsePeriod/1000;    //  1064     vysledok v [ms]
  
  printf("* Pulse %lu [ms]\n",pulsePeriod); //aj tu musime zmenit %u na %lu
  oldICR = ICR1;

}


Keďže rozdiel ICR môže byť aj dosť veľké číslo, ľahko by sa nám mohlo stať, že násobením hodnotou 64 by sme sa už nezmestili do rozsahu premennej typu int. Preto premennú pulsePeriod definujeme ako typ long. Mohli by sme síce prehodiť poradie operácií a najprv deliť 1000 a až potom násobiť, ale tým by sme zasa zbytočne strácali presnosť merania (stále pracujeme s celými číslami, desatiny sa stratia).

4. Meranie plnenia (striedy)

Ak chceme merať aj striedu, alebo plnenie, ktoré je definované ako pomer času log.1 k celej perióde, potrebujeme zmerať len šírku impulzu log. 1 a nie celú periódu. Preto musíme najprv zachytiť stav TCNT1 pri nábežnej hrane a potom druhý raz pri dobežnej hrane. Meranie budeme opäť realizovať s prerušením a v obsluhe prerušenia preklopíme konfiguračný bit, ktorý rozhoduje o polarite aktivačného signálu. Je to bit ICES1, ktorý je v TCCR1B.6.

  toggle_bit(TCCR1B,ICES1);   // toggle Edge Select bit

Pripomíname, že plnenie (duty cycle) je pre obdĺžnikový signál definované ako



D = \frac{T_{on}}{T_{total}} = \frac{T_{on}}{T_{on} + T_{off}}


Úloha:

Zdrojové kódy na tejto stránke majú nastavené trocha inú periódu aj plnenie ako je uvedené v texte. Vaša úloha je zmerať

  • Periódu signálu v [ms]
  • Plnenie signálu [-] (pozor na definíciu)

Literatúra