W odniesieniu do Arduino Uno, Mega2560, Leonardo i podobnych płyt:
- Jak działa SPI?
- Jak szybki jest SPI?
- Jak połączyć między urządzeniem nadrzędnym a urządzeniem podrzędnym?
- Jak zrobić SPI slave?
Uwaga: jest to pytanie referencyjne.
arduino-uno
arduino-mega
c++
arduino-leonardo
spi
Nick Gammon
źródło
źródło
Odpowiedzi:
Wprowadzenie do SPI
Interfejs szeregowej magistrali interfejsu peryferyjnego (SPI) służy do komunikacji między wieloma urządzeniami na krótkich dystansach i z dużą prędkością.
Zwykle jest to jedno „urządzenie nadrzędne”, które inicjuje komunikację i dostarcza zegar sterujący szybkością przesyłania danych. Może być jeden lub więcej niewolników. W przypadku więcej niż jednego urządzenia podrzędnego każdy ma własny sygnał „wyboru urządzenia podrzędnego”, opisany później.
Sygnały SPI
W pełnym systemie SPI będziesz mieć cztery linie sygnałowe:
Kiedy do urządzenia MISO podłączonych jest wiele urządzeń podrzędnych, oczekuje się, że potróją (utrzymują wysoką impedancję) tę linię MISO, dopóki nie zostaną wybrane przez potwierdzenie Slave Select. Normalnie Slave Select (SS) przechodzi w stan niski, aby go potwierdzić. Oznacza to, że jest niski. Po wybraniu określonego urządzenia podrzędnego powinien on skonfigurować linię MISO jako wyjście, aby mógł wysyłać dane do urządzenia nadrzędnego.
Ten obraz pokazuje sposób wymiany danych podczas wysyłania jednego bajtu:
Zauważ, że trzy sygnały są wyjściami z urządzenia nadrzędnego (MOSI, SCK, SS), a jeden to wejście (MISO).
wyczucie czasu
Sekwencja zdarzeń jest następująca:
SS
idzie nisko, aby to potwierdzić i aktywować niewolnikaSCK
Linia przełącza się wskazywać, kiedy linie danych należy pobrać próbkiSCK
(przy użyciu domyślnej fazy zegara)SCK
(używając domyślnej fazy zegara), zmieniającMISO
/ wMOSI
razie potrzebySS
do stanu wysokiego, aby anulować potwierdzenieUwaga:
Ponieważ dane są wysyłane i odbierane w tym samym impulsie zegarowym, urządzenie podrzędne nie może natychmiast zareagować na urządzenie nadrzędne. Protokoły SPI zwykle oczekują, że master zażąda danych podczas jednej transmisji i otrzyma odpowiedź na kolejną.
Korzystając z biblioteki SPI na Arduino, pojedynczy transfer wygląda tak:
Przykładowy kod
Przykład tylko wysyłania (ignorowanie jakichkolwiek przychodzących danych):
Okablowanie dla SPI tylko wyjściowego
Powyższy kod (który wysyła tylko) może być użyty do sterowania wyjściowym szeregowym rejestrem przesuwnym. Są to urządzenia tylko wyjściowe, więc nie musimy się martwić o przychodzące dane. W ich przypadku pin SS może być nazywany pinem „store” lub „zatrzask”.
Przykładem tego jest szeregowy rejestr przesuwny 74HC595 i różne paski LED, żeby wymienić tylko kilka. Na przykład ten 64-pikselowy wyświetlacz LED napędzany przez układ MAX7219:
W tym przypadku widać, że twórca płyty użył nieco innych nazw sygnałów:
Większość desek będzie miała podobny wzór. Czasami DIN to po prostu DI (Data In).
Oto kolejny przykład, tym razem 7-segmentowy wyświetlacz LED (również oparty na układzie MAX7219):
Używa dokładnie takich samych nazw sygnałów, jak na drugiej płycie. W obu tych przypadkach widać, że płytka potrzebuje tylko 5 przewodów, trzy do SPI, plus moc i uziemienie.
Faza zegara i polaryzacja
Istnieją cztery sposoby próbkowania zegara SPI.
Protokół SPI pozwala na zmianę polaryzacji impulsów zegara. CPOL jest polaryzacją zegara, a CPHA jest fazą zegara.
Są one zilustrowane na tej grafice:
Aby uzyskać prawidłową fazę i polaryzację, należy zapoznać się z arkuszem danych urządzenia. Zwykle jest schemat pokazujący, jak próbkować zegar. Na przykład z arkusza danych dla układu 74HC595:
Jak widać zegar jest normalnie niski (CPOL = 0) i jest próbkowany na zboczu wiodącym (CPHA = 0), więc jest to tryb SPI 0.
Możesz zmienić polaryzację zegara i fazę w kodzie w ten sposób (oczywiście wybierz tylko jedną):
Ta metoda jest przestarzała w wersjach 1.6.0 Arduino IDE. W przypadku najnowszych wersji zmieniasz tryb zegara w
SPI.beginTransaction
rozmowie, w ten sposób:Kolejność danych
Domyślnie jest to bit najbardziej znaczący jako pierwszy, jednak można powiedzieć sprzętowi, aby najpierw przetworzył bit o najmniejszym znaczeniu:
Ponownie jest to przestarzałe w wersjach 1.6.0 Arduino IDE. W przypadku najnowszych wersji zmieniasz kolejność bitów w
SPI.beginTransaction
wywołaniu, w następujący sposób:Prędkość
Domyślnym ustawieniem dla SPI jest użycie szybkości zegara systemowego podzielonej przez cztery, to znaczy jeden impuls zegarowy SPI co 250 ns, przy założeniu zegara procesora 16 MHz. Możesz zmienić dzielnik zegara, używając
setClockDivider
następującego sposobu:Gdzie „dzielnik” jest jednym z:
Najszybsza szybkość to „dzielenie przez 2” lub jeden impuls zegarowy SPI co 125 ns, przy założeniu zegara procesora 16 MHz. W związku z tym przesłanie jednego bajtu wymagałoby 8 * 125 ns lub 1 µs.
Ta metoda jest przestarzała w wersjach 1.6.0 Arduino IDE. W przypadku najnowszych wersji zmieniasz szybkość przesyłania w
SPI.beginTransaction
rozmowie, w ten sposób:Jednak testy empiryczne pokazują, że konieczne jest posiadanie dwóch impulsów zegarowych między bajtami, więc maksymalna szybkość, z jaką bajty mogą zostać wyrejestrowane, wynosi 1,125 µs każdy (z dzielnikiem zegara 2).
Podsumowując, każdy bajt może być wysyłany z maksymalną szybkością 1 na 1,125 µs (z zegarem 16 MHz), co daje teoretyczną maksymalną szybkość transferu 1 / 1,125 µs lub 888,888 bajtów na sekundę (z wyłączeniem narzutu, jak ustawienie niskiego SS i tak dalej na).
Łączenie z Arduino
Arduino Uno
Podłączanie za pomocą pinów cyfrowych 10 do 13:
Łączenie za pomocą nagłówka ICSP:
Arduino Atmega2560
Podłączanie za pomocą pinów cyfrowych od 50 do 52:
Możesz także użyć nagłówka ICSP, podobnego do powyższego Uno.
Arduino Leonardo
Leonardo i Micro nie odsłaniają pinów SPI na pinach cyfrowych, w przeciwieństwie do Uno i Mega. Jedyną opcją jest użycie pinów nagłówka ICSP, jak pokazano powyżej dla Uno.
Wielu niewolników
Mistrz może komunikować się z wieloma niewolnikami (jednak tylko jednym naraz). Robi to, zapewniając SS jednemu niewolnikowi i odznaczając go dla wszystkich pozostałych. Slave, który potwierdził SS (zwykle oznacza to LOW) konfiguruje swój pin MISO jako wyjście, aby slave i ten slave sam mogli odpowiedzieć na master. Inni niewolnicy ignorują wszelkie przychodzące impulsy zegara, jeśli SS nie jest zapewnione. Dlatego potrzebujesz jednego dodatkowego sygnału dla każdego urządzenia podrzędnego, takiego jak ten:
Na tej grafice widać, że MISO, MOSI, SCK są współużytkowane przez oba urządzenia podrzędne, jednak każde urządzenie podrzędne ma własny sygnał SS (wybór urządzenia podrzędnego).
Protokoły
Specyfikacja SPI nie określa protokołów jako takich, więc ustalenie znaczenia danych zależy od indywidualnych par master / slave. Chociaż możesz wysyłać i odbierać bajty jednocześnie, odebrany bajt nie może być bezpośrednią odpowiedzią na wysłany bajt (ponieważ są one składane jednocześnie).
Byłoby więc bardziej logiczne, aby jeden koniec wysłał żądanie (np. 4 może oznaczać „wylistuj katalog dysku”), a następnie wykonać transfery (być może po prostu wysyłając zera na zewnątrz), aż otrzyma pełną odpowiedź. Odpowiedź może zakończyć się znakiem nowej linii lub znakiem 0x00.
Przeczytaj arkusz danych dla urządzenia slave, aby zobaczyć, jakich sekwencji protokołów oczekuje.
Jak zrobić SPI slave
Wcześniejszy przykład pokazuje Arduino jako urządzenie nadrzędne, wysyłające dane do urządzenia podrzędnego. Ten przykład pokazuje, jak Arduino może być niewolnikiem.
Konfiguracja sprzętu
Połącz dwa Arduino Unos razem z następującymi pinami połączonymi ze sobą:
13 (SCK)
+ 5 V (jeśli wymagane)
W Arduino Mega piny to 50 (MISO), 51 (MOSI), 52 (SCK) i 53 (SS).
W każdym razie MOSI na jednym końcu jest podłączony do MOSI na drugim, nie zamieniasz ich (to znaczy, że nie masz MOSI <-> MISO). Oprogramowanie konfiguruje jeden koniec MOSI (koniec master) jako wyjście, a drugi koniec (koniec slave) jako wejście.
Przykład wzorcowy
Przykład niewolnika
Niewolnik jest całkowicie sterowany przerwaniami, więc może robić inne rzeczy. Przychodzące dane SPI są gromadzone w buforze i ustawiana flaga, gdy nadejdzie „znaczący bajt” (w tym przypadku nowa linia). To mówi slave'owi, aby wszedł i zaczął przetwarzać dane.
Przykład podłączenia Master do Slave za pomocą SPI
Jak uzyskać odpowiedź od niewolnika
Kontynuując powyższy kod, który wysyła dane z Master SPI do Slave, poniższy przykład pokazuje wysyłanie danych do Slave, każąc mu coś z tym zrobić i zwrócić odpowiedź.
Mistrz jest podobny do powyższego przykładu. Ważną kwestią jest jednak to, że musimy dodać niewielkie opóźnienie (około 20 mikrosekund). W przeciwnym razie urządzenie podrzędne nie ma szansy zareagować na przychodzące dane i coś z tym zrobić.
Przykład pokazuje wysłanie „polecenia”. W tym przypadku „a” (dodaj coś) lub „s” (odejmij coś). Ma to pokazać, że slave faktycznie robi coś z danymi.
Po potwierdzeniu wyboru slave (SS) w celu zainicjowania transakcji, master wysyła polecenie, a następnie dowolną liczbę bajtów, a następnie podnosi SS, aby zakończyć transakcję.
Bardzo ważną kwestią jest to, że niewolnik nie może odpowiedzieć na nadchodzący bajt w tym samym momencie. Odpowiedź musi być w następnym bajcie. Wynika to z faktu, że wysyłane bity i odbierane bity są wysyłane jednocześnie. Aby dodać coś do czterech liczb, potrzebujemy pięciu przelewów, takich jak to:
Najpierw prosimy o działanie na numer 10. Ale nie otrzymujemy odpowiedzi do następnego przelewu (ten dla 17). Jednak „a” zostanie ustawione na odpowiedź na 10. W końcu wysyłamy „manekina” numer 0, aby uzyskać odpowiedź za 42.
Master (przykład)
Kod dla urządzenia podrzędnego w zasadzie robi prawie wszystko w procedurze przerwania (wywoływanej, gdy nadchodzą przychodzące dane SPI). Pobiera przychodzący bajt i dodaje lub odejmuje zgodnie z zapamiętanym „bajtem polecenia”. Zauważ, że odpowiedź zostanie „zebrana” następnym razem przez pętlę. Dlatego mistrz musi wysłać jeden końcowy „fałszywy” transfer, aby uzyskać ostateczną odpowiedź.
W moim przykładzie używam pętli głównej, aby po prostu wykryć, kiedy SS idzie w górę, i wyczyścić zapisane polecenie. W ten sposób, gdy SS ponownie zostanie obniżone do następnej transakcji, pierwszy bajt jest uważany za bajt polecenia.
Bardziej niezawodnie można to zrobić z przerwą. Oznacza to, że fizycznie podłączysz SS do jednego z wejść przerwań (np. Na Uno, podłączysz pin 10 (SS) do pinu 2 (wejście przerwania) lub użyjesz przerwania zmiany pin na pinie 10.
Następnie przerwanie może być wykorzystane do zauważenia, kiedy SS jest ściągane na niskie lub wysokie.
Slave (przykład)
Przykładowe dane wyjściowe
Wyjście analizatora logicznego
To pokazuje czas pomiędzy wysłaniem a odbiorem w powyższym kodzie:
Nowa funkcjonalność w IDE 1.6.0 i nowszych
Wersja 1.6.0 IDE do pewnego stopnia zmieniła sposób działania SPI. Państwo nadal trzeba zrobić
SPI.begin()
przed użyciem SPI. To konfiguruje sprzęt SPI. Jednak teraz, kiedy masz zamiar zacząć komunikować się z niewolnika Państwo również zrobićSPI.beginTransaction()
, aby skonfigurować SPI (w tym slave) z popraw:Kiedy skończysz komunikować się z niewolnikiem, zadzwonisz
SPI.endTransaction()
. Na przykład:Dlaczego warto korzystać z SPI?
To doskonałe pytanie. Moje odpowiedzi to:
Obie metody mają swoje miejsce. I 2 C pozwala podłączyć wiele urządzeń do jednej magistrali (dwa przewody plus uziemienie), więc byłby to preferowany wybór, gdybyś potrzebował przesłuchać znaczną liczbę urządzeń, być może dość rzadko. Jednak szybkość SPI może być bardziej istotna w sytuacjach, w których trzeba szybko wysyłać dane (np. Pasek LED) lub szybko (np. Konwerter ADC).
Bibliografia
Moja strona o SPI - zawiera również szczegółowe informacje na temat SPI bit-banged i używania USART do uzyskania drugiego SPI sprzętowego na chipie Atmega328.
Serial Peripheral Interface Bus - Wikipedia
Strony referencyjne biblioteki Arduino SPI
Dokumentacja SPI w PJRC
Protokół SPI - Sparkfun
źródło
Are you going to cover the weirdness that is the Due's SPI?
- Nic nie wiem o interfejsie SP Due (oprócz założenia, że ogólny protokół jest taki sam). Możesz dodać odpowiedź dotyczącą tego aspektu.