• Email
  • Forum

Wyświetlacze 7SEG LED - multipleksowanie i nie tylko

[Rozmiar: 37989 bajtów]

Zacznijmy od podstaw. Jeśli ktoś zna to przewinie.

Wyprowadzenia pojedynczego wyświetlacza najczęściej wyglądają tak jak na poniższym obrazku.

[Rozmiar: 6507 bajtów]

Na początek podłączyłem jeden wyświetlacz do mikrokontrolera Atmega32. Kolejne wyprowadzenia wyświetlacza podłączyłem następująco:

A - PORTA.0
B - PORTA.1
C - PORTA.2
D - PORTA.3
E - PORTA.4
F - PORTA.5
G - PORTA.6
DP- PORTA.7

Ważne by ograniczyć prąd na diodach wstawiając pomiędzy wyprowadzenia mikrokontrolera a piny wyświetlacza rezystory ograniczające ten prąd.

Teraz kiedy się przyjrzymy widać, że żeby wyświetlać cyfrę 1 należy włączyć segmenty B i C. Żeby wyświetlić 0 należy włączyć segmenty A,B,C,D,E i F.
Wyświetlacze to zestaw diod połączonych ze sobą. Mogą być połaczone ze sobą Anodami. Wtedy do wyprowadzenia wyświetlacza o oznaczeniu COM podłączamy plus zasilania. Włączenie poszczególnych segmentów polega wtedy na podaniu zera (masy) na wymagane wyprowadzenia.
Dlatego w PORTA musimy na tych pinach ustawiać stan niski i ustawienia dla tej konfiguracji (mojego podłączenia) wyglądają następująco:

'         *GFEDCBA
Porta = &B11000000                                          '0
Porta = &B11111001                                          '1
Porta = &B10100100                                          '2
Porta = &B10110000                                          '3
Porta = &B10011001                                          '4
Porta = &B10010010                                          '5
Porta = &B10000010                                          '6
Porta = &B11111000                                          '7
Porta = &B10000000                                          '8
Porta = &B10010000                                          '9

Żeby wyświetlić cyfrę jeden ustawiam stan niski na PORTA.1 i PORTA.2
W zapisie dwójkowym "liczymy" niejako od prawej strony, dlatego w zapisie cyfry 1 dwa zera znajdują się po prawej, licząc od zera, na pierwszym i drugim miejscu. Całkiem z brzegu jest pozycja zero.

Teraz podająć plus na wyprowadzenie COM możemy wyświetlić sobie każdą cyfrę ustawiając PORTA wartością z tabelki

Czas trochę zautomatyzować procedurę zmiany wyświetlanych cyferek.
Można je umieścić w specjalnej tabeli. Kiedy mikrokontroler będzie musiał wyświetlić cyfrę zajrzy do tabeli by pobrać ustawienia dla PORTA.

Poniższy kod co sekundę wyświetla kolejną cyfrę od 0 do 9 w kółko

$regfile = "m32def.dat"
$crystal = 8000000
$hwstack = 80
$swstack = 64
$framesize = 64

Config Porta = Output

 Dot Alias Porta.7       'przyjazna nazwa dla kropki, pin wydzielony z całego portu

Dim Cyfra As Byte

' TE ZAPISY ZOSTAŁY PRZENIESIONE DO TABELI "Znaki"

'Porta = &B11000000                                          '0
'Porta = &B11111001                                          '1
'Porta = &B10100100                                          '2
'Porta = &B10110000                                          '3
'Porta = &B10011001                                          '4
'Porta = &B10010010                                          '5
'Porta = &B10000010                                          '6
'Porta = &B11111000                                          '7
'Porta = &B10000000                                          '8
'Porta = &B10010000                                          '9

Do

 Porta = Lookup(cyfra , Znaki)

  Incr Cyfra
   If Cyfra > 9 Then Cyfra = 0

  Wait 1

Loop
End

Znaki:
Data &B11000000 , &B11111001 , &B10100100 , &B10110000 , &B10011001
Data &B10010010 , &B10000010 , &B11111000 , &B10000000 , &B10010000

Dwa wyświetlacze...

Skoro czytasz ten temat to pewnie wiesz, że można sterować większa ilością wyświetlaczy używając tych samych wyprowadzeń mikrokontrolera. Dołączając kolejny wyświetlacz piny A,B,C,D,E,F,G i DP podłączamy równolegle do tych samych pinów, tak samo jak pierwszy wyświetlacz. Jedynie wyprowadzenia COM będziemy musieli zacząć sterować za pomocą kolejnych pinów mikrokontrolera. Będziemy je uruchamiać naprzemiennie. W jednej chwili tylko jeden wyświetlacz będzie aktywny, ale zmiany będą zachodziły tak szybko, że nasze oko będzie widziało jakby świeciły wszystkie wyświetlacze

Teraz trzeba przypomnieć sobie, że ludzkie oko widzi bezwładnie przy zmianie obrazu szybszym niż 50 klatek na sekundę. Co w drugą stronę oznacza, że potrafi dostrzec mruganie jeśli coś się dzieje wolniej...
Musimy więc przełączać te wyświetlacze minimum 50 razy na sekundę. 50 razy na sekundę to nic innego jak 50Hz
Do odmierzania czasu najlepiej użyć Timera. Napiszę kiedyś osobny artykuł na ich temat. Na razie przedstawię podstawowe obliczenia.
Jeśli mikrokontroler taktujemy wewnętrznym oscylatorem (ten wybrałem żeby każdy mógł spróbować) to :
Wyświetlacze mamy dwa. Mnożymy 50Hz x 2 = 100Hz
8MHz = 8 000 000 Hz, Podzielone Prescalerem 1024 = 7812,5 , To dzielimy przez 100 i otrzymujemy wynik 78,125 .
Od takiego wyniku odejmujemy jeszcze jeden i mamy gotową wartość jaką Timer musi odliczyć żeby zgłosić się 100 razy na sekundę.

Teraz zejdźmy na Ziemię. Umiejętność policzenia sobie czasów się na pewno przydaje, ale komputery stworzono dlatego, że potrafią wykonywać działania powtarzalnie bez błędów. Proponuję użyć jakiegoś kalkulatora.

[Rozmiar: 62588 bajtów]

Jak widać obliczenia się zgadzają. Możemy więc skonfigurować Timer by odmierzał 10ms ;)
Skonfigurujemy dwa piny które będą włączać na przemian pierwszy lub drugi wyświetlacz.
Żeby prościej było wytłumaczyć jak to działa będę wyświetlał dwie stałe cyfry - 1 i 2.
Program działa tak: Kiedy Timer zgłasza, że pora właczyć kolejny wyświetlacz to wyłączam poprzedni, ustawiam PORTA wartością która ma się pokazać na drugim wyświetlaczu i dopiero włączam ten wyświetlacz. Piny W1 i W2 to piny odpowiedzialne za włączanie wyświetlaczy. Najczęściej sterują tranzystorami które podają plus na wyprowadzenia COM wyświetlaczy.

$regfile = "m32def.dat"
$crystal = 8000000
$hwstack = 80
$swstack = 64
$framesize = 64

Config Porta = Output

 Dot Alias Porta.7            'przyjazna nazwa dla kropki, pin wydzielony z całego portu

Config Portc.3 = Output : Set Portc.3 : W2 Alias Portc.3
Config Portc.2 = Output : Set Portc.2 : W1 Alias Portc.2

Dim Cyfra As Byte , Mux As Byte , Timer_flag As Byte

Config Timer0 = Timer , Prescale = 1024 , Compare = Disconnect , Clear Timer = 1
Enable Compare0 : On Compare0 Timer0_isr : Compare0 = 77  '100Hz @8MHz

Enable Interrupts

Do

 If Timer_flag = 1 Then             'jeśli Timer zglosił że odliczył 77
     Timer_flag = 0                 'skasuj flage 

     Incr Mux                       'zwieksz o jeden numer wyswietlacza
   If Mux > 2 Then Mux = 1          'na razie mamy tylko 1 lub 2

   Select Case Mux  'na podstawie wartości zmiennej Mux wybierz którym wyświetlaczem się zajmiemy

    Case 1
       Set W1                       'wylącz poprzedni wyświetlacz'
        Porta = Lookup(1 , Znaki)   'ustaw PORTA wartością dla drugiego wyswietlacza
         Reset W2                   'teraz go wlacz

    Case 2                          'jeśli pora na drugi wyswietlacz
                                    'to powyższe operacje wykonaj dla tego wyswietlacza
       Set W2
        Porta = Lookup(2 , Znaki)   'szukaj w tabeli wartości by wyswietlic cyfrę "2"
         Reset W1

   End Select


 End If

Loop
End


Timer0_isr:                         'Timer zgłasza tutaj że odliczył 77 (z prescalerem 1024)

  Timer_flag = 1

Return

Znaki:
Data &B11000000 , &B11111001 , &B10100100 , &B10110000 , &B10011001
Data &B10010010 , &B10000010 , &B11111000 , &B10000000 , &B10010000

Jak widac udało się za pierwszym podejściem :D

[Rozmiar: 86271 bajtów]

Idźmy dalej...

Jeśli chcemy uruchomić 4 wyświetlacze to musimy przyspieszyć odświeżanie bo kiedy zgasilibyśmy pierwszy wyświetlacz i dalej robili zmiany co 10ms to do 4 wyświetlacza doszlibyśmy dużo później..
Najprościej pomnożyć 50Hz razy ilość wyświetlaczy 50Hz x 4 = 200Hz i dla takiej częstotliwości robimy obliczenia.

Config Timer0 = Timer , Prescale = 256 , Compare = Disconnect , Clear Timer = 1
Enable Compare0 : On Compare0 Timer0_isr : Compare0 = 155  '200Hz @8MHz

To będzie działać na pewno więc możemy zając się inną sprawą.
Taki wyświetlacz z czterema polami najczęściej przedstawia jakąś liczbę. Żeby ją poprawnie wyświetlić trzeba obliczyć wartości dla każdego wyświetlacza.
Jak wiadomo do bajtu zapisać możemy jedynie 255. Taka czterocyfrowa liczba ma na pewno więcej więc musi być zapisana co najmniej w zmiennej typu Word lub Integer. Tutaj jest pierwsza pułapka czekająca na nieuważnego programistę.
Żeby otrzymać wartości dla poszczególnych wyświetlaczy musimy tę dużą liczbę podzielić. Nie można jednak wyniku dzielenia zmiennej typu Word zapisywać bezpośrednio do zmiennej typu Byte. Należy użyć zmiennej pomocniczej tego samego typu do której zapisujemy wynik, a dopiero potem rzutować wartość na zmienną typu Byte.
Pora na kolejne usprawnienie. Zamiast zadeklarować cztery osobne bajty przechowujące wartości dla poszczególnych wyświetlaczy możemy zadeklarować tablice czterech bajtów. Przyda się i przy obliczaniu wartości i przy samym multipleksowaniu.
Na wszelki wypadek napisze co to jest Modulo
Jest to reszta z dzielenia przez wartość która stoi za wyrażeniem Mod
Przykład 1 : Wynik = 5 Mod 2 Wynikiem będzie jeden, bo pięć podzielone przez dwa to dwa, ale reszta to jeden.
Przykład 2 : Wynik = 12 Mod 10 Wynikiem będzie dwa, bo 12 podzielone przez dziesięć to jeden , ale reszta to dwa.

Tym razem użyjemy nawet kropki ;)

$regfile = "m32def.dat"
$crystal = 8000000
$hwstack = 80
$swstack = 64
$framesize = 64

Config Porta = Output

 Dot Alias Porta.7    'przyjazna nazwa dla kropki, pin wydzielony z całego portu

Config Portc.5 = Output : Set Portc.5 : W4 Alias Portc.5
Config Portc.4 = Output : Set Portc.4 : W3 Alias Portc.4
Config Portc.3 = Output : Set Portc.3 : W2 Alias Portc.3
Config Portc.2 = Output : Set Portc.2 : W1 Alias Portc.2

Config Timer0 = Timer , Prescale = 256 , Compare = Disconnect , Clear Timer = 1
Enable Compare0 : On Compare0 Timer0_isr : Compare0 = 155  '200Hz @8MHz

Dim Liczba As Word , Help As Word , Wysw(4) As Byte , N As Byte
Dim Cyfra As Byte , Mux As Byte , Timer_flag As Byte

Enable Interrupts

Liczba = 1234  'przykładowa wartosc by rozróżnić wyswietlacze

For N = 1 To 4
 Help = Liczba Mod 10                                       'zapis do pomocniczej zmiennej
  Wysw(n) = Help                                            'rzutowanie Word na Byte
   Liczba = Liczba / 10
Next N


Do

 If Timer_flag = 1 Then
     Timer_flag = 0

     Incr Mux
   If Mux > 4 Then Mux = 1

   Select Case Mux

    Case 1
       Set W4
        Porta = Lookup(wysw(mux) , Znaki)                   'załaduj do PORTA wartość dla wyswietlacza o numerze -
         Reset W1                                           ' - aktualnej wartosci zmiennej MUX

    Case 2

       Set W1
        Porta = Lookup(wysw(mux) , Znaki)
         Reset W2
          Reset Dot  

    Case 3
       Set W2
        Porta = Lookup(wysw(mux) , Znaki)
         Reset W3
         
    Case 4

       Set W3
        Porta = Lookup(wysw(mux) , Znaki)
         Reset W4


   End Select

 End If

Loop
End

Timer0_isr:

  Timer_flag = 1

Return

Znaki:
Data &B11000000 , &B11111001 , &B10100100 , &B10110000 , &B10011001
Data &B10010010 , &B10000010 , &B11111000 , &B10000000 , &B10010000

Znowu chyba udało się za pierwszym podejściem ;)

[Rozmiar: 101533 bajtów]

Podnieśmy poprzeczkę ;)

Każdy kto zbudował coś na tych wyświetlaczach wie, że w nocy strasznie "dają po oczach" czyli świecą za jasno. Zegarek oświetliłby pół sypialni :D
Przydałaby się możliwość ściemniania. Oczywiście taka istnieje i to w postaci regulacji czasu świecenia wyświetlaczy.
Zabierając się za budowę warto przemyśleć który Timer użyjemy. Najlepiej taki trick wychodzi kiedy Timer ma dwa rejestry porównania COMPAREA i COMPAREB, ale nawet jeśli ma jeden to nic straconego.
Możemy Timer "puścić" do końca i w przerwaniu generowanym kiedy Timer się przepełnia multipleksować i włączać wyświetlacze. Do tego kiedy uaktywnimy również przerwanie kiedy wartość Timera jest zgodna z rejestrem porównania mamy możliwośc wcześniejszego wyłączenia wyświetlacza... Hmmm jak to działa?
Włączam wyświetlacz kiedy Timer zgłasza przepełnienie, ale wyłączam go już kiedy Timer osiągnie wartość zapisaną w rejestrze COMPARE0. Dodałem też zmianę wartości liczby by coś się działo na wyświetlaczach.

$regfile = "m32def.dat"
$crystal = 8000000
$hwstack = 80
$swstack = 64
$framesize = 64

Config Porta = Output

 Dot Alias Porta.7         'przyjazna nazwa dla kropki, pin wydzielony z całego portu

Config Portc.5 = Output : Set Portc.5 : W4 Alias Portc.5
Config Portc.4 = Output : Set Portc.4 : W3 Alias Portc.4
Config Portc.3 = Output : Set Portc.3 : W2 Alias Portc.3
Config Portc.2 = Output : Set Portc.2 : W1 Alias Portc.2

Config Timer0 = Timer , Prescale = 64 , Compare = Disconnect

Enable Timer0 : On Timer0 Timer0_isr Nosave
Enable Compare0 : On Compare0 Compare0_isr Nosave


Dim Liczba As Word , Help As Word , Wysw(4) As Byte , N As Byte
Dim Cyfra As Byte , Mux As Byte , Timer_flag As Byte
Dim Ticks As Byte , Copy As Word , Brightness As Byte
Dim Up_down As Byte


Enable Interrupts

Liczba = 1234                                               'przykładowa wartosc by rozroznic wyswietlacze
Brightness = 255


Do

 If Timer_flag = 1 Then
     Timer_flag = 0

     Incr Mux
   If Mux > 4 Then Mux = 1

   Select Case Mux

    Case 1
       Set W4
        Porta = Lookup(wysw(mux) , Znaki)                   'załaduj do PORTA wartość dla wyswietlacza o numerze -
         Reset W1                                           ' - aktualnej wartosci zmiennej MUX

    Case 2

       Set W1
        Porta = Lookup(wysw(mux) , Znaki)
         Reset W2

    Case 3
       Set W2
        Porta = Lookup(wysw(mux) , Znaki)
         Reset W3

    Case 4

       Set W3
        Porta = Lookup(wysw(mux) , Znaki)
         Reset W4


   End Select

   Incr Ticks
    If Ticks = 100 Then
        Ticks = 0

        If Liczba > 0 Then
         Decr Liczba
          Copy = Liczba

         For N = 1 To 4
          Help = Copy Mod 10                                'zapis do pomocniczej zmiennej
           Wysw(n) = Help                                   'rzutowanie Word na Byte
            Copy = Copy / 10
         Next N

        End If


        If Up_down = 0 Then
         If Brightness > 1 Then
          Decr Brightness                                   'zmniejszaj jasnośc do 1
         Else
          Up_down = 1
         End If
        Else
         If Brightness < 254 Then
          Incr Brightness                                   'zwiekszaj jasność do 254
         Else
          Up_down = 0
         End If
        End If

        Compare0 = Brightness                               'wpisz wartosc do rejestru porównania Timer0


    End If

 End If

Loop
End

Compare0_isr:

  PUSH R23
  PUSH R24
  !in R24, sreg
  PUSH  R24

  Porta = 255
   'Tuned by NoSave Tool
  POP  R24
  !out sreg, r24
  Pop R24
  POP R23

Return


Timer0_isr:

  PUSH R24
  !in R24, sreg
  PUSH  R24

  Timer_flag = 1
   'Tuned by NoSave Tool
  POP  R24
  !out sreg, r24
  POP R24

Return

Znaki:
Data &B11000000 , &B11111001 , &B10100100 , &B10110000 , &B10011001
Data &B10010010 , &B10000010 , &B11111000 , &B10000000 , &B10010000

Poniżej filmik. W rzeczywistości wygląda to dużo lepiej.
Po prostu kamera telefonu kiepsko sobie radzi z tym światłem. Można to połączyć z fotorezystorem i wyświetlacz sam będzie regulował swoją jasność.

Na końcu możesz sięgnąć po przewijanie napisów na sześciu wyświetlaczach ;)

...ale to nie wszystko o czym chciałem napisać.



Shift Registers czyli rejestry przesuwne

Układy te są tak proste w obsłudze i tak tanie, że można rozważyć ich użycie pod każdym wyświetlaczem znacznie odciążając mikrokontroler


[Rozmiar: 376862 bajtów]

Kod który steruje tym rejestrem ma dosłownie cztery linijki. Reszta to konfiguracja.
W kodzie podane są nawet piny do których należy się podłączyć.

$regfile = "m32def.dat"
$crystal = 8000000

Dim B As Byte
'-----------------------74HC595
Config Porta = Output

Ds_ser Alias Porta.0                                        'Pin (14)
                                                            'Pin (13) connect to Ground (when High then outputs Hi-Z)
Stcp_rclk Alias Porta.1                                     'Pin (12)
Shcp_srclk Alias Porta.2                                    'Pin (11)
Mr_srclr Alias Porta.3                                      'Pin (10)

'opcjonalne czyszczenie rejestru po włączeniu zasilania
Reset Mr_srclr
Set Mr_srclr

 B = 1

Do

   Shiftout Ds_ser , Shcp_srclk , B  'wyslij wartosc bajtu B
   Set Stcp_rclk                     'zatwierdź
   Reset Stcp_rclk

   Waitms 50

   Rotate B , Right , 1              'przesun bit w bajcie B

Loop
End

Email

Jeśli mogę w czymś pomóc, napisz.