Digilent Pmod i STM32 (cz. 8) – PmodMTDS, PmodRTCC i PmodCMPS2
W ósmej części cyklu dotyczącego modułów Digilent Pmod przedstawimy moduły PmodMTDS z wyświetlaczem graficznym o przekątnej 2.8″ i rozdzielczości 320×240 pikseli, PmodRTCC zawierający układ zegara czasu rzeczywistego z kalendarzem, a także PmodCMPS2 z 3-osiowym czujnikiem pola magnetycznego. Wszystkie wymienione moduły posiadają biblioteki dostarczane przez producenta, dostosowane na potrzeby niniejszego artykułu dla zestawu KAmeleon. Przykłady zostały przygotowane dla środowiska Atollic TrueSTUDIO, a także zestawu uruchomieniowego KAmeleon (www.kameleonboard.org) z wykorzystaniem biblioteki STM32Cube_FW_L4.
PmodMTDS
Moduł PmodMTDS posiada kolorowy wyświetlacz graficzny o rozdzielczości 320×240 pikseli z panelem dotykowym mogącym obsłużyć do dwóch punktów dotyku. Dodatkowo moduł posiada gniazdo microSD przeznaczone dla karty pamięci zawierającej grafiki do wykorzystania w aplikacji. Do kontroli wyświetlacza służy mikrokontroler PIC32MZ, z którym można się komunikować za pośrednictwem interfejsu SPI.
Biblioteka do obsługi modułu
Do obsługi kontrolera znajdującego się na płytce PmodMTDS służy dostarczona przez producenta biblioteka MTDS zaimplementowana w języku C++. Można ją pobrać ze strony: https://reference.digilentinc.com/reference/software/mtds/start. Jest ona przeznaczona dla platformy Arduino, jednak na potrzeby niniejszego przykładu została ona zmodyfikowana tak, aby współpracowała z zestawem KAmeleon. Biblioteka składa się z dwóch warstw: mtds, a także MyDisp. Pierwsza z nich zawiera interfejs umożliwiający komunikację z kontrolerem wyświetlacza i udostępniający podstawowe operacje graficzne. Moduł MyDisp stanowi warstwę nadrzędną, ułatwiającą korzystanie z wyświetlacza, a także rozszerzającą funkcjonalność warstwy niższej. W przykładzie pokazano w jaki sposób można korzystać z obu warstw w celu zbudowania prostego interfejsu graficznego. Ze względu na wielkość interfejsów obu warstw, opiszemy tylko te metody, które wykorzystano w przykładowej aplikacji. Umożliwiają one wyświetlanie tekstu na wyświetlaczu, a także tworzenie przycisków obsługiwanych za pomocą panelu dotykowego.
Połączenie z zestawem KAmeleon
Moduł PmodMTDS posiada 12-pinowe złącze typu 2A z interfejsem SPI, a także sygnałem RESET. Może być ono podłączone do gniazda Pmod-SPI zestawu KAmeleon, tak jak na fotografii 2. Piny mikrokontrolera i odpowiadające im sygnały modułu PmodMTDS przedstawiono w tabeli 1.
Fotografia 2. Moduł PmodMTDS podłączony do płytki KAmeleon
Tabela 1. Sygnały PmodMTDS 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 PmodMTDS (J1) | Pin STM32L496ZG (KAmeleon Pmod-SPI) |
CS | 1 | PB0 |
MOSI | 2 | PA7 |
MISO | 3 | PE14 |
SCLK | 4 | PA1 |
RESET | 8 | PE13 |
Kod projektu
Konfiguracja i obsługa interfejsu SPI dla modułu jest zaimplementowana w pliku MtdsHal.cpp w warstwie mtds. Znajdujące się tam funkcje zostały zmodyfikowane tak, aby moduł PmodMTDS mógł działać razem z zestawem KAmeleon. Ich fragmenty zaprezentowano na listingach 1 i 2. Pierwsza z nich – MtdsHalInitSpi, jest odpowiedzialna za inicjalizację interfejsu SPI1 w trybie 0 (CPOL = 0, CPHA = 0) z programową kontrolą linii CS, natomiast druga – MtdsHalPutSpiByte, realizuje transmisję i odczyt bajtu przez SPI.
Listing 1. Inicjalizacja SPI dla modułu PmodMTDS
void MtdsHalInitSpi(uint32_t pspiInit, uint32_t frq) { __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOE_CLK_ENABLE(); __HAL_RCC_SPI1_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_PULLDOWN; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF5_SPI1; GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_7; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_InitStruct.Pin = GPIO_PIN_14; HAL_GPIO_Init(GPIOE, &GPIO_InitStruct); GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Pin = GPIO_PIN_0; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); pmodMtdsSpi.Instance = SPI1; pmodMtdsSpi.Init.Mode = SPI_MODE_MASTER; pmodMtdsSpi.Init.Direction = SPI_DIRECTION_2LINES; pmodMtdsSpi.Init.DataSize = SPI_DATASIZE_8BIT; pmodMtdsSpi.Init.CLKPolarity = SPI_POLARITY_LOW; pmodMtdsSpi.Init.CLKPhase = SPI_PHASE_1EDGE; pmodMtdsSpi.Init.NSS = SPI_NSS_SOFT; pmodMtdsSpi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; pmodMtdsSpi.Init.FirstBit = SPI_FIRSTBIT_MSB; pmodMtdsSpi.Init.TIMode = SPI_TIMODE_DISABLE; pmodMtdsSpi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; HAL_SPI_Init(&pmodMtdsSpi); }
Listing 2. Transmisja i odczyt bajtu za pośrednictwem interfejsu SPI
uint8_t MtdsHalPutSpiByte(uint8_t bSnd) { uint8_t bRcv; HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(&pmodMtdsSpi, &bSnd, &bRcv, 1, 100); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); return bRcv; }
Oprócz obsługi SPI, w pliku MtdsHal.cpp zostały zmodyfikowane także funkcje odpowiedzialne za zarządzanie czasem: MtdsHalTmsElapsed – zwracająca liczbę milisekund od startu programu, a także MtdsHalDelayMs i MtdsHalDelayUs – wprowadzające zadane opóźnienie. Funkcje zostały zaimplementowane przy użyciu wywołań z biblioteki STM32Cube: HAL_GetTick i HAL_Delay. Do obsługi modułu zmieniona została także funkcja MtdsHalResetDisplay, ustawiająca stan niski na wyprowadzeniu RESET przez 1 ms w celu wyzerowania układu.
Warstwy biblioteki MTDS
Obsługa wyświetlacza w prezentowanym przykładzie odbywa się przy użyciu obu warstw dostarczonej biblioteki. Warstwa MyDisp zawiera wysokopoziomowe funkcje do inicjalizacji wyświetlacza, a także rysowania podstawowych komponentów interfejsu, takich jak przyciski, tekst i proste kształty geometryczne. Umożliwia ona też korzystanie z panelu dotykowego. Lista funkcji warstwy MyDisp użytych w przykładzie znajduje się w tabeli 2.
Tabela 2. Użyte w przykładzie funkcje warstwy MyDisp
Funkcja | Opis |
begin | Inicjalizacja biblioteki i wyświetlacza. |
clearDisplay | Ustawienie wszystkich pikseli na wybrany kolor. |
createButton | Utworzenie przycisku. |
enableButton | Włączenie obsługi przycisku przez panel dotykowy. |
drawButton | Rysowanie przycisku na wyświetlaczu. |
setForeground | Ustawienie koloru pierwszego planu dla rysowanych obiektów. |
setBackground | Ustawienie koloru tła dla rysowanych obiektów. |
setPen | Ustawienie typu pisaka. |
setTransparency | Włączenie lub wyłączenie przezroczystości. |
drawText | Rysowanie tekstu na wyświetlaczu. |
checkTouch | Aktualizacja stanu panelu dotykowego. |
isTouched | Sprawdzenie stanu wciśnięcia wybranego przycisku. |
Druga z warstw – mtds, również może być użyta do rysowania kształtów i tekstu, jednak w przykładzie została wykorzystana jej możliwość generacji bitmap dla przycisków warstwy MyDisp. Tworzone bitmapy można używać wielokrotnie, co pozwala na przykład utworzyć różne motywy graficzne dla przycisków. Listę użytych funkcji warstwy mtds przedstawiono w tabeli 3.
Tabela 3. Użyte w przykładzie funkcje warstwy mtds
Funkcja | Opis |
GetDs | Pobranie aktualnego kontekstu graficznego. |
CreateBitmap | Utworzenie nowej bitmapy o zadanym rozmiarze i formacie kolorów. |
SetDrawingSurface | Ustawienie powierzchni rysowania, np. na bitmapę. |
SetBgColor | Ustawienie koloru tła. |
SetFgColor | Ustawienie koloru pierwszego planu. |
Rectangle | Rysowanie prostokąta. |
SetPen | Ustawienie typu pisaka. |
SetFont | Ustawienie czcionki. |
TextOut | Rysowanie tekstu. |
ReleaseDs | Zwolnienie kontekstu graficznego. |
Generowanie interfejsu
Przykładowy interfejs jest generowany w funkcji main, którą zaprezentowano na listingach 3 i 4. Pierwszy z nich zawiera generację bitmapy dla przycisku. Znajdują się na nim dwie zmienne – HDS – reprezentująca kontekst graficzny, a także HBMP, która jest uchwytem do tworzonej bitmapy. Sama bitmapa jest tworzona przez odpowiednie ustawienie powierzchni rysowania (SetDrawingSurface) i nanoszenie na nią kształtów i tekstu.
Listing 3. Generacja bitmapy za pomocą funkcji warstwy mtds.
HDS hds; HBMP hbmp; hds = mtds.GetDs(); hbmp = mtds.CreateBitmap(80, 30, 16); mtds.SetDrawingSurface(hds, hbmp); mtds.SetBgColor(hds, clrWhite); mtds.SetFgColor(hds, clrBlack); mtds.Rectangle(hds, 0, 0, 100, 50); mtds.SetPen(hds, penSolid); mtds.SetFont(hds, hfntConsole); mtds.TextOut(hds, 20, 10, 5, "HELLO"); mtds.ReleaseDs(hds);
Listing 4. Tworzenie tekstu i przycisku oraz obsługa panelu dotykowego w funkcji main
int main(void) { HAL_Init(); SystemClock_Config(); mydisp.begin(); mydisp.clearDisplay(clrMedBlueGray); //... //Tu tworzenie bitmapy z Listingu 3. //... mydisp.createButton(0, hbmp, hbmp, 10, 110); mydisp.enableButton(0, true); mydisp.drawButton(0, BUTTON_UP); mydisp.setForeground(clrBlack); mydisp.setBackground(clrDkGreen); mydisp.setPen(penSolid); mydisp.setTransparency(false); mydisp.drawText((char*) "Hello PmodMTDS!", 50, 200); mydisp.drawText((char*) "This example shows how to use", 4, 160); mydisp.drawText((char*) " the PmodMTDS With the ", 4, 169); mydisp.drawText((char*) " KAmeLeon board ", 4, 178); char textBuffer[32]; int touchCounter = 0; while (1) { mydisp.checkTouch(); if (mydisp.isTouched(0)) { sprintf(textBuffer, "Counter: %d", touchCounter++); mydisp.drawText(textBuffer, 100, 120); } } }
W pozostałej części funkcji main (na listingu 4) inicjalizowany jest moduł PmodMTDS, a także tworzone są tekst i przycisk, który dodatkowo wykorzystuje przygotowana bitmapę. Pętla główna programu cyklicznie aktualizuje stan panelu dotykowego. Jeżeli wykryte zostało wciśnięcie przycisku, to dodatkowo zwiększa się licznik wyświetlany na ekranie. Efekt działania aplikacji zaprezentowano na fotografii 3.
Fotografia 3. Efekt działania przykładowej aplikacji dla modułu PmodMTDS