czwartek, 14 kwietnia 2011

Problemy C - Przerwania


Autor: Dondu

Na początek zapamiętaj najważniejszą zasadę:

PRZERWANIA POWINNY WYKONYWAĆ SIĘ
JAK NAJSZYBCIEJ

I wcale nie mam na myśli szybkiego zegara procesora np. 64MHz, tylko sposób pisania programu.

Do częstych problemów z przerwaniami należą:
  1. brak VOLATILE w deklaracji zmiennych
  2. używanie niewłaściwych lub starych wektorów przerwań
  3. oczekiwanie w przerwaniu
  4. włączanie przerwań w funkcji obsługującej przerwanie
  5. wykonywanie skomplikowanych obliczeń w przerwaniu




1. BŁĄD: Brak VOLATILE w deklaracji zmiennych

To najbardziej popularny problem opisałem tutaj: Problemy C - VOLATILE




2. BŁĄD:  Używanie niewłaściwych lub starych wektorów przerwań

To także istotny choć nie zawsze powodujący błędy problem. Aktualnie standardem jest używanie wektorów przerwań według standardu ISR(), a nie SIGNAL().

Dokładny opis tego problemu z listą wektorów i mikrokontrolerów znajdziesz tutaj: AVR Interrupts




3. BŁĄD: Oczekiwanie w przerwaniu

To bardzo często spotykany błąd polegający na wstawianiu w procedurę obsługi przerwania:

  • funkcji opóźnienia (delay)
  • pętli oczekujących na pojawienie się jakiegoś zdarzenia

Funkcje opóźnienia:
ISR(tutaj wektor przerwania){  //wystąpiło przerwanie

   ....    //jakiś kod
 
   _delay_ms(100);   //opóźnienie

   ...     //jakiś kod
}

Pętla oczekująca:
ISR(tutaj wektor przerwania){  //wystąpiło przerwanie

   ....    //jakiś kod
 
   while (ADCSRA & (1<<ADSC));     //oczekiwanie w pętli na zakończenie pomiaru

   ....    //jakiś kod

}

Wprowadzenie opóźnienia za pomocą DELAY czy dowolnej PĘTLI może doprowadzić do sytuacji, w której przerwania będą tracone z powodu dużych opóźnień, ponieważ nowe przerwania przychodzą zanim poprzednie się zakończą.

Co robią wtedy początkujący? Włączają przerwania w funkcji obsługi przerwania, co tylko powiększa katastrofalne działanie programu, co opisuję poniżej.


Są też tacy, którzy wiedzą, że nie powinni stosować DELAY w przerwaniu, ale mimo tego robią to, i w dodatku o czasie 1 sekundy (!!!), jak na przykład kol:

nsmarcin
Za kod z góry przepraszam
Wiem, że nie powinienem stosować delay w przerwaniach :-)

... a później dziwią się, że program nie działa prawidłowo.




4. BŁĄD: Włączanie przerwań w funkcji obsługującej przerwanie

To jeden z największych błędów jakie może popełnić początkujący programista robiąc tak:

ISR(tutaj wektor przerwania){  //wystąpiło przerwanie

   ....   //jakiś kod
 
   sei();   //włącz przerwania

   ...    //jakiś kod
}

Dlaczego? Ponieważ:
  • odblokowanie przerwań, podczas wykonywania procedury obsługi przerwania, bardzo często prowadzi do nieprzewidywalnego działania programu, przez co ciężko jest ustalić przyczynę błędów.
  • wiele kolejnych nie zakończonych przerwań może doprowadzić do przepełnienia stosu
  • i pewnie jeszcze wiele innych problemów
Dla baaaaardzo doświadczonego programisty, włączenie przerwań w funkcji przerwania (w specyficznych przypadkach) może być dobrym rozwiązaniem, ale Ty tego nie rób.

Czy można na wszelki wypadek dodać cli() i sei(), na przykład tak?:
ISR(tutaj wektor przerwania) 
{ 
   cli(); 
   ...     //jakiś kod
   sei(); 
}
Zobaczmy, co na to datasheet mikrokontrolera


Tłumaczenie:

Datasheet ATmega8 - str. 11
Bit I (flaga zezwolenia na przerwania globalne) jest zerowana sprzętowo w momencie wystąpienia przerwania, a ustawiany przez instrukcję RETI w celu umożliwienia następnych przerwań.

Jak widzisz nie musisz wstawiać rozkazów cli() oraz sei() w funkcji obsługi przerwania.

Jeżeli jednak to zrobisz, to wygenerujesz sobie problem. Zobaczmy co zrobi kompilator GCC dla mikrokontrolerów AVR (asemblerowy kod wynikowy funkcji obsługi przerwania):
//prolog
PUSH      R1             Push register on stack
PUSH      R0             Push register on stack
IN        R0,0x3F        In from I/O location
PUSH      R0             Push register on stack
CLR       R1             Clear Register
PUSH      R24            Push register on stack

CLI                      Global Interrupt Disable //Twój rozkaz cli()
                                                  //nic nie zmienia bo już 
                                                  //przerwania są wyłączone

 ... //jakiś program


SEI                      Global Interrupt Enable  //Twój rozkaz sei() 
                                                  //od tego miejsca przerwania 
                                                  //włączasz - widzisz problem?
//epilog
POP       R24            Pop register from stack
POP       R0             Pop register from stack
OUT       0x3F,R0        Out to I/O location
POP       R0             Pop register from stack
POP       R1             Pop register from stack
RETI                     Interrupt return

Problem który sobie wygenerujesz na własne życzenie polega na tym, że począwszy od linii 17 kodu powyżej przerwania będą włączone, choć powinny być włączone dopiero ostatnim rozkazem RETI. Oznacza to, że jeżeli od linii 17-tej nastąpi przerwanie, zostanie ono obsłużone pomimo, że poprzednie jeszcze nie zostało zakończone. Skutki opisałem na początku tego punktu.




5. BŁĄD: Przerwanie z funkcjami z ukrytym oczekiwaniem

Ten problem jest związany z punktem nr 3 (opisany wyżej), choć bezpośrednio programista tego nie widzi. Chodzi o to, że w przerwaniu nie powinno się wykorzystywać funkcji, które zawierają w sobie pętle oczekujące lub opóźnienia.

Niby oczywiste, ale często początkujący nie są świadomi, że wykorzystywane przez nich funkcje bibliotek kompilatora lub innych (np. do obsługi wyświetlacza LCD, komunikacji, itd.), zawierają w sobie pętle oczekujące lub opóźnienia.

Przykład:

Częstym błędem jest takie stosowanie przerwań (wyświetlanie w przerwaniu):
ISR(tutaj wektor przerwania){  //wystąpiło przerwanie

     ....    //jakiś kod
 
     LCD_GoTo(1,1);
     LCD_WriteText('Test LCD');

     ...     //jakiś kod
}

Wykorzystamy (nota bene bardzo dobrą) bibliotekę obsługi wyświetlaczy LCD, której autorem jest Radosław Kwiecień: źródło

Oto definicje kilku najczęściej wykorzystywanych funkcji:
void LCD_Clear(void){
     LCD_WriteCommand(HD44780_CLEAR);
     _delay_ms(2);
}

void LCD_GoTo(unsigned char x, unsigned char y){
     LCD_WriteCommand(HD44780_DDRAM_SET | (x + (0x40 * y)));
}

void LCD_Home(void){
     LCD_WriteCommand(HD44780_HOME);
     _delay_ms(2);
}
Zauważyłeś na pewno, że dwie z powyższych funkcji zawierają opóźnienia _delay_ms() generujące opóźnienie, aż 2 ms(!).

Ale nie tylko to jest problemem, ponieważ wszystkie wyżej pokazane funkcje używają funkcji LCD_WriteCommand(). Zobaczmy więc, co w niej jest:
void LCD_WriteCommand(unsigned char commandToWrite){
     LCD_RS_PORT &= ~LCD_RS;
     _LCD_Write(commandToWrite);
     while(LCD_ReadStatus()&0x80);
}
I tutaj następna niespodzianka, czyli pętla while() oczekująca na potwierdzenie przyjęcia komendy przez wyświetlacz.

Podobnie ma się sprawa z funkcją LCD_WriteText(), której definicja wygląda tak:
void LCD_WriteText(char * text){
   while(*text) LCD_WriteData(*text++);
}
Tutaj nie dość, że jest pętla wysyłająca po kolei znaki napisu, który chcesz wyświetlić (inaczej się nie da), to jeszcze wykorzystuje dla przesłania każdego znaku funkcję LCD_WriteData(), wyglądającą tak:
void LCD_WriteData(unsigned char dataToWrite){
     LCD_RS_PORT |= LCD_RS;
     _LCD_Write(dataToWrite);
     while(LCD_ReadStatus()&0x80);
}
A to oznacza dla każdego wysyłanego do LCD znaku napisu, oczekiwanie w pętli na zakończenie jego odbioru przez LCD, czyli łącznie kupa czekania :-)


Stosowanie bibliotek w funkcjach przerwania, powinno zostać poprzedzone analizą ich zawartości, by nie narazić się na problem zbyt długiej realizacji przerwania. 




6. BŁĄD: Wykonywanie skomplikowanych obliczeń w przerwaniu

To jeden z częściej pojawiających się problemów. W skutkach jest on podobny do punktu nr 5, czyli zbyt długiego czasu obsługi przerwania. Mikrokontroler nie nadąża wykonywać funkcji przerwania przed nadejściem następnych przerwań, przez co gubi przerwania.

W funkcjach obsługi przerwań staraj się nie wykonywać bardzo skomplikowanych obliczeń.

W szczególności problem ten dotyczy wykonywania w funkcji obsługi przerwania operacji na danych zmiennoprzecinkowych.





Flagi - rozwiązaniem problemu

Rozwiązaniem problemu zbyt długiego czasu wykonywania się funkcji obsługi przerwania może być zastosowanie metody ustawiania w przerwaniu jedynie jakiejś flagi (zmiennej), a wykonywanie właściwego kodu przerwania, dopiero w pętli głównej programu po stwierdzeniu, że flaga została ustawiona przez przerwanie. 


W takim przypadku pamiętaj, że flaga musi być jako volatile, co opisałem w punkcie nr 1 niniejszego artykułu.

Przykład obsługi przerwań z wykorzystaniem flagi:
//flaga musi być zmienną globalną z volatile
volatile char flaga_1;

//funkcja obsługi przerwania
ISR(tutaj wektor przerwania){

  // ... jakiś kod

  //ustaw flagę
  flaga_1 = 1;   

}


//program główny
int main(void){

  // ... jakiś kod

  //pętla główna
  while(1){

     // ... jakiś kod

     //sprawdzamy, czy wystąpiło przerwanie
     if(flaga_1){
       
       //tak, wystąpiło przerwanie

       flaga_1 = 0;  //zgaś flagę
  
       // ... kod, który ma się wykonać w odpowiedzi na przerwanie

     } //koniec if()

     // ... jakiś kod

  } //koniec pętli głównej
}

Takie rozwiązanie ma oczywiście także pewną wadę.
Mianowicie, kod który zostanie wykonany w odpowiedzi na przerwanie nie jest wykonywany dokładnie w momencie rozpoczęcia wykonywania funkcji przerwania. Dzieje się tak dlatego, że przerwanie ustawia jedynie flagę, a pętla główna wykonuje się w bliżej nieokreślonym miejscu. Musi więc dojść do momentu sprawdzania flagi i dopiero wtedy wykona się właściwy kod przerwania.

Dlatego sam musisz zadecydować, czy takie opóźnienie ma dla Ciebie istotne znaczenie. Nie ma to np. znaczenia dla projektu typu zegarek (opóźnienie nie wpłynie na dokładność odmierzanego czasu), ale dla projektu typu bardzo szybki sterownik generujący impulsy z dużą dokładnością, może mieć to znaczenie.

Jeżeli przerwanie ma jedynie ustawiać flagę, to takie rozwiązanie jest nieekonomiczne. W takiej sytuacji należy w pętli głównej sprawdzać flagę przerwania i gasić ją programowo po jej wykryciu (w AVR uważaj na ten wyjątek).

Taki kod wykonywać się będzie szybciej ponieważ nie jest generowany skok do funkcji obsługi przerwania i nie są odkładane na stos niezbędne rejestry.



13 komentarzy:

  1. Flagi - rozwiązaniem problemu

    Jeżeli deklarujemy dodatkową zmienną typu char, która ma 8 bitów to tak naprawdę 7 zostaje nie wykorzystanych.

    Czy jest taka flaga w jakimś rejestrze? Jeśli tak to jak ją wykorzystać?

    Janek

    OdpowiedzUsuń
  2. Zgadza się, w tym wypadku marnujemy bity. Czasami można sobie na to pozwolić, a czasami nie.

    Aby ich nie marnować, możemy w wyżej pokazanej przykładowej zmiennej flaga_1, poszczególne bity przeznaczyć na różne flagi, które są nam potrzebne w programie.

    Innymi słowy każdy z ośmiu bitów zmiennej typu char, może być taką osobną flagą. Pomocne w zapisie, odczycie i sprawdzaniu konkretnej flagi bitowej są operatory bitowe.

    Pytasz o to, czy jest jakiś specjalny rejestr na tego typu flagi - odpowiedź brzmi: Nie ma.

    OdpowiedzUsuń
    Odpowiedzi
    1. Witam,
      Co z bitem T w rejestrze SREG? Z tego co wiem, można do niego wrzucać wartość instrukcjami BLD i BST. Można ten bit wykorzystywać jako własną flagę?

      Pozdrawiam :)

      Usuń
  3. Dzięki za odpowiedź.

    Chciałem jeszcze zapytać do czego służy rejestr TIFR?

    Janek

    OdpowiedzUsuń
  4. Powinieneś zaglądnąć do datasheet mikrokontrolera AVR.

    TIFR - Timer Interrupt Flag Register
    Rejestr ten zawiera szereg flag przerwań, pochodzących z różnych wewnętrznych "bebechów" mikrokontrolera. Flagi te są automatycznie (sprzętowo) ustawiane przez mikrokontroler w momencie wystąpienia przerwania i zerowane także automatycznie w momencie wejścia mikrokontrolera w funkcję obsługi danego przerwania.

    Nie należy ich mylić z przykładem flagi ustawianych za pomocą zmiennych, o które pytałeś wcześniej.

    OdpowiedzUsuń
  5. "Powinieneś zaglądnąć do datasheet mikrokontrolera AVR."

    Właśnie zajrzałem i dlatego chciałem się upewnić.

    Nieco inaczej to pojmuję. Myślę, że chciałeś uprościć abym zrozumiał? Jednak trochę bardziej mnie to wciągneło.

    Mianowicie, wydaje mi się(jeśli TIFR nie zawiera jakichś pod rejestrów czy coś to jestem pewien), że zawiera flagi tylko do wszystkich timerów(ich trybów), ale nie koniecznie dla przerwania tylko dla "wykonanego przez nie zadania"(nie wiem jak to nazwać), a następnie można ją wykorzystać aby uruchomić przerwanie.

    "Flagi te są automatycznie (sprzętowo) ustawiane przez mikrokontroler w momencie wystąpienia przerwania..."

    Wydaje mi się, jeśli flaga wystąpi to dopiero wówczas wystąpi przerwanie, nie na odwrót. A zerowanie tak jak napisałeś.

    Pisząc "wykonane przez timer zadanie" mam na myśli np. przepełnienie. Jeśli Timer osiągnie maksymalną wartość i zacznie liczyć od nowa wówczas flaga jest aktywna. A wyzerować ją możemy poprzez wpisanie logicznej jedynki - jak wynika z dokumentacji.

    Na podstawie tego rozumowania napisałem przykładowy program:
    //ATmega8 1Mhz
    //Mrugająca LED z częstotliwością<4Hz
    #include avr/io.h

    int main()
    {
    //Timer2///////////////////
    TCCR2 |= (1<<CS22) | (1<<CS21) | (1<<CS20);//P1024
    ///////////////////////////

    //IO///////////////////////
    DDRB |= (1<<PB1);//wyjście LED
    ///////////////////////////

    while(1)
    {
    if(TIFR & (1<<TOV2))//Jeśli flaga Overflow dla timera2 w rejestrze TIFR
    {
    TIFR |= (1<<TOV2); //Zerowanie flagi przez wpisanie logicznej jedynki
    PORTB ^= (1<<PB1); //Zmiana stanu diody
    }}}

    Zdaję sobie sprawę z tego, że instrukcję najprawdopodobniej nie zostaną wykonane w momencie przepełnienia(analogicznie do zaproponowanego rozwiązania z dodatkową flagą). Timer2 w momencie osiągnięcia maksymalnej wartości przez pewien czas będzie posiadał flagę aktywną, ale nie będzie zatrzymany.

    Analizując zaproponowane rozwiązanie i własny przykład doszedłem do takich wniosków:
    Jeżeli chcemy uchronić się przed przerwaniem w przerwaniu instrukcje wykonujemy w głównej pętli - Ok, Ale co jeśli będzie zbyt dużo instrukcji w pętli głównej, tak dużo, że timer przepełni się dwa lub więcej razy? - domyślam się, że flaga nie zostanie wyzerowana i tak jakby stracimy przerwanie.
    Sprawdziłem to dodając funkcję delay_ms() do pętli głównej, no i faktycznie dioda migała z mniejszą częstotliwością.

    Czy mój tok myślenia jest poprawny od początku do końca?

    "Pytasz o to, czy jest jakiś specjalny rejestr na tego typu flagi - odpowiedź brzmi: Nie ma."

    Zgoda nie ma takiego rejestru.
    A gdybym się uparł, że wykorzystałem rejestr TIFR w ten właśnie sposób co dało moim zdaniem podobny rezultat? - No chyba, że się mylę.

    PS:Proszę o jakąkolwiek odpowiedź.
    Pozdro!

    Janek

    OdpowiedzUsuń
  6. "Wystąpienie przerwania" o którym pisałem to moment, w którym sprzęt stwierdza, że zaistniały okoliczności do ustawienia sprzętowej flagi konkretnego przerwania np. timer się przepełnił.


    ------------

    W swoim programie wykorzystujesz właściwość:
    "The TOV2 bit is set (one) when an overflow occurs in Timer/Counter2."

    oraz:

    "Alternatively, TOV2 is cleared by writing a logic one to the flag."

    czyli programowo sprawdzasz bit TOV2 badając rejestr TIFR i zerujesz go także programowo.

    Takie działanie jest możliwe.

    Nie wykorzystujesz natomiast:
    "TOV2 is cleared by hardware when executing the corresponding interrupt Handling Vector."

    czyli automatycznego gaszenia bitu TOV2 w momencie wejścia do funkcji obsługi przerwania.

    Innymi słowy Twój program musi sam sprawdzać, czy timer się przepełnił i wyzerować bit TOV2, gdy to nastąpiło.

    Jednakże przerwania po to wymyślono, by nie obciążąć programu takimi zadaniami jak ciągłe sprawdzanie, czy timer się przepełnił.

    -----------

    cyt. "... przez pewien czas będzie posiadał flagę aktywną, ale nie będzie zatrzymany. "

    Flaga będzie ustawiona i czeka, aż zostanie wyzerowana programowo lub sprzętowo (gdy wywołana zostanie funkcja obsługi tego przerwania). Ponieważ nie włączasz globalnych przerwań oraz nie masz funkcji ich obsługi, to falga ta będzie czekać, aż ją zgasisz programowo. Nic wiec nie stracisz, pod warunkiem, że zdąrzysz ją zgasić zanim ponownie timer się przepełni.


    ------------

    cyt. "Jeżeli chcemy uchronić się przed przerwaniem w przerwaniu instrukcje wykonujemy w głównej pętli ..."

    Skąd taki wniosek? W momencie sprzętowego wywołania funkcji przerwania następuje zablokowanie globalnych przerań - flaga I w SREG - i ponownie odblokowane w momencie wyjścia z tej funkcji:

    "The I-bit is cleared by hardware after an interrupt has occurred, and is set by the RETI instruction to enable subsequent interrupts."

    Dlatego ewentualne inne przerwanie, którego przyczyny ziszczą się w momencie, w którym aktualne przerwanie jest wykonywane, ustawi tylko flagę tego przerwania, ale nie przerwie funkcji aktualnie wykonywanej. Uruchomienie tego czekającego na obsługę przerwania nastąpi zaraz po wyjściu z aktualnego.

    Od tej zasady są wyjątki - czytaj: ISR_NOBLOCK


    ------------

    cyt. "A gdybym się uparł, że wykorzystałem rejestr TIFR w ten właśnie sposób co dało moim zdaniem podobny rezultat?"

    Tak jak napisałem wyżej - można, ale odradzam.

    Pomyśl co będzie, gdybyś chciał oszczędzać energię (zasilanie z baterii), a musisz czekać na przerwanie w programie, zamiast zlecić to sprzętowi. Albo gdy zależy Ci, przerwanie nastąpiło od razu, gdy timer się przepełnia, aw main() jest sporo innych rzeczy do zrobienia.

    OdpowiedzUsuń
  7. Dzięki. Cenię Twoje odpowiedzi, pozwalają mi wyjaśnić niektóre kwestie, jednak chyba się nie zrozumieliśmy.

    Odpowiedziałeś tłumacząc mi wyższość przerwań nad flagami - jak najbardziej to rozumiem.
    Jednak chciał bym zaznaczyć, że od samego początku odnoszę się do punktu 6.

    Odradzasz ten sposób ze względu na programowe sprawdzanie i zerowanie flagi(TIFR) w pętli głównej, Ty sprawdzasz i zerujesz sprzętowo, ale jaki masz z tego zysk jeżeli pracujesz programowo z dodatkową zmienną?

    Nie ma przerwania w przerwaniu(poza wyjątkami) - Ok, thx.

    Odradzasz mi stosowanie flag rejestru TIFR, które wykorzystuję analogicznie do zmiennej jako flagi. Skoro to taki zły pomysł to czemu jest zaproponowany? Jeszcze raz zaznaczam, że odnoszę się do punktu 6.

    Od samego początku miałem na celu ominięcie dodania zmiennej aby zaoszczędzić tego nieszczęsnego bajta skoro istnieje flaga którą możemy wykorzystać - różni się tylko zrezygnowaniem z przerwania, które i tak tylko by zmieniało flagę, która była by sprawdzana w pętli.

    "Rozwiązaniem problemu zbyt długiego czasu wykonywania się funkcji obsługi przerwania może być zastosowanie metody ustawiania w przerwaniu jedynie jakiejś flagi (zmiennej), a wykonywanie właściwego kodu przerwania, dopiero w pętli głównej programu po stwierdzeniu, że flaga została ustawiona przez przerwanie."

    Czy nie tak radzicie w pkt.6?

    Janek

    OdpowiedzUsuń
  8. Jak już wspomniałem Twój sposób można oczywiście stosować.

    Podałem także na końcu poprzedniego postu przykład dlaczego nie warto tak robić i to jest odpowiedź na pytanie:

    cyt. "Odradzasz ten sposób ze względu na programowe sprawdzanie i zerowanie flagi(TIFR) w pętli głównej, Ty sprawdzasz i zerujesz sprzętowo, ale jaki masz z tego zysk jeżeli pracujesz programowo z dodatkową zmienną?"

    Innym przypadkiem będzie sytuacja, w której np. zliczasz programowo nierównomiernie występujące impulsy na jakimś pinie np. INT, a jednocześnie wykonujesz bardzo skomplikowane program i obliczenia w main().

    A takim przypadku flaga ustawiana w funkcji obsługi przerwania (w przykładzie w artykule nazwana flaga_1), może być inkrementowana (czyli zwiększana o jeden). Wtedy oprócz funkcji flagi ma także funkcję licznika impulsów, które trafiły do mikrokontrolera podczas wykonywania czasochłonnej części funkcji main().

    Przy takich założeniach Twoje rozwiązanie polegnie, gdyż może przegapić niektóre impulsy. Z tego samego powodu poległoby rozwiązanie, gdyby te czasochłonne obliczenia były wykonywane w funkcji obsługi przerwania, o czym właśnie jest ten artykuł.

    I to jest odpowiedź na Twoje ostatnie pytanie.

    Poza tym często nie ma potrzeby oszczędzania tych siedmiu niewykorzystanych bitów. Po prostu się to nie opłaca :-)



    Mam nadzieję, że wytłumaczyłem zrozumiale :-)

    ---------

    A tak na marginesie:

    Gdy opanuje się przerwania i stosowanie flag gdzie jest to wymagane, to nagle okazuje się, że ten mały mikrokontroler ma olbrzymie zasoby mocy obliczeniowej wystarczające na przykład do np. zbudowania gry na lampie oscyloskopu

    czy też jednoczesnego generowania muzyki (WAV) z karty SD oraz obrazu wideo na monitorze VGA i realizowania mniej lub bardziej skomplikowanej gry (filmów pełno na Youtube).

    Dlatego sugeruję wykorzystywanie przerwań na maksa, nawet w prostych projektach, by nabierać wprawy, bo to procentuje później w dużych projektach.

    OdpowiedzUsuń
  9. Aby wyjaśnić jeszcze dokładniej dlaczego rozwiązanie z flagą jest często wykorzystywane, popatrzmy na to tak:

    Mikrokontroler realizuje jednocześnie następujące zadania:
    - skanowanie klawiatury matrycowej 3x3 przyciski,
    - odmierza datę i czas z dokładnością do 1/100 sekundy,
    - rejestruje 8 czujników temperatury (termistory) za pomocą ADC,
    - wyniki przelicza i wyświetla na LCD oraz,
    - rejestruje pomiary i obliczenia na karcie SD.

    Bez zastosowanie przerwań oraz w niektórych z tych funkcjonalności flag w przerwaniu, nie zrealizujesz tych zadań metodą, którą proponujesz, albo będziesz musiał się baaaardzo napracować, by to zsynchronizować.

    OdpowiedzUsuń
  10. Nie no nie chciałem robić awantury o 8 bit :) , po prostu niektórzy twierdzą: jeśli da się coś zaoszczędzić to wykorzystaj to. Ale przesada w jedną czy drugą stronę nie jest wskazana. Mam nadzieję, że Twoje komentarze pomogą również innym początkującym.

    Inkrementacja flagi - w sumie przeszło mi to przez myśl, z wykorzystaniem TIFR nie da rady tego zrobić - wystarczający argument jak dla mnie.

    Proponuję dopisać podpunkt o inkrementacji flag.

    Pozdro!

    Janek

    OdpowiedzUsuń
  11. Właśnie zaczynam przygodę z AVR, ale mam pewne przemyślenia związane z powyższym tekstem.

    Pracuję włąśnie nad niewielkim urządzeniem audio ze sterowaniem na ATmega88, obsługą szeregową wyświetlacza VFD, potencjometru cyfrowego przez SPI, enkodera obrotowego, sterowaniem RC6 i innymi drobiazgami. Zadanie układu jest oczywiste. Ponieważ jest to mój debiut zarówno z AVR jak i językiem C, to pewnie z braku wiedzy oraz doświadczenia cały program oparłem na zewnętrznych przerwaniach i timerach.
    Uważam jednak, że w tym konkretnym przypadku jest to znacznie lepsze rozwiązanie, niż chociażby korzystanie z flag. Jeżeli dana funkcja układu wykonywana jest przez konkretne przerwanie, to mam pewność, że zostanie ona zrealizowana od początku do końca. Przykład: dekodowanie RC6 - zewnętrzne przerwanie od odbiornika IR załącza przerwanie od timera, który krok po kroku dekoduje kod z pilota, uruchamiając odpowiedni podprogram (głośniej, ciszej itp). Praktycznie każdy podprogram wykonywany w ramach pracy przerwania kończy się wyświetleniem informacji na wyświetlaczu. Gdyby praca wyświetlacza realizowana byłaby w głównej pętli programu, często mogłoby dochodzić do zablokowania "pisania" przez jakieś przerwanie (np z pilota).
    Program w zasadzie reaguje tylko na bodźce z zewnątrz. Jedynym wyjątkiem ciągłej pracy jest przerwanie od przepełnienia timera analizujące zmianę stanów wyjść enkodera obrotowego, ponieważ ATmega88 ma tylko dwa przerwania sprzętowe (jedno wykorzystane do podczerwieni, drugie do przycisku zasilania i obsługi selektora wejść). Myślę, że zwłaszcza w tym przypadku, gdybym korzystał z jedynie z flagi, a dekodowanie realizował w pętli programu, uC zgubiłby połowę informacji z enkodera.
    Warto jeszcze dodać, że wzajemne blokowanie się przerwań nie przeszkadza, ponieważ z reguły nie wykorzystuje się jednocześnie pilota i pokrętła zmiany głośności.

    Pozdrawiam
    Adam

    OdpowiedzUsuń