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

[ATtiny2313][C] Problem z przerwaniami

iwd2 16 Wrz 2009 17:52 1788 3
  • #1 7024910
    iwd2
    Poziom 2  
    Witam,
    W chwili obecnej pracuje nad termometrem, sterowanym na pilota. Pilot wysyła dane w kodzie RC5. Wysłane dane trafiają na odbiornik podczerwieni TSOP31236 (36kHz). W momencie gdy dane nie docierają na wyjściu odbiornika jest stan wysoki, gdy odbierze jakiś sygnał na wyjściu jest poziom niski. Wyjście odbiornika podczerwieni jest podpięte do wyjścia INT0. A więc, w momencie gdy poziom przejdzie z 1 na 0 powinno zostać wywołane przerwanie (niezależnie od kodu RC5, ma tylko zapalić diodę).

    Obsługa przerwania jest prosta
    
    ISR(INT0_vect)
    {	
    	_delay_ms(50);
    	PORTD |= _BV(5);	// Zapal diodę 
    }
    


    Oczywiście, wcześniej musiałem odblokować przerwania czyniąc to w następujący sposób
    
    	MCUCR |= 0x02;	        // przerwanie aktywne opadającym zboczem
    	GIMSK |= 0b01000000;	// zezwol na INT0;
           ...
    	sei();                                // globalne przerwanie odblokowane
    


    Teraz trochę o samym programie i układzie. Program w pętli pobiera temperaturę otoczenia po czym ją konwertuje (trwa to 750ms, a w międzyczasie multipleksuje wyświetlanie temperatury na wyświetlaczu). Następnie skonwertowana temperatura jest poddawana obróbce, aby przerobić ją na taką, którą mogę wyświetlić na wyświetlaczu po czym znów następuje multipleksowanie. Wszystko działa ok, pod warunkiem, że w kodzie nie ma linijki sei(); (ale wtedy nie działa odbieranie danych z pilota). Gdy włączenie globalnego przerwania występuje w kodzie cale działanie układu ulega zmianie. Tzn układ pobiera temperaturę, jednak wyświetla tylko jedną cyfrę temperatury i to jedynie na krótką chwile. Potem znika na blisko sekundę i pojawia się na nowo.

    Ponadto, obsługa przerwania powinna spowodować zapalenie się diody. Jednakże, gdy dane z pilota zostają odebrane dioda się zapala, a następnie gaśnie (gdy odbiornik przestaje odbierać podczerwień). W programie jednak nigdzie nie wygaszam diody.

    Poniżej przedstawiam kod programu, postarałem się go skomentować w miarę jasno. Nie należy zawracać sobie głowy przy definicjach i funkcjach obsługi 1-wire. One działają poprawnie bo temperatura była już pobierana wielokrotnie dobrze wyświetlana na wyświetlaczu.

    
    
    ///////////////////////////////////////////////////////////////////////////////
    // MAKRODEFINICJE
    ///////////////////////////////////////////////////////////////////////////////
    #define OW_PIN 		6                                          // numer pinu magistrali 1wire
    #define OW_WY 		DDRD |= _BV(OW_PIN)          // ustawienie magistrali jako wyjscie
    #define OW_WE 		DDRD &= ~_BV(OW_PIN)       // ustawienie magistrali jako wejscie
    #define WE_1 		(PIND >> OW_PIN) & 0x01             // bit na wejsciu ustawiony
    #define WE_0 		!WE_1                                          // bit na wejsciu wyzerowany
    
    #define UST255 PORTB = 0xFF                                      // gasi wszystkie segmenty wyswietlacza
    
    #define SEGA 		~_BV(5)                                // zapala segment A itd
    #define SEGB 		~_BV(4)
    #define SEGC 		~_BV(0)
    #define SEGD 		~_BV(2)
    #define SEGE 		~_BV(3)
    #define SEGF 		~_BV(7)
    #define SEGG 		~_BV(6)
    #define SEGADP 		~_BV(1)
    
    #define ZERO		UST255; PORTB &= SEGA; PORTB &= SEGB; PORTB &= SEGC; PORTB &= SEGD; PORTB &= SEGE; PORTB &= SEGF          // zapala konkretne cyfry 
    #define JEDEN 		UST255; PORTB &= SEGB; PORTB &= SEGC
    #define DWA			UST255; PORTB &= SEGA; PORTB &= SEGB; PORTB &= SEGG; PORTB &= SEGE; PORTB &= SEGD
    #define TRZY		UST255; PORTB &= SEGA; PORTB &= SEGG; PORTB &= SEGD; PORTB &= SEGC; PORTB &= SEGB
    #define CZTERY		UST255; PORTB &= SEGF; PORTB &= SEGG; PORTB &= SEGB; PORTB &= SEGC
    #define PIEC		UST255; PORTB &= SEGA; PORTB &= SEGF; PORTB &= SEGG; PORTB &= SEGC; PORTB &= SEGD
    #define SZESC		PIEC; PORTB &= SEGE
    #define SIEDEM		JEDEN; PORTB &= SEGA;
    #define OSIEM		ZERO; PORTB &= SEGG;
    #define DZIEWIEC	UST255; PORTB &= SEGA; PORTB &= SEGB; PORTB &= SEGC; PORTB &= SEGD; PORTB &= SEGF; PORTB &= SEGG
    
    
    #define DZIESIATKI 	PORTA = 0b10   // wybiera do wyswietlenia cyfrę dziesiatek
    #define JEDNOSCI	PORTA = 0b01  // wybiera do wyswietlenia cyfrę jednosci
    
    
    ///////////////////////////////////////////////////////////////////////////////
    // PLIKI NAGŁOWKOWE
    ///////////////////////////////////////////////////////////////////////////////
    #include <avr/io.h>
    #include <util/delay.h>
    #include <avr/interrupt.h>
    #include <stdlib.h>
    
    
    
    ///////////////////////////////////////////////////////////////////////////////
    // ZMIENNE GLOBALNE
    ///////////////////////////////////////////////////////////////////////////////
    char bufor[2]; // przechowuje cyfrę jednosci i dziesiatek
    
    
    
    ///////////////////////////////////////////////////////////////////////////////
    // WYSYŁANIE
    ///////////////////////////////////////////////////////////////////////////////
    void WyslijBit(char bit)		// WYSŁANIE BITU (1-wire)
    {
    	_delay_us(3);
    	OW_WY;
    	_delay_us(5);
    	if (bit == 1)
    	{
    		OW_WE;
    		_delay_us(90);	
    	}
    	else 
    	{
    		_delay_us(90);
    		OW_WE;
    	}
    }
    
    
    void WyslijBajt(char znak)		// WYSŁANIE BAJTU (1-wire)
    {
    
    	unsigned char i, pom;
    	
    	for (i=0; i<8; i++)
    	{
    		pom = znak>>i;
    		pom &= 0x01;
    		WyslijBit(pom);
    	}
    	_delay_us(100);
    }
    
    
    ///////////////////////////////////////////////////////////////////////////////
    // INICJALIZACJA
    ///////////////////////////////////////////////////////////////////////////////
    int Reset(void)					// RESET (1-wire)
    {
    	int podlaczony = 0;
    
    	OW_WY;
    	_delay_us(500);
    	OW_WE;
    	_delay_us(45);
    	if (WE_0)
    		podlaczony = 1;
    
    	_delay_us(350);
    	if (WE_1)
    		podlaczony = 1;
    	else 
    		podlaczony = 0;
    		
    	return podlaczony;
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // ODBIERANIE
    ///////////////////////////////////////////////////////////////////////////////
    
    unsigned char OdbierzBit(void)			// ODEBRANIE BITU (1-wire)
    {
    	char bit = 0;
    		
    	_delay_us(5);
    	OW_WY;
    	_delay_us(5);
    	OW_WE;
    	_delay_us(15);
    	if (WE_1)
    		bit = 0x01;		
    	_delay_us(50);	
    	return bit;
    	
    }
    
    
    
    
    unsigned char OdbierzBajt(void)			// ODEBRANIE BAJTU (1-wire)
    {
    	unsigned char i, wartosc = 0;
    
    	for (i=0; i<8; i++)
    	{
    		if (OdbierzBit())
    			wartosc |= 0x01<<i;
    		_delay_us(15);
    	}
    	
    	return wartosc;
    }
    
    
    ///////////////////////////////////////////////////////////////////////////////
    // MULTIPLEKSOWANIE
    ///////////////////////////////////////////////////////////////////////////////
    void Multipleksuj(int x)
    {
    	int i;
    	for (i=0; i<x; i++)
    	{				
    		JEDNOSCI;                          // wyswietl cyfrę jako cyfrę jednosci
    		switch (bufor[1])                 // w zaleznosci jaka cyfra byla w buforze rob switch
    		{
    			case '0':
    				ZERO;                // wyswietl 0 itd
    				break;
    			case '1':
    				JEDEN;
    				break;
    			case '2':
    				DWA;
    				break;
    			case '3':
    				TRZY;
    				break;
    			case '4':
    				CZTERY;
    				break;
    			case '5':
    				PIEC;
    				break;
    			case '6':
    				SZESC;
    				break;
    			case '7':
    				SIEDEM;
    				break;
    			case '8':
    				OSIEM;
    				break;
    			case '9':
    				DZIEWIEC;
    				break;
    		}		
    		_delay_ms(10);
    
    		DZIESIATKI;                            // zapal cyfrę dziesiatek i wyswietl zawartosc bufora
    		switch (bufor[0])
    		{
    			case '0':
    				ZERO;
    				break;
    			case '1':
    				JEDEN;
    				break;
    			case '2':
    				DWA;
    				break;
    			case '3':
    				TRZY;
    				break;
    			case '4':
    				CZTERY;
    				break;
    			case '5':
    				PIEC;
    				break;
    			case '6':
    				SZESC;
    				break;
    			case '7':
    				SIEDEM;
    				break;
    			case '8':
    				OSIEM;
    				break;
    			case '9':
    				DZIEWIEC;
    				break;
    		}	
    		_delay_ms(10);
    	}
    }
    
    
    ///////////////////////////////////////////////////////////////////////////////
    // PRZERWANIA
    ///////////////////////////////////////////////////////////////////////////////
    
    ISR(INT0_vect)
    {	
    	_delay_ms(50);
    	PORTD |= _BV(5);	// zapal diode (reakacja na dane wyslane z pilota)
    }
    
    
    ///////////////////////////////////////////////////////////////////////////////
    // PROGRAM GŁÓWNY
    ///////////////////////////////////////////////////////////////////////////////
    int main(void)					// MAIN
    {	
    	char temperatura[2];
    	int temp;
    	char temporary;	
    	div_t wynik;
    
    	MCUCR |= 0x02;	// zbocze opadajace
    	GIMSK |= 0b01000000;	// zezwol na INT0;
    
    	DDRD = 0b1111010;
    	PORTD = 0x00;
    
    	DDRA = 0xFF;
    	PORTA = 0b00;
    
    	DDRB = 0xFF;
    	PORTB = 0xFF;
    
    	UCSRB = 1 << TXCIE | 1 << TXEN;  // przygoruj USART do nadawania
      	UBRRL = 25; 
    
    	_delay_ms(100);
    	sei();    // wlacz globalne przerwania
    
    
    	while (1)    // nieskonczona petla
    	{ 
    		if(Reset())   jesli znaleziono urzadzenie podpiete do 1 wire wykonaj kod
    		{		
    			WyslijBajt(0xCC);   // skip rom
    			WyslijBajt(0x44);   // konwersja temperatury
    			Multipleksuj(38);    // multipleksowanie w czasie trwania konwersji (zeby nie bylo przerwy w wyswietlaniu na czas konwersji)
    			Reset();
    			WyslijBajt(0xCC);   // skip rom
    			WyslijBajt(0xBE);   // odczytaj temperaturę
    			temporary = OdbierzBajt();			// przerabianie temperatury na znaki (tylko cyfra dziesiatek i jednosci) 
    			temperatura[0] = (temporary >> 4);									
    			temperatura[1] = (OdbierzBajt() << 4);	
    			
    			temp = temperatura[0];
    			temp |= temperatura[1];	
    			
    			
    			Reset();
    			WyslijBajt(0xCC);
    			WyslijBajt(0xBE);
    	 		
    		        UDR = OdbierzBajt();			
    			UDR = OdbierzBajt();	// wysyla temperaturę USART -> RS232 		
    			wynik = div(temp, 10);			
    			bufor[0] = wynik.quot + 48;
    			bufor[1] = wynik.rem + 48;			
    
    			Multipleksuj(100);	// ponownie multipleksowane (ok 2sek)				
    			
    			Reset();		
    		}
    	}
    } 
    



    Zdaje sobie sprawę, że kod jest dość długi do sprawdzenia. Ale najprawdopodobniej błąd jest w funkcji main (), a większość kodu to obsługa 1wire.
    Streszczając to co napisałem wyżej:
    1. Dlaczego dioda gaśnie mimo, że przerwanie ją tylko zapala. (podpięta jest do portu D, pinu 5)
    2. Dlaczego błędnie wyświetlana jest temperatura tj. widać jedną migotającą cyfrę.

    Będę wdzięczny, za wszelką pomoc :)
  • Pomocny post
    #2 7024999
    Freddie Chopin
    Specjalista - Mikrokontrolery
    po pierwsze i najwazniejsze - w przerwaniu N_I_G_D_Y nie daje sie opoznien, a juz szczegolnie tak dlugich jak 50ms.

    Popraw to i zobacz jakie da to efekty.

    A tak apropo - włączasz przerwanie od TXC z UARTu, ale nigdzie nie masz jego obsługi - jeśli przerwanie wystąpi, to procek się resetuje (czyli dioda gaśnie).

    4\/3!!
  • #3 7025086
    iwd2
    Poziom 2  
    Usunięcie opóźnienia nic nie dało, ale z ciekawości zapytam dlaczego nie można stosować takich opóźnień? Co w przypadku jeżeli chciałbym (tutaj ten problem nie występuje) zlikwidować drgania zestyku? Oczywiście mógłbym to zrobić sprzętowo, ale jak zrealizować to programowo?

    Po drugie, zastąpiłem kod
    UCSRB = 1 << TXCIE | 1 << TXEN;

    następującym
    UCSRB = 1 << TXEN;

    i wszystko działa jak powinno :)

    Także dziękuje za ekspresowe rozwiązanie problemu ;).
  • #4 7025135
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Opóźnienia nie można stosować, bo mija się to z celem używania przerwań. Ich istotą jest błyskawiczna reakcja na zdarzenie. W czasie trwania przerwania główny wątek aplikacji (u ciebie multipleksowanie, pomiar, wysyłanie itd.) zostaje wstrzymany, co nie jest sytuacją pożądaną (multipleksowany obraz się pogarsza, pomiary mogą wychodzić błędne, wysyłane dane mogą zawierać błędy, etc.). Z tego względu przerwania powinny być tak krótkie jak to tylko możliwe.

    Jak rozwiązać drganie styków? Na przykład:

    
    volatile uint8_t button_was_pressed;
    ...
    // w przerwaniu
    {
    button_was_pressed = 1;
    disable_button_interrupt();
    }
    ...
    // w kodzie głównym
    {
    if(button_was_pressed == 1)
    {
    button_was_pressed = 0;
    do_something();
    delay_ms(20);
    enable_button_interrupt();
    }
    }
    


    Krauser napisał:
    Wywołanie funkcji _delay_ms(50); w przerwaniu jest błędem, ponieważ ta funkcja już jest wykonywana w programie głównym. Wywołanie jej jeszcze raz zmienia użyte przez nią rejestry(w to się nie zagłębiam) i po powrocie funkcja ta robi nie to co potrzeba.

    Bzdura - wszystkie funkcje z przerwań, jeśli tylko nie wykorzystują zmiennych statycznych / globalnych (czyli są "reentrant") mogą być wywoływane równocześnie w kodzie głównym. Mechanizmy kompilatora odtwarzają zawartość wszystkich rejestrów które zostaną zmodyfikowane w przerwaniu.

    4\/3!!
REKLAMA