Ten post chciałbym poświęcić na opisanie sposobu wyświetlanie zdjęć na ekranie TFT umieszczonym na płytce discovery z wykorzystaniem biblioteki STemWin oraz FATFS.
Opis uruchomienia obu bibliotek za pomocą CubeMx został przedstawiony wcześniej. Jeśli chodzi o funkcję włączającą wszystkie zegary to powinna ona wyglądać tak:
Po wygenerowaniu standardowych ustawień można ją podmienić w pliku main.c bądź dobrać parametry z kodu do programu CubeMx.
Do otwarcia zdjęć na ekranie wykorzystuje funkcje zdefiniowane w bibliotece STemWin zdefiniowane w pliku GUI.h mianowicie:
Ich odmianą są funkcje GUI_BMP_Draw itd., które pozwalają na wyświetlenie obrazów zdefiniowanych w plikach w projekcie.
Aby wyświetlać PNG to należałoby dodać odpowiednie dodatkowe implementacje do biblioteki, które są inne niż dla BMP, GIF czy JPEG.
Jako pierwszy argument tej funkcji należy podać funkcje APP_GetData, którą trzeba wcześniej przygotować. Jej zadaniem jest pobranie danych z pliku i ustawienie ich w buforze. W dokumentacji na stronie 190 (UM03001_emWin) ma ona następującą postać:
Ona musi zostać odpowiednio przerobiona na odczytywanie danych z pliku, co wymaga zmiany dwóch linijek.
Jeśli byłby problem z otwieraniem większych obrazów to należy zwiększać bufor _acBuffer.
Kolejny zestaw funkcji będzie miał za zadanie odczytanie różnego rodzaju obrazów z karty SD.
Do funkcji podawana jest nazwa pliku jako string. W niej oprócz otwierania pliku i wykonania funkcji rysowania na ekranie, wyłączane są przerwania wraz z sprawdzeniem aktualnego stanu. Sprawdzenie stanu jest w niej mniej potrzebne, natomiast powinno to być wykonywane w taki sposób w każdej funkcji. Dzięki temu jeśli po wyłączeniu przerwania, nastąpi wejście do funkcji, która ma też zdeklarowane wyłączanie i włączenia przerwania, to nie nastąpi jego ponowne włączenie. Możliwe jest to dzięki pobraniu danych dotyczących bitu primask, w którym przechowywany jest aktualny stan dotyczący statusu przerwań w systemie. Funkcje DrawEx zwracają dwa parametry 0 w przypadku poprawnego wykonania operacji, oraz 1 gdy wystąpi błąd. W drugim przypadku dobrze, gdy nie udaje się otworzyć obrazu dobrze byłoby zwiększyć wspomniany wcześniej buffor danych. Mi w przypadku niektórych obrazów to znacznie pomogło.
Dane można wyświetlić także przy użyciu funkcji:
GUI_JPEG_DrawScaledEx(GUI_GET_DATA_FUNC * pfGetData, void *p, int x0, int y0, int Num, int Denom);
Jeśli poda się wartość Num na 1 a Denom na 2 to uzyska się obraz pomniejszony o połowę.
Wyświetla ona obraz wyskalowany za pomocą dwóch ostatnich parametrów czyli Num, Denom. Dla przykładu jest obraz jpg w rozmiarze 320x240. który chce się wyświetlić na jak największej powierzchni wyświetlacza to te parametry należy ustawić na 1000(NUM) oraz 883(DENOM). Dzięki temu uzyska się pełne pokrycie osi Y, natomiast oś X będzie miała kawałek wolnej przestrzeni.
Wyświetlanie danych np obrazu jpg można uzyskać poprzez:
Funkcja włączająca zegary:
Opis uruchomienia obu bibliotek za pomocą CubeMx został przedstawiony wcześniej. Jeśli chodzi o funkcję włączającą wszystkie zegary to powinna ona wyglądać tak:
- void CACHE_ENABLE_FOR_CPU(void);
- {
- (*(uint32_t *) 0xE000ED94) &= ~0x5;
- (*(uint32_t *) 0xE000ED98) = 0x0; //MPU->RNR
- (*(uint32_t *) 0xE000ED9C) = 0x20010000 | 1 << 4; //MPU->RBAR
- (*(uint32_t *) 0xE000EDA0) = 0 << 28 | 3 << 24 | 0 << 19 | 0 << 18 | 1 << 17 | 0 << 16 | 0 << 8 | 30 << 1 | 1 << 0; //MPU->RASE WT
- (*(uint32_t *) 0xE000ED94) = 0x5;
- SCB_InvalidateICache();
- //Enable branch prediction
- SCB->CCR |= (1 << 18);
- __DSB();
- SCB_EnableICache();
- SCB_InvalidateDCache();
- SCB_EnableDCache();
- }
- void SystemClock_Config2(void)
- {
- RCC_OscInitTypeDef RCC_OscInitStruct;
- RCC_ClkInitTypeDef RCC_ClkInitStruct;
- RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
- /**Configure the main internal regulator output voltage
- */
- __HAL_RCC_PWR_CLK_ENABLE();
- __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);
- /**Initializes the CPU, AHB and APB busses clocks
- */
- RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
- RCC_OscInitStruct.HSEState = RCC_HSE_ON;
- RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
- RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
- RCC_OscInitStruct.PLL.PLLM = 15;
- RCC_OscInitStruct.PLL.PLLN = 108;
- RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
- RCC_OscInitStruct.PLL.PLLQ = 5;
- if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
- {
- Error_Handler();
- }
- /**Initializes the CPU, AHB and APB busses clocks
- */
- RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
- |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
- RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
- RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
- RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
- if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
- {
- Error_Handler();
- }
- PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC|RCC_PERIPHCLK_USART6
- |RCC_PERIPHCLK_I2C3|RCC_PERIPHCLK_SDMMC1
- |RCC_PERIPHCLK_CLK48;
- PeriphClkInitStruct.PLLSAI.PLLSAIN = 60;
- PeriphClkInitStruct.PLLSAI.PLLSAIR = 2;
- PeriphClkInitStruct.PLLSAI.PLLSAIQ = 2;
- PeriphClkInitStruct.PLLSAI.PLLSAIP = RCC_PLLSAIP_DIV2;
- PeriphClkInitStruct.PLLSAIDivQ = 1;
- PeriphClkInitStruct.PLLSAIDivR = RCC_PLLSAIDIVR_4;
- PeriphClkInitStruct.Usart6ClockSelection = RCC_USART6CLKSOURCE_PCLK2;
- PeriphClkInitStruct.I2c3ClockSelection = RCC_I2C3CLKSOURCE_PCLK1;
- PeriphClkInitStruct.Clk48ClockSelection = RCC_CLK48SOURCE_PLL;
- PeriphClkInitStruct.Sdmmc1ClockSelection = RCC_SDMMC1CLKSOURCE_CLK48;
- if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
- {
- Error_Handler();
- }
- /**Configure the Systick interrupt time
- */
- HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
- /**Configure the Systick
- */
- HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
- /* SysTick_IRQn interrupt configuration */
- HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
- }
- //Kolejnosc wywolania
- //CACHE_ENABLE_FOR_CPU(void);
- //HAL_Init();
- //SystemClock_Config();
- //Cala reszta
Po wygenerowaniu standardowych ustawień można ją podmienić w pliku main.c bądź dobrać parametry z kodu do programu CubeMx.
Otwarcie zdjęć na ekranie:
Do otwarcia zdjęć na ekranie wykorzystuje funkcje zdefiniowane w bibliotece STemWin zdefiniowane w pliku GUI.h mianowicie:
- int GUI_BMP_DrawEx (GUI_GET_DATA_FUNC * pfGetData, void * p, int x0, int y0);
- int GUI_PNG_DrawEx (GUI_GET_DATA_FUNC * pfGetData, void * p, int x0, int y0);
- int GUI_JPEG_DrawEx (GUI_GET_DATA_FUNC * pfGetData, void * p, int x0, int y0);
- int GUI_GIF_DrawEx (GUI_GET_DATA_FUNC * pfGetData, void * p, int x0, int y0);
Ich odmianą są funkcje GUI_BMP_Draw itd., które pozwalają na wyświetlenie obrazów zdefiniowanych w plikach w projekcie.
Aby wyświetlać PNG to należałoby dodać odpowiednie dodatkowe implementacje do biblioteki, które są inne niż dla BMP, GIF czy JPEG.
Jako pierwszy argument tej funkcji należy podać funkcje APP_GetData, którą trzeba wcześniej przygotować. Jej zadaniem jest pobranie danych z pliku i ustawienie ich w buforze. W dokumentacji na stronie 190 (UM03001_emWin) ma ona następującą postać:
- int APP_GetData(void * p, const U8 ** ppData, unsigned NumBytes, U32 Off)
- {
- static char _acBuffer[0x200];
- HANDLE * phFile;
- DWORD NumBytesRead;
- phFile = (HANDLE *)p;
- //Sprawdzenie rozmiaru bufora
- if (NumBytes > sizeof(acBuffer))
- {
- NumBytes = sizeof(acBuffer);
- }
- //Ustawienie wzkaznika na wymagana pozycje
- SetFilePointer(*phFile, Off, 0, FILE_BEGIN);
- //Dane do bufora
- ReadFile(*phFile, acBuffer, NumBytes, &NumBytesRead, NULL);
- //Ustawienie wskaznika na poczatek bufora
- *ppData = acBuffer;
- //Zwrócenie liczby odczytanych danych
- return NumBytesRead;
- }
Ona musi zostać odpowiednio przerobiona na odczytywanie danych z pliku, co wymaga zmiany dwóch linijek.
- int APP_GetData(void * p, const U8 * * ppData, unsigned NumBytesReq, U32 Off)
- {
- FIL * phFile;
- UINT NumBytesRead;
- static U8 _acBuffer[2000];
- phFile = (FIL *) p;
- //Sprawdzenie rozmiarów bufora
- if (NumBytesReq > sizeof(_acBuffer))
- {
- NumBytesReq = sizeof(_acBuffer);
- }
- //Przesuniecie wskaznika
- f_lseek(phFile, Off);
- //Wprowadzenie danych do bufora
- res = f_read(phFile, _acBuffer, NumBytesReq, (UINT *)&NumBytesRead);
- //Ustawienie wskaznika na poczatek bufora
- *ppData = _acBuffer;
- // Return number of available bytes
- return NumBytesRead;
- }
Jeśli byłby problem z otwieraniem większych obrazów to należy zwiększać bufor _acBuffer.
Kolejny zestaw funkcji będzie miał za zadanie odczytanie różnego rodzaju obrazów z karty SD.
- void Print_BMP(char nazwapliku[20])
- {
- uint32_t prim;
- if(f_mount(&SDFatFs, "", 1) == FR_OK)
- {
- if(f_open(&myFile, nazwapliku , FA_READ) == FR_OK)
- {
- prim = __get_PRIMASK();
- __disable_irq();
- GUI_BMP_DrawEx(APP_GetData, &myFile, 0, 0);
- if (!prim) {
- __enable_irq();
- }
- }
- f_close(&myFile);
- f_mount(0, "", 1);
- }
- else
- {
- //Blad otwarcia pliku
- }
- }
Do funkcji podawana jest nazwa pliku jako string. W niej oprócz otwierania pliku i wykonania funkcji rysowania na ekranie, wyłączane są przerwania wraz z sprawdzeniem aktualnego stanu. Sprawdzenie stanu jest w niej mniej potrzebne, natomiast powinno to być wykonywane w taki sposób w każdej funkcji. Dzięki temu jeśli po wyłączeniu przerwania, nastąpi wejście do funkcji, która ma też zdeklarowane wyłączanie i włączenia przerwania, to nie nastąpi jego ponowne włączenie. Możliwe jest to dzięki pobraniu danych dotyczących bitu primask, w którym przechowywany jest aktualny stan dotyczący statusu przerwań w systemie. Funkcje DrawEx zwracają dwa parametry 0 w przypadku poprawnego wykonania operacji, oraz 1 gdy wystąpi błąd. W drugim przypadku dobrze, gdy nie udaje się otworzyć obrazu dobrze byłoby zwiększyć wspomniany wcześniej buffor danych. Mi w przypadku niektórych obrazów to znacznie pomogło.
Dane można wyświetlić także przy użyciu funkcji:
GUI_JPEG_DrawScaledEx(GUI_GET_DATA_FUNC * pfGetData, void *p, int x0, int y0, int Num, int Denom);
Jeśli poda się wartość Num na 1 a Denom na 2 to uzyska się obraz pomniejszony o połowę.
Wyświetla ona obraz wyskalowany za pomocą dwóch ostatnich parametrów czyli Num, Denom. Dla przykładu jest obraz jpg w rozmiarze 320x240. który chce się wyświetlić na jak największej powierzchni wyświetlacza to te parametry należy ustawić na 1000(NUM) oraz 883(DENOM). Dzięki temu uzyska się pełne pokrycie osi Y, natomiast oś X będzie miała kawałek wolnej przestrzeni.
Wyświetlanie danych np obrazu jpg można uzyskać poprzez:
- //Wczesniejsze inicjalizacje
- GUI_SetLayerVisEx (0, 1);
- GUI_SetLayerVisEx (1, 0);
- GUI_SelectLayer(0);
- Print_JPEG("image02_320x240.jpg");