KursyPoradnikiInspirujące DIYForum

Kurs STM32 – #9 – SPI w praktyce, ekspander I/O

Kurs STM32 – #9 – SPI w praktyce, ekspander I/O

Poznaliśmy już jeden interfejs szeregowy UART, który był asynchroniczny. Teraz poznamy kolejny, tym razem synchroniczny SPI.

Jest to prosty i szybki interfejs, pozwalający na podłączanie różnych układów peryferyjnych. W tej części kursu wykorzystamy go do podłączenia ekspandera portów.

SPI vs. UART - najważniejsze różnice

Interfejs UART wykorzystywaliśmy do komunikacji z komputerem PC. Jego podstawową cechą jest asynchroniczność - czyli brak konieczności przesyłania sygnału zegarowego. Jak pamiętamy interfejs do komunikacji wykorzystuje raptem dwie linie RX oraz TX. Wadą takiego rozwiązania jest niewielka prędkość transmisji. Więcej na ten temat znaleźć można w 5 części kursu STM32.

Teraz poznamy nowy interfejs - SPI. Jest to interfejs synchroniczny, więc sygnał zegarowy będzie przesyłany między komunikującymi się układami. W odróżnieniu od UART, w przypadku SPI, komunikujące się układy nie są równorzędne. Jeden jest układem nadzorującym (ang. Master), a drugi podwładnym (ang. Slave). Komunikacja podobnie jak poprzednio odbywa się jednocześnie w obu kierunkach.

Podłączenie SPI

Schemat podstawowego podłączenia jest następujący:

381px-SPI_single_slave.svg

Źródło: Wikipedia

Jak widzimy do podłączenia potrzebujemy 4 linii. Najczęściej master-em jest mikrokontroler i takim przypadkiem będziemy się dalej zajmować. Można również wykorzystywać STM32 jako slave, ale wbrew pozorom jest to trudniejszy przypadek, niż wersja master.

Pierwsza linia jest oznaczana SS (Slave Select), która służy do wyboru urządzenia – pozostałe linie mogą być podpięte do wielu układów, ale tylko jeden może być w danej chwili aktywny. Linia ta często jest nazywana CS, czyli Chip Select - i tej nazwy będziemy używać podczas kursu.

Pozostałe linie to:

  • MOSI (Master Output Slave Input) – dane wysyłane z mastera do slave
  • MISO (Master Input Slave Output) – dane wysyłane z slave do mastera
  • SCLK (Serial Clock) – linia zegara

Dzięki temu, że sygnał zegarowy jest przesyłany na liniach interfejsu, komunikacja jest znacznie łatwiejsza. Możliwe jest również uzyskanie znaczenie większych prędkości transmisji niż w przypadku interfejsu UART.

Wybór aktywnego układu dokonywany jest przez wystawienie stanu niskiego na odpowiedniej linii CS. Tylko jeden układ może być aktywny w danej chwili (pozostałych liniach CS stan wysoki).

363px-SPI_three_slaves.svg

Źródło: Wikipedia

Teraz pora na przejście do praktyki. Na początku zajmiemy się ekspanderem portów, a w kolejnej części wyświetlaczem graficznym!

Czas przejść do praktyki!

Czas przejść do praktyki!

Komunikacja przez SPI

Mikrokontroler STM32F103RB posiada dwa interfejsy SPI, każdy o prędkości transmisji do 18 Mbit/s. Do przykładów wykorzystamy pierwszy interfejs, czyli SPI1. Na początek musimy odszukać w dokumentacji, na których pinach dostępne są wyprowadzenia interfejsu:

  • SPI1_SCK - PA5
  • SPI1_MISO - PA6
  • SPI1_MOSI - PA7

Moglibyśmy wykorzystać sprzętowe sterowanie linią CS (dostępna na linii PA4), jednak w przypadku master-a sterowanie jest bardzo proste - wystarczy wystawić logiczne 0 na początek transmisji oraz 1, gdy  komunikacja zostanie zakończona.

Wykorzystamy więc zwykłą linię GPIO. Dzięki temu będziemy mogli podłączyć więcej niż jeden układ slave (sprzętowo możemy obsłużyć tylko jedną linię CS, więc tylko jeden układ slave). Sprzętowe sterowanie linią CS jest natomiast bardzo wygodnie, gdybyśmy implementowali układ typu slave.

Gotowe zestawy do kursów Forbota

 Komplet elementów  Gwarancja pomocy  Wysyłka w 24h

Zestaw ponad 120 elementów do przeprowadzenia wszystkich ćwiczeń z kursu można nabyć u naszych dystrybutorów! Dostępne są wersje z płytką Nucleo lub bez niej!

Zamów w Botland.com.pl »

Wstępne ustawienia SPI

Chcąc oprogramować interfejs SPI, zaczynamy jak zawsze - od podłączenia zegarów:

Następnie konfigurujemy piny interfejsu. Zaczniemy od linii wyjściowych, czyli SCK oraz MOSI:

Konfiguracja przypomina stosowaną dla linii TX interfejsu UART. Jest  to wyjście sterowane sprzętowo, w trybie push-pull. Zmieniamy prędkość linii na 50 MHz - domyślnie konfiguracja używa linii o maksymalnej prędkości 2MHz, więc zbyt wolnych dla interfejsu, który może osiągać prędkość 18 Mbit/s (czyli 18 MHz).

Kolejny krok to konfiguracja linii wejściowej MISO. Tutaj też zobaczymy podobieństwo do RX interfejsu UART:

Ostatni krok to linia CS. Ponieważ stosujemy sterowanie programowe, możemy wybrać dowolny, dostępny pin. Wykorzystamy PC0 ponieważ mamy już opanowane sterowanie portem C:

Zaraz po inicjalizacji linii wystawiamy na niej logiczną 1. Linia CS jest aktywowana stanem niskim, a nie chcemy, żeby układ podłączony do tej linii CS działał w tej chwili - wystawiając 1, wyłączamy komunikację z podłączonym układem slave.

Konfiguracja interfejsu SPI

Mając skonfigurowane piny, możemy uruchomić sam interfejs SPI. Postępowanie przebiega jak zwykle: deklarujemy zmienną konfiguracyjną typu SPI_InitTypeDef, inicjalizujemy jej pola domyślnymi wartościami za pomocą funkcji SPI_StructInit, ustawiamy interesującą nas konfigurację, po czym wywołujemy SPI_Init, aby przygotować moduł SPI do pracy:

Ustawiamy tryb pracy jako SPI_Mode_Master. Ponieważ będziemy sami sterować linią CS (NSS to inna nazwa CS) wybieramy tryb programowy SPI_NSS_Soft. Zegar systemowy, jak pamiętamy ma częstotliwość 64 MHz - jest to zbyt wysoka wartość dla interfejsu SPI. Stosujemy więc dzielnik 16, aby otrzymać prędkość 4 MHz (czyli 4 Mbit/s).

Po inicjalizacji SPI, uruchamiamy interfejs wywołując funkcję SPI_Cmd.

Procedura komunikacji

Gdy mamy skonfigurowane SPI, możemy napisać procedurę komunikującą się poprzez ten interfejs. Dane są jednocześnie wysyłane (przez linię MOSI) oraz odbierane (linia MISO), więc zamiast typowych dwóch procedur - do wysyłania i odbierania, napiszemy jedną. Jeśli będziemy chcieli tylko wysyłać, zignorujemy zwracaną wartość. Natomiast odbierając, i tak musimy coś wysłać (najczęściej wysyła się wtedy same logiczne jedynki, czyli wartość 0xff).

Kod procedury wygląda następująco:

Aby skorzystać z interfejsu, musimy jeszcze wysterować odpowiednio linię CS. Przykladowe wysłanie jednego bajtu wygląda więc następująco:

W pierwszej linii zerujemy stan CS, czyli aktywujemy układ slave. Następnie wysyłamy bajt o wartości 0x40 (przykładowa wartość). Możemy wysłać na raz więcej danych i najczęściej właśnie tak się postępuje. Gdy skończymy komunikować się z układem, zwalniamy interfejs wystawiając logiczne 1 na linii CS.

Pełny kod przykładu:

Ekspander portów MCP23S08

Umiemy już skonfigurować SPI, czas wykorzystać naszą wiedzę w praktyce. Jako pierwszy przykład wykorzystamy układ MCP23S08, czyli ekspander wyprowadzeń I/O.

Opis wyprowadzeń układu MCP23S08.

Opis wyprowadzeń układu MCP23S08.

Zaczniemy od powtórzenia przykładu z migającą diodą, który wykorzystywaliśmy w części 4 kursu, omawiając GPIO. Tym razem diodę podłączymy nie bezpośrednio do mikrokontrolera, ale do I/O ekspandera. Schemat podłączenia przedstawia rysunek:

Sterowanie wyjściem ekspandera przez SPI.

Sterowanie wyjściem ekspandera przez SPI.

Połączenie między mikrokontrolerem, a MCP23S08 jest zgodne z tym, co mówiliśmy o SPI. Jako linię CS wykorzystujemy PC0, poza tym podłączyliśmy MISO, MOSI oraz SCLK. MCP23S08 posiada 8 linii wejścia/wyjścia. Są one sterowane za pomocą rejestrów, podobnie jak np.: układy AVR. W dokumentacji znajdziemy pełny opis dostępnych rejestrów:

MCP23S08_reg

Rejestry dostępne w MCP23S08.

Nas będą interesowały rejestry:

  • IODIR - ustawia kierunek działania linii, podobnie jak DDRA w przypadku AVR
  • GPIO - pozwala na odczyt stanów linii, odpowiada PINA dla AVR
  • OLAT - zapis do tego rejestru ustawia stan linii wyjściowych - odpowiada PORTA
  • GPPU - pozwala na podłączenie rezystorów podciągających

Ponieważ niewygodne jest pamiętanie adresu każdego rejestru, zdefiniujemy odpowiednie stałe:

Komunikacja z ekspanderem

Kolejny krok, to odszukanie w dokumentacji, jak wygląda zapis i odczyt z odpowiednich rejestrów. Zapis wygląda następująco

  1. wysyłamy identyfikator urządzenia 0x40,
  2. następnie adres rejestru, przykładowo MCP_OLAT,
  3. oraz wartość do zapisania.

Całość możemy zapisać jako krótką procedurę:

Teraz możemy napisać właściwy program. Po skonfigurowaniu SPI trzeba ustawić odpowiednie rejestry układu MCP23S08. Chcemy, aby pin zerowy był wyjściem. W tym celu musimy wyzerować pierwszy bit rejestru IODIR (1 - oznacza wejście, 0 - wyjście):

Włączenie i wyłączenie diody odbywa się poprzez zapis do rejestru OLAT:

Kompletny program migający diodą wygląda następująco:

Działanie programu w praktyce widoczne jest na poniższym filmie:

Zadanie domowe 9.1

Podłącz do ekspandera kilka diod świecących np.: 4 i napisz program, który pozwala na włączanie i wyłączanie diod poprzez wysyłanie odpowiednich komend sterujących przez UART (przykładowo 1on, 1off itd.).

Zadanie domowe 9.2

Wykorzystaj podłączenie z zadanie 9.1 i stwórz licznik Johnsona. Do układu podłącz dodatkowo potencjometr, którym będzie można regulować prędkość pracy licznika (w tym celu użyj ADC).

Obsługa przycisku

Sprawdziliśmy jak sterować wyjściem ekspandera. Teraz przetestujemy linię wejściową. W tym celu możemy dodać na płytce jakiś przełącznik, lub najprościej wykorzystać przewód, którym będziemy zwierać odpowiednie sygnały. Na poniższym schemacie montażowym role przełącznika odgrywa pomarańczowy przewód, który wyróżniony został strzałką.

Odczytywanie wejść ekspandera przez SPI.

Odczytywanie wejść ekspandera przez SPI.

Ponieważ nie podłączyliśmy zewnętrznego rezystora podciągającego, będziemy musieli włączyć odpowiedni rezystor wbudowany w MCP23S08. W tym celu wystarczy ustawić odpowiedni bit rejestru GPPU:

Stan przycisku odczytamy z rejestru GPIO. Musimy więc przygotować procedurę odczytywania rejestrów (zapisywać już potrafimy).

Opis protokołu znajdziemy w dokumentacji MCP23S08 - odczyt z rejestru przebiega następująco:

  1. wysyłamy identyfikator urządzenia 0x41,
  2. następnie adres rejestru np. MCP_GPIO,
  3. odbieramy zawartość rejestru.

Funkcja odczytująca rejestr ma więc postać:

Jak pamiętamy komunikacja przez SPI za każdym razem odbywa się w obu kierunkach. Więc kiedy wysyłamy np. identyfikator 0x41, jednocześnie odbieramy wartość od układu peryferyjnego. Jednak ta wartość jest nieistotna, więc nie zapamiętujemy jej. Podobnie dzieje się podczas wysyłania adresu rejestru.

Teraz możemy napisać program, który będzie włączał diodę po naciśnięciu przycisku:

Zadanie domowe 9.3

Skonfiguruj jeden pin ekspandera jako wejście, a pozostałe jako wyjścia, do których podłączone będą diody świecące. Stan diod powinien wizualizować wyjście licznika binarnego, którego kierunek pracy określany ja za pomocą stanu panującego na jedynym wejściu ekspandera.

Podsumowanie

W tej części poznaliśmy interfejs SPI, za pomocą którego możemy rozszerzać możliwości mikrokontrolera poprzez podłączanie układów peryferyjnych. Jako przykład posłużył nam moduł dodatkowych linii wejścia-wyjścia MCP23S08. Układy tego okazują się niezastąpione, gdy zaczyna brakować wolnych portów I/O mikrokontrolerze.

Nawigacja kursu

W kolejnej części kursu zobaczymy jak za pomocą SPI sterować graficznym wyświetlaczem LCD! Od tego momentu wszystkie projekty będą mogły komunikować się z użytkownikiem w kolejny, ciekawy sposób.

A w następnym odcinku...

A w następnym odcinku...

Autor kursu: Piotr (Elvis) Bugalski
Redakcja: Damian (Treker) Szymański

ekspander, kursSTM32, SPI, stm32

Trwa ładowanie komentarzy...