Przypomnienie podstawowych informacji o SPI
SPI to skrót od Serial Peripheral Interface. Do magistrali dołączane są równolegle układy z interfejsem SPI, z których jeden zawsze pełni funkcję nadrzędną (master) a pozostałe – podrzędną (slave). Magistrala składa się z czterech linii. Linią MOSI przesyłane są szeregowo dane od mastera do slave’a, a linią MISO – od slave’a do mastera. Na linię SCL podawane są impulsy zegara, który synchronizuje transmisję danych. Linia Slave Select (SS) służy do wybierania aktywnego urządzenia podrzędnego (slave). Połączenia pomiędzy urządzeniem nadrzędnym i podrzędnym oraz kierunki przepływu sygnałów pokazane zostały na rysunku 1. Przesył danych zawsze inicjuje master, wystawiając na linii SCL impulsy zegara i wybierając poziomem napięcia na wyprowadzeniu SS odpowiednie urządzenie podrzędne. Po zakończeniu przesyłania wszystkich bitów transmisji master dezaktywuje linię SS. Może potem przeprowadzić nowy cykl wymiany danych z tym samym urządzeniem lub aktywować inną linię SS, jeżeli do magistrali podłączone są jeszcze inne slave’y.
Moduły ESP32 mają 4 niezależne sterowniki SPI, przy czym dwa pierwsze (SPI0 i SPI1) używane są przez system do dostępu do wewnętrznej pamięci podręcznej FLASH i nie powinny być wykorzystywane przez oprogramowanie użytkownika. Natomiast dwa pozostałe sterowniki HSPI (SPI2) i VSPI (SPI3) mogą być używane i konfigurowane do pracy w trybie nadrzędnym (master) lub podrzędnym (slave). Stabilna szybkość transmisji sięga 10 MB/s.
Interfejsy API sterowników SPI
ESP-IDF dostarcza biblioteki, które sterują urządzeniami peryferyjnymi SPI modułu ESP32. W przypadku trybu slave jest to biblioteka driver/spi_slave.h, której najistotniejsze procedury to:
spi_slave_initialize() – inicjalizcja sterownika SPI jako urządzenia podrzędnego. Funkcja ma cztery parametry:
- host – wybór bloku SPI; parametr może przyjąć wartość SPI2_HOST lub SPI3_HOST,
- bus_config – wskaźnik na strukturę spi_bus_config_t która służy między innymi do przypisania funkcji linii magistrali SPI do portów GPIO,
- slave_config – wskaźnik na strukturę spi_slave_interface_config_t, zawierającą szczegóły interfejsu podrzędnego,
- dma_chan – wybór kanału DMA; dopuszczalne wartości to SPI_DMA_DISABLED i SPI_DMA_CH_AUTO.
spi_slave_transmit() wykonuje transakcję przez urządzenie podrzędne. Funkcja ma trzy parametry:
- host – numer sterownika SPI,
- trans_desc – wskaźnik na zmienną typu spi_slave_transaction_t, która zawiera opis wykonywanej transakcji, między innymi rozmiar danych (liczbę bitów) i wskaźniki na bufory, odbiorczy i nadawczy,
- ticks_to_wait – limit czasu oczekiwania na zakończenie transakcji.
Do obsługi trybu master przeznaczona jest biblioteka driver/spi_master.h, której najistotniejsze procedury to:
spi_bus_initialize() inicjalizacja sterownika w trybie master. Funkcja ma trzy parametry:
- host_id – parametr może przyjąć wartość SPI2_HOST lub SPI3_HOST,
- bus_config – wskaźnik na strukturę typu spi_bus_config_t. Zmienne struktury konfigurują wyprowadzenia GPIO jako line MISO, MOSI, CLK. Przypisanie wartości –1 oznacza, że dana linia magistrali nie będzie używana. Zmienna max_transfer_sz określa maksymalny rozmiar transmisji w bajtach. Na listingu 1 pokazano przykładową inicjalizację struktury spi_bus_config_t.
- dma_chan – jest to zmienna wskazująca, który kanał DMA ma być używany podczas transmisji. DMA (Direct Memory Access) to mechanizm umożliwiający instancji SPI bezpośredni dostęp do pamięci RAM jako bufora transferu. Można wybrać pomiędzy SPI_DMA_DISABLED, SPI_DMA_CH1, SPI_DMA_CH2, SPI_DMA_CH_AUTO.
spi_bus_config_t buscfg={
.miso_io_num = -1,
.mosi_io_num = 32,
.sclk_io_num = 33,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 32,
};
Listing 1.
spi_bus_add_device() – rejestracja urządzenia podrzędnego. Funkcja ma 3 parametry:
- host_id – taka sama wartość, jaką podano w spi_bus_initialize(),
- dev_config – wskaźnik na stałą strukturę typu spi_device_interface_config_t. Struktura podaje informacje o urządzeniu podrzędnym, takie jak tryb SPI, w którym pracuje, prędkość zegara urządzenia podrzędnego, numer portu GPIO, który ma być wykorzystywany do sterowania wyprowadzeniem SS urządzenia podrzędnego itd. Na listingu 2 pokazano przykładową inicjalizację struktury spi_device_interface_config_t.
- handle – wskaźnik do zmiennej typu spi_device_handle_t. Po wywołaniu spi_bus_add_device zwrócony zostanie uchwyt pozwalający użyć zmiennej do odwołania się do bieżącego urządzenia podrzędnego.
spi_device_interface_config_t devcfg={
.clock_speed_hz = 1000000, // 1 MHz
.mode = 0, //SPI mode 0
.spics_io_num = 25, // CS Pin
.queue_size = 1,
.flags = SPI_DEVICE_HALFDUPLEX,
.pre_cb = NULL,
.post_cb = NULL,
};
Listing 2.
spi_device_polling_transmit() – przesyłanie danych do urządzenia podrzędnego SPI metodą odpytywania (pollingu). Funkcja ma 2 parametry:
- handle – wartość typu spi_device_handle_t, odnosząca się do urządzenia podrzędnego i uzyskana po wcześniejszym wywołaniu funkcji spi_bus_add_device(),
- trans_desc – wskaźnik do zmiennej typu spi_transaction_t zawierającej parametry danych do przesłania. Listing 3 to przykład wpisu do struktury spi_transaction_t, ustawianej przed wysłaniem do urządzenia podrzędnego 2 bajtów danych.
uint8_t data[2] = { 0x01, 0x02 };
spi_transaction_t t = {
.tx_buffer = data,
.length = 2 * 8
};
Listing 3.
W przypadku programów wielowątkowych procedura spi_device_polling_transmit() nie jest bezpieczna i może prowadzić do zakłócenia transmisji SPI. Korzystając z trybu odpytywania, lepiej w takim przypadku posłużyć się procedurami spi_device_polling_start() i spi_device_polling_end().
Przykład programu transmisji SPI do urządzenia podrzędnego
W katalogach przykładów firmowych dostępnych w ramach środowiska IDF:
esp-idf/esp-idf-v4.4/examples/peripherals/spi_master
esp-idf/esp-idf-v4.4/examples/peripherals/spi_slave
znaleźć można przykłady oprogramowania korzystającego z bibliotek sterowników SPI pracujących zarówno w trybie nadrzędnym, jak i podrzędnym. Jest tam m.in. przykład komunikacji pomiędzy pamięcią EEPROM a modułem ESP32 czy też sterowania wyświetlaczem graficznym LCD wyposażonym w interfejs SPI. Nie zabrakło także klasycznego przykładu komunikacji dwóch modułów ESP32 poprzez magistralę SPI. Niestety, są to jednak przykłady rozbudowane i wymagające sporej uwagi do zrozumienia ich działania.