Trochę teorii
SPI wprowadza podział na urządzenia typu master oraz slave. Master rozpoczyna i kończy transmisję oraz generuje sygnał zegarowy. Slave musi odpowiadać na polecenia mastera, ale sam nie może zainicjować transmisji. Przy użyciu SPI można połączyć jednego mastera z – teoretycznie – nieskończoną liczbą układów slave. Zaletą SPI jest duża szybkość transmisji – bez najmniejszego problemu uzyskamy przepustowość rzędu 10 Mbit/s, nawet w przypadku prostych i tanich mikrokontrolerów. Współczesne wyświetlacze LCD potrafią odbierać dane przez SPI z prędkością nawet 80 MHz.
SPI korzysta z czterech linii sygnałowych:
- SCK (Serial Clock) – sygnał zegarowy generowany przez mastera i odbierany przez wszystkie układy slave.
- MOSI (Master Output, Slave Input) – wyjście danych z układu master, połączone z wejściami wszystkich układów slave. Wszystkie slave’y mają zatem możliwość odbierania danych wysyłanych przez mastera, niezależnie od tego, który slave został przez niego wybrany za pomocą aktywnego sygnału CS.
- MISO (Master Input, Slave Output) – wyjście danych z układu slave, połączone z wejściem mastera. Jeżeli jest więcej niż jeden slave, to ich wyjścia połączone są ze sobą. Aby uniknąć wzajemnego zakłócania się wyjść (konfliktu na szynie danych), wyposaża się je w bufor trójstanowy, który odcina wyjście, kiedy slave nic nie nadaje. Przy takim rozwiązaniu możliwe jest, aby tylko jeden slave miał aktywne wyjście MISO, podczas gdy wszystkie pozostałe slave’y muszą ustawić te wyjścia w stan wysokiej impedancji.
- CS (Chip Select) – wyjście z układu master doprowadzone do wejścia wybierającego układu slave. Jeżeli na magistrali SPI mamy więcej układów slave, potrzebujemy po jednej osobnej linii CS na każdego slave’a. Kiedy linia CS pozostaje w stanie wysokim, slave jest nieaktywny. Aby uaktywnić slave’a, master ustawia na tej linii stan niski i utrzymuje go przez cały czas transmisji. Ustawienie CS w stan wysoki oznacza zakończenie komunikacji i dezaktywowanie układu slave. Czasami na wspomnianą linię dodaje się rezystory pull-up, aby mieć pewność, że slave nie uaktywni się przez przypadek, kiedy master nie działa (np. kiedy program procesora nie zdążył się jeszcze uruchomić).
Istnieją cztery tryby pracy interfejsu SPI, ponumerowane od 0 do 3. Można wybrać polaryzację oraz fazę sygnału zegarowego. Omówimy skrótowo tryb pracy 0.
Kiedy interfejs pozostaje nieaktywny, wówczas linia CS jest w stanie wysokim, linia zegarowa w stanie niskim, a MISO i MOSI znajdują się w stanie wysokiej impedancji (w niektórych implementacjach MOSI może mieć taki stan, jaki miał ostatni transmitowany wcześniej bit).
Bity przesyłane są w kolejności od najstarszego do najmłodszego, zatem na liniach MISO i MOSI pojawiają się najstarsze bity z bajtów przesyłanych pomiędzy masterem a slave'em.
Kiedy sygnał zegarowy SCK przechodzi ze stanu niskiego na wysoki, wówczas master i slave odczytują stany swoich wejść danych i zapisują je w swoich rejestrach przesuwnych, w których przechowywane są kolejne bity odbieranych bajtów. W momencie wystąpienia zbocza opadającego sygnału SCK master i slave aktualizują stany linii MISO oraz MOSI.
Można powiedzieć, że transmisja bajtu danych przez SPI składa się z ośmiu cykli zegarowych, przy czym zmiana stanu linii MISO i MOSI następuje na początku każdego cyklu, a odczytanie stanu tych linii – w połowie cyklu.
Po przesłaniu ośmiu bitów możliwe jest przesłanie kolejnych danych, a jeżeli nie ma więcej bajtów do transmisji – linia CS przechodzi w stan wysoki, co sygnalizuje zakończenie transmisji.
Moduł SlaveSPI
Kod umożliwi realizację urządzenia slave SPI, pracującego tylko w trybie 0. Pozwoliłem sobie na takie uproszczenie z dwóch powodów. Po pierwsze, tryb 0 jest najczęściej używany w różnego rodzaju pamięciach, wyświetlaczach oraz czujnikach. Po drugie, nasz układ FPGA będzie połączony z jakimś procesorem, odgrywającym rolę mastera (na płytce User Interface Board jest to popularny ESP32). Zmiana trybu pracy w mikrokontrolerze pozostaje tylko kwestią ustawiania jednego czy dwóch rejestrów. Zatem jest to dużo prostsze niż przygotowanie kodu w Verilogu, który byłby konfigurowalny za pomocą parametrów.
Omówmy listę portów modułu SlaveSPI:
- Clock – wejście zegara taktującego FPGA.
- Reset – wejście resetujące, aktywne w stanie niskim.
- CS_i, SCK_i, MOSI_i – wejścia interfejsu SPI,
- sterowane przez układ master. Trzeba pamiętać, że sygnały na tych wejściach nie są zsynchronizowane z domeną zegarową w FPGA.
- MISO_o – wyjście danych ze slave’a do mastera.
- DataToSend_i[7:0] – bajt danych, który ma zostać wysłany do mastera poprzez linię MISO.
- DataReceived_o[7:0] – bajt danych, który został odebrany od mastera poprzez linię MOSI.
- TransactionDone_o – wyjście informujące, że bajt danych został odebrany i można go odczytać z wyjścia DataReceived_o.
- TransmissionStart_o – wyjście informujące o rozpoczęciu transmisji (wykrycie zbocza opadającego na CS).
- TransmissionEnd_o – wyjście informujące o zakończeniu transmisji (wykrycie zbocza rosnącego na CS).
Na trzech ostatnich wyjściach pojawiać się będą sygnały strobe, czyli szpilki stanu wysokiego o długości jednego taktu zegarowego.