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.
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