Język C dla mikrokontrolerów 8051. Multipleksowanie wyświetlaczy LED.

Język C dla mikrokontrolerów 8051. Multipleksowanie wyświetlaczy LED.

Sterowanie pojedynczą cyfrą LED wymaga dołączenia co najmniej dziewięciu wyprowadzeń. Należy bowiem dołączyć 7 segmentów cyfr, kropkę dziesiętną i wspólną anodę czy katodę wyświetlacza. Z tego osiem wyprowadzeń (segmenty i kropka dziesiętna) musi być połączone z mikrokontrolerem lub innym układem sterującym. Co zrobić, gdy jest niezbędne wyświetlenie liczby na przykład na 6 pozycjach? To aż 48 wyprowadzeń! Mało który mikrokontroler ma ich aż tyle. W takiej sytuacji jedynym ratunkiem jest multipleksowanie cyfr, czyli przełączanie ich w czasie wyświetlania tak, że w danym momencie świeci tylko jedna z nich. Multipleksowanie powinno być robione na  tyle szybko, aby oko ludzkie nie dostrzegało zmian cyfr. Ma się wówczas wrażenie, iż wszystkie cyfry świecą światłem ciągłym. To cała tajemnica.

Na rysunku 1 pokazano propozycję połączenia 3 wyświetlaczy LED z dowolnym zestawem ewaluacyjnym z mikrokontrolerem z rdzeniem 8051. Tak zbudowałem swój układ próbny na płytce uniwersalnej, gdy testowałem program. Użyłem trzech wyświetlaczy ze wspólną anodą – segmenty są załączane przez podanie niskiego potencjału do wyprowadzenia katody segmentu. Multipleksowanie wymaga załączania i wyłączania całej cyfry. Najprościej będzie, jeśli zrobimy to za pomocą tranzystora MOS. Jest on bardzo wygodny w użyciu, ponieważ nie wymaga rezystorów ograniczających prąd bazy. W ten sposób oszczędnie i nowocześnie można zbudować moduł wyświetlacza. Oczywiście, zamiast tranzystorów MOS z kanałem typu P można użyć również bipolarnych tranzystorów PNP, jednak wówczas w szereg z każdą z baz tranzystora powinien być włączony rezystor o rezystancji około 4,7kΩ.


Rysunek 1. W taki sposób podłączyłem swój testowy wyświetlacz LED (kliknij, aby powiększyć).
 

Jak pokazano na schemacie, segmenty cyfr połączone są razem – odpowiednio „A” z „A”, „B” z „B” i tak dalej. Proponuję, aby w tym momencie, dla celów testowych zrobić, swój układ podobnie. Oczywiście można połączyć segmenty cyfr inaczej, ale to będzie wymagać zmian w programie.
W opisie zasady sterowania tak połączonymi wyświetlaczami pomocny będzie rysunek 2, który maksymalnie uprościłem/ Nie uwzględniłem rezystorów ograniczających prąd, porty wyjściowe mikrokontrolera symbolizowane są przez wyłączniki zwierające segmenty cyfr do masy. Podobnie z tranzystorami kluczami – również i te są narysowane jako wyłączniki. Na rysunku, wyświetlacz numer 1 pokazuje cyfrę 2, natomiast wyświetlacz numer 2 jest wyłączony. Wyłącznik reprezentujący T1 jest zamknięty (co odpowiada przepływowi prądy w kanale tranzystora, pomiędzy elektrodami D-S), na wspólną anodę cyfr podawane jest napięcie, kombinacja portu wyjściowego mikrokontrolera odpowiada cyfrze 2.

 


Rysunek 2. Zasada działania wyświetlacza multipleksowanego (kliknij, aby powiększyć).

Zamknięcie wyłącznika numer T2 i otwarcie T1 spowoduje, że cyfra 2 „przeniesie się” na wyświetlacz 2. W praktyce najczęściej stosuje się takie rozwiązanie, że podczas „przenoszenia” cyfry jest ona gaszona. Czyli cała sekwencja konieczna do wyświetlenia dwóch cyfr wygląda tak:

1.  ustaw stan portu właściwy cyfrze numer 1
2.  załącz cyfrę numer 1
3.  opóźnienie 20ms
4.  zgaś cyfrę numer 1
5.  opóźnienie 1ms
6.  ustaw stan portu właściwy do cyfry numer 2
7.  załącz cyfrę 2
8.  opóźnienie 20ms
9.  zgaś cyfrę 2
10. opóźnienie 1ms
11. powtarzaj od punktu 2

Czas świecenia i zgaszenia cyfry będzie wpływać na czytelność wyświetlacza, dobrze jest go dobrać eksperymentalnie tak, aby uzyskać pewien kompromis pomiędzy jasnością świecenia a pojawianiem się niepożądanych efektów w czasie wyświetlania. Z mojej praktyki wynika, że czas świecenia powinien wynosić co najmniej 20 ms a czas zgaszenia około 1...3 ms. Teraz program.

Opis działania programu.

Na początku programu umiściłem definicje znaków, tablic, linii podłączenia segmentów i bramek tranzystorów kluczy. Jeśli układ zbudowany będzie jak na moim schemacie, to obojętne czy będziemy chcieli załączyć segment, czy też cyfrę – stanem aktywnym będzie logiczne „0”. W takiej sytuacji należy wyzerować tylko bit stałej odpowiedzialny za określoną akcję, reszta bitów ma stan logicznej „1”. Oto jak załączane są poszczególne cyfry:

#define  PortSW    P1      //port, gdzie dołączone tranzystory kluczy
#define  LED_1     0xFE    //kody załączenia cyfr wyświetlacza
#define  LED_2     0xFD
#define  LED_3     0xFB
#define  LED_off   0xFF

PortSW, to definicja portu, do którego są dołączone bramki tranzystorów MOS (ewentualnie bazy tranzystorów bipolarnych) tj. kluczy załączających cyfry. Przypisanie PortSW = LED_1 załącza pierwszą cyfrę, PortSW = LED_2 - drugę, PortSW = LED_3 - trzecią, natomiast PortSW = LED_off powoduje ich wyłączenie. Dalej znajduje się funkcja Delay. Jest to rodzaj pętli zatrzymującej mikrokontroler na wielokrotność jednej milisekundy podaną jako parametr wywołania. Korzystając z trzech cyfr można wyświetlić liczby od 0 do 999. Wynika z tego, że należy wybrać ze zmiennej do wyświetlania poszczególne wagi dziesiętne, odpowiednio do wyświetlanych pozycji. I dopiero waga może stanowić indeks tablicy zawierającej wzorce wyświetlanych znaków. Aby tego dokonać, niezbędna jest konwersja liczb szesnastkowych (binarnych) na dziesiętne.
Operacja zamiany liczby na jej wagi to tak zwana konwersja BCD. Podczas niej liczba szesnastkowa jest zamieniana na dziesiętną. Napisałem funkcję, która wybiera liczbę odpowiednią do danej wagi. Oczywiście można zrobić to inaczej, jednak przyjrzyjmy się użytej metodzie i jej implementacji w postacji mojej funkcji. Być może nie jest ona najbardziej efektywna, ale za to czytelna i dobrze posłuży jako ilustracja przykładu.

//funkcja zwraca cyfrę będącą na pozycji POSITION, to jest de facto konwersja HEX
//na równoważne BCD
char Subnumber(unsigned int number, char position)
{
    switch (position)
    {
        case 1:

            return(number / 100);  //cyfra 1 - zwróć część całkowitą
            break;                 //z dzielenia przez 100
        case 2:
            number %= 100;         //cyfra 2 - dzielenie całkowite
                                   //przez 10 reszty z return(number / 10);

                                   //dzielenia przez 100
            break;
        case 3:
            return(number % 10);   //cyfra 3 - reszta z dziel. przez 10

            break;
        default:
            return(0);             //przypadek zabroniony
            break;
    }
}

Funkcja konwersji działa w taki sposób, że zwraca sobą wagę zmiennej number znajdującej się na pozycji position. Liczba poddawana konwersji jest liczbą całkowitą, bez znaku, typu usigned int z zakresu 0...999. Parametr position może przyjmować wartość od 1 do 3, gdzie 1 oznacza pozycję najstarszą, a 3 najmłodszą.
Do sterowania konwersją BCD zastosowałem klauzulę switch/case. Switch to określenie, która zmienna będzie rozpatrywana (w naszym wypadku position). Case funkcjonuje tak, jak etykieta warunku. Do jednego polecenia case można przypisać jeden warunek. Nic nie stoi jednak na przeszkodzie, aby polecenia case tworzyły pewną listę warunków do rozpatrzenia. Jeśli brakuje polecenia break, mikrokontroler po rozpatrzeniu jednego warunku przechodzi do następnego. Break powoduje zakończenie przeglądania warunków i przejście do wykonywania instrukcji znajdujących się bezpośrednio za konstrukcją switch. W wypadku naszej funkcji, to czysta strata czasu, toteż każdy warunek zawiera polecenie break. Do dobrego stylu programowania należy, aby co najmniej ostatnia instrukcja zawierała break.
Konwersja BCD jest wykonywana w miarę potrzeb. Jeśli np. chcę wyświetlić cyfrę z pozycji pierwszej (position = 1) odpowiadającej wadze setek, wówczas wystarczy, że podzielę liczbę przez 100 i funkcja odrzucając resztę z dzielenia zwróci tylko część całkowitą. Kompilator C ma operator dzielenia całkowitego, więc operacja taka nie przedstawia żadnej trudności. Wystarczające jest użycie instrukcji return (number / 100). Podobnie jasną sytuację spotkamy na pozycji 3 wyświetlacza odpowiadającej wadze dziesiątek (position = 3). Tu badam resztę z dzielenia przez 10. Również i taki przypadek uwzględniają operatory języka C Wystarczy instrukcja return (number % 10). Najtrudniejszą jest sytuacja podczas konwersji na pozycji 2, odpowiadającej wadze dziesiątek. Tu najpierw wyliczam resztę z dzielenia przez 100 i wstawiam ją do zmiennej number %= 100. Dla przykładu: niech zmienna number = 576, czyli reszta z dzielenia przez 100, to 76. Dalej sytuacja jest prosta – wystarczy całkowite dzielenie przez 10 i instrukcja return (number / 10). Dla większej liczby cyfr wyświetlacza wymyśliłbym jakąś inną, bardziej uniwersalną metodę, jednak dla 3 wyświetlanych pozycji taka funkcja w zupełności wystarczy. Wywołuj ją zawsze w postaci Subnumber (liczba_do_konwersji, pozycja_wyświetlacza).
Nieskomplikowany program sterujący multipleksowaniem umieściłem na listingu 1. Waga wyodrębniona z liczby jest indeksem tablicy zawierającej wzorce znaków. Z tablicy umieszczonej w ROM pobierany jest wzorzec, który wyprowadzany jest przez port. Również z analizą fragmentu programu odpowiedzialnego za multipleksowanie cyfr nie powinno być żadnego kłopotu. Przyjrzyjmy się jego najważniejszej części pokazanej na listingu 1.

Listing 1. Ilustracja zasady działania multipleksowania podczas wyświetlania

while (1)                                        //pętla nieskończona
{

    for (loop = 0; loop < 30; loop++)
    {
        PortLED = Digits[Subnumber(counter,3)];  //zapis kodu cyfry numer

                                                 //3 do portu
        PortSW = LED_3;                          //załączenie wyświetlania cyfry 3
        Delay(20);                               //opóźnienie około 20ms
        PortSW = LED_off;                        //wyłączenie wyświetlania

        PortLED = Digits[Subnumber(counter,2)];  //zapis kodu cyfry numer
                                                 //2 do portu
        PortSW = LED_2;                          //załączenie wyświetlania cyfry 2
        Delay(20);                               //opóźnienie około 20ms
        PortSW = LED_off;                        //wyłączenie wyświetlania

        PortLED = Digits[Subnumber(counter,1)];  //zapis kodu najstarszej
                                                 //cyfry do portu
        PortSW = LED_1;                          //załączenie cyfry numer 1
        Delay(20);                               //opóźnienie około 20ms
        PortSW = LED_off;                        //wyłączenie wyświetlania
    }
    counter++;                                   //zwiększenie licznika o 1
}

Użycie pętli while(1)jest konieczne, aby program wykonywał się bez końca. Mikrokontroler nie ma systemu operacyjnego takiego jak DOS czy Windows, który przejmie kontrolę po zakończeniu wykonywania programu. Musimy spowodować, aby program nigdy się nie skończył lub mikrokontroler został wyłączony po realizacji wszystkich poleceń. Inaczej zdarzy się tak, że będą wykonywane instrukcje znajdujące się w pamięci tuż za funkcją main(),niekoniecznie mające jakiś sens... Zmienna counter to „coś” do wyświetlenia, do demonstracji faktu, że program działa. Zmienia się ona od wartości 0 do końca swego zakresu, co 30 przebiegów pętli wyświetlającej. Konwersja na kod wyświetlacza po napisaniu funkcji zamiany liczby na odpowiednią wagę dziesiętną jest nieskomplikowana i polega na przypisaniu bitom portu wyjściowego wartości odpowiedniego wzorca z tablicy Digits. Realizuje to polecenie PortLED = Digits[Subnumber(counter,3)]. Zgodnie z tym, co pisałem wcześniej, cyfra jest zaświecana na okres około 20 milisekund. Czas jej świecenia ustala instrukcja polecenie Delay(20).
Na listingu 2 zamieściłem być może nieco abstrakcyjny przykład realizacji multipleksowanego wyświetlacza. Należy traktować go jak pomysł, ideę, wytłumaczenie zasady działania i inspirację do samodzielnych poszukiwań. Do tematu wyświetlania wrócę również w następnym przykładzie. Tam pokażę jak można do multipleksowania wyświetlacza wykorzystać Timer odmierzający czas i generowane przez niego przerwanie. Potem zajmiemy się jeszcze wyświetlaczem LCD. Wyświetlaniu poświecimy sporo uwagi, jest to chyba najważniejszy z elementów tak zwanego interfejsu użytkownika. Niestety, dla przeważającej ilości aplikacji interfejs użytkownika będzie stanowił 80% Twojego wysiłku włożonego w przygotowanie aplikacji.

Listing 2. Przykład realizacji multipleksowanego wyświetlacza – 3 siedmiosegmentowe cyfry LED bez użycia przerwań.

/* prosty program demonstracyjny "obsługa 3 cyfr led"
   cyfry LED podłączona do portu P0, stan aktywny = L,
   załączanie cyfr przy pomocy kluczy BS250 podłączonych do P1
   rezonator kwarcowy 8MHz
   seg.A = P0.4
   seg.B = P0.3
   seg.C = P0.6
   seg.D = P0.2
   seg.E = P0.5
   seg.F = P0.1
   seg.G = P0.0
   seg.dp = P0.7
   załączenie cyfry 1 = P1.2
   załączanie cyfry 2 = P1.1
   załączanie cyfry 3 = P1.0 */

#pragma SMALL       //wybór modelu pamięci

#include <reg51.h>  //definicja rejestrów mikrokontrolera
#define  PortLED  P0  //port, do którego są dołączone segmenty LED
#define  PortSW   P1  //port,
do którego są dołączone tranzystory klucze

#define  _A  0xEF     //definicje segmentów cyfry
#define  _B  0xF7
#define  _C  0xBF
#define  _D  0xFB
#define  _E  0xDF
#define  _F  0xFD
#define  _G  0xFE
#define  _H  0x7F

#define  LED_1   0xFE //kody załączenia cyfr wyświetlacza
#define  LED_2   0xFD
#define  LED_3   0xFB
#define  LED_off 0xFF

//definicje wyglądu cyfr od 0 do 9
char code Digits[10] = {_A & _B & _C & _D & _E & _F,      //0
                        _B & _C,                          //1
                        _A & _B & _D & _E & _G,           //2
                        _A & _B & _C & _D & _G,           //3
                        _B & _C & _F & _G,                //4
                        _A & _C & _D & _F & _G,           //5
                        _A & _C & _D & _E & _F & _G,      //6
                        _A & _B & _C,                     //7
                        _A & _B & _C & _D & _E & _F & _G, //8
                        _A & _B & _C & _D & _F & _G };    //9

//opóźnienie około 1 milisekundy dla kwarcu 8MHz
void Delay(unsigned int time)
{
    unsigned int j;  //pętla FOR jest powtarzana TIME razy
    while (time-- >= 1) for (j=0; j<65; j++);
}

//funkcja zwraca cyfrę będącą na pozycji POSITION, to jest de facto konwersja HEX
//na równoważne BCD
char Subnumber(unsigned int number, char position)
{
    switch (position)
    {
        case 1:
            return(number / 100); //cyfra 1 - zwróć część całkowitą
            break;                //z dzielenia przez 100
        case 2:
            number %= 100;        //cyfra 2 - dzielenie całkowite
                                  //przez 10 reszty z
            return(number / 10);  //dzielenia przez 100
            break;
        case 3:
            return(number % 10);  //cyfra 3 - reszta z dziel. przez 10
            break;
        default:
            return(0);            //przypadek zabroniony
            break;
    }
}

//początek programu głównego
void main(void)
{
    unsigned int loop, counter;   //zmienne pętli i licznika zgodnie
                                  //ze specyfikacją standardu wartość
                                  //inicjująca wynosi 0
    while (1)                     //pętla nieskończona

    {
        for (loop = 0; loop < 30; loop++)
        {
            PortLED = Digits[Subnumber(counter,3)]; //zapis kodu cyfry
                                                    //numer 3 do portu
            PortSW = LED_3;      //załączenie wyświetlania cyfry 3
            Delay(20);           //opóźnienie około 20ms
            PortSW = LED_off;    //wyłączenie wyświetlania
            PortLED = Digits[Subnumber(counter,2)]; //zapis kodu cyfry
                                 //numer 2 do portu
            PortSW = LED_2;      //załączenie wyświetlania cyfry 2
            Delay(20);           //opóźnienie około 20ms
            PortSW = LED_off;    //wyłączenie wyświetlania
            PortLED = Digits[Subnumber(counter,1)];//zapis kodu
                                 //najstarszej cyfry do portu
            PortSW = LED_1;      //załączenie cyfry numer 1
            Delay(20);           //opóźnienie około 20ms
            PortSW = LED_off;    //wyłączenie wyświetlania
        }
        counter++;               //zwiększenie licznika o 1
    }
}

 

Jacek Bogusz
j.bogusz@easy-soft.net.pl

 

http://www.tomaszbogusz.blox.pl/

Dodaj nowy komentarz

Zawartość pola nie będzie udostępniana publicznie.