piątek, 11 marca 2011

LCD HD44780 na 3 pinach z 74HC595


Autor: Sheep94
Redakcja + dodatki: dondu

Na pewno każdy z nas tworząc swój projekt spotkał się z problemem brakujących pinów procesora. Przykładowo - podłączyliśmy już termometry, klawiaturę matrycową (dzięki której już zaoszczędziliśmy kilka pinów), kilka diod LED informujących nas o stanie pracy mikrokontrolera ...

... ale musimy jeszcze podpiąć ten nieszczęsny wyświetlacz do procesora, który potrzebuje, aż 6 pinów lub nawet 7 (gdy podłączymy linię RW do wyświetlacza w celu odczytania jego zawartości).

Postawiłem zatem pytanie:

Jak obsłużyć wyświetlacz używając mniejszej ilości pinów procesora?

Przyszło mi na myśl rozwiązanie, by wykorzystać do obsługi wyświetlacza rejestr przesuwny 74HC595, do którego wysyłać będziemy szeregowo informację (8 bitów), a po wysłaniu wszystkich bitów każemy mu ustawić odebraną wartość na swoich wyjściach.

Zalety:
  • zyskujemy co najmniej 3 piny procesora, które możemy przeznaczyć na inne działanie.

Wady:
  • wzrost objętości programu (~200 bajtów, w porównaniu do obsługi wyświetlacza za pomocą 6 pinów),
  • konieczność umieszczenia dodatkowego układu scalonego na płytce (między mikrokontrolerem a wyświetlaczem musimy umieścić rejestr 74HC595) + jego koszt (0,60zł w 2012r.).

Krótkie objaśnienie zasady działania rejestru 74HC595.


Datasheet: 74HC595, 74HCT595

Rejestr sterowany jest za pomocą 3 pinów:
  • DS - (data serial input) za pomocą którego odbiera wysłane szeregowo bity do ukrytego rejestru SHIFT REGISTER.
  • SHCP - pin, który podczas zbocza narastającego (przejście ze stanu niskiego w stan wysoki) zapamiętuje w buforze pamięci stan pinu DS i przesuwa informację w SHIFT REGISTER, o jeden bit w celu ustąpienia miejsca następnej porcji informacji. Stan niski (0) musi nastąpić przed wprowadzeniem kolejnych bitów.
  • STCP - pin jest w stanie niskim podczas wprowadzania danych do SHIFT REGISTER  Natomiast zbocze narastające powoduje pojawienie się zawartości rejestru SHIFT REGISTER do rejestru STATE OUTPUTS. Jeżeli sygnał OE jest w stanie niskim, to informacja ta od razu pojawi się  na pinach Q0 - Q7.

Układ posiada 8 wyjść, do których możemy podłączyć dowolne urządzenie w standardzie TTL.

Musimy znać jeszcze przeznaczenie pozostałych pinów:
  • OE - (Ouput Enable) - musi występować stan niski (0), by móc sterować rejestrem. Możemy spokojnie podłączyć w naszym projekcie na stałe do masy.
  • MR  (Master Reset) - musi występować stan wysoki (1), by rejestr nie był w stanie ciągłego resetu. Możemy spokojnie podłączyć w naszym projekcie na stałe do VCC.
  • Q7S - serial data output - w momencie wprowadzania 9 bitu informacji na wyjściu Q7S pojawia się bit informacji wprowadzony jako pierwszy. Dzięki temu sygnałowi możemy podłączyć go do pinu DS kolejnego rejestru przesuwnego, by ułożyć kaskadowo kolejne rejestry. Tym sposobem możemy za pomocą 3 pinów obsłużyć znacznie więcej pinów urządzeń (wielokrotność 8 bitów), które nie wymagają odczytu stanu logicznego (linijka LED, wyświetlacz 7-segmentowy, itp.).

Jaki problem napotkałem podczas pisania tego rozwiązania?

Wszyscy wiemy, że wyświetlaczem możemy sterować w trybie wykorzystującym tylko 4 bitów danych (DB4-DB7). Natomiast podczas obsługi wyświetlacza musimy mu „pokazać”, czy wprowadzamy komendy (stan niski na linii RS), czy wprowadzamy dane do wyświetlenia (stan wysoki linii RS). Do tego, by przesłać młodszą część bajtu, musimy wysterować odpowiednio linię E.


Przykład: HY-1602

Tu powstaje pytanie:

Jak sterować poszczególnymi wyjściami Qn rejestru 74HC595, nie zmieniając jednocześnie stanów na pozostałych liniach wyjściowych rejestru?

Problemu nie ma, gdy chcemy sterować w ten sposób wyjściami mikrokontrolera, ponieważ używamy wtedy operacji bitowych sumy OR, mnożenia AND oraz sumy wyłączającej XOR.

Więcej informacji o operacjach bitowych oraz możliwość ich ćwiczenia online, znajdziesz tutaj: Kurs języka C z kompilatorem CManiak online

Wpadłem na rozwiązanie, aby rejestr posiadał własną zmienną statyczną, którą odczytywałby i prezentował jej zawartość na liniach Q0-Q7. Dalej nie ma już problemu z operacją bitową na zmiennej (dodawanie wartości, wyłączanie poszczególnych bitów).

W moim rozwiązaniu jest to zmienna o nazwie tempdata (nazwa wymyślona na szybko, potem okazało się, że jest w tym trochę prawdy, iż jest to zmienna temporary (tymczasowa) ponieważ przez cały czas sterowania wyświetlaczem następuje zmiana jej zawartości).

Do funkcji sterującej rejestrem wstawiłem 2 argumenty:
  • unsigned char WriteOrErase - która w zależności od stanu 0 lub 1 decyduje o tym, czy wykonujemy dodawanie bitów do zmiennej za pomocą operacji bitowej OR | (1), lub wyłączamy poszczególne bity za pomocą mnożenia AND oraz koniunkcji bitowej ~(0).
  • unsigned char data - zmienna, która w zależności od wartości zmiennej lokalnej WriteOrErase dodaje lub usuwa bity ze zmiennej statycznej tempdata.

Reszta funkcji polega na odczycie poszczególnych bitów ze zmiennej tempdata i wystawienie jej zawartości na pinach Q0-Q7, oraz wysterowaniu odpowiednio sygnałami pinów rejestru.

Całość prezentuje się mniej więcej tak:

// DS_x, STCP_x, SHCP_x to nazwy sygnałów sterujących rejestrem 74HC595,
// gdzie x to stan logiczny wystawiany do rejestru

void rejestr(unsigned char data, unsigned char WriteOrErase)
{
 if(WriteOrErase==1)
  tempdata=(tempdata|data);
 else
  tempdata&=~(data);
 STCP_0;
 SHCP_0;
 if(tempdata & 0x80)DS_1;else DS_0;
 SHCP_1;
 SHCP_0;
 if(tempdata & 0x40)DS_1;else DS_0;
 SHCP_1;
 SHCP_0;
 if(tempdata & 0x20)DS_1;else DS_0;
 SHCP_1;
 SHCP_0;
 if(tempdata & 0x10)DS_1;else DS_0;
 SHCP_1;
 SHCP_0;
 if(tempdata & 0x08)DS_1;else DS_0;
 SHCP_1;
 SHCP_0;
 if(tempdata & 0x04)DS_1;else DS_0;
 SHCP_1;
 SHCP_0;
 if(tempdata & 0x02)DS_1;else DS_0;
 SHCP_1;
 SHCP_0;
 if(tempdata & 0x01)DS_1;else DS_0;
 SHCP_1;
 STCP_1;
}

Jeżeli na przykład na pinie Q7 chcesz ustawić jedynkę lub zero, wystarczy wywołać funkcję:
rejestr(0x80,1);  //ustaw 1 na Q7  
rejestr(0x80,0);  //ustaw 0 na Q7

ale możesz także użyć zdefiniowanych:
Q71;  //ustaw 1 na Q7  
Q70;  //ustaw 0 na Q7

Gotowe.

Możemy już niezależnie sterować  8 pinami z rejestru. Reszta kodu oparta jest na przykładzie z tego kursu. Autor wyraził zgodę na publikację zmodyfikowanego kodu na potrzeby rozwiązania obsługi wyświetlacza LCD z wykorzystaniem mniejszej ilości pinów procesora.

Dopisek Dondu (2012r.):
Wspomniany wyżej kurs oparty jest o przestarzałe środowisko WINAvr, które wymaga tworzenia we własnym zakresie pliku makfile, a który często jest powodem problemów początkujących. Jeżeli będziesz korzystał z tego kursu, to używaj co najmniej AVR Studio 4 , które automatycznie tworzy plik makefile i co najważniejsze zawsze poprawnie. Kurs AVR znajdziesz także na naszym blogu:  Drzaśkowy pamiętnik, czyli kurs mikrokontrolerów AVR.





Którą wersję rejestru 74xx595 wybrać?

W zależności od parametrów Twojego urządzenia możesz wybrać różne wersje tego układu. W niniejszym artykule wybrałem wersję HC (74HC595) jako tanią i najbardziej wszechstronną. Ale można wykorzystać inne wersje: Logic family




Biblioteka

W załączniku zamieszczone zostały 3 wersje biblioteki.

Biblioteka jest uniwersalna i może być stosowana na dowolnym 
mikrokontrolerze np. AVR, PIC, STM, itd.

Na potrzeby niniejszego artykułu posłużę się mikrokontrolerem ATmega8.

Biblioteka składa się z dwóch plików:
  • hd44780.h (plik nagłówkowy),
  • hd44780.c (plik z funkcjami obsługi wyświetlacza).

Rejestr przesuwny 74HC595 może być podłączony pod praktycznie każdy pin, który może być wyjściem cyfrowym mikrokontrolera. Podłączane sygnały to SHCP, STCP oraz DS (patrz schematy poniżej).

Aby wykorzystać bibliotekę, musisz ją poinformować, do których pinów mikrokontrolera podłączyłeś te trzy sygnały. Wykonuje się to poprzez zmianę definicji w pliku nagłówkowym hd44780.h:

//DS
#define pin_DS     PD0
#define DDR_DS     DDRD
#define PORT_DS    PORTD

//STCP
#define pin_STCP   PD1
#define DDR_STCP   DDRD
#define PORT_STCP  PORTD

//SHCP
#define pin_SHCP   PD2
#define DDR_SHCP   DDRD
#define PORT_SHCP  PORTD


Niektóre wyświetlacze LCD ze sterownikiem HD44780 wymagają opóźnienia pomiędzy włączeniem zasilania, a jego inicjalizacją. W pliku nagłówkowym hd44780.h znajdziesz do tego komentarz oraz odpowiednią definicję:

//Niektóre wyświetlacze wymagają opóźnienia pomiędzy włączeniem
//zasilania, a inicjalizacją. Jeżeli chcesz włączyć opóźnienie usuń znak
//komentarza i podaj liczbę milisekund, sugeruję około 20.
//
//#define LCD_START_DELAY_MS 20

Następnie tak przygotowaną bibliotekę linkujesz na początku programu w ten sposób:

#include "hd44780.h"

Wybrane funkcje dostępne w bibliotece:


Funkcja/definicjaOpis
lcd_init();Inicjuje wyświetlacz po włączeniu zasilania. Jeżeli zdefiniujesz opóźnienie to wykona się ono na początku tej funkcji.
LCD_CLEAR;Definicja wywołująca funkcję kasowania zawartości wyświetlacza.
LCD_LOCATE(x,y);Definicja wywołująca funkcję ustawienia kursora w kolumnie x i wierszu y. Współrzędne liczone są począwszy od zera.
lcd_puts(nazwa_bufora);Funkcja wyświetlająca zawartość tablicy nazwa_bufora[] począwszy od miejsca w którym jest ustawiony kursor. Może to być dowolna inna tablica.
LCD_HOME;Ustawia kursor w pozycji (0,0).
LCD_SHIFT_DISPLAY(kierunek);Przesuwa zawartość wyświetlacza w lewo, gdy kierunek = LCDLEFT lub w prawo, gdy LCDRIGHT.
rejestr(piny, stan);Ustawia jedynkę (gdy stan=1) lub zero (gdy stan=0) na wybranych pinach rejestru 74HC595 (czytaj w artykule powyżej tabelki).

W pliku nagłówkowym znajdziesz także inne funkcjonalności starownika HD44780, a przykład wykorzystania biblioteki znajdziesz w dalszej części artykułu.


Wersja A

Pin RW wyświetlacza podłączony na stałe do masy. Wyjścia rejestru Q6 oraz Q7 są zaprogramowane do obsługi innych urządzeń niezależnie od stanu pracy wyświetlacza.




Pliki biblioteki: LCD-3piny-74hc595-v1-wersja-A.zip



Wersja B

Pin RW wyświetlacza podłączony na stałe do masy. Wyjścia rejestru Q6 oraz Q7 nie są obsługiwane - wersja dla czytelników, którzy walczą o każdy bajt pamięci FLASH.

Schemat ten sam co w wersji A.



Wersja C

Pin RW wyświetlacza podłączony do rejestru. Wyjście rejestru Q7 jest zaprogramowane do obsługi innego urządzenia. Wyjście Q6 jest wykorzystane do LCD. Rozwiązanie to wymaga podłączenia pinu RW do wyjścia rejestru (np.  sytuacja,  w której nie możemy na schemacie poprowadzić masy do RW, a rejestr przesuwny umieściliśmy blisko wyświetlacza).





Pliki biblioteki: LCD-3piny-74hc595-v1-wersja-C.zip




Przykład 

Czas zrealizować jeden z przykładów. Wykorzystamy wersję A biblioteki. W tym celu zmontujemy taki układ:



Zmontowany układ:



Jako przykład wykorzystamy program, który będzie:
  • generował jakieś napisy na LCD, 
  • chował zawartość LCD w prawo lub lewo (z wykorzystaniem funkcji SHIFT wyświetlacza)
  • mrugał LED'ami podpiętymi pod piny Q6 i Q7 rejestru 74HC595.

#include <avr/io.h>
#include <util/delay.h>
#include "hd44780.h"

int main(void)
{

 unsigned char i;
 unsigned char kierunek = 0;
    
 char napis[] = "LCD 3 piny " ;
 char text[]  = "74HC595 ";

 lcd_init();
 LCD_CLEAR;

 while(1)
 {

  _delay_ms(500);
  LCD_LOCATE(5,1);
  lcd_puts(napis);
    
  _delay_ms(500);
  LCD_LOCATE(8,0);
  lcd_puts(text);

  _delay_ms(500);
  LCD_LOCATE(1,1);
  lcd_puts(text);

  _delay_ms(500);
  LCD_LOCATE(1,0);
  lcd_puts(napis);

  _delay_ms(1000);

  //chowamy zawartość wyświetlacza w jedną ze stron
  for(i=0; i<16; i++){
    
    if(kierunek)
     LCD_SHIFT_DISPLAY(LCDLEFT);
    else 
     LCD_SHIFT_DISPLAY(LCDRIGHT);
    
    _delay_ms(50);
  }

  LCD_CLEAR;

  kierunek ^= 1; //zmień kierunek na przeciwny

  //migamy diodami kilka razy
  for(i=0; i<16; i++){

   //LED na pinie Q7  
   if(i & 1) rejestr(0x80,0); //zgaś  gdy i jest nieparzyste
   else      rejestr(0x80,1); //zapal gdy i jest parzyste

   //LED na pinie Q6
   if(i & 1) Q61;             //zgaś  gdy i jest nieparzyste
   else      Q60;             //zapal gdy i jest parzyste

    _delay_ms(100);
  }
 }
}

Projekt do pobrania: Projekt AVR Studio 4 + pliki biblioteki + main.c + plik HEX dla 1MHz


Efekt działania programu:



Jeżeli macie jakiś pomysł na zoptymalizowanie kodu, zaproponowanie zmian - piszcie śmiało w komentarzach.

Pozdrawiam
Sheep94

24 komentarze:

  1. No bardzo sprytny sposob :) Na to bym nie wpadl, a mozna wykorzystac przy innych rzeczach :)

    OdpowiedzUsuń
  2. Co do optymalizacji to w funkcji rejestr na pewno mniej miejsca w pamięci zajmie pętla zwinięta niż rozwinięta jak u Ciebie realizująca transmisję, czyli zamiast sprawdzać
    if(tempdata & 0x80)DS_1;else DS_0;
    SHCP_1;
    SHCP_0;
    To porównać tempdata z ze zmienną przechowującą maskę (jeden bajt w RAM) i dzielić przez 2 (np. niearytmetycznie przez obrót w prawo). Całość powinna zająć mniej miejsca we flashu, a tracimy 1 bajt RAMu

    OdpowiedzUsuń
  3. zamiast kaskady ifów proponuję wykorzystać sprzetowe SPI albo funkcję podobną do mojej:
    int SPI_SEND_BYTE(unsigned char byte){
    for(unsigned char mask=0x80;mask>0;mask>>=1){ //generator kolejnych masek bitowych
    SPI_PORT &= ~(1<<SPI_CLK); //CLK na 0
    if(mask&byte) SPI_PORT |= (1<<SPI_DOUT); //wystawienie 1
    else SPI_PORT &= ~(1<<SPI_DOUT); //albo 0
    SPI_PORT |= (1<<SPI_CLK); //CLK na wysoki
    }
    SPI_PORT &= ~(1<<SPI_CLK); //CLK na niski
    }

    OdpowiedzUsuń
  4. @chodzikman
    Ta biblioteka z założenia jest dla dowolnych pinów i dla dowolnego mikrokontrolera.

    OdpowiedzUsuń
  5. w takim razie programowa obsługa jest ok, ta moja funkcja robi to samo co te ify, tylko trochę zgrabniej :)
    ogólnie wykorzystanie rejestru szeregowo-równoległego to świetny pomysł na zaoszczędzenie na pinach :)
    takie rejestry w wersji zintegrowanej z driverami led (np. TLC59025) to też świetna opcja, kiedy nie chcemy marnować timera na multipleksowanie wyświetlaczy segmentowych albo matrycowych - wrzucamy przez SPI i mamy spokój (a jasność możemy wyregulować za pomocą PWM sterującego pinem OE

    OdpowiedzUsuń
  6. Zgadza się. Autor czekał właśnie na takie komentarze :)

    OdpowiedzUsuń
  7. ciekawy pomysł, ale mało optymalny kod

    OdpowiedzUsuń
  8. pomyślałem sobie, że wyświetlacze znakowe LCD są na tyle mało wymagające pod kątem prędkości transferu, że można by użyć rejestrów sterowanych protokołem i2c albo nawet 1-wire i stworzyć obsługę wyświetlacza opartą o tylko (!) 1 pin

    OdpowiedzUsuń
  9. No to może potraktuj to jako wyzwanie i powstanie następna biblioteka do kolekcji: Biblioteki za free
    :-)

    OdpowiedzUsuń
  10. Mogę spróbować jak tylko uda mi się wygrzebać trochę wolnego czasu między bojami ze studiami :)

    OdpowiedzUsuń
  11. Dziękuję za komentarze zarówno te pozytywne jak i proponujące zmiany w
    kodzie. Mi również 8 ifów nie podchodziło, chciałem w pętli stworzyć
    zmienną i mnożyć ją *2, niestety "coś" się nie udało. Zastosuję w wolnej
    chwili Wasze rozwiązania i prawdopodobnie jeśli zmniejszy się rozmiar
    kodu o znaczną ilość umieścimy jako kolejne wersje rozwiązań.

    Czekam na dalsze opinie

    POzdrawiam
    Sheep94

    OdpowiedzUsuń
  12. Nie wychodziło, bo po pomnożeniu 0x80 * 2 otrzymywałeś 0x00 a nie 0x100, a podejrzewam, że warunek dałeś zmienna<=0x80 albo coś w tym stylu

    Przejrzałem trochę internet w poszukiwaniu 1-wire'owego rejestru, ale jedyne co znalazłem to aktualnie trudno dostępny DS2408 (znalazłem go tylko na allegro, u gościa który jak podejrzewam po scalakach jakie proponuje - odsprzedaje sample), więc jeśli nie znajdzie się coś innego, to pomysł z 1-przewodowym sterowaniem LCD można włożyć do szuflady

    OdpowiedzUsuń
  13. Czy z innymi wyświetlaczami na tym sterowniku, ale nie 16x2 tylko na przykład 8x1 lub 40x4 także biblioteka będzie działać?

    OdpowiedzUsuń
  14. Spokojnie da się na jednym kablu i *595:)
    http://mbed.org/cookbook/1-wire-shifting-2x16-LCD

    OdpowiedzUsuń
  15. Oczywiście, że na innych wyświetlaczach o różnej ilości znaków i wierszy będzie działać, musisz pamiętać tylko o odpowiednim wykorzystaniu funkcji LCD_LOCATE(x,y)
    Z tego co mi wiadomo wyświetlacze 4-wierszowe mają dwie linie E, ta biblioteka nie jest obsłuży dwóch linii E, musisz ją odpowiednio przerobić. Nie mam takiego wyświetlacza by zrobić kolejną wersję biblioteki. Może ktoś, kto posiada taki LCD podejmie się tego wyzwania? ;) A może ktoś z Poznania? :)

    Pozdrawiam

    OdpowiedzUsuń
  16. Warto też wspomnieć że rejstry szeregowe można łączyć w szereg, więc za pomocą tych samych 3 pinów można obsługiwać kolejne wyświetlacze LCD, lub inne peryferia.

    OdpowiedzUsuń
  17. Witam!

    Kolega Sheep94 jakiej użył biblioteki w Eaglu dla rejestru przesuwnego?

    OdpowiedzUsuń
  18. można by też obsługiwać po I2C, to znaczy podpiąć RS, RW, EN oraz DB4-DB7 do PCF8574
    i już są tylko 2 piny :D przy czym sam używam wersji 8bit z RW=0 tak więc w moim przypadku są to 4 piny :)

    OdpowiedzUsuń
  19. Witam,
    próbuję zmusić swój wyświetlacz (20x4) do pracy z tą biblioteką i mam następujący problem:
    Po podpięciu zasilania do mikrokontrolera na ekranie pojawiają się krzaki. Ale jeśli na chwilę odepnę od mikrokontrolera kabelki od rejestru (STCP, SHCP i DS) i po chwili podepne je znowu to wyświetlacz zaczyna działać tak jak trzeba. W czym może leżeć problem? :/

    OdpowiedzUsuń
    Odpowiedzi
    1. odezwij się na maila pomogę Ci: modecom601@wp.pl

      Usuń
    2. Temat na forum założył tutaj: Re: HD44780 + 74HC595 + ATMEGA - Po pewnym czasie wyświetla śmieci

      Usunę więc Twój post z adresem email :)

      Usuń
  20. Czy można za pomocą rejestru 74HC595 wysterować dwa wyświetlacze LCD ?
    Lub użyć 2 rejestrów, do każdego wyświetlacza osobny ale z mikrokontrolera dalej sterować 3 pinami?
    Dziękuję :)

    OdpowiedzUsuń
    Odpowiedzi
    1. Oczywiście można, ale nie prościej użyć jakiegoś nowszego modułu z kontrolerem SPI?

      Usuń
  21. Linki wygasły ;(

    OdpowiedzUsuń