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

ATMega8 + AVR-GCC - sterowanie wyświetlaczy LED

ogryz 24 Kwi 2009 23:25 9664 16
REKLAMA
  • #1 6453763
    ogryz
    VIP Zasłużony dla elektroda
    Witam!
    Właśnie przesiadam się z 8051 (Bascom) na ATMegę8 i C. Zacząłem od prostego programu pod układ z rysunku (pominąłem czwarty wyświetlacz, sterowany z PC3):
    ATMega8 + AVR-GCC - sterowanie wyświetlaczy LED
    Oczywiście jest to tylko schemat uproszczony, obrazujący jedynie sposób sterowania wyświetlacza, proszę nie patrzeć na brak innych elementów.

    Kod programu:
    
    #include <avr\io.h>
    
    int main(void)
    {
      DDRC = 1<<0 | 1<<1 | 1<<2;
      DDRD = 255;
      while (1<5)
      {
    
    	PORTC = 0;    //wygaszanie
    	PORTD = ~(1<<1 | 1<<2);    //wpisanie cyfry "1"
    	PORTC = 1<<0;    //zapalenie pierwszego wyświetlacza
    //	PORTC = 1<<0;
    
    	PORTC = 0;    //wygaszanie
    	PORTD = ~(1<<0 | 1<<1 | 1<<3 | 1<<4 | 1<<6);    //wpisanie cyfry "2"
    	PORTC = 1<<1;    //zapalenie drugiego wyświetlacza
    //	PORTC = 1<<1;
    
    	PORTC = 0;    //wygaszenie
    	PORTD = ~(1<<0 | 1<<1 | 1<<2 | 1<<3 | 1<<6);    //wpisanie cyfry "3"
    	PORTC = 1<<2;    /zapalenie trzeciego wyświetlacza
    	
      }
     return 0;
    }
    


    Program ma za zadanie tylko wyświetlać na wyświetlaczach liczbę 123 i nic więcej. Problem polega na tym, że ostatnia cyfra świeci wyraźnie jaśniej od pozostałych i za nic nie mogę dojść dlaczego.
    Zauważyłem coś ciekawego w symulacji (AVR Studio), mianowicie przy uaktywnianiu wyświetlaczy 1 i 2 (PC0 i PC1) dane wpisywane do PORTC pojawiają się najpierw w PORTC, później w PINC, natomiast przy ostatnim wyświetlaczu dane pojawiają się jednocześnie w obu rejestrach. Właśnie tu znajduje się przyczyna problemu, tylko nie wiem jak sobie z tym poradzić. Dlaczego tak się dzieje? Jeśli dodam do kodu linie oznaczone komentarzem, wszystkie wyświetlacze świecą jednakowo.
    Nie ma też znaczenia liczba używanych wyświetlaczy - zawsze ostatni świeci 2x jaśniej od pozostałych.

    Jeśli coś zagmatwałem lub pominąłem w opisie, proszę pytać.

    Pozdrawiam.
  • REKLAMA
  • #2 6453970
    cavendish
    Poziom 17  
    Witam,

    Zauważ, że pomiędzy zapaleniem a zgaszeniem ostatniego wyświetlacza znajduje się przejście z końca na początek while'a. Niby niedużo ale, jako że nie ma dodatkowych opóźnień między zapaleniem / zgaszeniem wyświetlaczy, jest to czas podobnego rzędu co czas na jaki zapalasz pojedynczy wyświetlacz (co relatywnie wydłuża czas przez jaki pali się ten ostatni w kolejności). Dodatkowe instrukcje wprowadzają drobne opóźnienia, które jednak wystarczą do zbalansowania wpływu przejścia między końcem początkiem pętli. Taka jest moja teoria. Pouczające byłoby zobaczyć kod assemblerowy.
  • REKLAMA
  • #3 6453980
    ogryz
    VIP Zasłużony dla elektroda
    Wiem o co chodzi i też o tym pomyślałem. Dałem nawet, wykonujące się 10000 razy, pętle "for" po zapaleniu każdego wyświetlacza. Oczywiście nic to nie dało. No i nadal nie potrafię zrozumieć, w symulacji, zachowania rejestrów PORTC i PINC przy ostatnim wyświetlaczu...
  • REKLAMA
  • Pomocny post
    #4 6453997
    Dr.Vee
    VIP Zasłużony dla elektroda
    W kodzie wynikowym pomiędzy wyświetlaniem cyfr 1 i 2 oraz 2 i 3 są takie same ilości instrukcji. Pomiędzy 3 i 1 będzie dodatkowo instrukcja skoku - dzięki temu trzeci wyświetlacz jest aktywny przez dłuższy czas niż wyświetlacze 1 i 2, a przy tak małej ilości instrukcji różnica 2 cykli zegara to dużo.

    Jak dodasz
    asm volatile("nop; nop;");
    pomiędzy 1 i 2 oraz 2 i 3, to wszystko będzie ok :)

    Pozdrawiam,
    Dr.Vee
  • #5 6454984
    ogryz
    VIP Zasłużony dla elektroda
    No ok, dwa głosy już mnie przekonują :) Czyli rozumiem, żeby zbytnio nie przejmować się tym, co pokazuje symulator AVR Studio?
  • Pomocny post
    #6 6455133
    _Robak_
    Poziom 33  
    Nie no z avr studio wszystko wporzadku, przy przejsciu whilea nie robi tylko jednej instrukcji, tylko jeszcze wlasnie to przejscie. Dlatego tez jeden krok w symulatorze to powiedzmy dwie instrukcje w rzeczywistosci (opis TYLKO obrazowy). W pierwszych dwoch przypadkach najpierw pojawia sie tylko na porcie, dajesz next step, potem na pinie. W przypadku ostatnim dajac next step on wykonuje skok ktory ma wieksza liczbe cykli i juz zdazy wystawic na pinie jedynke. Wynika to z tego ze jeden krok w symulatorze, jesli piszesz w c, to nie jest jeden cykl maszynowy procka. Troche zagmatwanie wytlumaczylem ale mysle ze bedziesz wiedzial :) Zreszta zobacz sobie ile cykli bierze jeden step bez skoku, a ile bierze bierze w momencie skoku do poczatku whilea.
  • #7 6455277
    ogryz
    VIP Zasłużony dla elektroda
    OK rozumiem. Nie wpadłem na to :) Teraz bawię się dalej i natknąłem się na kolejny problem.
    Poniższy program ma za zadanie wyświetlać po kolei liczby od 0 do 999 z wykorzystaniem przerwania od timera. Zmienna "licznik" przechowuje aktualna liczbę, która ma być inkrementowana po wywołaniu przerwania. Pętla "while" (może trochę zagmatwana) służy wyłącznie do obrazowania liczby na wyświetlaczach LED i działa na 100% ok (po wstawieniu "licznik++" do pętli, liczy poprawnie). TIMSK i TCCR0 poustawiane zgodnie z datasheetem. Niestety na wyświetlaczu mam ciągle "0". Sam licznik liczy, ponieważ udało mi się wyrzucić jego zawartość na wyświetlacze.

    
    #include <avr\io.h>
    #include <avr\delay.h>
    #include <avr\interrupt.h>
    #include <avr\signal.h>
    
    int licznik, tmp;  //zmienne pomocnicze
    unsigned char cyfra[10] = {192, 249, 164, 176, 153, 146, 130, 216, 128, 144};  //tablica z cyframi dla LEDów
    
    int main(void)
    {
      DDRC = 1<<0 | 1<<1 | 1<<2 | 1<<3;
      DDRD = 255;
      TCCR0 |= 1<<0 | 1<<2;
      TIMSK |= 1<<0;
      sei();
      while (1<5)
      {
      	tmp = licznik;
        if (licznik > 99)
    	{
    //		int temp = tmp/100;
    		tmp = tmp-100*(tmp/100);
    		PORTC = 0;
    		PORTD = cyfra[licznik/100];
    		PORTC = 1<<1;
    		_delay_loop_2(1000);
    	}
    	if (licznik > 9)
    	{
    		PORTC = 0;
    		PORTD = cyfra[tmp/10];
    		PORTC = 1<<2;
    //		int temp = licznik/10;
    		tmp = tmp-10*(tmp/10);
    		_delay_loop_2(1000);
    	}
    
    	PORTC = 0;
    	PORTD = cyfra[tmp];
    	PORTC = 1<<3;
    	_delay_loop_2(1000);
    
    //	licznik++;
    //	if (licznik>999) licznik=0;
    
      }
    return 0;
    }
    
    SIGNAL(SIG_OVERFLOW0)
    {
    licznik++;	
    }
    
  • #8 6455471
    mirekk36
    Poziom 42  
    ogryz -> proponuję ci poszukać sobie nawet tu na elektrodzie - tematu o multipleksowaniu dla wyświetlaczy LED, tak żebyś zobaczył i załapał od razu jak to się robi prawidłowo - a potem już będzie z górki.

    o np z googla polecam chociażby taki pierwszy link, n/t multipleksowania LED w języku C akurat:

    http://wkretak.pl/readarticle.php?article_id=19
  • #10 6458163
    ogryz
    VIP Zasłużony dla elektroda
    :arrow: mirekk36
    Wiem, że tak się nie multipleksuje. Po prostu miałem gotową obsługę wyświetlacza w pętli, a chciałem poeksperymentować z licznikiem.

    :arrow: Freddie Chopin
    O to chodziło. Dzięki.
  • #11 6480221
    ogryz
    VIP Zasłużony dla elektroda
    Witam ponownie z kolejnym problemem!
    Otóż zabrałem się za budowę zegara na wyświetlaczach LED i szło mi dobrze, dopóki nie utknąłem przy obsłudze przycisku do ustawiania czasu. Przycisk podłączony jest do wyprowadzenia PB0 i podczas przyciskania, zwiera je do masy.
    Kod programu:
    
    int main(void)
    {
    
    	// tu odbywa się inicjacja zegara, konfiguracja rejestrów, itp.
    
    	DDRB = 7;
    	PORTB = 7;
    	volatile int temp;
    	while(1<5)
    	{
    
    //tu odczyt danych z i2c, ich przetwarzanie, itp.
    
    		while (!(PINB & 1<<0))  //przycisk +
    		{
    			increment();    //zwiększenie wartości np. minut
    			update_dsp(Tryb/2);  //uaktualnienie tablicy z zawartością wyświetlacza
    			while (!(PINB & 1<<0) && temp<10000)  // opóźnienie po wciśnięciu przycisku
    			{	
    				temp++;
    				_delay_loop_2(400);
    			} 
    			_delay_loop_2(20000);
    		} //przycisk +
    
    	}  //petla glowna
    
    return 0;
    }  //main()
    


    Przycisk ma działać tak: przy krótkim naciśnięciu ma zwiększyć wartość jakiejś zmiennej (np. minut - dalej będę opisywał na tym przykładzie) o 1. Jeśli jednak przycisk nie zostanie zwolniony, po chwili zacznie szybko inkrementować minuty. Za opóźnienie pomiędzy momentem naciśnięcia a szybkiego zliczania odpowiada pętla while ze zmienną temp. Gdy warunek zostanie spełniony, pętla zakończy działanie i wykonywać się będzie "duża" pętla while tak długo, jak długo będziemy trzymać przycisk. _delay_loop_2() ogranicza szybkość zliczania.
    No i wszystko działa jak należy, do momentu gdy zwolnię przycisk i nacisnę go ponownie. Zmienna temp nie jest nigdzie zerowana, w związku z czym, przy następnym naciśnięciu, pętla rozpoczyna od razu szybkie zliczanie. Jakakolwiek próba wyzerowania zmiennej temp poza pętlą kończy się całkowita blokadą szybkiego zliczania. Nawet zadeklarowanie temp jako zmiennej globalnej uniemożliwia szybkie zliczanie.
    Gdzie popełniam błąd?
    Jeśli coś niejasno opisałem, proszę pytać.

    Pozdrawiam.
  • #12 6488026
    ogryz
    VIP Zasłużony dla elektroda
    Problem rozwiązany :) Po wielu próbach z poprzednią funkcją, napisałem nową od zera. Oto jej kod:
    
    if (!(PINB & 1<<0))  //przycisk +
    {									//bezpośrednio po naciśnięciu
    	increment();				//zwiększ wartość zmiennej
    	for (int i=0; i<10; i++)	//pauza 750ms, umożliwiająca jednocześnie...
    		if (!(PINB & 1<<0))	//...inkrementację poprzez szybkie naciskanie przycisku
    			_delay_ms(75);
    	if (!(PINB & 1<<0))		//jeśli przycisk nadal wciśnięty
    	while (!(PINB & 1<<0))
    	{
    		increment();		//szybka inkrementacja zmiennej
    		_delay_ms(100);
    	}
    }  //przycisk +
    


    Pozdrawiam.
  • #13 6715559
    Mihó
    Poziom 27  
    Zapoznałem się z tematem multipleksowania z tej strony:
    http://wkretak.pl/readarticle.php?article_id=19
    Jest on opracowany dla wyświetlaczy ze wspólną katodą. Rozumiem, że jeśli będę miał wyświetlacz ze wspólną anodą, to zamieniam tranzystory NPN na PNP i podciągam do zasilania, tak ?

    Pozostaje jeszcze kwestia częstotliwości - docelowo chcę sterować pięcioma wyświetlaczami, rozumiem, że częstotliwość przerwań wzrasta do 250Hz.

    Docelowo pewnie jeszcze będę sterował silnikami krokowymi i taktował częstotliwość dla PWM. Czy pojawianie się dodatkowych przerwań może zakłócić wyświetlanie ? Czy jedynie opóźnienia (jeśli takowe się pojawią w moim programie) ?
  • REKLAMA
  • #14 6722913
    endo_mif
    Poziom 11  
    dołączę się do tematu:
    mój problem polega na tym, że program zachowuje się tak, jakby nie wychodził z obsługi przerwania - nie wykonuje instukcji z pętli głównej.
    wszystko się normalnie wyświetla (każda zainicjowana wartość i).
    wie ktoś w czym może leżeć problem?

    kod (w C):

    http://wklej.org/id/114343/
  • #15 6728818
    rpal
    Poziom 27  
    kol. endo_mif zdecydowanie bardziej bym polecił zapisanie "matrycy" kolejnych liczb od 0 do 9 w tablicy i ustawianie pinów portu onsługującego LED poprzez odwołanie np. tablica[n] lub *(tablica+n) niż instrukcje case, select. Będzię o wiele szybciej.
  • #16 6729474
    szelus
    Poziom 34  
    :arrow: endo_mif

    'volatile'. To już było tyle razy, że staje się powoli nudne. Zmienne współdzielone pomiędzy procedurą obsługi przerwania a programemm głównym muszą być zadeklarowane jako volatile.
  • #17 6753510
    endo_mif
    Poziom 11  
    Działa. Dzięki wielkie szanowni Koledzy, przepraszam za moje luki w wiedzy ;)

    edit: o, nawet się zrymowało :)
REKLAMA