logo elektroda
logo elektroda
X
logo elektroda
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

[C] Atmega8 przerwania - nie zlicza równomiernie

onidpl 07 Lip 2010 09:17 2466 25
  • #1 8269124
    onidpl
    Poziom 10  
    Witam,
    napisałem program, w którym chciałem użyć przerwania do zliczania ilości naciśnięć, ale program nie działa prawidłowo, nie zlicza równomiernie, tak jakbym za jednym naciśnięciem naliczało po 2 czy 3 razy, oto kod programu:
    
    /*
    	Program obsługujący przerwania, po naciśnięciu przycisku podpiętego pod PD2 (INT0)
    	Diody zapalają się kolejno od Led1 do Led5 po każdym naciśnięciu zapala się kolejna dioda
    	Po naciśnięciu przycisku PD3 (INT1) Sekwencja się resetuje i diody gasną
    	Diody podłączone są pod PC0, PC1, Pc2, PC3, PC4
    	
    	uC: Atmega8
    	
    	Autor: Adrian Wojak
    */
    
    #include <avr/io.h> 
    #include <util/delay.h>
    #include <avr/interrupt.h>
    
    #define Led1 0
    #define Led2 1
    #define Led3 2
    #define Led4 3
    #define Led5 4
    
    
    volatile long n; //zmienna przechowująca ilośc naciśnięć przycisku PD2(INT0)
    
    void clear(void) {
    	PORTC &= ~(1<<Led1 | 1<<Led2 | 1<<Led3 | 1<<Led4 | 1<<Led5); //zgaszenie diód
    	n = 0;
    }
    
    
    SIGNAL (SIG_INTERRUPT0) {
    	//_delay_ms(80); //czekanie aż dragania styków wygasną
    	//while(!(PIND & 0x04)) {} //czekanie na zwolnienie przycisku
    	//_delay_ms(80); //czekanie aż dragania styków wygasną
    	
    	n++; //zwiększ zmienną n
    	//cli(); //wyłącz przerwania
    
    }
    
    SIGNAL (SIG_INTERRUPT1) { //przywrócenie początkowego stanu mrugania diodami
    	//_delay_ms(80); //czekanie aż dragania styków wygasną
    	//while(!(PIND & 0x08)) {} //czekanie na zwolnienie przycisku
    	//_delay_ms(80); //czekanie aż dragania styków wygasną
    	
    	
    	clear();
    	//cli(); //wyłącz przerwania
    
    }
    
    int main(void) {
    
    	n = 0; //inicjacja zmiennej n 
    	
    	DDRD = 0x00; //Wszystkie PinyD wejściami
    	PORTD = ( 1<<PORTD2 | 1<<PORTD3 ); //Pull-up na wejściach PD2(INT0) i PD3(INT1)
    	DDRC = ( 1<<Led1 | 1<<Led2 | 1<<Led3 | 1<<Led4 | 1<<Led5 ); //ustawienie wyjść dla diód led
    	PORTC = 0x00;
    	DDRB = (1<<Led1 | 1<<Led2 | 1<<Led3 | 1<<Led4 | 1<<Led5); //ustawienie wyjść dla diód led 
    	PORTB = 0x00;
    	
    	MCUCR|=(1<<ISC01) | (1<<ISC00); //zbocze opadające na INT0
    	MCUCR|=(1<<ISC11) | (1<<ISC10); //zbocze opadające na INT1
    	GICR |= (1<<INT0 | 1<<INT1); //Włączamy przerwanie INT0 i INT1
    	sei(); //pozwolenie na przerwania
    	// cli(); //zabronienie wykonywania przerwania
    	
    	
    	//gówna pętla programu
    	for(;;){ 
    	
    	switch(n) {
    		case 1:
    			PORTC |= 1<<Led1;
    			break;
    		case 2:
    			PORTC |= 1<<Led2;
    			break;
    		case 3:
    			PORTC |= 1<<Led3;
    			break;
    		case 4:
    			PORTC |= 1<<Led4;
    			break;
    		case 5:
    			PORTC |= 1<<Led5;
    			break;
    		default:
    			clear();
    			
    			
    			
    	}
    	//sei(); //pozwolenie na pezerwania
    	
    	}
    }
    
  • #2 8269208
    Samuraj
    Poziom 35  
    Dlaczego w przerwaniu masz cli() w tym miejscu.
    Z tego co widzę to źle eliminujesz drgania styków. Początek masz dobry tylko po drugim czekaniu sprawdź raz jeszcze czy przycisk jest wciśnięty.
    No i wypadało by na czas trwania przerwania zablokować możliwość wykonania kolejnego - cli() na samym początku a przy wyjściu sei()
  • #3 8269403
    onidpl
    Poziom 10  
    Dokonałem tych zmian w kodzie i dalej nie działa tak jak trzeba, diody nie zapalają się po kolei, po każdym kliknięciu. Tylko tak jakby losowa a czasami się nic nie dzieje jak nacisnę przycisk. Może to być dlatego że taktowanie mikro kontrolera to 16Mhz?
    
    /*
    	Program obsługujący przerwania, po naciśnięciu przycisku podpiętego pod PD2 (INT0)
    	Diody zapalają się od Led1 - Led5
    	Po naciśnięciu przycisku PD3 (INT1) Sekwencja się resetuje i diody gasną
    	Diody podłączone są pod PC0, PC1, Pc2, PC3, PC4
    	
    	uC: Atmega8 16Mhz
    	
    	Autor: Adrian Wojak
    */
    
    #include <avr/io.h> 
    #include <util/delay.h>
    #include <avr/interrupt.h>
    
    #define Led1 0
    #define Led2 1
    #define Led3 2
    #define Led4 3
    #define Led5 4
    
    
    volatile long n; //zmienna przechowująca ilośc naciśnięć przycisku PD2(INT0)
    
    void clear(void) {
    	PORTC &= ~(1<<Led1 | 1<<Led2 | 1<<Led3 | 1<<Led4 | 1<<Led5); //zgaszenie diód
    	n = 0;
    }
    
    
    SIGNAL (SIG_INTERRUPT0) {
    	cli(); //wyłącz przerwania
    	_delay_ms(80); //czekanie aż dragania styków wygasną
    	while(!(PIND & 0x04)) {} //czekanie na zwolnienie przycisku
    	_delay_ms(80); //czekanie aż dragania styków wygasną
    	
    	n++; //zwiększ zmienną n
    	sei();
    	
    
    }
    
    //przywrócenie początkowego stanu mrugania diodami
    SIGNAL (SIG_INTERRUPT1) { 
    	cli();
    	_delay_ms(80); //czekanie aż dragania styków wygasną
    	while(!(PIND & 0x08)) {} //czekanie na zwolnienie przycisku
    	_delay_ms(80); //czekanie aż dragania styków wygasną
    	
    	
    	clear();
    	sei();
    
    }
    
    int main(void) {
    
    	n = 0; //inicjacja zmiennej n
    	
    	DDRD = 0x00; //Wszystkie PinyD wejściami
    	PORTD = ( 1<<PORTD2 | 1<<PORTD3 ); //Pull-up na wejściach PD2(INT0) i PD3(INT1)
    	DDRC = ( 1<<Led1 | 1<<Led2 | 1<<Led3 | 1<<Led4 | 1<<Led5 ); //ustawienie wyjść dla diód led
    	PORTC = 0x00;
    	DDRB = (1<<Led1 | 1<<Led2 | 1<<Led3 | 1<<Led4 | 1<<Led5); //ustawienie wyjść dla diód led 
    	PORTB = 0x00;
    	
    	MCUCR|=(1<<ISC01) | (1<<ISC00); //zbocze opadające na INT0
    	MCUCR|=(1<<ISC11) | (1<<ISC10); //zbocze opadające na INT1
    	GICR |= (1<<INT0 | 1<<INT1); //Włączamy przerwanie INT0 i INT1
    	sei(); //pozwolenie na przerwania
    	// cli(); //zabronienie wykonywania przerwania
    	
    	
    	//gówna pętla programu
    	for(;;){ 
    	switch(n) {
    		case 1:
    			PORTC |= 1<<Led1;
    			break;
    		case 2:
    			PORTC |= 1<<Led2;
    			break;
    		case 3:
    			PORTC |= 1<<Led3;
    			break;
    		case 4:
    			PORTC |= 1<<Led4;
    			break;
    		case 5:
    			PORTC |= 1<<Led5;
    			break;
    		default:
    			clear();
    			
    				
    	}
    	
    	}
    }
    
  • #5 8269577
    Brutus_gsm
    Poziom 25  
    Ciągle źle eliminujesz drgania styków. Zrób np. tak:

    SIGNAL (SIG_INTERRUPT0) { 
       cli(); //wyłącz przerwania 
       _delay_ms(80); //czekanie aż dragania styków wygasną 
       if (! (PIND & 0x04)) // sprawdz ponownie czy przycisk wcisniety
       {
         n++; //zwiększ zmienną n 
       }
       sei(); 
        
    
    } 
    
  • #6 8269957
    Andrzej__S
    Poziom 28  
    Brutus_gsm napisał:

    
    SIGNAL (SIG_INTERRUPT0) { 
       cli(); //wyłącz przerwania
       .....
       sei();
    }
    



    Przepraszam za dygresję nie na temat.

    Atmel Corporation napisał:

    When an interrupt occurs, the Global Interrupt Enable I-bit is cleared and all interrupts are disabled.
    ...
    The I-bit is automatically set when a Return from Interrupt instruction – RETI – is executed.


    Zgodnie z powyższym, wyłączanie przerwań w trakcie obsługi przerwania mija się z celem, chociaż nie przeszkadza. Z drugiej strony, jeśli chcemy zezwolić na inne przerwania podczas obsługi aktualnego przerwania musimy samodzielnie je włączyć (wewnątrz procedury obsługi np. poprzez sei(); ).

    To tylko tak dla ścisłości :)
  • #7 8271610
    Brutus_gsm
    Poziom 25  
    Nie chodziło mi konkretnie o wyłączenie przerwania, bo tego nie ruszałem (w kodzie autora tak jest). Chodziło mi o sprawdzanie, czy klawisz jest wciśnięty.
  • #8 8271835
    Andrzej__S
    Poziom 28  
    Sorry!
    Nie chodziło mi konkretnie o Ciebie. Chciałem tylko zasygnalizować, że nie ma potrzeby wyłączania przerwań wewnątrz procedury obsługi przerwania, bo dzieje się to automatycznie. Nie chciałem się nikogo "czepiać". Po prostu Twój post był ostatni w wątku, więc jakoś tak akurat Ciebie zacytowałem. Wiem, powinienem w takim wypadku zamieścić cytat bez podawania autora. Jeszcze raz przepraszam.
  • #9 8272666
    Samuraj
    Poziom 35  
    Andrzej__S napisał:


    Zgodnie z powyższym, wyłączanie przerwań w trakcie obsługi przerwania mija się z celem, chociaż nie przeszkadza. Z drugiej strony, jeśli chcemy zezwolić na inne przerwania podczas obsługi aktualnego przerwania musimy samodzielnie je włączyć (wewnątrz procedury obsługi np. poprzez sei(); ).

    To tylko tak dla ścisłości :)


    A nie jest tak przypadkiem że:
    
    SIGNAL (nazwa_uchwytu)
    {
    // Instrukcje tu zawarte będą wykonywane jako obsługa przerwania
    }
    

    Funkcja obsługi przerwania z nazwą SIGNAL będzie obsługiwana z wyłączoną obsługą innych przerwań.
    lub:

    
    INTERRUPT (nazwa_uchwytu)
    {
    // Instrukcje tu zawarte będą wykonywane jako obsługa przerwania
    }
    

    Funkcja obsługi przerwania z nazwą INTERRUPT będzie obsługiwana z włączoną obsługą innych przerwań - jej realizacja może być przerwana przez przerwanie o wyższym priorytecie.
  • #10 8272762
    Andrzej__S
    Poziom 28  
    Flaga I w SREG jest zerowana automatycznie w momencie wystąpienia przerwania. To zerowanie jest realizowane przez sprzęt niezależnie od programu. Jeśli programista zamierza zezwolić na inne przerwania podczas wykonywania procedury obecnego przerwania, musi to zrobić poprzez ponowne ustawienie tej flagi, np. poprzez instrukcję sei();

    Nie wiem jakiego kompilatora używasz, ale ja znam 2 makra w WinAvr do definiowania procedur obsługi przerwania: SIGNAL oraz ISR. Pierwsze z nich jeszcze działa, choć już jest właściwie wycofane. Drugie makro - owszem - możemy wywołać z atrybutem ISR_NOBLOCK, np.:
    
    ISR(TIMER0_OVF_vect, ISR_NOBLOCK)
    {
    ....
    }
    

    Podczas obsługi takiego przerwania inne przerwania są dozwolone. Różni się to jednak od użycia sei(); tylko tym, że flaga I jest ustawiana automatycznie przez kompilator najszybciej jak to możliwe po wejściu do procedury obsługi przerwania.

    avr-libc-user-manual napisał:

    22.15.2.9 #define ISR_NOBLOCK
    # include <avr/interrupt.h>
    ISR runs with global interrupts initially enabled. The interrupt enable flag is activated
    by the compiler as early as possible within the ISR to ensure minimal processing delay
    for nested interrupts.

    Czyli nie zmienia to faktu, że flagę I trzeba "ręcznie" ustawić. Pominięcie atrybutu ISR_NOBLOCK spowoduje wykonanie procedury obsługi przerwania z globalnie wyłączonymi przerwaniami. Chyba, że jak - już wcześniej napisałem - włączymy je poprzez sei();
  • #11 8273693
    czmi3l
    Poziom 14  
    A czy w ogóle jest możliwe w Atmegdze8 przerwanie wykonywania f-cji przerwania innym przerwaniem? Nie jest tak, że w wszystkie przerwania mają ten sam priorytet?
  • #12 8273910
    m.bartczak
    Poziom 16  
    W 99% przerywanie przerwań to bardzo zły pomysł, kończący się przepełnieniem stosu i najdziwniejszymi błedami.

    Jest to możliwe ale bardzo nie polecam :)
  • #13 8274227
    onidpl
    Poziom 10  
    Korzystam ze środowiska WinAVR-20100110 i właściwie nie wiem dlaczego ten kod mi nie działa tak jak trzeba, albo myślę inaczej niż kompilator.. Chcę żeby w momencie gdy zostanie naciśnięty przycisk podpięty do PD2(INT0), zostało wykryte przerwanie i wartość zmiennej n została zwiększona o 1 i mi to nie wychodzi, przycisk jest podpięty do GND i PD2(INT0) z ustawionym pull-up.
  • #14 8274233
    Brutus_gsm
    Poziom 25  
    Przerwania mają różne priorytety (jest o tym w nocie), ale nie można ich zmienić. Dopiero w bodajże XMegach można ustalać priorytety przerwań.

    Zrobiłeś eliminację drgań, tak, jak ja ci to zaproponowałem?
  • #15 8274241
    onidpl
    Poziom 10  
    Zrobiłem w ten sposób i nie działa.
  • #16 8274314
    gaskoin
    Poziom 38  
    może pokaż kod, bo pytasz czemu nie działa a skąd mamy wiedzieć co tam napisałeś ?
  • #17 8274326
    Andrzej__S
    Poziom 28  
    czmi3l napisał:

    Nie jest tak, że w wszystkie przerwania mają ten sam priorytet?


    Nie jest to tak. Generalna zasada jest taka, że w pierwszej kolejności zostaje wykonana procedura obsługi przerwania, które pojawiło się prędzej. Jeśli w czasie wykonywania tej procedury nie włączymy flagi globalnej I a pojawi się nowe przerwanie, będzie musiało czekać na zakończenie obecnej procedury. Po jej zakończeniu flaga globalna I zostaje automatycznie włączona i następuje wykonanie procedury obsługi tego oczekującego przerwania.

    Kiedy jednak korzystamy z 3 lub więcej różnych przerwań, może dojść do sytuacji, w której podczas obsługi jednego przerwania wystąpią 2 (lub więcej) przerwania. Wtedy po zakończeniu obecnej procedury o kolejności decyduje priorytet. Priorytet danego przerwania jest związany z jego pozycją w "interrupt vector table", która jest zależna od typu mikro-kontrolera. Niemniej pewne zasady są wspólne, np. zawsze najwyższy priorytet ma RESET, a INT0 ma zawsze wyższy priorytet niż np. przerwania timerów.

    m.bartczak napisał:

    W 99% przerywanie przerwań to bardzo zły pomysł, kończący się przepełnieniem stosu i najdziwniejszymi błedami.


    Nie masz racji. Trzeba tylko dobrze rozumieć mechanizm działania przerwań i przestrzegać pewnych zasad, ale za długo by o tym pisać.

    "Przerywanie przerwań" może być bardzo przydatne. Wyobraź sobie sytuację, że masz jakąś procedurę obsługi przerwania (dowolnego), której czas wykonania wynosi 10 mikrosekund. Oprócz tego chciałbyś użyć przerwania INT0 do zliczania impulsów pojawiających co ok. 2 mikrosekundy. Jeśli nie włączysz flagi globalnej podczas tej pierwszej procedury (trwającej 10 mikrosekund), to "zgubisz" kilka impulsów z INT0. A obsługa INT0 może być przecież banalna (np. tylko incrementowanie jakiejś zmiennej globalnej), krótka, szybko się wykona i w żaden sposób nie zakłóci działania procedury obsługi tego "przerwanego przerwania".
  • #18 8274377
    czmi3l
    Poziom 14  
    Doczytałem dokumentację i muszę przyznać, że masz absolutną rację Andrzeju. Pozornie nie ma zagnieżdżonych przerwań:
    Cytat:
    normally interrupts will remain disabled inside the handler until the handler exits


    Można jednak sobie poradzić z ręcznym priorytetowaniem. W obsłudze dowolnego przerwania można maskować tylko te przerwania, które faktycznie mogą być kluczowe i następnie odblokować globalnie przerwania. I przed opuszczeniem przerwania odmaskować resztę.

    Kwestia czy to się opłaca czasowo...
    Jeżeli wchodzimy do długiego przerwania, może to mieć sens. Jednak ja wyznaję zasadę: im krócej w przerwaniach tym lepiej.
  • #19 8274600
    Andrzej__S
    Poziom 28  
    czmi3l napisał:

    Kwestia czy to się opłaca czasowo...


    Wszystko zależy od tego, co chcemy osiągnąć.
    Nie próbuję tutaj forsować używania na siłę przerwań, ich zagnieżdżania i maskowania. Niemniej uważam, że to cenna umiejętność.

    czmi3l napisał:

    ... ja wyznaję zasadę: im krócej w przerwaniach tym lepiej


    Myślę, że to dobra zasada, jednak nie zawsze się da tak krótko, jak by to było pożądane, nawet przy użyciu assemblera. Nie wszystko też można "odroczyć" do wykonania w pętli głównej.
  • #20 8275350
    gaskoin
    Poziom 38  
    w ogóle to mógłbyś zastosować rozwiązanie sprzętowe, a naliczać ilość naciśnięć za pomocą Timer/Counter 0
  • #21 8275742
    Brutus_gsm
    Poziom 25  
    Spróbuj zmienić sposób wywoływania przerwania z falling na low level. U mnie to pierwsze nie chciało działać, nie wiem czemu, natomiast drugie działa cały czas.
  • #22 8276169
    Andrzej__S
    Poziom 28  
    Znalazłem trochę więcej czasu, żeby przyjrzeć się problemowi i zauważyłem:

    onidpl napisał:

    
       ...
       MCUCR|=(1<<ISC01) | (1<<ISC00); //zbocze opadające na INT0 
       MCUCR|=(1<<ISC11) | (1<<ISC10); //zbocze opadające na INT1
       ...
    



    No to akurat jest ustawienie dla zbocza narastającego, czyli przerwanie następuje po puszczeniu przycisku, a nie po jego wciśnięciu.
  • #23 8277093
    gaskoin
    Poziom 38  
    jak masz drgania na stykach to nie ma to znaczenia, a jak wyżej pisałem, zamiast je eliminować programowo w przerwaniu lepiej zastosować rozwiązanie sprzętowe, a liczbę przyciśnięć liczyć timerem
  • #24 8277201
    Andrzej__S
    Poziom 28  
    Jeśli w procedurze obsługi przerwania jest sprawdzany warunek:
    Cytat:

    
        ...
        if (! (PIND & 0x04))
        ...
    



    to chyba ma znaczenie, na jakie zbocze przerwanie reaguje?

    Dodano po 31 [minuty]:

    Jeszcze jedna uwaga, która mi się nasuwa. Skoro styki mają drgania, to po wejściu do procedury obsługi przerwania jego flaga zostanie ustawiona ponownie. Jak skończy się wykonywać obecna procedura, globalnie przerwania zostaną włączone i ponownie zostanie wygenerowane przerwanie. To może powodować właśnie efekt podwójnego zliczania. Proponuję pod koniec obsługi przerwania wyzerowć jego flagę, np. dla przerwania INT0:

    
       GIFR |= (1<<INTF0);
    


    Mam nadzieję, że pomoże.
  • #25 8278074
    janbernat
    Poziom 38  
    "pod koniec obsługi przerwania wyzerowć jego flagę".
    Po czym następuje proces zdejmowania ze stosu.
    Ileś cykli trwa.
    W tym czasie teoretycznie może nastąpić jeszcze jedno ustawienie flagi.
    Teoretycznie- bo czekanie na wygaszenie drgań 2x80ms to jest wieczność.
    Dlatego sądzę że używanie przerwań zewnętrznych do sprawdzania stanu przycisku tylko komplikuje program.
    Nie należy używać _delay rozrzutnie.
    Należy sprawdzać stan przycisku w pętli głównej i sprawdzić ten stan po 20ms za pomocą inkrementowania jakiejś zmiennej w przerwaniu od któregoś Timera.
  • #26 8278355
    Andrzej__S
    Poziom 28  
    ...ale okienko trochę zawęziłem ;)

    Niemniej masz absolutną rację, lepiej rozwiązać to inaczej.
REKLAMA