This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
et:examples:timer:delay [2009/12/21 10:46] – eemaldatud mikk.leini | et:examples:timer:delay [2020/07/20 09:00] (current) – external edit 127.0.0.1 | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== Viide ====== | ||
+ | //Vajalikud teadmised: | ||
+ | [HW] [[et: | ||
+ | [AVR] [[et: | ||
+ | [LIB] [[et: | ||
+ | |||
+ | ===== Teooria ===== | ||
+ | |||
+ | Tihti on mikrokontrollerite programmis vaja tekitada viiteid, et tegevusi ajastada või nende lõppu oodata. Üks idee poolest lihtsamaid meetodeid mikrokontrolleri töös paus tekitada on selle protsessor mingi muu tegevusega üle koormata - näiteks panna see lugema suuri arve. Protsessori taktsagedusest saab välja arvutada, mitmeni see arve loendama peaks, et kindlat ajalist viidet tekitada. Mingi arvu loendamine nullist protsessori taktsageduse väärtuseni hertsides tekitaks teoreetiliselt viite üks sekund. Praktikas see erinevatel põhjustel päris nii lihtne ei ole. | ||
+ | |||
+ | Kui mikrokontrolleri protsessor arvutab arvudega, mille kahendkuju on sama lai kui selle sisemine siin (AVR puhul 8-bitti), siis protsessoritel võtab üks aritmeetiline tehe, näiteks arvu liitmine ühega, aega 1 protsessori töötakt. Selleks, et arvutada tuhandete või miljonitega, | ||
+ | |||
+ | ===== Praktika ====== | ||
+ | |||
+ | Järgnevalt on toodud näide tarkvaralise viite tekitamisest AVR mikrokontrolleriga. Kirjutatud on C-keele programmilõik, | ||
+ | |||
+ | <code c> | ||
+ | unsigned char x; | ||
+ | // Tsükkel seni kuni x on 100 | ||
+ | for (x = 0; x < 100; x++) | ||
+ | { | ||
+ | // Tühiinstruktsiooniga nop | ||
+ | asm volatile (" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Siinkohal on aga toodud sama C-keele programmilõik pärast kompileerimist. Vasakpoolsed 2 heksadetsimaalarvu on masinkood ja paremal on assemblerkeeles käsk koos operandi(de)ga. Masinkood ja assemblerkeel on üks-üheselt seotud, assembler on lihtsalt masinkoodi inimesele loetaval kujul esitamiseks. Kompileerimisel on kasutatud programmi pikkuse optimeerimist (kompilaatori parameeter -Os). | ||
+ | |||
+ | <code asm> | ||
+ | 80 e0 | ||
+ | 00 00 | ||
+ | 8f 5f subi r24, 0xFF ; r24 registrist 255 lahutamine, ehk +1 liitmine | ||
+ | 84 36 | ||
+ | e1 f7 brne .-8 ; Kui võrdlus oli väär, siis siire 8 baiti tagasi | ||
+ | </ | ||
+ | |||
+ | Kompileeritud kujul on näha, mis tegelikult C-keele tsüklist saab, ja selle järgi saab arvutada, mitu takti ühe tsükli perioodi täitmiseks kulub. Infot instruktsioonide toime ja tööaja kohta leiab AVR käsustiku andmelehest. Antud näites kulub ühe tsükli perioodis 4 instruktsiooni täitmiseks 4 takti, sest kõik instruktsioonid võtavad ühe töötakti. Lisaks kulub enne tsüklit 1 takt laadimisinstruktsiooni jaoks ja tsüklist väljudes 1 lisatakt. Oletades, et kontrolleri töötakt on 14,7456 MHz, võib välja arvutada kogu programmilõigu tekitatud ajalise viite: | ||
+ | |||
+ | (1 + 100 ⋅ 4 + 1) / 14745600 = 27,26 μs | ||
+ | |||
+ | Näites tekitatud viide on mikrosekundites ja kasutatav muutuja on 8-bitine, seega on ka masinkood üsna lihtne. Selleks, et tekitada pausi millisekundites, | ||
+ | |||
+ | Käesoleva teooriaosa eesmärk ei ole siiski masinkoodi tasandil täpset tarkvaralist viidet tekitada, sest see on üsna peen töö ja pealegi on viite tekitamiseks avr-libc ja Kodulabori teegis juba funktsioonid olemas. Need kasutatakse ka järgmistes näidetes. | ||
+ | |||
+ | Tarkvaralise viite puhul on aga oluline teada, et hoolimata oma põhimõttelisest lihtsusest on see äärmiselt ebaefektiivne meetod energiatarbe seisukohast. Kõigil neil taktidel, mil mikrokontroller tegeleb kasutu loendamisega, | ||
+ | |||
+ | [{{ : | ||
+ | |||
+ | Tarkvaraline viide pole ainus meetod pausi tekitamiseks. Sama asja saab teha ka taimeriga. Taimer on riistvaraline kindla sagedusega suurenev või vähenev loendur. Loenduri taktsignaali saab enamasti tekitada mikrokontrolleri töötaktist või mingist välisest taktist. Taktsignaali sagedust saab üldjuhul ka mingi teguriga jagada väiksem taktsageduse saavutamiseks - seda tehakse taktijaguriga, | ||
+ | |||
+ | AVR loendurit saab panna teavitama loenduri ületäitumisest (inglise keeles // | ||
+ | |||
+ | Selleks, et taimeriga viide tekitada, piisabki vaid loenduri seadistamisest ja olekubiti kõrgeks minemise ootamisest. Erinevalt tarkvaralisest viitest ei sõltu taimerite töö kompilaatorist, | ||
+ | |||
+ | Järgnev programmikood käib tarkvaralise viite funktsiooni // | ||
+ | |||
+ | <code c> | ||
+ | // Tarkvaraline viide millisekundites | ||
+ | void sw_delay_ms(unsigned short count) | ||
+ | { | ||
+ | // Viite muutuja nullini loendamine | ||
+ | while (count-- > 0) | ||
+ | { | ||
+ | // 1 ms viide spetsiaalse funktsiooniga | ||
+ | _delay_ms(1); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Toodud funktsiooni kasutamiseks on järgnev programm, mis tekitab lõputus tsüklis kaks viidet: 100 ms ja 900 ms. Lühema viite jooksul LED põleb ja pikema ajal on kustunud - tulemusena LED perioodiliselt vilgatab. | ||
+ | |||
+ | <code c> | ||
+ | // Kodulabori tarkvaralise viite näidisprogramm | ||
+ | // Programm vilgutab ~1 sekundi järel hetkeks LED-i | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | // Põhiprogramm | ||
+ | int main(void) | ||
+ | { | ||
+ | // LED-i viigu väljundiks seadmine | ||
+ | pin_setup_output(led_debug); | ||
+ | |||
+ | // Lõputu tsükkel | ||
+ | while (1) | ||
+ | { | ||
+ | // LED-i süütamine | ||
+ | pin_clear(led_debug); | ||
+ | |||
+ | // Tarkvaraline paus 100 millisekundit | ||
+ | sw_delay_ms(100); | ||
+ | |||
+ | // LED-i kustutamine | ||
+ | pin_set(led_debug); | ||
+ | |||
+ | // Tarkvaraline paus 900 millisekundit | ||
+ | sw_delay_ms(900); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Kuigi näib, et LED vilgatab tõesti 1 sekundi järel, on aeg tegelikult siiski natuke pikem, sest LED-i ja viite funktsioonide väljakutsumised võtavad ka mõned mikrokontrolleri taktid aega. | ||
+ | |||
+ | |||
+ | Järgnev programmikood on taimeril põhinev ehk riistvaraline viitefunktsioon, | ||
+ | |||
+ | Tsüklis toimub loenduri algväärtustamine ja ületäitumise lipukese nullimine (sellesse 1 kirjutades). Seejärel oodatakse, kuni loendur loendab algväärtusest 256-ni, ehk ületäitumiseni. Ületäitumise hetkel läheb ületäitumise lipuke kõrgeks ja 1 ms viide ongi toimunud. Funktsiooni lõpus taimer peatatakse. | ||
+ | |||
+ | <code c> | ||
+ | // Riistvaraline viide millisekundites ATmega kontrolleril | ||
+ | void hw_delay_ms(unsigned short count) | ||
+ | { | ||
+ | // Taimeri algväärtuse arvutamine | ||
+ | register unsigned char timer_start = 256 - F_CPU / 1000 / 64; | ||
+ | |||
+ | // Taimeri käivitamine | ||
+ | timer0_init_normal(TIMER0_PRESCALE_64); | ||
+ | |||
+ | // Viite muutuja nullini loendamine | ||
+ | while (count-- > 0) | ||
+ | { | ||
+ | // Taimeri algväärtustamine | ||
+ | timer0_set_value(timer_start); | ||
+ | |||
+ | // Ületäitumise lipukese nullimine | ||
+ | timer0_overflow_flag_clear(); | ||
+ | |||
+ | // Ületäitumise ootamine | ||
+ | while (!timer0_overflow_flag_is_set()) | ||
+ | { | ||
+ | asm volatile (" | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Ületäitumise lipukese nullimine | ||
+ | timer0_overflow_flag_clear(); | ||
+ | |||
+ | // Taimeri peatamine | ||
+ | timer0_stop(); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Esitatud viite funktsioon kasutab aga taimerite teeki, mille lähtekood ATmega kontrollerile näeb välja järgmine: | ||
+ | |||
+ | <code c> | ||
+ | // Taimer 0 taktijaguri valiku tüüp | ||
+ | typedef enum | ||
+ | { | ||
+ | TIMER0_NO_PRESCALE | ||
+ | TIMER0_PRESCALE_8 | ||
+ | TIMER0_PRESCALE_32 | ||
+ | TIMER0_PRESCALE_64 | ||
+ | TIMER0_PRESCALE_128 | ||
+ | TIMER0_PRESCALE_256 | ||
+ | TIMER0_PRESCALE_1024 | ||
+ | } | ||
+ | timer0_prescale; | ||
+ | |||
+ | // Taimer 0 normaalrežiimi seadistamine | ||
+ | inline void timer0_init_normal(timer0_prescale prescale) | ||
+ | { | ||
+ | TCCR0 = prescale & 0x07; | ||
+ | } | ||
+ | |||
+ | // Taimer 0 peatamine | ||
+ | inline void timer0_stop() | ||
+ | { | ||
+ | TCCR0 = 0x00; | ||
+ | } | ||
+ | |||
+ | // Taimer 0 loenduri väärtuse määramine | ||
+ | inline void timer0_set_value(unsigned char value) | ||
+ | { | ||
+ | TCNT0 = value; | ||
+ | } | ||
+ | |||
+ | // Taimer 0 ületäitumise lipukese nullimine | ||
+ | inline void timer0_overflow_flag_clear(void) | ||
+ | { | ||
+ | bit_set(TIFR, | ||
+ | } | ||
+ | |||
+ | // Taimer 0 ületäitumise lipukese oleku lugemine | ||
+ | inline bool timer0_overflow_flag_is_set(void) | ||
+ | { | ||
+ | return (bit_is_set(TIFR, | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Järgnevalt on toodud samasugune programm nagu tarkvaralise viite näiteski. Lühemal 100 ms poolperioodil LED süüdatakse, | ||
+ | |||
+ | <code c> | ||
+ | // Kodulabori riistvaralise viite näidisprogramm | ||
+ | // Programm vilgutab ~1 sekundi järel hetkeks LED-i (led_debug) | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | // Põhiprogramm | ||
+ | int main(void) | ||
+ | { | ||
+ | // LED-i viigu väljundiks seadmine | ||
+ | pin_setup_output(led_debug); | ||
+ | |||
+ | // Lõputu tsükkel | ||
+ | while (1) | ||
+ | { | ||
+ | // LED-i süütamine | ||
+ | pin_clear(led_debug); | ||
+ | |||
+ | // Riistvaraline paus 100 millisekundit | ||
+ | hw_delay_ms(100); | ||
+ | |||
+ | // LED-i kustutamine | ||
+ | pin_set(led_debug); | ||
+ | |||
+ | // Riistvaraline paus 900 millisekundit | ||
+ | hw_delay_ms(900); | ||
+ | } | ||
+ | } | ||
+ | </ |