STM32 i SPI
: czwartek 16 kwie 2020, 13:07
Hej
Miałem napisać coś o wyświetlaczu LCD, jednak ten z którym ostatnio walczyłem komunikował się z mikrokontorlerem z użyciem interfejsu SPI. Postanowiłem więc przed opisem LCD trochę czasu poświęcić na SPI. Interfejs ten jest dość szeroko stosowany, często opisywany, więc zbytnio nie będę poświęcał uwagi na jego opis. Po krótkim ogólnym wstępie opisze SPI w STM32. W opisie samego STM zagłębię się w rejestry, będzie to przydatne do zrozumienia kodu, który będzie w przykładach obsługi SPI. Oczywiście użytkownik w czasie swojej pracy nie musi tak nisko schodzić w struktury rejestrów, może zdać się na to co mu oprogramowanie CubeMx wygeneruje, a lwią część wykona za niego. Do zrozumienia samego SPI warto jednak zajrzeć pod maskę i popatrzeć co się tam dzieje.
Ogólnie o SPI
SPI (Serial Peripheral Interface) to szeregowy interfejs komunikacyjny pozwalający na wymianę danych pomiędzy dwoma lub więcej układami. O czym warto nadmienić, jest to interfejs synchroniczny, więc do przesyłania danych potrzebna jest synchronizacja układów na magistrali sygnałem zegarowym, może pracować także jako full duplex, czyli w jednym momencie możliwe jest zarówno nadawanie jak i odbiór danych.
Schemat połączeń układów na magistrali SPI przedstawiony jest poniżej.
Magistrala SPI wymaga jednego układu nadrzędnego (Master) zarządzającego magistralą. Pozostałe układy to układy podrzędne (Slave)
W skład SPI wchodzą trzy podstawowe linie sygnałowe:
MOSI – Master Output Slave Input, linia którą master, czyli układ nadrzędny wysyła dane do układów podrzędnych (slave). Układy podrzędne odbierają dane przychodzące na tej linii.
MISO – Master Input Slave Output, linia którą układy slave, czyli układy podrzędne wysyłają dane do układu nadrzędnego. Układ nadrzędny na tej linii odbiera dane z układów podrzędnych.
CLK – sygnał zegarowy, sygnał generowany przez master służący do synchronizacji układów na magistrali.
W SPI nie ma potrzeby krzyżowania linii nadawczej i odbiorczej jak w przypadku UART, gdzie linia TX jednego układu jest łączona z linią RX drugiego układu. Zgodnie z zasadą działania w interfejsie SPI linia nadawcza dla mastera jest linią odbiorczą dla slave i odwrotnie linia nadawcza dla slave jest linią odbiorczą dla mastera.
W magistrali SPI jest jeszcze czwarta linia, oznaczona jako SS (Slave Select) lub CS (Chip Select) Służy ona do aktywacji interfejsu komunikacyjnego w konkretnym układzie podrzędnym, do którego master ma wysłać dane. Linia ta jest aktywna w stanie niskim. Oznacza to, że normalnym stanem na liniach SS jest stan wysoki, układ nadrzędny przed rozpoczęciem transmisji wybiera konkretny układ podrzędny opuszczając stan (logiczne 0) na określonym pinie podpiętym do wybranej linii SS połączonej z żądanym układem. Po zakończeniu transmisji master na linii SS ponownie wystawia stan wysoki "odpinając” wcześniej wybrany układ podrzędny od interfejsu.
Transmisja
SPI jak na początku wspomniałem to interfejs szeregowy, więc transmitowany jest kolejno bit po bicie. Układ nadrzędny opuszczając linię SS aktywuje interfejs SPI w układzie podrzędnym, następnie zaczyna transmisję do układu podrzędnego przesyłając bit po bicie linią MOSI, w czasie transmisji slave może odesłać swoje dane wystawiając je na linię MISO. Do synchronizacji transmisji pomiędzy układami służy sygnał zegarowy generowany przez układ nadrzędny. Po zakończeniu transmisji master podnosi linię SS odpinając układ podrzędny od interfejsu. Poniżej przykładowa ramka danych.
Implementacja interfejsu SPI nie narzuca kolejności przesyłania danych. SPI pozwala na konfigurację kolejności przesyłanych bitów, od najstarszego do najmłodszego bitu lub odwrotnie. Nie precyzuje też sygnału zegarowego, pozwala określić polaryzacje sygnału zegarowego jak i wybór zbocza (narastającego lub opadającego) na których będą próbkowane dane na liniach wejściowych (MOSI oraz MISO) Kolejność przesyłanych bitów oraz określenie parametrów sygnału zegarowego należy dobrać zgodnie ze specyfikacją układów które będą podpięte do magistrali.
Myślę że tyle wystarczy wstępu o SPI, przejdźmy teraz do implementacji SPI w STM32.
SPI w STM32
Po krótkim opisie przyszedł czas na przedstawienie SPI jaki znajdziemy w STM32, pod ręką mam STM32G071KB, jednak w innych STMach SPI będzie bardzo podobne. SPI które jest w STMach może pracować w czterech trybach, full-duplex master, half-duplex master, full-duplex slave oraz half-duplex slave. STM32G071KB zawiera dwa interfejsy SPI.
Na wstępie spójrzmy na schemat blokowy, który będzie nam pomocny do zrozumienia dalszego opisu SPI. Nie jest to skomplikowany układ, znajdziemy w nim kontroler kontrolujący pracę SPI, generator sygnału zegarowego oraz część nadawczą i odbiorczą w skład której wchodzą bufory oraz rejestry przesuwne połączone z nadajnikami.
Tryb pracy
Na początek określmy tryb pracy naszego mikrokontrolera. Masz mikrokontroler może być układem nadrzędnym (master) inicjującym komunikację i zarządzającym komunikacją na magistrali, lub rzadziej układem podrzędnym (slave) Za wybór trybu master lub slave odpowiada bit MSTR znajdujący się w rejestrze SPIx_CR1.
Konfiguracja pozwala pracę w różnych trybach, ja jednak skupię się na najprostszej konfiguracji najczęściej spotykanej i używanej, nie będę się zagłębiał w liczenie CRC, tryby half-duplex czy multi-master, w którym na jednej magistrali może być więcej niż jeden układ nadrzędny.
Sygnał zegarowy
Sygnał zegarowy (SCK) służący do synchronizacji komunikacji między układami master oraz slave, generowany jest przez układ master. Pochodzi on z bloku “baud rate generator”, jest to dzielnik częstotliwości, który dzieli częstotliwość dostarczaną przez fPCLK, dla omawianego mikrokontrolera (STM32G071KB) maksymalna częstotliwość z jaką mogą być transmitowane dane to fPCLK/2, a więc 32Mb/s. Jest to dość duża wartość, nie wszystkie układy na magistrali SPI mogą z tak dużą prędkością pracować, a czasem konstrukcja urządzania uniemożliwia transfer danych z taką prędkością. Dzielnik częstotliwości pozwala na wybór częstotliwości zegara taktującego magistralę, do wyboru mamy podział fPCLK przez wartości 2, 4, 8, 16, 32, 128, 256. Wybór dzielnika dokonujemy w SPI control register 1 (SPIx_CR1, bity BR[2:0]: Baud rate control)
SPI pozwala nam na określenie polaryzacji sygnału zegarowego oraz wybór zbocza zegara (narastającego lub opadającego) na którym będą odczytywane transmitowane bity. Konfiguracja ta związana jest ze specyfikacją układów jakie współpracują na magistrali SPI. Za wybór polaryzacji sygnału zegarowego odpowiada bit CPOL, za wybór zbocza odpowiada bit CPHA, oba bity znajdują się w rejestrze SPIx_CR1. Samą zasadę wyboru polaryzacji zegara i zbocza najlepiej pokazuje dokumentacja od ST
Format I długość ramki
SPI pozwala określić kolejność transmisji: od najstarszego do najmłodszego bitu lub odwrotnie (MSB -> LSB lub LSB -> MSB) jak i długość ramki. Do wyboru mamy długość ramki od 4 do 16 bitów. Kolejność wysyłania bitów określmy bitem LSBFIRST w rejestrze konfiguracyjnym SPIx_CR1, natomiast długość ramki określają bity DS[3:0] w rejestrze konfiguracyjnym 2 (SPIx_CR2 bity DS[3:0]: Data size)
Sterowanie pinem SS
Aby aktywować interfejs w układzie podrzędnym master na czas transmisji musi ustawić na linii SS logiczne zero. Możemy to zrobić programowo, na określony pin, który służy jako linia SS ustawić logiczne zero. Możemy też polegać na sprzętowym zarządzaniu sygnałem SS, wówczas w czasie transmisji SPI samo zajmie się zarządzaniem pinem SS (w dokumentacji oznaczony jako NSS) konfiguracja zarzadzaniem sygnałem SS (w dokumentacji ST NSS) jest w rejestrze kontrolnym 1 (SPIx_CR1, bit SSM)
Transmisja danych
Przyjrzyjmy się teraz części nadawczo odbiorczej, jak pokazuje schemat blokowy składa się on dwóch bliźniaczych par w skład których wchodzi bufor 4x8bit tworzący kolejkę FIFO oraz rejestr przesuwny. Jedna para to część nadawcza, druga jest częścią odbiorczą. Obie te części nie są dostępne bezpośrednio dla użytkownika, użytkownik / programista ma do dyspozycji tylko rejestr DR, do którego zapisuje dane do wysłania jak i z którego odczytuje odebrane dane. SPI po załadowaniu przez użytkownika rejestru DR nową wartością przerzuca ją w tle do bufora nadawczego TXFIFO, który kolejkuje dane do wysłania. Dane w buforze oczekują na gotowość rejestru przesuwnego na przyjęcie kolejnego bajta danych do finalnej wysyłki na magistralę SPI. Zgodnie z zasadą kolejki FIFO do rejestru przesuwnego trafiają dane w kolejności jakiej były ładowane do bufora, a więc również w kolejności jakiej były zapisywane do rejestru DR. Po zwolnieniu rejestru przesuwnego w części nadawczej pobierany jest z kolejki TXFIFO kolejny bajt danych, który bit po bicie jest wystawiany na magistralę SPI. Dane odczytywane przez SPI również są kolejkowane w buforze odbiorczym RXFIFO, odczyt rejestru DR zwraca najstarszą wartość z kolejki bufora odbiorczego.
Statusy buforów są sygnalizowane poprzez flagi w rejestrze "SPI status register", flaga dla bufora nadawczego (TXE) jest ustawiania w momencie, kiedy poziom załadowania bufora jest mniejszy lub równy połowie swojej pojemności. W przeciwnym razie flaga jest kasowana a bufor uważany jest za pełny. Bufor odbiorczy ma również swoja flagę (RXNE) jednak próg, przy którym flaga jest ustawiana jest konfigurowalny, w konfiguracji SPI możemy zdecydować czy flaga ma być ustawiana kiedy zapełnienie bufora odbiorczego osiągnie ¼ jego pojemności (8bitów) czy ½ pojemności (16bitów) To jest ważne ze względu na długość ramki, jaką wysyłamy przez SPI. Domyślnie flaga RXNE dla bufora odbiorczego jest ustawiania po osiągnięciu połowy swojej pojemności, co przy ramce 8 bitów będzie problematyczne, o czym wspomnę pokazując przykładowe kody.
Tu jedna mała uwaga odnośnie bufora nadawczego, z reguły wysyłamy przez SPI słowa o długości 8 bitów, bufor nadawczy ma całkowitą pojemność 32 bity, zapisaliśmy 3 bajty, więc jest jeszcze miejsce na jeden bajt danych. Dlaczego flaga TXE już informuje, że bufor jest pełny? Tu przypomnę, że długość ramki wysyłanej przez SPI jest konfigurowalna i może sięgać 16bitów, czyli połowę wielkości bufora, a nie ma możliwości konfiguracji progu zgłaszania zapełnienia bufora, jak przypadku bufora odbiorczego. A więc załadowanie dwóch słów 16bitowych zapełnia bufor w 100%.
W drugiej części zaprezentuję od strony praktycznej konfigurację i wykorzystanie SPI.
Miałem napisać coś o wyświetlaczu LCD, jednak ten z którym ostatnio walczyłem komunikował się z mikrokontorlerem z użyciem interfejsu SPI. Postanowiłem więc przed opisem LCD trochę czasu poświęcić na SPI. Interfejs ten jest dość szeroko stosowany, często opisywany, więc zbytnio nie będę poświęcał uwagi na jego opis. Po krótkim ogólnym wstępie opisze SPI w STM32. W opisie samego STM zagłębię się w rejestry, będzie to przydatne do zrozumienia kodu, który będzie w przykładach obsługi SPI. Oczywiście użytkownik w czasie swojej pracy nie musi tak nisko schodzić w struktury rejestrów, może zdać się na to co mu oprogramowanie CubeMx wygeneruje, a lwią część wykona za niego. Do zrozumienia samego SPI warto jednak zajrzeć pod maskę i popatrzeć co się tam dzieje.
Ogólnie o SPI
SPI (Serial Peripheral Interface) to szeregowy interfejs komunikacyjny pozwalający na wymianę danych pomiędzy dwoma lub więcej układami. O czym warto nadmienić, jest to interfejs synchroniczny, więc do przesyłania danych potrzebna jest synchronizacja układów na magistrali sygnałem zegarowym, może pracować także jako full duplex, czyli w jednym momencie możliwe jest zarówno nadawanie jak i odbiór danych.
Schemat połączeń układów na magistrali SPI przedstawiony jest poniżej.
Magistrala SPI wymaga jednego układu nadrzędnego (Master) zarządzającego magistralą. Pozostałe układy to układy podrzędne (Slave)
W skład SPI wchodzą trzy podstawowe linie sygnałowe:
MOSI – Master Output Slave Input, linia którą master, czyli układ nadrzędny wysyła dane do układów podrzędnych (slave). Układy podrzędne odbierają dane przychodzące na tej linii.
MISO – Master Input Slave Output, linia którą układy slave, czyli układy podrzędne wysyłają dane do układu nadrzędnego. Układ nadrzędny na tej linii odbiera dane z układów podrzędnych.
CLK – sygnał zegarowy, sygnał generowany przez master służący do synchronizacji układów na magistrali.
W SPI nie ma potrzeby krzyżowania linii nadawczej i odbiorczej jak w przypadku UART, gdzie linia TX jednego układu jest łączona z linią RX drugiego układu. Zgodnie z zasadą działania w interfejsie SPI linia nadawcza dla mastera jest linią odbiorczą dla slave i odwrotnie linia nadawcza dla slave jest linią odbiorczą dla mastera.
W magistrali SPI jest jeszcze czwarta linia, oznaczona jako SS (Slave Select) lub CS (Chip Select) Służy ona do aktywacji interfejsu komunikacyjnego w konkretnym układzie podrzędnym, do którego master ma wysłać dane. Linia ta jest aktywna w stanie niskim. Oznacza to, że normalnym stanem na liniach SS jest stan wysoki, układ nadrzędny przed rozpoczęciem transmisji wybiera konkretny układ podrzędny opuszczając stan (logiczne 0) na określonym pinie podpiętym do wybranej linii SS połączonej z żądanym układem. Po zakończeniu transmisji master na linii SS ponownie wystawia stan wysoki "odpinając” wcześniej wybrany układ podrzędny od interfejsu.
Transmisja
SPI jak na początku wspomniałem to interfejs szeregowy, więc transmitowany jest kolejno bit po bicie. Układ nadrzędny opuszczając linię SS aktywuje interfejs SPI w układzie podrzędnym, następnie zaczyna transmisję do układu podrzędnego przesyłając bit po bicie linią MOSI, w czasie transmisji slave może odesłać swoje dane wystawiając je na linię MISO. Do synchronizacji transmisji pomiędzy układami służy sygnał zegarowy generowany przez układ nadrzędny. Po zakończeniu transmisji master podnosi linię SS odpinając układ podrzędny od interfejsu. Poniżej przykładowa ramka danych.
Implementacja interfejsu SPI nie narzuca kolejności przesyłania danych. SPI pozwala na konfigurację kolejności przesyłanych bitów, od najstarszego do najmłodszego bitu lub odwrotnie. Nie precyzuje też sygnału zegarowego, pozwala określić polaryzacje sygnału zegarowego jak i wybór zbocza (narastającego lub opadającego) na których będą próbkowane dane na liniach wejściowych (MOSI oraz MISO) Kolejność przesyłanych bitów oraz określenie parametrów sygnału zegarowego należy dobrać zgodnie ze specyfikacją układów które będą podpięte do magistrali.
Myślę że tyle wystarczy wstępu o SPI, przejdźmy teraz do implementacji SPI w STM32.
SPI w STM32
Po krótkim opisie przyszedł czas na przedstawienie SPI jaki znajdziemy w STM32, pod ręką mam STM32G071KB, jednak w innych STMach SPI będzie bardzo podobne. SPI które jest w STMach może pracować w czterech trybach, full-duplex master, half-duplex master, full-duplex slave oraz half-duplex slave. STM32G071KB zawiera dwa interfejsy SPI.
Na wstępie spójrzmy na schemat blokowy, który będzie nam pomocny do zrozumienia dalszego opisu SPI. Nie jest to skomplikowany układ, znajdziemy w nim kontroler kontrolujący pracę SPI, generator sygnału zegarowego oraz część nadawczą i odbiorczą w skład której wchodzą bufory oraz rejestry przesuwne połączone z nadajnikami.
Tryb pracy
Na początek określmy tryb pracy naszego mikrokontrolera. Masz mikrokontroler może być układem nadrzędnym (master) inicjującym komunikację i zarządzającym komunikacją na magistrali, lub rzadziej układem podrzędnym (slave) Za wybór trybu master lub slave odpowiada bit MSTR znajdujący się w rejestrze SPIx_CR1.
Konfiguracja pozwala pracę w różnych trybach, ja jednak skupię się na najprostszej konfiguracji najczęściej spotykanej i używanej, nie będę się zagłębiał w liczenie CRC, tryby half-duplex czy multi-master, w którym na jednej magistrali może być więcej niż jeden układ nadrzędny.
Sygnał zegarowy
Sygnał zegarowy (SCK) służący do synchronizacji komunikacji między układami master oraz slave, generowany jest przez układ master. Pochodzi on z bloku “baud rate generator”, jest to dzielnik częstotliwości, który dzieli częstotliwość dostarczaną przez fPCLK, dla omawianego mikrokontrolera (STM32G071KB) maksymalna częstotliwość z jaką mogą być transmitowane dane to fPCLK/2, a więc 32Mb/s. Jest to dość duża wartość, nie wszystkie układy na magistrali SPI mogą z tak dużą prędkością pracować, a czasem konstrukcja urządzania uniemożliwia transfer danych z taką prędkością. Dzielnik częstotliwości pozwala na wybór częstotliwości zegara taktującego magistralę, do wyboru mamy podział fPCLK przez wartości 2, 4, 8, 16, 32, 128, 256. Wybór dzielnika dokonujemy w SPI control register 1 (SPIx_CR1, bity BR[2:0]: Baud rate control)
SPI pozwala nam na określenie polaryzacji sygnału zegarowego oraz wybór zbocza zegara (narastającego lub opadającego) na którym będą odczytywane transmitowane bity. Konfiguracja ta związana jest ze specyfikacją układów jakie współpracują na magistrali SPI. Za wybór polaryzacji sygnału zegarowego odpowiada bit CPOL, za wybór zbocza odpowiada bit CPHA, oba bity znajdują się w rejestrze SPIx_CR1. Samą zasadę wyboru polaryzacji zegara i zbocza najlepiej pokazuje dokumentacja od ST
Format I długość ramki
SPI pozwala określić kolejność transmisji: od najstarszego do najmłodszego bitu lub odwrotnie (MSB -> LSB lub LSB -> MSB) jak i długość ramki. Do wyboru mamy długość ramki od 4 do 16 bitów. Kolejność wysyłania bitów określmy bitem LSBFIRST w rejestrze konfiguracyjnym SPIx_CR1, natomiast długość ramki określają bity DS[3:0] w rejestrze konfiguracyjnym 2 (SPIx_CR2 bity DS[3:0]: Data size)
Sterowanie pinem SS
Aby aktywować interfejs w układzie podrzędnym master na czas transmisji musi ustawić na linii SS logiczne zero. Możemy to zrobić programowo, na określony pin, który służy jako linia SS ustawić logiczne zero. Możemy też polegać na sprzętowym zarządzaniu sygnałem SS, wówczas w czasie transmisji SPI samo zajmie się zarządzaniem pinem SS (w dokumentacji oznaczony jako NSS) konfiguracja zarzadzaniem sygnałem SS (w dokumentacji ST NSS) jest w rejestrze kontrolnym 1 (SPIx_CR1, bit SSM)
Transmisja danych
Przyjrzyjmy się teraz części nadawczo odbiorczej, jak pokazuje schemat blokowy składa się on dwóch bliźniaczych par w skład których wchodzi bufor 4x8bit tworzący kolejkę FIFO oraz rejestr przesuwny. Jedna para to część nadawcza, druga jest częścią odbiorczą. Obie te części nie są dostępne bezpośrednio dla użytkownika, użytkownik / programista ma do dyspozycji tylko rejestr DR, do którego zapisuje dane do wysłania jak i z którego odczytuje odebrane dane. SPI po załadowaniu przez użytkownika rejestru DR nową wartością przerzuca ją w tle do bufora nadawczego TXFIFO, który kolejkuje dane do wysłania. Dane w buforze oczekują na gotowość rejestru przesuwnego na przyjęcie kolejnego bajta danych do finalnej wysyłki na magistralę SPI. Zgodnie z zasadą kolejki FIFO do rejestru przesuwnego trafiają dane w kolejności jakiej były ładowane do bufora, a więc również w kolejności jakiej były zapisywane do rejestru DR. Po zwolnieniu rejestru przesuwnego w części nadawczej pobierany jest z kolejki TXFIFO kolejny bajt danych, który bit po bicie jest wystawiany na magistralę SPI. Dane odczytywane przez SPI również są kolejkowane w buforze odbiorczym RXFIFO, odczyt rejestru DR zwraca najstarszą wartość z kolejki bufora odbiorczego.
Statusy buforów są sygnalizowane poprzez flagi w rejestrze "SPI status register", flaga dla bufora nadawczego (TXE) jest ustawiania w momencie, kiedy poziom załadowania bufora jest mniejszy lub równy połowie swojej pojemności. W przeciwnym razie flaga jest kasowana a bufor uważany jest za pełny. Bufor odbiorczy ma również swoja flagę (RXNE) jednak próg, przy którym flaga jest ustawiana jest konfigurowalny, w konfiguracji SPI możemy zdecydować czy flaga ma być ustawiana kiedy zapełnienie bufora odbiorczego osiągnie ¼ jego pojemności (8bitów) czy ½ pojemności (16bitów) To jest ważne ze względu na długość ramki, jaką wysyłamy przez SPI. Domyślnie flaga RXNE dla bufora odbiorczego jest ustawiania po osiągnięciu połowy swojej pojemności, co przy ramce 8 bitów będzie problematyczne, o czym wspomnę pokazując przykładowe kody.
Tu jedna mała uwaga odnośnie bufora nadawczego, z reguły wysyłamy przez SPI słowa o długości 8 bitów, bufor nadawczy ma całkowitą pojemność 32 bity, zapisaliśmy 3 bajty, więc jest jeszcze miejsce na jeden bajt danych. Dlaczego flaga TXE już informuje, że bufor jest pełny? Tu przypomnę, że długość ramki wysyłanej przez SPI jest konfigurowalna i może sięgać 16bitów, czyli połowę wielkości bufora, a nie ma możliwości konfiguracji progu zgłaszania zapełnienia bufora, jak przypadku bufora odbiorczego. A więc załadowanie dwóch słów 16bitowych zapełnia bufor w 100%.
W drugiej części zaprezentuję od strony praktycznej konfigurację i wykorzystanie SPI.