Ten post chciałbym poświęcić na przygotowanie wyświetlacza na płytce discovery. Program będę przygotowywał w oparciu o biblioteki HAL'a bez bibliotek BSP. Przedstawię tutaj funkcje graficzne, służące do rysowania elementów na ekranie oraz wypisywanie tekstu. Przygotowane funkcje, w pewnej części będą się opierać na bibliotece udostępnionej przez ST.
Link do pobrania niezbędnych elementów znajduje się na dole strony.
Jak zwykle rozpocznę od przygotowania niezbędnych elementów za pomocą środowiska CubeMx.
Iinimalizacja jest standardowa i bardzo podobna do tej przedstawionej w poprzednich postach do wyświetlacza.
Program został wygenerowany dla środowiska Keil, natomiast nie ma problemu aby go wykorzystać np. AC6 pod Eclipsem.
Do wygenerowanego projektu należy dołożyć kilka elementów.
Na samym początku należy pamiętać aby uruchomić układ pamięci RAM czyli MT48LC4M32B2 od Microna.
Procedura inicjalizacyjna wygenerowana przez program CubeMx wygląda następująco:
Kolejna funkcja jest odpowiedzialna za procedurę startową pamięci, gdzie należy przesłać do pamięci komendę konfiguracji zegara, zamknięcie wierszy w podanym banku pamięci (precharge all), włączenie trybu automatycznego odświeżania, ustawienia trybów pracy oraz wybrania szybkości odświeżania.
Kolejnym elementem jest inicjalizacja wyświetlacza za pomocą funkcji wygenerowanych przez program CubeMx.
Prace z wyświetlaczem należy rozpocząć od ustawienia bufora w pamięci RAM, gdzie dane będą przerzucane.
Dalej wykorzystuje się przygotowane funkcje które pozwolą na rysowanie obiektów na ekranie, wypisanie tekstu itp. W bibliotece są jeszcze zdefiniowane funkcje dla zestawu kolorów RGB565 natomiast wymagają one zmiany koloru podczas inicjalizacji. Poniżej znajdują się funkcje które wykorzystują DMA2D.
Wypełnienie ekranu podanym kolorem:
Rysowanie piksela na ekranie:
Wypełnienie ekranu kolorem:
Rysowanie linii:
Rysowanie prostokąta z wypełnieniem:
Rysowanie pustego prostokąta:
Rysowanie pustego koła:
Rysowanie koła z wypełnieniem:
Rysowanie bitmapy:
Rysowanie elipsy:
Rysowanie elipsy z wypełnieniem:
Wypisanie znaku:
Ta funkcja wymaga odrobiny wyjaśnienia. Na początku sprawdza czy podany znak jest znakiem polskim ( o sposobie generowania polskiej czcionki na dole strony ). Jeśli nie jest to rozpoczyna wypisanie znaku standardowego. Natomiast jeśli jest to znak z zakresu znaków polskich to przeszukuje i ustawia odpowiednią pozycję w tablicy z wygenerowaną czcionką.
Wypisanie ciągu znaków:
Rysowanie bitmapy wykonuje się dwiema funkcjami. Najpierw należy ją otworzyć i załadować do pamięci, po czym z tej pamięci obraz jest ładowany na ekran:
Pozostałe funkcje są opisane w bezpośrednio w bibliotece.
Funkcje zostały przygotowane w oparciu o przykłady udostępnione przez firmę ST.
Inicjalizacja panelu dotykowego wygląda następująco:
Obsługa dotyku odbywa się, jak już wspomniałem wcześniej, w przerwaniu od timera. Do testów można ją spokojnie wywołać w pętli while:
Jeśli chce się wykorzystywać multitouch wtedy obsługa wygląda następująco:
W przypadku chęci wykorzystania przycisków można to zrobić w sposób następujący:
Po włączeniu programu należy po lewej stronie wpisać następującą sekwencję znaków znajdującą się na obrazku poniżej.
Znaki polskie zostaną przerzucone na koniec pliku, z pominięciem znaków specjalnych, których nie definiowałem w czcionce.
W celu wygenerowania należy wybrać czcionkę:
Przed generacją należy przygotować ustawienia:
Aby czcionka poprawnie działała w całym projekcie to należy ustawić kodowanie dla projektu na UTF-8. Wykonuje się to poprzez wejście w Window -> Preferences -> Workspace i tam w groupboxie Text file encoding należy wybrać Other i UTF8. Jeśli nie wprowadzono żadnych zmian w projekcie to będzie on to kodowanie dziedziczył. Aby się upewnić należy kliknąć prawym przyciskiem myszy na projekt po czym wybrać Properties. Tam w zakładce Resource znajduje się wybór sposobu kodowania.
Po wygenerowaniu kodu dla czcionki należy dodać na początku danych w tablicy znak spacji. Jego rozmiar będzie zależny od rozmiaru czcionki. Poniżej przedstawiam przykładowe rozwiązanie:
Struktura z danymi dla czcionki znajduje się w pliku z zdefiniowaną czcionką. Dane do niej powinny być zawarte w pliku fonts.h oraz w bibliotece do wyświetlacza jako extern.
Funkcje odczytujące dane z czcionek zostały zawarte w bibliotece dla wyświetlacza.
Wyrysowanie znaku oraz znalezienie polskiego znaku zostało przedstawione we wcześniejszej części posta.
Cały projekt dla wyświetlacza wraz z wygenerowanymi czcionkami można pobrać z dysku Google pod tym linkiem. Znajduje się tam folder z czcionkami przygotowanymi przez ST oraz wygenerowanymi w programie przedstawionym powyżej.
Link do pobrania niezbędnych elementów znajduje się na dole strony.
Cube Mx:
Jak zwykle rozpocznę od przygotowania niezbędnych elementów za pomocą środowiska CubeMx.
Iinimalizacja jest standardowa i bardzo podobna do tej przedstawionej w poprzednich postach do wyświetlacza.
Konfiguaracja LTDC:
Włączenie DMA2D:
Ustawienie kontrolera FMC:
Program został wygenerowany dla środowiska Keil, natomiast nie ma problemu aby go wykorzystać np. AC6 pod Eclipsem.
Kod programu dla wyświetlacza oraz pamięci SDRAM:
Do wygenerowanego projektu należy dołożyć kilka elementów.
Na samym początku należy pamiętać aby uruchomić układ pamięci RAM czyli MT48LC4M32B2 od Microna.
Procedura inicjalizacyjna wygenerowana przez program CubeMx wygląda następująco:
- /* FMC initialization function */
- static void MX_FMC_Init(void)
- {
- FMC_SDRAM_TimingTypeDef SdramTiming;
- /** Perform the SDRAM1 memory initialization sequence
- */
- hsdram1.Instance = FMC_SDRAM_DEVICE;
- /* hsdram1.Init */
- hsdram1.Init.SDBank = FMC_SDRAM_BANK1;
- hsdram1.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_8;
- hsdram1.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_12;
- hsdram1.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16;
- hsdram1.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_2;
- hsdram1.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_2;
- hsdram1.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
- hsdram1.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;
- hsdram1.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE;
- hsdram1.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_0;
- /* SdramTiming */
- SdramTiming.LoadToActiveDelay = 2;
- SdramTiming.ExitSelfRefreshDelay = 6;
- SdramTiming.SelfRefreshTime = 4;
- SdramTiming.RowCycleDelay = 6;
- SdramTiming.WriteRecoveryTime = 2;
- SdramTiming.RPDelay = 2;
- SdramTiming.RCDDelay = 2;
- if (HAL_SDRAM_Init(&hsdram1, &SdramTiming) != HAL_OK)
- {
- Error_Handler();
- }
- }
Kolejna funkcja jest odpowiedzialna za procedurę startową pamięci, gdzie należy przesłać do pamięci komendę konfiguracji zegara, zamknięcie wierszy w podanym banku pamięci (precharge all), włączenie trybu automatycznego odświeżania, ustawienia trybów pracy oraz wybrania szybkości odświeżania.
- uint8_t MT48LC4M32B2_SDRAM_Initial(SDRAM_HandleTypeDef *hsdram)
- {
- __IO uint32_t tmpmrd=0;
- /* Configure a clock configuration enable command */
- command.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE;
- command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
- command.AutoRefreshNumber = 1;
- command.ModeRegisterDefinition = 0;
- halStatus = HAL_SDRAM_SendCommand(hsdram,&command,SDRAM_TIMEOUT);
- if(halStatus != HAL_OK) { return 0x00; }
- /* Delay at least 100us */
- HAL_Delay(1);
- /* Configure a PALL (precharge all) command */
- command.CommandMode = FMC_SDRAM_CMD_PALL;
- command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
- command.AutoRefreshNumber = 1;
- command.ModeRegisterDefinition = 0;
- halStatus = HAL_SDRAM_SendCommand(hsdram,&command,SDRAM_TIMEOUT);
- if(halStatus != HAL_OK) { return 0x00; }
- /* Configure an Auto Refresh command */
- command.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
- command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
- command.AutoRefreshNumber = 8;
- command.ModeRegisterDefinition = 0;
- halStatus = HAL_SDRAM_SendCommand(hsdram,&command,SDRAM_TIMEOUT);
- if(halStatus != HAL_OK) { return 0x00; }
- /* Configure an Auto Refresh command */
- tmpmrd = (uint32_t) SDRAM_MODEREG_BURST_LENGTH_1 |
- SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |
- SDRAM_MODEREG_CAS_LATENCY_2 |
- SDRAM_MODEREG_OPERATING_MODE_STANDARD |
- SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
- command.CommandMode = FMC_SDRAM_CMD_LOAD_MODE;
- command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
- command.AutoRefreshNumber = 1;
- command.ModeRegisterDefinition = tmpmrd;
- halStatus = HAL_SDRAM_SendCommand(hsdram,&command,SDRAM_TIMEOUT);
- if(halStatus != HAL_OK) { return 0x00; }
- /* Set the device refresh rate */
- HAL_SDRAM_ProgramRefreshRate(hsdram, REFRESH_COUNT);
- return 1;
- }
Kolejnym elementem jest inicjalizacja wyświetlacza za pomocą funkcji wygenerowanych przez program CubeMx.
- MX_DMA2D_Init();
- MX_FMC_Init();
- MX_I2C3_Init();
- MX_LTDC_Init();
- MX_SDMMC1_SD_Init();
- MX_FATFS_Init();
Prace z wyświetlaczem należy rozpocząć od ustawienia bufora w pamięci RAM, gdzie dane będą przerzucane.
- HAL_LTDC_SetAddress(&hltdc,(uint32_t)0xC0000000,0);
Dalej wykorzystuje się przygotowane funkcje które pozwolą na rysowanie obiektów na ekranie, wypisanie tekstu itp. W bibliotece są jeszcze zdefiniowane funkcje dla zestawu kolorów RGB565 natomiast wymagają one zmiany koloru podczas inicjalizacji. Poniżej znajdują się funkcje które wykorzystują DMA2D.
Wypełnienie ekranu podanym kolorem:
- void ROCKTECH_FillScreen_DMA2D(uint32_t color)
- {
- hdma2d.Init.Mode = DMA2D_R2M; /* DMA2D register to memory transfer mode */
- hdma2d.Init.OutputOffset = 0; /* Min_Data = 0x0000 and Max_Data = 0x3FFF */
- if((HAL_DMA2D_Init(&hdma2d) == HAL_OK) &&
- (HAL_DMA2D_Start(&hdma2d, color, hltdc.LayerCfg[0].FBStartAdress,hltdc.LayerCfg[0].ImageWidth,
- hltdc.LayerCfg[0].ImageHeight) == HAL_OK))
- {
- /* Polling for transfer complete or CLUT loading. */
- HAL_DMA2D_PollForTransfer(&hdma2d, 10);
- }
- }
Rysowanie piksela na ekranie:
- void ROCKTECH_DrawPixel_DMA2D(uint16_t Xpos, uint16_t Ypos, uint32_t color)
- {
- *(volatile uint32_t*) (hltdc.LayerCfg[0].FBStartAdress +
- (4*(Ypos*hltdc.LayerCfg[0].ImageWidth + Xpos))) = color;
- }
Wypełnienie ekranu kolorem:
- void ROCKTECH_FillScreen_DMA2D(uint32_t color)
- {
- hdma2d.Init.Mode = DMA2D_R2M; /* DMA2D register to memory transfer mode */
- hdma2d.Init.OutputOffset = 0; /* Min_Data = 0x0000 and Max_Data = 0x3FFF */
- if((HAL_DMA2D_Init(&hdma2d) == HAL_OK) &&
- (HAL_DMA2D_Start(&hdma2d, color, hltdc.LayerCfg[0].FBStartAdress,hltdc.LayerCfg[0].ImageWidth,
- hltdc.LayerCfg[0].ImageHeight) == HAL_OK))
- {
- /* Polling for transfer complete or CLUT loading. */
- HAL_DMA2D_PollForTransfer(&hdma2d, 10);
- }
- }
Rysowanie linii:
- void ROCKTECH_DrawLine_DMA2D(uint16_t start_x, uint16_t start_y,
- uint16_t end_x, uint16_t end_y, uint32_t color)
- {
- int dx=0;
- int dy=0;
- int err=0;
- int ystep;
- int steep = abs(end_y-start_y)>abs(end_x-start_x);
- if (steep){
- SWAPVARIABLES(start_x,start_y);
- SWAPVARIABLES(end_x,end_y);
- }
- if(start_x>end_x){
- SWAPVARIABLES(start_x,end_x);
- SWAPVARIABLES(start_y,end_y);
- }
- dx=end_x-start_x;
- dy=abs(end_y-start_y);
- err=dx/2;
- if(start_y<end_y){
- ystep = 1;
- }
- else{
- ystep = -1;
- }
- for (;start_x<=end_x;start_x++)
- {
- if (steep){
- ROCKTECH_DrawPixel_DMA2D(start_y,start_x,color);
- }
- else{
- ROCKTECH_DrawPixel_DMA2D(start_x,start_y,color);
- }
- err-=dy;
- if (err<0)
- {
- end_y += ystep;
- err+=dx;
- }
- }
- }
Rysowanie prostokąta z wypełnieniem:
- void ROCKTECH_FillRectangle_DMA2D(uint16_t start_x, uint16_t start_y,
- uint16_t end_x, uint16_t end_y, uint32_t color)
- {
- uint32_t addr = 0;
- if(start_x>end_x){ SWAPVARIABLES(start_x,end_x); }
- if(start_y>end_y){ SWAPVARIABLES(start_y,end_y); }
- addr = (hltdc.LayerCfg[0].FBStartAdress) + 4*(start_y*hltdc.LayerCfg[0].ImageWidth + start_x);
- hdma2d.Init.Mode = DMA2D_R2M;
- hdma2d.Init.OutputOffset = hltdc.LayerCfg[0].ImageWidth-(end_x-start_x);
- if((HAL_DMA2D_Init(&hdma2d) == HAL_OK) &&
- (HAL_DMA2D_Start(&hdma2d, color, addr, end_x-start_x, end_y-start_y) == HAL_OK))
- {
- HAL_DMA2D_PollForTransfer(&hdma2d, 10);
- }
- }
Rysowanie pustego prostokąta:
- void ROCKTECH_DrawClearRectangle(uint16_t start_x, uint16_t start_y,
- uint16_t end_x, uint16_t end_y, uint16_t color)
- {
- ROCKTECH_DrawLine_DMA2D(start_x,start_y,end_x,start_y,color);
- ROCKTECH_DrawLine_DMA2D(end_x, start_y, end_x, end_y, color);
- ROCKTECH_DrawLine_DMA2D(start_x, start_y, start_x, end_y, color);
- ROCKTECH_DrawLine_DMA2D(start_x, end_y, end_x, end_y, color);
- }
Rysowanie pustego koła:
- void ROCKTECH_DrawCircle_DMA2D(uint16_t x_start, uint16_t y_start, int Rad, uint32_t color)
- {
- int f = 1-Rad;
- int ddF_x=1;
- int ddF_y=-2*Rad;
- int x = 0;
- int y = Rad;
- ROCKTECH_DrawPixel_DMA2D(x_start,y_start+Rad,color);
- ROCKTECH_DrawPixel_DMA2D(x_start,y_start-Rad,color);
- ROCKTECH_DrawPixel_DMA2D(x_start+Rad,y_start,color);
- ROCKTECH_DrawPixel_DMA2D(x_start-Rad,y_start,color);
- while (x<y)
- {
- if (f>=0)
- {
- y--;
- ddF_y+=2;
- f+=ddF_y;
- }
- x++;
- ddF_x+=2;
- f+=ddF_x;
- ROCKTECH_DrawPixel_DMA2D(x_start+x,y_start+y,color);
- ROCKTECH_DrawPixel_DMA2D(x_start-x,y_start+y,color);
- ROCKTECH_DrawPixel_DMA2D(x_start+x,y_start-y,color);
- ROCKTECH_DrawPixel_DMA2D(x_start-x,y_start-y,color);
- ROCKTECH_DrawPixel_DMA2D(x_start+y,y_start+x,color);
- ROCKTECH_DrawPixel_DMA2D(x_start-y,y_start+x,color);
- ROCKTECH_DrawPixel_DMA2D(x_start+y,y_start-x,color);
- ROCKTECH_DrawPixel_DMA2D(x_start-y,y_start-x,color);
- }
- }
Rysowanie koła z wypełnieniem:
- void ROCKTECH_FillCircle_DMA2D(uint16_t x_start, uint16_t y_start, uint16_t Rad, uint32_t color)
- {
- int32_t change_pos; /* Decision Variable */
- uint32_t x_current_pos; /* Current X Value */
- uint32_t y_current_pos; /* Current Y Value */
- change_pos = 3 - (Rad << 1);
- x_current_pos = 0;
- y_current_pos = Rad;
- while (x_current_pos <= y_current_pos)
- {
- if(y_current_pos > 0)
- {
- ROCKTECH_DrawLine_DMA2D((x_start - y_current_pos),
- (y_start + x_current_pos),
- (x_start - y_current_pos) + 2*y_current_pos,
- (y_start + x_current_pos),
- color);
- ROCKTECH_DrawLine_DMA2D((x_start - y_current_pos),
- (y_start - x_current_pos),
- (x_start - y_current_pos) + 2*y_current_pos,
- (y_start - x_current_pos),
- color);
- }
- if(x_current_pos > 0)
- {
- ROCKTECH_DrawLine_DMA2D((x_start - x_current_pos),
- (y_start - y_current_pos),
- (x_start - x_current_pos) + 2*x_current_pos,
- (y_start - y_current_pos),
- color);
- ROCKTECH_DrawLine_DMA2D((x_start - x_current_pos),
- (y_start + y_current_pos),
- (x_start - x_current_pos) + 2*x_current_pos,
- (y_start + y_current_pos),
- color);
- }
- if (change_pos < 0)
- {
- change_pos += (x_current_pos << 2) + 6;
- }
- else
- {
- change_pos += ((x_current_pos - y_current_pos) << 2) + 10;
- y_current_pos--;
- }
- x_current_pos++;
- }
- ROCKTECH_DrawCircle_DMA2D(x_start, y_start, Rad, color);
- }
Rysowanie bitmapy:
- void ROCKTECH_DrawBitmap_DMA2D(uint32_t xpositon, uint32_t yposition, uint8_t *bmp_picture)
- {
- uint32_t index=0;
- uint32_t width=0;
- uint32_t height=0;
- uint32_t bit_pixel=0;
- uint32_t address;
- /* Get bitmap data addres offset*/
- index = *(__IO uint16_t *) (bmp_picture+10);
- index |= (*(__IO uint16_t *) (bmp_picture+12)) << 16;
- /* Read bitmap width */
- width = *(__IO uint16_t *) (bmp_picture+18);
- width |= (*(__IO uint16_t *) (bmp_picture+20)) << 16;
- /* Read bitmap height */
- height = *(__IO uint16_t *) (bmp_picture+22);
- height |= (*(__IO uint16_t *) (bmp_picture+24)) << 16;
- /* Read bit/pixel */
- bit_pixel = *(__IO uint16_t *) (bmp_picture+28);
- /* Set the addres*/
- address = hltdc.LayerCfg[0].FBStartAdress+(((X_AXIS_SIZE*yposition) + xpositon)*4);
- /* Bypass the bitmap header */
- bmp_picture += (index + (width * (height-1) * (bit_pixel/8)));
- /* Get the layer pixel format */
- if((bit_pixel/8) == 4) { ROCKTECH_FillScreen_DMA2D(0xFFFF0000); } //ARGB8888
- else if((bit_pixel/8) == 2) { ROCKTECH_FillScreen_DMA2D(0xFF00FF00); } //RGB565
- else //if RGB888 then convert it into ARGB8888
- {
- for(index=0; index < height; index++) /* Convert picture to ARGB8888 pixel format */
- {
- hdma2d.Init.Mode = DMA2D_M2M_PFC;
- hdma2d.Init.ColorMode = DMA2D_ARGB8888;
- hdma2d.Init.OutputOffset = 0;
- hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA;
- hdma2d.LayerCfg[1].InputAlpha = 0xFF;
- hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB888;
- hdma2d.LayerCfg[1].InputOffset = 0;
- if(HAL_DMA2D_Init(&hdma2d) == HAL_OK){
- if(HAL_DMA2D_ConfigLayer(&hdma2d, 1) == HAL_OK){
- if(HAL_DMA2D_Start(&hdma2d, (uint32_t) bmp_picture, address, width, 1) == HAL_OK){
- HAL_DMA2D_PollForTransfer(&hdma2d, 10);
- }
- }
- }
- address += X_AXIS_SIZE*4;
- bmp_picture -= width*(bit_pixel/8);
- }
- }
- hdma2d.Init.Mode = DMA2D_M2M_BLEND;
- hdma2d.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
- hdma2d.Init.OutputOffset = 0;
- hdma2d.LayerCfg[1].InputOffset = 0;
- hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_ARGB8888;
- hdma2d.LayerCfg[1].AlphaMode = DMA2D_REPLACE_ALPHA;
- hdma2d.LayerCfg[1].InputAlpha = 0;
- hdma2d.LayerCfg[0].InputOffset = 0;
- hdma2d.LayerCfg[0].InputColorMode = DMA2D_INPUT_ARGB8888;
- hdma2d.LayerCfg[0].AlphaMode = DMA2D_REPLACE_ALPHA;
- hdma2d.LayerCfg[0].InputAlpha = 0;
- if (HAL_DMA2D_Init(&hdma2d) == HAL_OK)
- {
- HAL_DMA2D_ConfigLayer(&hdma2d, 0);
- HAL_DMA2D_ConfigLayer(&hdma2d, 1);
- }
- bit_pixel = 0;
- }
Rysowanie elipsy:
- void ROCKTECH_LCD_DrawEllipse(int x_start, int y_start, int x_Rad, int y_Rad, uint32_t color)
- {
- int x = 0;
- int y = -y_Rad;
- int err = 2-2*x_Rad;
- int e2 = 0;
- float k = 0;
- float rad1 = 0;
- float rad2 = 0;
- rad1 = x_Rad;
- rad2 = y_Rad;
- k = (float)(rad2/rad1);
- do {
- ROCKTECH_DrawPixel_DMA2D((x_start-(uint16_t)(x/k)), (y_start+y), color);
- ROCKTECH_DrawPixel_DMA2D((x_start+(uint16_t)(x/k)), (y_start+y), color);
- ROCKTECH_DrawPixel_DMA2D((x_start+(uint16_t)(x/k)), (y_start-y), color);
- ROCKTECH_DrawPixel_DMA2D((x_start-(uint16_t)(x/k)), (y_start-y), color);
- e2 = err;
- if (e2 <= x) {
- err += ++x*2+1;
- if (-y == x && e2 <= y) e2 = 0;
- }
- if (e2 > y) err += ++y*2+1;
- }
- while (y <= 0);
- }
Rysowanie elipsy z wypełnieniem:
- void ROCKTECH_LCD_FillEllipse_DMA2D(int x_start, int y_start, int x_Rad, int y_Rad, uint32_t color)
- {
- int x = 0, y = -y_Rad, err = 2-2*x_Rad, e2;
- float k = 0, rad1 = 0, rad2 = 0;
- rad1 = x_Rad;
- rad2 = y_Rad;
- k = (float)(rad2/rad1);
- do{
- ROCKTECH_DrawLine_DMA2D((x_start-(uint16_t)(x/k)),
- (y_start+y),
- (x_start-(uint16_t)(x/k)) + (2*(uint16_t)(x/k) + 1),
- (y_start+y),
- color);
- ROCKTECH_DrawLine_DMA2D((x_start-(uint16_t)(x/k)),
- (y_start-y),
- (x_start-(uint16_t)(x/k)) + (2*(uint16_t)(x/k) + 1),
- (y_start-y),
- color);
- e2 = err;
- if (e2 <= x)
- {
- err += ++x*2+1;
- if (-y == x && e2 <= y) e2 = 0;
- }
- if (e2 > y) err += ++y*2+1;
- }while (y <= 0);
- }
Wypisanie znaku:
- void ROCKTECH_DrawChar(uint16_t xposition, uint16_t yposition, const uint8_t c)
- {
- uint32_t i = 0, j = 0;
- uint16_t height, width;
- uint8_t offset;
- uint8_t *pchar;
- uint32_t line;
- uint8_t loop = 127;
- /* 127 - Ó - (uint8_t)co odpowiada 147 - (uint16_t)50067
- * 128 - ó - (uint8_t)co odpowiada 179 - (uint16_t)50099
- * 129 - Ą - (uint8_t)co odpowiada 132 - (uint16_t)50308
- * 130 - ą - (uint8_t)co odpowiada 133 - (uint16_t)50309
- * 131 - Ć - (uint8_t)co odpowiada 134 - (uint16_t)50310
- * 132 - ć - (uint8_t)co odpowiada 135 - (uint16_t)50311
- * 133 - Ę - (uint8_t)co odpowiada 152 - (uint16_t)50328
- * 134 - ę - (uint8_t)co odpowiada 153 - (uint16_t)50329
- * 135 - Ł - (uint8_t)co odpowiada 129 - (uint16_t)50561
- * 136 - ł - (uint8_t)co odpowiada 130 - (uint16_t)50562
- * 137 - Ń - (uint8_t)co odpowiada 131 - (uint16_t)50563
- * 138 - ń - (uint8_t)co odpowiada 132 - (uint16_t)50564
- * 139 - Ś - (uint8_t)co odpowiada 154 - (uint16_t)50586
- * 140 - ś - (uint8_t)co odpowiada 155 - (uint16_t)50587
- * 141 - Ź - (uint8_t)co odpowiada 185 - (uint16_t)50617
- * 142 - ź - (uint8_t)co odpowiada 186 - (uint16_t)50618
- * 143 - Ż - (uint8_t)co odpowiada 187 - (uint16_t)50619
- * 144 - ż - (uint8_t)co odpowiada 188 - (uint16_t)50620
- */
- uint16_t PolishSignTable16b[18] = {
- 50067, 50099, 50308, 50309, 50310,
- 50311, 50328, 50329, 50561, 50562,
- 50563, 50564, 50586, 50587, 50617,
- 50618, 50619, 50620
- };
- uint16_t PolishSignTable8b[18] = {
- 147, 179, 132, 133, 134,
- 135, 152, 153, 129, 130,
- 131, 132, 154, 155, 185,
- 186, 187, 188
- };
- if((c < 127 || c > 144) && c < 50067){
- ch = &lcdprop.pFont->table[(c - ' ') * lcdprop.pFont->Height * ((lcdprop.pFont->Width + 7) / 8)];
- loop = 145;
- }
- for(uint8_t i = 0, loop = 127; loop < 145; i++, loop++)
- {
- if(c == PolishSignTable16b[i])
- {
- ch = &lcdprop.pFont->table[(loop - ' ') * lcdprop.pFont->Height * ((lcdprop.pFont->Width + 7) / 8)];
- break;
- }
- else if((uint8_t)c == PolishSignTable8b[i])
- {
- if(i == 2) { }
- else{
- ch = &lcdprop.pFont->table[(loop - ' ') * lcdprop.pFont->Height * ((lcdprop.pFont->Width + 7) / 8)];
- break;
- }
- }
- }
- height = lcdprop.pFont->Height;
- width = lcdprop.pFont->Width;
- offset = 8 *((width + 7)/8) - width;
- for(i = 0; i < height; i++)
- {
- pchar = ((uint8_t *)ch + (width + 7)/8 * i);
- switch((width + 7)/8)
- {
- case 1:
- line = pchar[0];
- break;
- case 2:
- line = (pchar[0]<< 8) | pchar[1];
- break;
- case 3:
- default:
- line = (pchar[0]<< 16) | (pchar[1]<< 8) | pchar[2];
- break;
- }
- for (j = 0; j < width; j++)
- {
- if(line & (1 << (width- j + offset- 1)))
- {
- ROCKTECH_DrawPixel((xposition + j), yposition, lcdprop.TextColor);
- }
- else
- {
- ROCKTECH_DrawPixel((xposition + j), yposition, lcdprop.BackColor);
- }
- }
- yposition++;
- }
- }
Ta funkcja wymaga odrobiny wyjaśnienia. Na początku sprawdza czy podany znak jest znakiem polskim ( o sposobie generowania polskiej czcionki na dole strony ). Jeśli nie jest to rozpoczyna wypisanie znaku standardowego. Natomiast jeśli jest to znak z zakresu znaków polskich to przeszukuje i ustawia odpowiednią pozycję w tablicy z wygenerowaną czcionką.
Wypisanie ciągu znaków:
- void ROCKTECH_DisplayString(uint16_t xposition, uint16_t yposition, uint8_t *Text, Text_AlignModeTypdef Mode)
- {
- uint16_t ref_column = 1;
- uint16_t i = 0;
- uint32_t size = 0;
- uint32_t xsize = 0;
- uint8_t *ptr = Text;
- /* Calculate text size */
- while(*ptr++) { size++; }
- xsize = (X_AXIS_SIZE/lcdprop.pFont->Width);
- /* Set position on the screen */
- switch(Mode)
- {
- case CENTER_MODE:{
- ref_column = xposition + ((xsize - size) * lcdprop.pFont->Width) / 2;
- break;
- }
- case LEFT_MODE:{
- ref_column = xposition;
- break;
- }
- case RIGHT_MODE:{
- ref_column = - xposition + ((xsize - size) * lcdprop.pFont->Width);
- break;
- }
- default:{
- ref_column = xposition;
- break;
- }
- }
- if ((ref_column < 1) || (ref_column >= 0x8000)) { ref_column = 1; }
- while ((*Text != '\n') & (((X_AXIS_SIZE - (i*lcdprop.pFont->Width)) & 0xFFFF) >= lcdprop.pFont->Width))
- {
- ROCKTECH_DrawChar(ref_column, yposition, *Text++);
- ref_column += lcdprop.pFont->Width;
- i++;
- }
Rysowanie bitmapy wykonuje się dwiema funkcjami. Najpierw należy ją otworzyć i załadować do pamięci, po czym z tej pamięci obraz jest ładowany na ekran:
- uint32_t OpenBMP(uint8_t *ptr, const char* fname)
- {
- uint32_t ind=0,sz=0,i1=0,ind1=0;
- static uint32_t bmp_addr;
- if(f_open(&MyFile,fname,FA_READ)!=FR_OK)
- {
- ROCKTECH_FillScreen_DMA2D(LCD_COLOR_RED);
- }
- else
- {
- if(f_read(&MyFile,sect,30,(UINT *)&bytesread)!=FR_OK)
- {
- Error_Handler();
- }
- else
- {
- bmp_addr=(uint32_t)sect;
- /*Get bitmap size*/
- sz=*(uint16_t*)(bmp_addr + 2);
- sz|=(*(uint16_t*)(bmp_addr + 4))<<16;
- /*Get bitmap data address offset*/
- ind=*(uint16_t*)(bmp_addr + 10);
- ind|=(*(uint16_t*)(bmp_addr + 12))<<16;
- f_close(&MyFile);
- f_open(&MyFile,fname,FA_READ);
- ind=0;
- do
- {
- if(sz<512)
- {
- i1=sz;
- }
- else
- {
- i1=512;
- }
- sz-=i1;
- f_lseek(&MyFile,ind1);
- f_read(&MyFile,sect,i1,(UINT *)&bytesread);
- memcpy((void*)(bmp1+ind1),(void*)sect,i1);
- ind1+=i1;
- }
- while(sz>0);
- f_close(&MyFile);
- }
- ind1=0;
- }
- return 0;
- }
- void ROCKTECH_DrawBitmap_DMA2D(uint32_t xpositon, uint32_t yposition, uint8_t *bmp_picture)
- {
- uint32_t index=0;
- uint32_t width=0;
- uint32_t height=0;
- uint32_t bit_pixel=0;
- uint32_t address;
- /* Get bitmap data addres offset*/
- index = *(__IO uint16_t *) (bmp_picture+10);
- index |= (*(__IO uint16_t *) (bmp_picture+12)) << 16;
- /* Read bitmap width */
- width = *(__IO uint16_t *) (bmp_picture+18);
- width |= (*(__IO uint16_t *) (bmp_picture+20)) << 16;
- /* Read bitmap height */
- height = *(__IO uint16_t *) (bmp_picture+22);
- height |= (*(__IO uint16_t *) (bmp_picture+24)) << 16;
- /* Read bit/pixel */
- bit_pixel = *(__IO uint16_t *) (bmp_picture+28);
- /* Set the addres*/
- address = hltdc.LayerCfg[0].FBStartAdress+(((X_AXIS_SIZE*yposition) + xpositon)*4);
- /* Bypass the bitmap header */
- bmp_picture += (index + (width * (height-1) * (bit_pixel/8)));
- /* Get the layer pixel format */
- if((bit_pixel/8) == 4) { ROCKTECH_FillScreen_DMA2D(0xFFFF0000); } //ARGB8888
- else if((bit_pixel/8) == 2) { ROCKTECH_FillScreen_DMA2D(0xFF00FF00); } //RGB565
- else //if RGB888 then convert it into ARGB8888
- {
- for(index=0; index < height; index++) /* Convert picture to ARGB8888 pixel format */
- {
- hdma2d.Init.Mode = DMA2D_M2M_PFC;
- hdma2d.Init.ColorMode = DMA2D_ARGB8888;
- hdma2d.Init.OutputOffset = 0;
- hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA;
- hdma2d.LayerCfg[1].InputAlpha = 0xFF;
- hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB888;
- hdma2d.LayerCfg[1].InputOffset = 0;
- if(HAL_DMA2D_Init(&hdma2d) == HAL_OK){
- if(HAL_DMA2D_ConfigLayer(&hdma2d, 1) == HAL_OK){
- if(HAL_DMA2D_Start(&hdma2d, (uint32_t) bmp_picture, address, width, 1) == HAL_OK){
- HAL_DMA2D_PollForTransfer(&hdma2d, 10);
- }
- }
- }
- address += X_AXIS_SIZE*4;
- bmp_picture -= width*(bit_pixel/8);
- }
- }
- hdma2d.Init.Mode = DMA2D_M2M_BLEND;
- hdma2d.Init.ColorMode = DMA2D_OUTPUT_ARGB8888;
- hdma2d.Init.OutputOffset = 0;
- hdma2d.LayerCfg[1].InputOffset = 0;
- hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_ARGB8888;
- hdma2d.LayerCfg[1].AlphaMode = DMA2D_REPLACE_ALPHA;
- hdma2d.LayerCfg[1].InputAlpha = 0;
- hdma2d.LayerCfg[0].InputOffset = 0;
- hdma2d.LayerCfg[0].InputColorMode = DMA2D_INPUT_ARGB8888;
- hdma2d.LayerCfg[0].AlphaMode = DMA2D_REPLACE_ALPHA;
- hdma2d.LayerCfg[0].InputAlpha = 0;
- if (HAL_DMA2D_Init(&hdma2d) == HAL_OK)
- {
- HAL_DMA2D_ConfigLayer(&hdma2d, 0);
- HAL_DMA2D_ConfigLayer(&hdma2d, 1);
- }
- bit_pixel = 0;
- }
Pozostałe funkcje są opisane w bezpośrednio w bibliotece.
Kod programu dla panelu dotykowego:
Panel dotykowy obsługiwany jest w przerwaniu od timera. Dzięki temu uzyskuje się płynniejsze działanie dotyku na wyświetlaczu.Funkcje zostały przygotowane w oparciu o przykłady udostępnione przez firmę ST.
Inicjalizacja panelu dotykowego wygląda następująco:
- void Touch_FT5336_Initialization(void)
- {
- uint8_t regValue = 0;
- HAL_Delay(200);
- if(Touch_ReadID(TS_I2C_ADDRESS) != FT5336_ID_VALUE){
- Error();
- }
- tsOrientation = TS_SWAP_XY;
- regValue = (FT5336_G_MODE_INTERRUPT_POLLING & (FT5336_G_MODE_INTERRUPT_MASK >> FT5336_G_MODE_INTERRUPT_SHIFT))
- << FT5336_G_MODE_INTERRUPT_SHIFT;
- TS_IO_Write(TS_I2C_ADDRESS, FT5336_GMODE_REG, regValue);
- ft5336_handle.i2cInitialized = FT5336_I2C_INITIALIZED;
- }
Obsługa dotyku odbywa się, jak już wspomniałem wcześniej, w przerwaniu od timera. Do testów można ją spokojnie wywołać w pętli while:
- /* Timer callback function */
- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
- {
- static uint16_t x=0;
- static uint16_t y=0;
- char str1[20];
- if(CheckForUserInput() == 0)
- {
- TS_GetState(&TS_State);
- if(TS_State.touchDetected) /* TS_State.touchDetected >= 1 */
- {
- if((x != TS_State.touchX[0]) && (y != TS_State.touchY[0]))
- {
- x = TS_State.touchX[0];
- y = TS_State.touchY[0];
- /* Display cords on screen */
- ROCKTECH_SetFont(&Font12);
- ROCKTECH_SetTextColor(LCD_COLOR_WHITE);
- ROCKTECH_SetBackColor(LCD_BACKGROUND_DEFAULT);
- sprintf(str1,"1: x=%03d; y=%03d\n",x,y);
- ROCKTECH_DisplayString(0, 240, (uint8_t *)str1, LEFT_MODE, 1);
- }
- }
- }
- }
Jeśli chce się wykorzystywać multitouch wtedy obsługa wygląda następująco:
- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
- {
- static uint16_t x=0;
- static uint16_t y=0;
- char str1[20];
- static uint32_t tscnt[5]={0};
- static uint16_t xstart[5]={0}, ystart[5]={0};
- while (CheckForUserInput()==0)
- {
- TS_GetState(&TS_State);
- if(TS_State.touchDetected)
- {
- x = TS_State.touchX[0];
- y = TS_State.touchY[0];
- /* Display cords on screen */
- ROCKTECH_SetFont(&Font12);
- ROCKTECH_SetTextColor(LCD_COLOR_WHITE);
- ROCKTECH_SetBackColor(LCD_BACKGROUND_DEFAULT);
- sprintf(str1,"1: x=%03d; y=%03d\n",x,y);
- ROCKTECH_DisplayString(0, 150, (uint8_t *)str1, LEFT_MODE, 1);
- xstart[0]=x; ystart[0]=y;
- tscnt[0]++;
- if (TS_State.touchDetected >= 2)
- {
- x = TS_State.touchX[1];
- y = TS_State.touchY[1];
- xstart[1]=x; ystart[1]=y;
- tscnt[1]++;
- sprintf(str1,"2: x=%03d; y=%03d\n",x,y);
- ROCKTECH_DisplayString(0, 180, (uint8_t *)str1, LEFT_MODE, 1);
- }
- if (TS_State.touchDetected >= 3)
- {
- x = TS_State.touchX[2];
- y = TS_State.touchY[2];
- xstart[2]=x; ystart[2]=y;
- tscnt[2]++;
- sprintf(str1,"3: x=%03d; y=%03d\n",x,y);
- ROCKTECH_DisplayString(0, 210, (uint8_t *)str1, LEFT_MODE, 1);
- }
- if (TS_State.touchDetected >= 4)
- {
- x = TS_State.touchX[3];
- y = TS_State.touchY[3];
- xstart[3]=x; ystart[3]=y;
- tscnt[3]++;
- sprintf(str1,"4: x=%03d; y=%03d\n",x,y);
- ROCKTECH_DisplayString(0, 230, (uint8_t *)str1, LEFT_MODE, 1);
- }
- if (TS_State.touchDetected >= 5)
- {
- x = TS_State.touchX[4];
- y = TS_State.touchY[4];
- xstart[4]=x; ystart[4]=y;
- tscnt[4]++;
- sprintf(str1,"5: x=%03d; y=%03d\n",x,y);
- ROCKTECH_DisplayString(0, 250, (uint8_t *)str1, LEFT_MODE, 1);
- }
- }
- else
- {
- tscnt[0]=0;tscnt[1]=0;tscnt[2]=0;tscnt[3]=0;tscnt[4]=0;
- }
- }
- }
W przypadku chęci wykorzystania przycisków można to zrobić w sposób następujący:
- /* Define button */
- typedef struct btn{
- const uint16_t buttonNumber; /* ID, for button */
- ButtonClickedHandler HandlerBtn; /* pointer to a handler function called when button is pressed */
- const uint16_t positionTop; /* top edge of the button (in pixels, counted from top) x0*/
- const uint16_t positionBot; /* bottom edge of the button (in pixels, counted from top) y0 */
- const uint16_t positionLeft; /* left edge of the button (in pixels, counted from left) */
- const uint16_t positionRight; /* right edge of the button (in pixels, counted from left) */
- const sFONT * pfont; /* font */
- ButtonState_Typedef status_enable; /* block/unblocks button Handler function */
- } Button_Typedef;
- /* Example initialization */
- static Button_Typedef exampleBtn = {
- .buttonNumber= 0x01,
- .HandlerBtn= exampleBtnClickedHandler,
- .positionTop= 0, /* Top position y */
- .positionBot= 50, /* Bottm position y */
- .positionLeft= 60, /* Left position x */
- .positionRight= 100, /* Right position x*/
- .pfont = &Font_ConsolasPL_18ptsBold,
- .status_enable= ENABLED
- };
- static Button_Typedef * tpButtons[BTN_COUNT] = {
- &entryBtn
- };
- /* Timer interrupt */
- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
- {
- static uint16_t x=0;
- static uint16_t y=0;
- char str1[20];
- Button_Typedef * btn = NULL;
- if(CheckForUserInput() == 0)
- {
- TS_GetState(&TS_State);
- if(TS_State.touchDetected)
- {
- if((x != TS_State.touchX[0]) && (y != TS_State.touchY[0]))
- {
- x = TS_State.touchX[0];
- y = TS_State.touchY[0];
- for(uint8_t i = 0; i < BTN_COUNT; i++)
- {
- if( (tpButtons[i]->status_enable== ENABLED) &&
- (x < tpButtons[i]->positionRight) && (x > tpButtons[i]->positionLeft) &&
- (y > tpButtons[i]->positionTop) && (y < tpButtons[i]->positionBot))
- {
- btn = tpButtons[i];
- }
- }
- if(btn != NULL) {
- countDelay = 0;
- btn->Handler(btn->buttonId);
- }
- ROCKTECH_SetFont(&Font12);
- ROCKTECH_SetTextColor(LCD_COLOR_WHITE);
- ROCKTECH_SetBackColor(LCD_BACKGROUND_DEFAULT);
- sprintf(str1,"1: x=%03d; y=%03d\n",x,y);
- ROCKTECH_DisplayString(0, 240, (uint8_t *)str1, LEFT_MODE, 1);
- }
- }
- }
- }
Przygotowanie polskiej czcionki
Możliwe jest także wygenerowanie polskiej czcionki za pomocą programu Dot Factory (ja wykorzystywałem program w wersji 0.14).Po włączeniu programu należy po lewej stronie wpisać następującą sekwencję znaków znajdującą się na obrazku poniżej.
Znaki polskie zostaną przerzucone na koniec pliku, z pominięciem znaków specjalnych, których nie definiowałem w czcionce.
W celu wygenerowania należy wybrać czcionkę:
Przed generacją należy przygotować ustawienia:
Aby czcionka poprawnie działała w całym projekcie to należy ustawić kodowanie dla projektu na UTF-8. Wykonuje się to poprzez wejście w Window -> Preferences -> Workspace i tam w groupboxie Text file encoding należy wybrać Other i UTF8. Jeśli nie wprowadzono żadnych zmian w projekcie to będzie on to kodowanie dziedziczył. Aby się upewnić należy kliknąć prawym przyciskiem myszy na projekt po czym wybrać Properties. Tam w zakładce Resource znajduje się wybór sposobu kodowania.
Po wygenerowaniu kodu dla czcionki należy dodać na początku danych w tablicy znak spacji. Jego rozmiar będzie zależny od rozmiaru czcionki. Poniżej przedstawiam przykładowe rozwiązanie:
- const uint8_t Font_ConsolasPL_34pts_Bold[] =
- {
- /* ' ' (18 pixels wide) */
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- 0x00, 0x00, 0x00, //
- //...
- //...
- //...
- }
Struktura z danymi dla czcionki znajduje się w pliku z zdefiniowaną czcionką. Dane do niej powinny być zawarte w pliku fonts.h oraz w bibliotece do wyświetlacza jako extern.
- typedef struct _tFont{
- const uint8_t *table;
- uint16_t Width;
- uint16_t Height;
- } sFONT;
- /* Example declaration, it is keep in every font file */
- sFONT Font_ConsolasPL_34ptsBold = {
- Font_ConsolasPL_34pts_Bold,
- 18, /* Width */
- 32, /* Height */
- };
Funkcje odczytujące dane z czcionek zostały zawarte w bibliotece dla wyświetlacza.
Wyrysowanie znaku oraz znalezienie polskiego znaku zostało przedstawione we wcześniejszej części posta.
Cały projekt dla wyświetlacza wraz z wygenerowanymi czcionkami można pobrać z dysku Google pod tym linkiem. Znajduje się tam folder z czcionkami przygotowanymi przez ST oraz wygenerowanymi w programie przedstawionym powyżej.