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/definicja | Opis |
---|---|
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.
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.
Pliki biblioteki: LCD-3piny-74hc595-v1-wersja-B.zip
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
No bardzo sprytny sposob :) Na to bym nie wpadl, a mozna wykorzystac przy innych rzeczach :)
OdpowiedzUsuń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ć
OdpowiedzUsuń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
zamiast kaskady ifów proponuję wykorzystać sprzetowe SPI albo funkcję podobną do mojej:
OdpowiedzUsuń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
}
@chodzikman
OdpowiedzUsuńTa biblioteka z założenia jest dla dowolnych pinów i dla dowolnego mikrokontrolera.
w takim razie programowa obsługa jest ok, ta moja funkcja robi to samo co te ify, tylko trochę zgrabniej :)
OdpowiedzUsuń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
Zgadza się. Autor czekał właśnie na takie komentarze :)
OdpowiedzUsuńciekawy pomysł, ale mało optymalny kod
OdpowiedzUsuń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ńNo to może potraktuj to jako wyzwanie i powstanie następna biblioteka do kolekcji: Biblioteki za free
OdpowiedzUsuń:-)
Mogę spróbować jak tylko uda mi się wygrzebać trochę wolnego czasu między bojami ze studiami :)
OdpowiedzUsuńDziękuję za komentarze zarówno te pozytywne jak i proponujące zmiany w
OdpowiedzUsuń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
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
OdpowiedzUsuń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
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ńSpokojnie da się na jednym kablu i *595:)
OdpowiedzUsuńhttp://mbed.org/cookbook/1-wire-shifting-2x16-LCD
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)
OdpowiedzUsuń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
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ńWitam!
OdpowiedzUsuńKolega Sheep94 jakiej użył biblioteki w Eaglu dla rejestru przesuwnego?
można by też obsługiwać po I2C, to znaczy podpiąć RS, RW, EN oraz DB4-DB7 do PCF8574
OdpowiedzUsuń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 :)
Witam,
OdpowiedzUsuń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? :/
odezwij się na maila pomogę Ci: modecom601@wp.pl
UsuńTemat na forum założył tutaj: Re: HD44780 + 74HC595 + ATMEGA - Po pewnym czasie wyświetla śmieci
UsuńUsunę więc Twój post z adresem email :)
Czy można za pomocą rejestru 74HC595 wysterować dwa wyświetlacze LCD ?
OdpowiedzUsuńLub użyć 2 rejestrów, do każdego wyświetlacza osobny ale z mikrokontrolera dalej sterować 3 pinami?
Dziękuję :)
Oczywiście można, ale nie prościej użyć jakiegoś nowszego modułu z kontrolerem SPI?
UsuńLinki wygasły ;(
OdpowiedzUsuń