Migotanie cyfr przy wyświetlaniu multipleksowym na wyś. LED

To forum jest dla wszystkich pasjonatów mikrokontrolerów AVR Atmela. Wymiana doświadczeń i pomoc dla początkujących w pisaniu programów zarówno w C, Asemblerze jak i BASCOM. Zapraszam znawców tematu, aby pomogli wszystkim początkującym!
slawek55
Użytkownik
Posty: 484
Rejestracja: 16 sie 2005, 11:47
Lokalizacja: Szczecin

Migotanie cyfr przy wyświetlaniu multipleksowym na wyś. LED

Post autor: slawek55 » 17 sty 2008, 20:19

Cześć.
Chcę Was prossić o porade bo jesteście bardziej dośwadczeni w tym niz ja.

Otóż robie bardzo prosty licznik przebytej drogi, czyli układ zlicza impulsy i przelicza na metry.

I teraz zastanawiam się nad taką sprawą.
W przerwaniu od przepełnienia Timera umieszczam procedurę multipleksowego wyświetlania na wyświetlaczu LED wyniku na 4 wyś.
Mam globalna tablice g_DaneWyswietlacza w której są już przerobione dane do wyświelenia.

Ma też osobną procedurę

Kod: Zaznacz cały

Void Translate(uint8_t var)
for(n=4; n>0; n--)
	{
	g_DaneWyswietlacza[n-1] = pgm_read_byte(&g_WzorCyfr[var%10]); 
	var /= 10; 
	}
Teraz w głównej funkci main zliczam te impulsy i wywołuje Translate.

I teraz pytanie.
Dlaczego w funkcji Translate niektórzy blokują przerwanie od Timera na czas konwersji. Niby ma to zapobiegać migotaniu cyfr.
Czy spotkał się ktos z Was z tym. To blokowanie to widziałem w programie pisanym na uC8051, na AVR nie spotkałem.
Możecie mi coś opisać o tym, jakie są wasze doświadczenia z tym.

P.S. ten przykład to z cz. 4 kursu z EdW

snow
Użytkownik
Posty: 794
Rejestracja: 16 sty 2007, 3:44
Lokalizacja: lubelskie
Kontakt:

Post autor: snow » 17 sty 2008, 20:28

Po pierwsze w funkcji brakuje { }. Po drugie ja robiłem multiplexowanie na Timerze i nie robiłem żadnego wstrzymania jego pracy. Ja robiłem tak że w przerwaniu zwiększałem zmienna która odpowiadała za załączenia tranzystora dla danego wyświetlacza i w tym samym momencie wystawiałem na port wartość odpowiadającą cyfrze która ma na danym wyświetlaczu się pokazać. Gdy doszedłem do ostatniego wyświetlacza zerowałem zmienną i wracało do początku.
Częstotliwość multiplexowania mogłem mieć od paru Hertz'ow do kilkunastu kiloherców.

Awatar użytkownika
gaweł
Użytkownik
Posty: 653
Rejestracja: 05 kwie 2004, 8:38
Lokalizacja: Białystok
Kontakt:

Re: Migotanie cyfr przy wyświetlaniu multipleksowym na wyś.

Post autor: gaweł » 17 sty 2008, 23:05

slawek55 pisze:Otóż robie bardzo prosty licznik przebytej drogi, czyli układ zlicza impulsy i przelicza na metry.

I teraz zastanawiam się nad taką sprawą.
W przerwaniu od przepełnienia Timera umieszczam procedurę multipleksowego wyświetlania na wyświetlaczu LED wyniku na 4 wyś.
Mam globalna tablice g_DaneWyswietlacza w której są już przerobione dane do wyświelenia.(....)
Jak zauważył już wcześniej snow, to brakuje nawiasów {}. Z tego można wysuć wniosek, że zaprezentowany jest jedynie fragment programu i trudno wyrokować co do całości.
Typowo wyświetlanie na wyśw. 7-segmentowym LED realizowane jest w taki sposób, że procek steruje jednocześnie świeceniem segmentów w danej cyfrze oraz przełącza cyfry. Taki sposób wymaga odpowiednio częstej obsługi. Wręcz idealnym rozwiązaniem jest użycie tu przerwań od timer'a. Daje to taki efekt, że można uzyskać całkowicie autonomiczny proces wyświetlania danych wielocyfrowych. Z punktu widzenia Twego programu, jako jego głównej funkcjonalności, wyświetlanie jest autonomiczne, czyli tak, jakby to realizował inny procek. Obsługa ogranicza się do umieszczenia w buforze wyświetlacza danych, które przez obsługę przerwań od zegara zostaną przeniesione na wyświetlacz. W przypadku AVR nie ma znaczenie proces konwersji, gdyż przygotowanie danych (jak przekodowanie przez tablicę) może być realizowane w dowolny spoosób i może trwać dowolnie długo (oczywiście przy odblokowanych przerwaniach) oraz sam wpis do bufora nie wpływa na sposób wyświetlania gdyż operacje przesyłania danych 8-bitowych są atomowe (niepodzielne). Blokowanie w tym miejscu przerwań wręcz może mieć szkodliwy wpływ, gdyż może znacząco zaburzać rytm przerwań od zegara.
Na proces wyświetlania istotny wpływ ma natomiast częstotliwość przerwań. Można przyjąć, że małe kilkadziesiąt Hz na jedną cyfrę daje efekt ciągłego świecenia. W przypadku 4 cyfr częstotliwość należy zwiększyć 4-krotnie. W efekcie poninieneś tak dobrać parametry dla timera by uzyskać minimum 100-120 Hz przełączania.
Jeżeli widać migotanie, to przełączasz za wolno. Może to być spowodowane właśnie niedpowiednim zaprogramowaniem timera. Nie można wykuczyć również, że używasz jakiegoś procka MEGA i zapomniałeś przeprogramować fuse, bo wiele z nich default'owo są ustawione na wewnętrzny generator ok. 1 MHz.
Zaburzenia rytmu przerwań mogą być spowodowane niewłaściwą obsługą innych przerwań. Przykładowo w obsłudze przerwania od UART będziesz realizował długą obsługę. W obsłudze typu SIGNAL ... blokuje to przerwania aż do wyjścia z obsługi czyli może dojść do spowolnienia przerwań od zegara.
Gdybyś obszerniej zaprozentował obsługę wyświetlacza, to można by poczynić bardziej precyzyjne uwagi. Przykładowo, pomijając częstotliwość przełączania. niewłaściwa obsługa może dać efekt "mroczków", czyli na danej cyfrze drobną poświatę segmentów z cyfry sąsiedniej.
Ogólnie to można długo jeszcze gdybać na temat przyczyn. Musisz napisać coś więcej o problemie.

slawek55
Użytkownik
Posty: 484
Rejestracja: 16 sie 2005, 11:47
Lokalizacja: Szczecin

Post autor: slawek55 » 18 sty 2008, 6:55

Dzięki, ale chyba źle opisałem pytanie.
Zapytam inaczej.
Jeżeli wyświetlanie multipleksowe na LED wykonuję cyklicznie w obsłudze przewania Timera a procedura zajmuje się tylko wypuszczeaniem na port zawartości tablicy DaneWyswiatlacza[4] (w niej juz sa gotowe wzorce do wyświetlenia) np PORTB=DaneWyświetlacza[1]; itd
A natomiast to co najduje się w tabeli DaneWyswietlacza[] sa przekształcane ze zmiennej int w funkcji Convert osobno zadeklarowanej i wywoływanej z głównej funkcji main() to czy na czas konwersji w funkcji Convert potrzeba jest blokowania przerwania od Timera.
Właśnie z tym się spotkałem w programie pisanym na uC4051, natomiast przeglądając listingi na AVR nie spotkałem się z tym.

snow
Użytkownik
Posty: 794
Rejestracja: 16 sty 2007, 3:44
Lokalizacja: lubelskie
Kontakt:

Post autor: snow » 18 sty 2008, 8:29

Przerwanie powinno się zajmować jedynie multipleksacją a konwersja i wystawianie na port powinno być raczej w pętli głownej. Nie ma potrzeby blokowania przerwań.

Awatar użytkownika
gaweł
Użytkownik
Posty: 653
Rejestracja: 05 kwie 2004, 8:38
Lokalizacja: Białystok
Kontakt:

Post autor: gaweł » 18 sty 2008, 10:29

slawek55 pisze:Dzięki, ale chyba źle opisałem pytanie.
Chyba nie. Po raz drugi pytasz o to samo więc napiszę o tym samym (tylko inaczej).
Pisząc o autonomicznym wyświetlaniu chodziło mi o "synchroniczne" sterowanie wyświetlaniem, czyli wysyłanie odpowiednich danych sterujących segmentami dotyczące właściwej cyfry w wyświetlaczu. To jest zmienna DaneWyświetlacza. Obsługa przerwania korzysta z danych tam zapisanych. Aby wyświetlić liczbę nalezy dokonać jej konwersji na znaki, czyli rozłozyć liczbę na poszczególne cyfry. W dalszej kolejności każdą cyfrę należy zamienić na informacje odzwierciedlające jej kształt na wyświetlaczu. Nie jest istotne (w sensie obsługi wyświetlacza w przerwaniach jako przełączanie segmentów i cyfr) jak to zostanie wykonane.

Kod: Zaznacz cały

for(n=4; n>0; n--) 
    { 
    g_DaneWyswietlacza[n-1] = pgm_read_byte(&g_WzorCyfr[var%10]); 
    var /= 10; 
    } 
W tej pętli robisz obie operacje. Obliczając resztę z dzielenia przez 10 i dzieląc przez 10 uzyskujesz rozkład liczby na poszczególne cyfry, które są przekodowane przez stałą tablicę na dane sterujące poszczególnymi segmentami. Możesz przykładowo:

Kod: Zaznacz cały

for(n=4; n>0; n--) 
    { 
    g_DaneWyswietlacza[n-1] =Funkcja_Konwersji(&g_WzorCyfr[var%10]); 
    var /= 10; 
    } 
Wywołać własną baaaardzo skomplikowaną funkcję. Hipotetycznie niech ona zamienia cyfrę na sterowanie segmentani przez 1 godzinę. Oznacza to, że do zmiennej DaneWyświetlacza informacje trafią za godzinę, ale w tym czasie przerwanie będzie wyświetlać stare dane. Czyli możesz wykonywać konwersję w dowolny sposób i dowolnie długo. Jeżeli zrobisz tak:

Kod: Zaznacz cały

for(n=4; n>0; n--) 
    { 
    cli();
    g_DaneWyswietlacza[n-1] =Funkcja_Konwersji(&g_WzorCyfr[var%10]); 
    sti();
    var /= 10; 
    } 
to zatrzymasz na czas konwersji (1 godzinę) proces wyświetlania. Reasumując: blokada przerwań w tym miejscu jest szkodliwa.
Przyczyn migotania należy poszukać w inym miejscu.
snow pisze:(...)wystawianie na port powinno być raczej w pętli głownej.(...)
Możesz rozwinąć tą myśl?

snow
Użytkownik
Posty: 794
Rejestracja: 16 sty 2007, 3:44
Lokalizacja: lubelskie
Kontakt:

Post autor: snow » 18 sty 2008, 10:49

gaweł pisze:Możesz rozwinąć tą myśl?
Napiszę jak ja do tej pory robiłem (Z pamięci bo nie mam przy sobie gotowego kodu więc będzie to raczej jako przedstawienie idei). W przerwaniu robimy :

Kod: Zaznacz cały

SIGNAL (SIG_OVERFLOW0)
{
led++;
}

W pętli głownej:

Kod: Zaznacz cały

if (led==0)
   {
   wysw1;
   PORTD=cyfra[jednostki];
   }

if (led==1)
   {
   wysw2;
   PORTD=cyfra[dziesiatki];
   }
if (led==2)
   {
   wysw3;
   PORTD=cyfra[setki];
   }
if (led==3)
   {
   led=0;
   }
Gdzie tablica cyfra[] posiada zdefiniowane wartości hex dla kolejnych cyfr od 0 do 9.
wysw1,wysw2,wysw3 odpowiadają za załączenie odpowiednich wyświetlaczy.

Awatar użytkownika
gaweł
Użytkownik
Posty: 653
Rejestracja: 05 kwie 2004, 8:38
Lokalizacja: Białystok
Kontakt:

Post autor: gaweł » 18 sty 2008, 11:06

OK. Teraz rozumiem.

tasza
Użytkownik
Posty: 1389
Rejestracja: 21 lut 2005, 15:02

Post autor: tasza » 18 sty 2008, 12:17

snow pisze:Przerwanie powinno się zajmować jedynie multipleksacją a konwersja i wystawianie na port powinno być raczej w pętli głownej.
No właśnie nie do końca tak...przecież multipleksacja to sekwencyjne wybieranie
danych dla wyświetlacza z buforka i wystawianie ich na porty celem wysterowania
displaya (zarówno segmentów jak i wspólnej anody/katody (led) lub siatki (vfd).

snow, ten fragment kodu, który załączyłeś...dopisałam pseudo-resztę i popatrz co będzie:

Kod: Zaznacz cały

// petla glowna programu
while ( 1 ) {
    if (led==0) {
       wysw1;
       PORTD=cyfra[jednostki];
    }
    if (led==1) {
       wysw2;
       PORTD=cyfra[dziesiatki];
    }
    if (led==2) {
       wysw3;
       PORTD=cyfra[setki];
    }
    if (led==3) {
       led=0;
    }
    // potem inne zadanie
    strasznie_dluuuuuuuuugo_trwajaca_funkcja();
}    
Jak myślisz, jak będzie wyglądał wyświetlacz? Bo ja sądzę, że będzie migać że hej...

Cała sztuka obsługi multipleksowania w przerwaniach polega na tym, aby program główny
miał tylko skonfigurować porty, odpowiednio ustawić i uruchomić timer.
Reszta (wybór danych, wystawienie danych) - to robi handler przerwania od timera.
A program główny nie zajmuje się wyświetlaczem, tylko odpowiednio do tego co robi
wstawia co potrzeba do bufora z danymi. I jego już nie obchodzi, że to się gdzieś tam
wyświetla...to jest proces zupełnie przezroczysty, jakby w tle...
Popatrzcie na te wypociny: https://elportal.pl/index.php?module=Co ... y&ceid=117 (plik demo3.c)
W handlerze przerwania jest wołana tylko i wyłącznie funkcja vfd_update_display()
I ona sekwencyjnie przerabia zawartość bufora ulVfdDataBuffer[] na obrazek na wyświetlaczu.
Aha, zauważcie proszę, że vfd_update_display() nie jest taka znowu trywialna,
ona ma sporo do zrobienia, głównie to przekonwertować i __szeregowo_załadować__
dane do sterownika VFD. Obraz na wyświetlaczu był stabilny, a przerwań nigdzie nie wyłączałam...
Natasza

snow
Użytkownik
Posty: 794
Rejestracja: 16 sty 2007, 3:44
Lokalizacja: lubelskie
Kontakt:

Post autor: snow » 18 sty 2008, 12:26

tasza pisze:Jak myślisz, jak będzie wyglądał wyświetlacz? Bo ja sądzę, że będzie migać że hej...
W sumie racja. Zazwczyaj nie mam dłuugo trwających funkcji w urządzenia z wyświetlaczem LED więc pewnie dlatego mi taka obsługa jak wyżej przedstawiłem odpowiadała ;)

tasza
Użytkownik
Posty: 1389
Rejestracja: 21 lut 2005, 15:02

Post autor: tasza » 18 sty 2008, 13:05

snow pisze:Zazwczyaj nie mam dłuugo trwających funkcji w urządzenia z wyświetlaczem LED więc pewnie dlatego mi taka obsługa jak wyżej przedstawiłem odpowiadała
Ok, ale wiesz...na różne zagadnienia, problemy są pewne wzorce rozwiązań,
to się czasem nazywa - design patterns. I tu chodzi o zasadę, teraz taka obsługa Ci
odpowiada, a gdy będzie trzeba zrobić bardziej skomplikowany program główny i zastosujesz
to rozwiązanie z rozpędu - to popłyniesz. Analogiczne zagadnienie to obsługa (skanowanie)
klawiatury. To też warto robić w przerwaniach (mając gwarancję, że kontroler kuknie
w klawiaturę co jakiś czas) a nie w pętli głównej (gdzie jest ryzyko, że procesor
zajęty czymś w danej chwili ważniejszym po prostu "prześpi" fakt naciskania guzików).
Natasza

snow
Użytkownik
Posty: 794
Rejestracja: 16 sty 2007, 3:44
Lokalizacja: lubelskie
Kontakt:

Post autor: snow » 18 sty 2008, 13:14

Wzorce są ale czy zawsze trzeba się do nich bezwzględnie stosować? czasem trzeba zrobić coś inaczej bo założenia tego wymagają (np. procesor może być taki a nie inny bo wiekszy nie wejdzie na płytke). Czasem trzeba być elastycznym :)

Ale nie ma co udowadniać wyższości jednego nad drugim. Autor wybierze odpowiednie dla niego rozwiązanie z podanych przez nas przykładów :)

slawek55
Użytkownik
Posty: 484
Rejestracja: 16 sie 2005, 11:47
Lokalizacja: Szczecin

Post autor: slawek55 » 19 sty 2008, 5:39

A czy nie trezba tablicy przechowującej wzorce cyfr wyświetlanych w przerwaniu w tym przypadku g_DaneWyswietlacza[] zadeklarować jako volatile.
Przecież te zmienne są używane w procedurze obsługi przerwania oraz w programie głównym.

Zastanawiałem się też czy jeżeli mam przerwanie i chcę opuścic to przerwanie wczesniej to mogę zastosować warunek np if(Pause)return; np tak:

Kod: Zaznacz cały

ISR(INT0_vect)
{
if(Pause)return;

Led++;

//...... i cała reszta przerwania

}

Awatar użytkownika
Koppel
Użytkownik
Posty: 500
Rejestracja: 24 lip 2005, 18:05
Lokalizacja: Gliwice

Post autor: Koppel » 19 sty 2008, 6:48

slawek55 pisze:A czy nie trezba tablicy przechowującej wzorce cyfr wyświetlanych w przerwaniu w tym przypadku g_DaneWyswietlacza[] zadeklarować jako volatile
Nie trzeba. Zmienna typu volatile ma zastosowanie wtedy gdy jej zawartość ma dla nas znaczenie i może zmienić się w niespodziewanym momencie. Tutaj nie ma takiej sytuacji - jej zawartość zmienia się w programie głównym. Obsługa przerwania, niezależnie od tego czy będziemy mieli ze zmienną volatile czy nie do czynienia i tak za każdym razem rozpoczyna swoje działanie od początku, więc wczytuje aktualną zawartość tablicy.
slawek55 pisze: Zastanawiałem się też czy jeżeli mam przerwanie i chcę opuścic to przerwanie wczesniej to mogę zastosować warunek np if(Pause)return
Nie tylko można ale wręcz tak się właśnie robi. Innym sposobem, jeśli chcemy jeszcze coś zrobić zawsze przed zakończeniem przerwania, jest użycie instrukcji goto do końcowego obszaru, który będzie realizował jeszcze jakieś zadanie. Ogólnie jednak używanie instrukcji goto nie jest zalecane i stosuje się różne sztuczki aby ją pominąć.

slawek55
Użytkownik
Posty: 484
Rejestracja: 16 sie 2005, 11:47
Lokalizacja: Szczecin

Post autor: slawek55 » 02 lut 2008, 16:19

Zastanawiam się teraz gdzie umieścić zapalenie kropki dzisiętnej.
Jeżeli wywołuję funkcję WyswietlDEC(1000); to ona powoduje konwersję liczby do postaci gotowej do wyświetlenia dla przerwania.
A gdzie mam teraz dopisać fragment zapalający kropkę na trzecim wyświetlaczu?
Czy można dpisać zapalenie kropki zaraz po wywołaniu WyswietlDEC?

Kod: Zaznacz cały

....
WyswietlDEC(1000);
g_DaneWyswietlacza[2]&=~(1<<LED_DP);
.....
CZy byłoby to poprawniwnie?

ODPOWIEDZ