piątek, 30 grudnia 2016

[6] STM32F7 - Discovery - StemWin, FatFS

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.

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:

  1. void CACHE_ENABLE_FOR_CPU(void);
  2. {
  3.     (*(uint32_t *) 0xE000ED94) &= ~0x5;
  4.     (*(uint32_t *) 0xE000ED98) = 0x0; //MPU->RNR
  5.     (*(uint32_t *) 0xE000ED9C) = 0x20010000 | 1 << 4; //MPU->RBAR
  6.     (*(uint32_t *) 0xE000EDA0) = 0 << 28 | 3 << 24 | 0 << 19 | 0 << 18 | 1 << 17 | 0 << 16 | 0 << 8 | 30 << 1 | 1 << 0; //MPU->RASE  WT
  7.     (*(uint32_t *) 0xE000ED94) = 0x5;
  8.     SCB_InvalidateICache();
  9.     //Enable branch prediction
  10.     SCB->CCR |= (1 << 18);
  11.     __DSB();
  12.     SCB_EnableICache();
  13.     SCB_InvalidateDCache();
  14.     SCB_EnableDCache();
  15. }
  16. void SystemClock_Config2(void)
  17. {
  18.   RCC_OscInitTypeDef RCC_OscInitStruct;
  19.   RCC_ClkInitTypeDef RCC_ClkInitStruct;
  20.   RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
  21.     /**Configure the main internal regulator output voltage
  22.     */
  23.   __HAL_RCC_PWR_CLK_ENABLE();
  24.   __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);
  25.     /**Initializes the CPU, AHB and APB busses clocks
  26.     */
  27.   RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  28.   RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  29.   RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  30.   RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  31.   RCC_OscInitStruct.PLL.PLLM = 15;
  32.   RCC_OscInitStruct.PLL.PLLN = 108;
  33.   RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  34.   RCC_OscInitStruct.PLL.PLLQ = 5;
  35.   if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  36.   {
  37.     Error_Handler();
  38.   }
  39.     /**Initializes the CPU, AHB and APB busses clocks
  40.     */
  41.   RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
  42.                               |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  43.   RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  44.   RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  45.   RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  46.   RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
  47.   if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  48.   {
  49.     Error_Handler();
  50.   }
  51.   PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC|RCC_PERIPHCLK_USART6
  52.                               |RCC_PERIPHCLK_I2C3|RCC_PERIPHCLK_SDMMC1
  53.                               |RCC_PERIPHCLK_CLK48;
  54.   PeriphClkInitStruct.PLLSAI.PLLSAIN = 60;
  55.   PeriphClkInitStruct.PLLSAI.PLLSAIR = 2;
  56.   PeriphClkInitStruct.PLLSAI.PLLSAIQ = 2;
  57.   PeriphClkInitStruct.PLLSAI.PLLSAIP = RCC_PLLSAIP_DIV2;
  58.   PeriphClkInitStruct.PLLSAIDivQ = 1;
  59.   PeriphClkInitStruct.PLLSAIDivR = RCC_PLLSAIDIVR_4;
  60.   PeriphClkInitStruct.Usart6ClockSelection = RCC_USART6CLKSOURCE_PCLK2;
  61.   PeriphClkInitStruct.I2c3ClockSelection = RCC_I2C3CLKSOURCE_PCLK1;
  62.   PeriphClkInitStruct.Clk48ClockSelection = RCC_CLK48SOURCE_PLL;
  63.   PeriphClkInitStruct.Sdmmc1ClockSelection = RCC_SDMMC1CLKSOURCE_CLK48;
  64.   if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  65.   {
  66.     Error_Handler();
  67.   }
  68.     /**Configure the Systick interrupt time
  69.     */
  70.   HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
  71.     /**Configure the Systick
  72.     */
  73.   HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
  74.   /* SysTick_IRQn interrupt configuration */
  75.   HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
  76. }
  77. //Kolejnosc wywolania
  78. //CACHE_ENABLE_FOR_CPU(void);
  79. //HAL_Init();
  80. //SystemClock_Config();
  81. //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:

  1. int  GUI_BMP_DrawEx      (GUI_GET_DATA_FUNC * pfGetData, void * p, int x0, int y0);
  2. int GUI_PNG_DrawEx    (GUI_GET_DATA_FUNC * pfGetData, void * p, int x0, int y0);
  3. int GUI_JPEG_DrawEx      (GUI_GET_DATA_FUNC * pfGetData, void * p, int x0, int y0);
  4. 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ć:

  1. int APP_GetData(void * p, const U8 ** ppData, unsigned NumBytes, U32 Off)
  2. {
  3.     static char _acBuffer[0x200];
  4.     HANDLE * phFile;
  5.     DWORD NumBytesRead;
  6.     phFile = (HANDLE *)p;
  7.     //Sprawdzenie rozmiaru bufora
  8.     if (NumBytes > sizeof(acBuffer))
  9.     {
  10.         NumBytes = sizeof(acBuffer);
  11.     }
  12.     //Ustawienie wzkaznika na wymagana pozycje
  13.     SetFilePointer(*phFile, Off, 0, FILE_BEGIN);
  14.     //Dane do bufora
  15.     ReadFile(*phFile, acBuffer, NumBytes, &NumBytesRead, NULL);
  16.     //Ustawienie wskaznika na poczatek bufora
  17.     *ppData = acBuffer;
  18.     //Zwrócenie liczby odczytanych danych
  19.     return NumBytesRead;
  20. }

Ona musi zostać odpowiednio przerobiona na odczytywanie danych z pliku, co wymaga zmiany dwóch linijek.

  1. int APP_GetData(void * p, const U8 * * ppData, unsigned NumBytesReq, U32 Off)
  2. {
  3.     FIL * phFile;
  4.     UINT NumBytesRead;
  5.     static U8 _acBuffer[2000];
  6.     phFile = (FIL *) p;
  7.     //Sprawdzenie rozmiarów bufora
  8.     if (NumBytesReq > sizeof(_acBuffer))
  9.     {
  10.         NumBytesReq = sizeof(_acBuffer);
  11.     }
  12.     //Przesuniecie wskaznika
  13.     f_lseek(phFile, Off);
  14.     //Wprowadzenie danych do bufora
  15.     res = f_read(phFile, _acBuffer, NumBytesReq, (UINT *)&NumBytesRead);
  16.     //Ustawienie wskaznika na poczatek bufora
  17.     *ppData = _acBuffer;
  18.     // Return number of available bytes
  19.     return NumBytesRead;
  20. }

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.

  1. void Print_BMP(char nazwapliku[20])
  2. {
  3.    uint32_t prim;
  4.    if(f_mount(&SDFatFs, "", 1) == FR_OK)
  5.    {
  6.     if(f_open(&myFile, nazwapliku , FA_READ) == FR_OK)
  7.     {
  8.         prim = __get_PRIMASK();
  9.         __disable_irq();
  10.         GUI_BMP_DrawEx(APP_GetData, &myFile, 0, 0);
  11.         if (!prim) {
  12.             __enable_irq();
  13.         }
  14.     }
  15.    f_close(&myFile);
  16.    f_mount(0, "", 1);
  17.   }
  18.   else
  19.   {
  20.         //Blad otwarcia pliku
  21.   }
  22. }

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:

  1.     //Wczesniejsze inicjalizacje
  2.     GUI_SetLayerVisEx (0, 1);
  3.     GUI_SetLayerVisEx (1, 0);
  4.     GUI_SelectLayer(0);
  5.     Print_JPEG("image02_320x240.jpg");