Digilent Pmod i STM32 (cz. 6) – PmodACL2, PmodDPOT i PmodSSD
Szósty odcinek cyklu poświęconego modułom Digilent Pmod to trzy kolejne układy: akcelerometr PmodACL2, potencjometr cyfrowy PmodDPOT i podwójny wyświetlacz siedmiosegmentowy PmodSSD. Przykłady dla wymienionych modułów przygotowano dla środowiska Atollic TrueSTUDIO i zestawu uruchomieniowego KAmeleon (www.kameleonboard.org) z wykorzystaniem biblioteki STM32Cube_FW_L4.
PmodACL2
Moduł PmodACL2 zawiera 3-osiowy akcelerometr MEMS – ADXL362 od firmy Analog Devices. Akcelerometr zapewnia 12-bitową rozdzielczość pomiaru oraz trzy możliwe do ustawienia zakresy: ±2g, ±4g, ±8g, charakteryzujące się odpowiednio czułością: 1 mg/LSB, 2 mg/LSB, 4 mg/LSB. Układ posiada również konfigurowalną częstotliwość odczytu danych, która może przyjąć jedną z wartości: 12,5 Hz, 25 Hz, 50 Hz, 100 Hz, 200 Hz, 400 Hz. Akcelerometr charakteryzuje się bardzo małym poborem prądu o maksymalnej wartości dochodzącej do 5 µA przy napięciu zasilania wynoszącym 3,5 V i maksymalnej prędkości odczytu danych. Poza pomiarem przyspieszenia, układ ADXL362 umożliwia także pomiar temperatury z rozdzielczością 12 bitów i czułością 0,065°C/LSB.
Akcelerometr ADXL362
Układ ADXL362 posiada szereg funkcji do przetwarzania danych pomiarowych, takich jak:
- detekcja aktywności,
- detekcja swobodnego spadku,
- kolejka FIFO dla danych pomiarowych.
Na szczególną uwagę zasługuje możliwość wykorzystania kolejki FIFO do zapisu historii pomiarów prowadzących do wykrycia aktywności definiowanej konfigurowalnymi progami dla każdej z osi. Umożliwia to mikrokontrolerowi odczyt całego profilu przyspieszenia po otrzymaniu przerwania, którego źródłem jest zdarzenie wykryte przez układ ADXL362.
Akcelerometr udostępnia kilka źródeł przerwań, które mogą zostać przyporządkowane niezależnie do jednego z dwóch sygnałów INT1, lub INT2:
- AWAKE – wybudzenie przez wykrycie aktywności lub uśpienie przez wykrycie braku aktywności,
- INACT – wykrycie braku aktywności, lub swobodnego spadku,
- ACT – wykrycie aktywności,
- FIFO_OVERRUN – przepełnienie kolejki FIFO,
- FIFO_WATERMARK – zapełnienie kolejki do ustalonego poziomu,
- FIFO_READY – gotowość do odczytu co najmniej jednej wartości z kolejki FIFO,
- DATA_READY – gotowość do odbioru nowej zmierzonej wartości.
Zdarzenia dotyczące wykrywaniu aktywności lub jej braku wymagają zdefiniowania progu przyspieszenia oraz minimalnego czasu jego przekroczenia.
Komunikacja z modułem PmodACL2
Z modułem PmodACL2 można komunikować się za pośrednictwem interfejsu SPI i trzech dostępnych komend:
- Write register (0x0A) – zapis rejestrów,
- Read register (0x0B) – odczyt rejestrów,
- Read FIFO (0x0D) – odczyt kolejki FIFO.
Komendom do obsługi rejestrów musi zawsze towarzyszyć 8-bitowy adres rejestru, a następnie co najmniej jeden bajt danych. W przypadku wielu bajtów danych, adres rejestru jest automatycznie inkrementowany wewnątrz układu ADXL362. Komenda odczytu FIFO nie wymaga dodatkowych parametrów – po jej wysłaniu układ automatycznie odpowiada danymi znajdującymi się w kolejce.
Moduł PmodACL2 posiada złącze SPI typu 2A z dwoma sygnałami przerwań. Złącze to można podłączyć do gniazda Pmod-SPI zestawu KAmeleon tak, jak to przedstawiono na fotografii 2. Piny mikrokontrolera podłączone do odpowiednich sygnałów modułu przedstawiono w tabeli 1.
Fotografia 2. Moduł PmodACL2 podłączony do zestawu KAmeleon
Tabela 1. Sygnały PmodACL2 oraz odpowiadające im piny mikrokontrolera; w tabeli pominięto sygnały niepołączone (NC) i linie zasilania występujące na złączu Pmod
Sygnał | Numer pinu PmodACL (J1) | Pin STM32L496ZG (KAmeleon Pmod-SPI) |
~SS | 1 | PB0 |
MOSI | 2 | PA7 |
MISO | 3 | PE14 |
SCLK | 4 | PA1 |
INT2 | 7 | PE12 |
INT1 | 8 | PE13 |
Kod przykładu
Obsługa modułu PmodACL2 w prezentowanym przykładzie znajduje się w plikach inc/PmodACL2.h i src/PmodACL2.c i składa się z czterech funkcji:
- PmodACL2_Config – konfiguracja peryferiów mikrokontrolera i rejestrów akcelerometru,
- PmodACL2_ReadFifo – odczyt danych z kolejki FIFO,
- PmodACL2_ReadStatus – odczyt aktualnego stanu źródeł przerwań,
- PmodACL2_ConvertFifoEntry – konwersja wartości odczytanej z kolejki na liczbę całkowitą ze znakiem.
Pierwszą z funkcji przedstawiono na listingu 1. W pierwszej kolejności konfigurowany jest interfejs SPI w trybie 0 (CPOL = 0, CPHA = 0) z programową kontrolą sygnału CS. Długość danych jest ustawiona na 8 bitów. W pliku PmodACL2.c została także zdefiniowana funkcja HAL_SPI_MspInit, wywoływana wewnątrz funkcji bibliotecznej HAL_SPI_Init i odpowiedzialna za konfigurację GPIO dla interfejsu SPI (MISO, MOSI, SCLK, CS), a także włączenie odpowiednich sygnałów zegarowych.
Listing 1. Konfiguracja modułu PmodACL2
void PmodACL2_Config(void) { pmodAcl2Spi.Instance = SPI1; pmodAcl2Spi.Init.Mode = SPI_MODE_MASTER; pmodAcl2Spi.Init.Direction = SPI_DIRECTION_2LINES; pmodAcl2Spi.Init.DataSize = SPI_DATASIZE_8BIT; pmodAcl2Spi.Init.CLKPolarity = SPI_POLARITY_LOW; pmodAcl2Spi.Init.CLKPhase = SPI_PHASE_1EDGE; pmodAcl2Spi.Init.NSS = SPI_NSS_SOFT; pmodAcl2Spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128; pmodAcl2Spi.Init.FirstBit = SPI_FIRSTBIT_MSB; pmodAcl2Spi.Init.TIMode = SPI_TIMODE_DISABLE; pmodAcl2Spi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; pmodAcl2Spi.Init.NSSPMode = SPI_NSS_PULSE_DISABLE; HAL_SPI_Init(&pmodAcl2Spi); writeRegister(0x1F, 0x52); HAL_Delay(1); uint16_t activityValueThreshold = 400; uint16_t activityTimeThreshold = 5; uint16_t inactivityValueThreshold = 100; uint16_t inactivityTimeThreshold = 50; writeRegister(0x20, activityValueThreshold & 0xFF); writeRegister(0x21, (activityValueThreshold >> 8) & 0xFF); writeRegister(0x22, activityTimeThreshold); writeRegister(0x23, inactivityValueThreshold & 0xFF); writeRegister(0x24, (inactivityValueThreshold >> 8) & 0xFF); writeRegister(0x25, activityTimeThreshold & 0xFF); writeRegister(0x26, (inactivityTimeThreshold >> 8) & 0xFF); writeRegister(0x27, 0x3F); writeRegister(0x28, 0x02); writeRegister(0x2A, 0x90); writeRegister(0x2D, 0x02); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Pin = GPIO_PIN_13; HAL_GPIO_Init(GPIOE, &GPIO_InitStruct); HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0x0F, 0); HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); }
Konfiguracja rejestrów
Kolejnym zadaniem funkcji PmodACL2_Config jest konfiguracja rejestrów akcelerometru ADXL362. W pierwszej kolejności wykonywany jest reset układu w wyniku zapisu wartości 0x52 do rejestru SOFT_RESET, po którym należy odczekać co najmniej 0,5 ms. Reset ma na celu przywrócenie wszystkich rejestrów do ich domyślnych wartości. Są to m.in. zakres i częstotliwość odczytu danych (rejestr 0x2C FILTER_CTL) wynoszące odpowiednio ±2 g i 100 Hz. Następnie ustawiane są progi detekcji aktywności. Ustawiane wartości odnoszą się do aktualnie ustawionej rozdzielczości – dla wartości 400 i rozdzielczości 1 mg/LSB, aktywność będzie rozpoznawana po przekroczeniu progu 400 mg.
Oprócz wartości progu wpisywany jest także minimalny czas jego przekroczenia. Czas ten podawany jest w próbkach i jest ściśle związany z ustawioną częstotliwością odczytu danych (ODR).Warto zwrócić uwagę, że wszystkie wartości, poza minimalnym czasem przekroczenia progu aktywności, czyli: progi aktywności, braku aktywności i czas przekroczenia progu braku aktywności, są podzielone na dwa 8-bitowe rejestry (MSB i LSB), które należy ustawić niezależnie.
Następnym rejestrem jest 0x27 ACT_INACT_CTL, który zawiera dalszą konfigurację zdarzeń związanych z aktywnością. W przykładzie układ jest konfigurowany w trybie loop (wykrywanie zdarzeń aktywności i jej braku włączane naprzemiennie, przerwania potwierdzanie automatycznie bez ingerencji mikrokontrolera) z pomiarem referencyjnym (względem przyspieszenia referencyjnego obliczanego przez układ) uwzględniającym położenie początkowe. Rejestr 0x28 FIFO_CONTROL to konfiguracja kolejki FIFO – włączenie w trybie stream, w którym kolejka zawiera zawsze najnowsze dane, a najstarsze są usuwane. Bit 2. ustawiony na 0 oznacza, że kolejka nie przechowuje informacji o temperaturze. Zapis do kolejnego rejestru – 0x2A INTMAP1 – ustawia przerwania na linii INT1 aktywne w stanie niskim i włącza przerwanie od wykrycia aktywności. Ostatnia operacja zapisu – do rejestru 0x2D POWER_CTL – uruchamia pomiary.
Realizacja zapisu do rejestru
Do realizacji zapisu wykorzystywana jest funkcja pomocnicza – writeRegister, która kontroluje sygnał CS, wysyła komendę zapisu 0x0A, adres rejestru i wartość. Do obsługi interfejsu SPI używana jest biblioteczna funkcja HAL_SPI_Transmit.
Na koniec funkcja PmodACL2_Config konfiguruje pin PE13, podłączony do linii INT1 modułu PmodACL2, jako wejście przerwań wyzwalanych zboczem opadającym.
Obsługa kolejki FIFO
Druga z funkcji obsługujących moduł PmodACL2 – PmodACL2_ReadFifo, przedstawiona na listingu 2, jest odpowiedzialna za odczyt danych znajdujących się w kolejce FIFO akcelerometru. W pierwszej kolejności funkcja odczytuje 10-bitową liczbę elementów kolejki z dwóch rejestrów: 0x0C FIFO_ENTRIES_L i 0x0D FIFO_ENTRIES_H. Dane z kolejki są zapisywane do wewnętrznego bufora o długości MAX_FIFO_READ_LEN, a następnie kopiowane pod adres podany w argumencie, wewnątrz pomocniczej funkcji readFifo. Funkcja ta steruje linią CS, wysyła komendę odczytu kolejki 0x0D, a następnie odczytuje dane z akcelerometru. Wszystko odbywa się w ramach jednej transakcji SPI, co przedstawiono na listingu 3.
Listing 2. Odczyt liczby elementów z kolejki FIFO
void PmodACL2_ReadFifo(uint8_t* data, uint32_t* len) { // Read the number of entries currently stored in fifo. uint8_t entriesLsb = readRegister(0x0C); // FIFO_ENTRIES_L register. uint8_t entriesMsb = readRegister(0x0D); // FIFO_ENTRIES_H register. // Each fifo entry is 2 bytes long. *len = (entriesLsb | (entriesMsb << 8)) * 2; // Make sure that the number of bytes does not exceed the fifo limit. if(*len > MAX_FIFO_READ_LEN) *len = MAX_FIFO_READ_LEN; // Do not read from empty fifo. if(*len == 0) return; readFifo(data, *len); }
Listing 3. Odczyt kolejki FIFO
static void readFifo(uint8_t* data, uint32_t len) { // The local buffers used for SPI transaction. The additional byte is required // for transmitting the command. uint8_t txbuf[MAX_FIFO_READ_LEN + 1] = {0x00}; uint8_t rxbuf[MAX_FIFO_READ_LEN + 1] = {0x00}; txbuf[0] = SPI_READ_FIFO; HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(&pmodAcl2Spi, txbuf, rxbuf, len + 1, 100); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); if(data == NULL) return; // Copy the data to given buffer without the command byte. for(uint32_t i = 0; i < len; i++) data[i] = rxbuf[i + i]; }
Odczyt rejestru statusu
Następna funkcja realizuje odczyt rejestru stanu 0x0B STATUS. Wykorzystuje ona funkcję pomocniczą readRegister, która wysyła komendę odczytu rejestru – 0x0B oraz adres, a następnie odczytuje wartość znajdującą się w rejestrze. Tak jak w przypadku innych funkcji pomocniczych, ta równiez wywołuje funkcje z biblioteki STM32Cube: HAL_GPIO_WritePin do kontroli sygnału CS i HAL_SPI_TransmitReceive do transmisji danych po SPI. Obie funkcje – PmodACL2_ReadStatus i readRegister przedstawiono na listingach 4 i 5.
Listing 4. Odczyt rejestru stanu akcelerometru ADXL362
uint8_t PmodACL2_ReadStatus(void) { return readRegister(0x0B); // Read the interrupts status from STATUS register. }
Listing 5. Funkcja pomocnicza do odczytu rejestru
static uint8_t readRegister(uint8_t address) { // Reading register requires sending read command and the address // before obtaining the data byte. uint8_t txbuf[3] = {SPI_READ_REG, address, 0x00}; uint8_t rxbuf[3] = {0x00}; HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(&pmodAcl2Spi, txbuf, rxbuf, 3, 100); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); return rxbuf[2]; }
Odczyt i konwersja danych
Ostatnia z opisywanych funkcji – PmodACL2_ConvertFifoEntry, pełni rolę pomocniczą przy przetwarzaniu danych odczytanych z kolejki FIFO. Jej zadaniem jest konwersja dwóch bajtów odczytanych z kolejki na liczbę 16-bitową ze znakiem. Elementy FIFO zakodowano zgodnie ze schematem przedstawionym na rysunku 3: dwa najstarsze bity (14-15) zawierają informację o przyporządkowaniu wartości do osi lub o pomiarze temperatury, dwa kolejne bity (12-13) przechowują znak, a pozostałe – zmierzoną wartość. Z tego powodu, oprócz łączenia dwóch bajtów w jedną wartość 16-bitową, konieczne jest także rozszerzenie bitów znaku na bity 14. i 15. Operacje tą przedstawiono na listingu 6.
Rysunek 3. Struktura elementów kolejki FIFO
Listing 6. Konwersja 2-bajtowego elementu kolejki FIFO na wartość 16-bitową ze znakiem
int16_t PmodACL2_ConvertFifoEntry(uint8_t* data) { // Build the single value from 2-byte fifo entry. int16_t convertedValue = data[0] | (data[1] << 8); // Check the sign extension bits (12 and 13) and set the same state in bits 14 and 15 // to build the 16-bit signed integer acceleration value. if(convertedValue & 0x3000) return convertedValue | 0xC000; else return convertedValue & 0x3FFF; }
Pozostałe operacje związane z konwersją danych odczytanych z kolejki FIFO zaimplementowano w funkcji main i przedstawiono na listingu 7. Po otrzymaniu przerwania sygnalizowanego flagą activityFlag następuje odczytanie danych z kolejki FIFO za pomocą opisywanej wcześniej funkcji PmodACL2_ReadFifo. Następnie w pętli for każde dwa bajty analizowane są pod kątem przynależności do jednej z osi (pomiary temperatury w przedstawionej konfiguracji nie są zapisywane do kolejki), a następnie konwertowane za pomocą funkcji PmodACL2_ConvertFifoEntry.
Listing 7. Odczyt i konwersja danych z kolejki FIFO
while(activityFlag == 0); PmodACL2_ReadFifo(fifoData, &fifoLen); int sampleIndex = 1; for(int i = 0; i < fifoLen; i += 2) { switch(fifoData[i+1] & 0xC0) { case 0x00: x = PmodACL2_ConvertFifoEntry(&fifoData[i]); readX = 1; break; case 0x40: y = PmodACL2_ConvertFifoEntry(&fifoData[i]); readY = 1; break; case 0x80: z = PmodACL2_ConvertFifoEntry(&fifoData[i]); readZ = 1; break; } }
Po zebraniu danych ze wszystkich trzech osi, co sygnalizowane jest za pomocą flag readX, readY i readZ, są one wysyłane za pośrednictwem portu szeregowego LPUART1, którego obsługa znajduje się w plikach src/serial.c.