Sterowanie rejestrami przesuwnymi z wykorzystaniem interfejsu SPI na przykładzie 6 cyfrowego wyświetlacza LED
http://www.easy-soft.pl/ Sterowanie rejestrami przesuwnymi z wykorzystaniem interfejsu SPI na przykładzie 6-cyfrowego wyświetlacza LED. Tym razem, korzystając z modułu wyświetlacza LED już wcześniej opisywanego na tej stronie (np. http://www.easy-soft.pl/modules.php?name=News&file=article&sid=43), podłączyłem go do mikrokontrolera ST7. Jednocześnie zastanawiałem się, czy nie można by było wykorzystać do jego obsługi interfejsu SPI, to znaczy czy rejestr 74HCT595 nadaje się do odbioru danych przesyłanych za pomocą SPI w którymś z trybów pracy. Przecież SPI posiada linię zegara w takt którego przesyła i odbiera dane. A użycie interfejsu SPI, zresztą któregokolwiek ze sprzętowych udogodnień wbudowanych w strukturę mikrokontrolera, znakomicie wręcz upraszcza program, wpływa na łatwość jego uruchomienia (a co za tym idzie skraca czas potrzebny na przetestowanie układu). Wyświetlacz LED Pole odczytowe wyświetlacza ma 6 cyfr LED, po 7 segmentów każda. Doliczając kropkę dziesiętną można powiedzieć, że wyświetlacz wymaga do sterowania 8 bitów, więc same sterowanie segmentami zajmie jeden pełny port. Dodatkowo sterowanie tranzystorami kluczami załączającymi napięcie na anody cyfr, będzie wymagać następnych sześciu bitów portu. To już razem 14 linii! A jeśli jeszcze konieczne stanie się jakiś układów zewnętrznych takich, jak na przykład klawiatura? Może braknąć wyprowadzeń mikrokontrolera. Schemat połączeń wyświetlacza LED przedstawiono zobaczyć na rysunku 1. Do eksperymentowania z tym przykładem trzeba sobie taki wyświetlacz zbudować, chociażby na płytce uniwersalnej. Moim zdaniem przyda on nie tylko do eksperymentów, ale również do wykorzystania w dowolnym innym układzie. Obsługa wyświetlacza oparta jest o przerwanie generowane przez Lite Timer B. Przy każdym wejściu w obsługę przerwania wyświetlana jest pojedyncza cyfra. Czas przełączania cyfr jest dobrany tak, aby spełniał dwa kryteria: 1) aby nie było widoczne migotanie cyfr, 2) aby czas wyświetlania (załączenia) cyfry był maksymalnie długi. Do konstrukcji rejestru wyświetlacza użyłem układów 74HCT595, które są rejestrami przesuwającymi z wejściem szeregowym, wyjściem równoległym i zatrzaskami na tychże wyjściach. Do zastosowania w prezentowanym układzie predysponowała je zwłaszcza ta druga cecha. Dzięki użyciu zatrzasków na wyjściach, układ nie wyprowadza informacji do momentu pojawienia się osobnego impulsu zegarowego, który ją tam przepisze. Nie ma więc efektu migotania cyfr w czasie wpisywania danych do rejestrów. Każdy impuls zegarowy docierający do wejścia zegara przesuwu, powoduje próbkowanie stanu wejścia, jego zapis do rejestru przesuwnego oraz przesunięcie o 1 bit w lewo. Układy można łączyć w szeregi budując rejestry o praktycznie nieograniczonej pojemności. Dla potrzeb tej aplikacji połączyłem szeregowo dwa układy 74HCT595 tworząc w ten sposób rejestr o pojemności 16 bitów. Jako pierwszy w szeregu znajduje się rejestr sterujący załączaniem segmentów cyfr (aktywny stan niski), jako drugi rejestr załączający poszczególne cyfry poprzez sterowanie kluczami tranzystorowymi (również aktywny stan niski). Wejście szeregowe danych taktowane jest sygnałem o częstotliwości około 4,65 kHz natomiast cyfry przełączane są z częstotliwością zbliżoną do 48Hz. Użyłem wyświetlaczy LED ze wspólną anodą. Zasilanie anod załączane jest przez tranzystory MOS z kanałem typu P (np. BS250). Wartości rezystorów podłączonych do poszczególnych segmentów wyświetlacza należy dobrać indywidualnie do posiadanych cyfr, pamiętając jednocześnie o tym, aby nie przekroczyć dopuszczalnego prądu wyjść rejestrów. Numery wyprowadzeń wyświetlacza LED podane na schemacie należy traktować jako orientacyjne. Istotne są literowe oznaczenia segmentów. Sterowanie wyświetlaniem Do sterowania wymagane są trzy linie jedna danych i dwie zegarowe. Wykorzystano w tym celu wyjście danych MOSI, wyjście zegarowe SCK i 4 wyprowadzenie portu B. W skrócie funkcjonowanie wyświetlacza wygląda następująco: dane przy pomocy opadającego zbocza sygnału zegarowego podawanego na wyprowadzenie 11 (SRCLK) wpisywane są z wejścia szeregowego na doprowadzeniu 14 (SER) do wewnętrznego rejestru. Mikrokontroler przesyła pełne słowo 16-to bitowe tak, aby działały oba układy rejestrów. Następnie, po wpisaniu 16 bitów do rejestrów, na wyprowadzenie PB4 (RCLK) podawany jest J.Bogusz Sterowanie rejestrami przesuwnymi z wykorzystaniem SPI strona 2 / 10 http://www.easy-soft.pl/ impuls, którego opadające zbocze powoduje przepisanie danych z wewnętrznego szeregowo równoległego rejestru do wyjściowego równoległego rejestru typu zatrzask. Prezentowana aplikacja wykorzystuje tryb SPI zwany Master, to znaczy mikrokontroler przesyła bajt generując sygnał zegara. Wyboru trybu pracy SPI dokonuje się poprzez nastawy rejestrów kontrolnych SPICR i SPICSR (w programie zadeklarowany jako SPISR). Samo przesyłanie danych przez SPI jest bardzo proste: wystarczy w rejestrze danych SPIDR umieścić bajt i poczekać, aż zostanie wysłany. Nie mniej jednak procedura wysyłająca zawiera kilka drobnych szczegółów, bez uwzględnienia których po prostu nie działa, lub zatrzymuje się po wysłaniu pojedynczego bajtu. Podprogram przesyłający dane umieszczono na listingu 1. Wymaga on kilku słów wyjaśnienia. ;------------------------------------------------------- ; funkcja wysyłająca zmienną LEDDATA do wyświetlacza LED ; z wykorzystaniem interfejsu SPI ;------------------------------------------------------- ledwrite push X ;zapamiętanie modyfikowanych rejestrów ;mikrokontrolera na stosie push A clr X ld A,(leddata,X) ;pierwszy bajt zmiennej ld SPIDR,A btjf SPISR,#7,* ;oczekiwanie na wysłanie bajtu ld A,SPIDR ;dla wyzerowania SPIF inc X ;drugi bajt zmiennej ld A,(leddata,X) ld SPIDR,A btjf SPISR,#7,* ;oczekiwanie na wysłanie bajtu ld A,SPIDR ;dla wyzerowania SPIF ld A,PBDR ;krótki impuls dodatni na PB4 (przepisanie rejest- or A,#$10 ;rów szeregowych na wyjścia) ld PBDR,A and A,#$EF ld PBDR,A pop A ;odtworzenie rejestrów roboczych mikrokontrolera pop X ret Listing 1. Funkcja przesyłająca 2-bajty zmiennej LEDDATA przez interfejs SPI. Na początku podprogramu zapamiętywane są na stosie modyfikowane przezeń rejestry, bez uwzględnienia flag mikrokontrolera. Następnie do akumulatora z pamięci RAM pobierany jest pierwszy bajt zmiennej. Wskaznikiem jest adres zmiennej a indeksem rejestr X, który w tym momencie ma wartość 0. Po pobraniu bajt zapisywany jest do rejestru danych interfejsu SPI, z którego to jest natychmiast wysyłany przez wyjście szeregowe MOSI, synchronicznie z zegarem, którego sygnał wyprowadzany jest przez SCK. Wysyłka kończy się ustawieniem bitu 7 (SPIF) w rejestrze SPISR. Stan wysoki tego bitu pociąga za sobą pewne reperkusje. Po pierwsze, jeśli ustawiony jest bit SPIE w rejestrze SPICR, to wygenerowane zostanie przerwanie. Po drugie, może znacznie dla nas ważniejsze, SPI nie wyśle żadnych danych do momentu, aż bitowi SPIF programowo zostanie nadana wartość logiczna 0. Zgodnie z dokumentacją producenta, bit zostaje wyzerowany w pewnym specjalnym cyklu: należy dokonać odczytu rejestru SPISR a następnie odczytu rejestru SPIDR. W prezentowanym na listingu podprogramie pierwszy warunek spełniany jest przez rozkaz BTJF testujący stan bitu 7, natomiast drugi przez specjalnie w tym celu dodany rozkaz ld A,SPIDR. Nie ma on w tym przypadku żadnej innej funkcji poza opisaną. Jeszcze raz pozwolę sobie przypomnieć: bez wykonania opisanej sekwencji flaga SPIF pozostaje ustawiona i nie pozwala interfejsowi na przyjmowanie następnych danych. W konsekwencji interfejs po prostu nie działa! J.Bogusz Sterowanie rejestrami przesuwnymi z wykorzystaniem SPI strona 3 / 10 http://www.easy-soft.pl/ Obsługa przerwania timera B. Każde wywołanie procedury obsługi przerwania timera B powoduje przesłanie dwóch bajtów informujących o załączeniu cyfry oraz jej wzorca do rejestrów. Wartość bieżących, przesyłanych przez SPI dwóch bajtów, zapamiętywana jest w zmiennej o nazwie leddata. Stan zmiennej jest dokładnym odwzorowaniem wysyłanych bajtów. Dwa bajty leddata tworzone są na podstawie tablic z pamięci programu. Starszy bajt zawiera kod załączenia wyświetlacza. Młodszy bajt zawiera kod załączeń segmentów wyświetlacza na danej pozycji. W jaki sposób tworzone są oba kody? Ich wzorce pamiętane są jako stałe umieszczone w pamięci programu (listing 2). Indeksem do tych tablic są: przy pobieraniu kodu załączenia cyfry zmienna licznikowa ledcnt zawierająca numer wyświetlanej pozycji, przy pobieraniu kodu załączenia segmentów wartość cyfry w kodzie BCD zapamiętana w zmiennej tablicowej ledbuf. Przy pobieraniu kodu załączenia, do rejestru X ładowana jest wartość licznika ledcnt o ile ten, nie przekroczył 5 (licznik liczy od 0 do 5). Jeśli jest on równy 6, to przed załadowaniem do X jest zerowany tak, aby ponownie wskazywał na pierwszą pozycję tablicy. Rejestr X jest offsetem dla adresu 16-bitowego w pamięci programu. Część stała adresu, to początek tablicy o nazwie sequence. Jak łatwo domyśleć się, w ten sposób pobierane będą wartości zależne od stanu licznika. Oczywiście dla potrzeb sterowania załączaniem kluczy można było na przykład przesuwać w prawo wartość zmiennej tworząc swego rodzaju wędrujące 0 . Po pobraniu z tablicy kod załączenia zapisywany jest w starszym bajcie zmiennej leddata. Kod załączenia segmentów tworzony jest w dwóch etapach. Najpierw, na bazie zmiennej licznikowej, wyznaczony jest offset do tablicy w pamięci RAM i z niej pobierana jest wartość cyfry w kodzie BCD. Ta wartość stanowi z kolei offset do tablicy patterns zawierającej wzorce znaków do wyświetlenia. Po pobraniu wzorzec zapamiętywany jest w młodszym bajcie zmiennej leddata. Funkcja obsługi przerwania kończona jest wysłanie obu bajtów (leddata) przez SPI. Dzieje się tak na skutek wywołania podprogramu ledwrite. ;------------------------------------------------------- ; deklaracje tabel związanych z cyframi ;------------------------------------------------------- ;kody załączeń cyfr (0 powoduje załączenie danego klucza tranzystorowego) sequence DC.B $FE,$FD,$FB,$F7,$EF,$DF ;kody załączeń segmentów cyfr (0 załącza segment) ;d0=G, d1=f, d2=D, d3=dp, d4=C, d5=A, d6=B, d7=E patterns DC.B %00001001,%10101111,%00011010,%10001010,%10101100 ;0,1,2,3,4, pat1 DC.B %11001000,%01001000,%10001111,%00001000,%10001000 ;5,6,7,8,9, pat2 DC.B $FF ;znak wyłączony Listing 2. Tablice sterujące załączeniem kluczy i segmentów. J.Bogusz Sterowanie rejestrami przesuwnymi z wykorzystaniem SPI strona 4 / 10 Rysunek 1. Schemat dołączenia wyświetlacza 6-cyfrowego LED do mikrokontrolera ST7LISTE29. http://www.easy-soft.pl/ Wyświetlanie wartości program główny. Kolejnym przełączaniem wyświetlanych cyfr i ich pobieraniem z pamięci mikrokontrolera zajmuje się opisywana wyżej procedura obsługi przerwania. Jedyne, co musi zrobić użytkownik, to na odpowiedniej pozycji zmiennej tablicowej ledbuf wstawić cyfrę w kodzie BCD. Numer pozycji w tablicy ściśle odpowiada numerowi wyświetlanej cyfry. I tak dla przykładu wykonanie rozkazów: ld A,#1 ld {ledbuf+1},A Spowoduje pojawienie się na 2-giej pozycji wyświetlacza cyfry 1 (znaki liczone są od lewej do prawej, pierwszy znak ma numer 0). Przykład takiego podstawienia zawiera program główny rozpoczynający się od etykiety main, a umieszczony na listingu 3. Należy pamiętać jeszcze o tym, aby program główny zawierał załączenie przerwań (rozkaz rim). Bez nich procedura wyświetlająca nie będzie działać! W programie użyto kilku makr tak, aby poprawić jego czytelność. Makro nie jest podprogramem wywoływanym przez CALL czy CALLR. Owszem, może zawierać definicję podprogramu. Upraszczając można powiedzieć, że makro to zestaw instrukcji asemblera powiązanych z definicją makro, umieszczany przez kompilator w miejscu pojawienia się jego nazwy. W prezentowanym przykładzie użyto odrębnych makr dla: " nastaw trybu pracy portu B (PORTS_INIT), " nastaw trybu pracy SPI (SPI_INIT), " nastaw przerwania od timera B (TIMB_INIT). Wszystkie bity portu PB są wyjściowymi z załączonymi rezystorami zasilającymi, za wyjątkiem bitu PB2. Wprowadzono następujące nastawy SPI: " częstotliwość zegara równą 1/8 częstotliwości zegara taktującego pracą CPU, " CPOL = 1, CPHA = 1, " tryb master (wymaga nastaw rejestrów SPICR i SPISR), " przerwania wyłączone. Timer B inicjowany jest w taki sposób, że częstotliwość sygnału doprowadzonego na jego wejście jest równa częstotliwości z jaką taktowane jest CPU i zostaje załączone przerwanie. Jacek Bogusz J.Bogusz Sterowanie rejestrami przesuwnymi z wykorzystaniem SPI strona 6 / 10 http://www.easy-soft.pl/ st7/ ;Program demonstracyjny demonstrujący użycie ;wyświetlacza LED: 6 znaków 7 segmentowych MOTOROLA ;format MOTOROLA (.s19) #include "st7flite29.inc" ;segmenty pamięci BYTES segment byte at 80-FF 'ram0' segment byte at 100-1FF 'stack' segment byte at 200-27F 'ram1' segment byte at 1000-10FF 'eeprom' segment byte at E000-FFDF 'program' segment byte at FFE0-FFFF 'intvect' BYTES ;deklaracje zmiennych segment 'ram0' ledcnt DS.B ;licznik znaków LED ledbuf DS.B 6 ;miejsce na 6 znaków leddata DS.B 2 ;bieżący wyświetlany znak ;deklaracje stałych WORDS segment 'program' ;------------------------------------------------------- ; deklaracje tabel związanych z cyframi ;------------------------------------------------------- sequence DC.B $FE,$FD,$FB,$F7,$EF,$DF patterns DC.B %00001001,%10101111,%00011010,%10001010,%10101100 ;0,1,2,3,4, pat1 DC.B %11001000,%01001000,%10001111,%00001000,%10001000 ;5,6,7,8,9, pat2 DC.B $FF ;znak wyłączony ;------------------------------------------------------- ; inicjacja używanych portów I/O ;port B PBDR_init EQU %00000000 PBDDR_init EQU %11111011 PBOR_init EQU %11111011 ;------------------------------------------------------- PORTS_INIT MACRO ld A,#PBDDR_init ;nastawa trybu pracy portu B ld PBDDR,A ld A,#PBOR_init ld PBOR,A ld A,#PBDR_init ld PBDR,A MEND ;------------------------------------------------------- ; inicjacja SPI: J.Bogusz Sterowanie rejestrami przesuwnymi z wykorzystaniem SPI strona 7 / 10 http://www.easy-soft.pl/ ; CLK = fcpu/3, divider = wyłączony, cpol = idle gdy "H", ; cpha = 1, przerwania = wyłączone, tryb = master SPISR_init EQU %00000011 SPICR_init EQU %00011100 SPI_enable EQU %01000000 ;------------------------------------------------------- SPI_INIT MACRO ld A,#SPISR_init ld SPISR,A ld A,#SPICR_init ld SPICR,A or A,#SPI_enable ld SPICR,A MEND ;------------------------------------------------------- ; inicjacja timera Lite (timer B) ;------------------------------------------------------- TIMB_INIT MACRO clr A ;f_CPU = f_OSC ld MCCSR,A ld A,#$12 ;f_TIMER = f_CPU ld ATCSR,A clr A ;konfiguracja 12-bitowego timera ld ATRL,A ld A,#$07 ld ATRH,A clr ledcnt ;zerowanie licznika znaków MEND ;------------------------------------------------------- ; funkcja obsługi przerwania timera B ;------------------------------------------------------- timerb ld A,ledcnt sub A,#6 ;C=1 dla licznika <= 6 jrc timbskip clr ledcnt ;jeśli licznik > 5,to zerowanie timbskip ld X,ledcnt ;załadowanie wzorca załączenia klucza ;tranzystorowego ld A,(sequence,X) ;na podstawie wartości licznika ld leddata,A ld A,(ledbuf,X) ;pobranie cyfry BCD do akumulatora ld X,A ;ustawienie rejestru indeksowego ld A,(patterns,X) ;pobranie wzorca cyfry ld {leddata+1},A ;załadowanie wzorca cyfry do bufora bieżącej cyfry call ledwrite ;wysłanie znaków do rejestrów cyfr inc ledcnt ld A,ATCSR ;odczyt ATCSR w celu zerowania flagi przerwania iret ;------------------------------------------------------- ; funkcja wysyłająca zmienną LEDDATA do wyświetlacza LED J.Bogusz Sterowanie rejestrami przesuwnymi z wykorzystaniem SPI strona 8 / 10 http://www.easy-soft.pl/ ;------------------------------------------------------- ledwrite push X ;zapamiętanie modyfikowanych rejestrów push A clr X ld A,(leddata,X) ;pierwszy bajt zmiennej ld SPIDR,A btjf SPISR,#7,* ;oczekiwanie na wysłanie bajtu ld A,SPIDR ;dla wyzerowania SPIF inc X ;drugi bajt zmiennej ld A,(leddata,X) ld SPIDR,A btjf SPISR,#7,* ;oczekiwanie na wysłanie bajtu ld A,SPIDR ;dla wyzerowania SPIF ld A,PBDR ;krótki impuls dodatni na PB4 or A,#$10 ld PBDR,A and A,#$EF ld PBDR,A pop A ;odtworzenie rejestrów roboczych pop X ret ;------------------------------------------------------- ; początek programu głównego ;------------------------------------------------------- main PORTS_INIT ;inicjalizacja portów I/O SPI_INIT ;inicjalizacja trybu pracy SPI TIMB_INIT ;inicjalizacja timera B ld A,#0 ;umieszczenie w buforze ciągu 012345 ld {ledbuf+0},A ld A,#1 ld {ledbuf+1},A ld A,#2 ld {ledbuf+2},A ld A,#3 ld {ledbuf+3},A ld A,#4 ld {ledbuf+4},A ld A,#5 ld {ledbuf+5},A rim ;załączenie przerwań jra * ;wyświetlanie w przerwaniu timera B ;------------------------------------------------------- ; "pusta" funkcja, zawiera tylko instrukcje powrotu ; z obsługi przerwania ;------------------------------------------------------- .it_ret iret segment 'intvect' ;------------------------------------------------------- J.Bogusz Sterowanie rejestrami przesuwnymi z wykorzystaniem SPI strona 9 / 10 http://www.easy-soft.pl/ ; wektory przerwań ;------------------------------------------------------- DC.W it_ret DC.W it_ret DC.W it_ret .sci DC.W it_ret .timb DC.W timerb .tima DC.W it_ret .spi DC.W it_ret DC.W it_ret DC.W it_ret .ext3 DC.W it_ret .ext2 DC.W it_ret .ext1 DC.W it_ret .ext0 DC.W it_ret DC.W it_ret .soft DC.W it_ret .rst DC.W main END Listing 3. Program do obsługi wyświetlacza LED przy pomocy SPI. J.Bogusz Sterowanie rejestrami przesuwnymi z wykorzystaniem SPI strona / 10