Ten post chciałbym poświęcić na przedstawienie obsługi działania wyświetlacza ze sterownikiem ILI9341. Cały program zostanie przygotowany za pomocą CubeMx oraz Keil uVision. W bardzo łątwy sposób można cały program przenieść do Eclipsa np. AC6.
Tutaj do uruchomienia jest kilka elementów. Najważniejszy jest interfejs wyświetlacza FSMC (ang. Flexible Static Memory Controler). Głównie pozwala on na obsługę pamięci Flash oraz SRAM. Natomiast dodatkowo można wybrać obsługę wyświetlacza w programie konfiguracyjnym:
Kolejnym obowiązkowym elementem jest zegar RCC:
Wraz z odpowiednią konfiguracją:
Dodatkowo można uruchomić kartę SD czy Pendriva. Pozwoli to na dostęp do czcionek oraz obrazów do wyświetlania. Na końcu można uruchomić USART1. Co pozwoli na debugowanie w czasie normalnej pracy urządzenia. Jego ustawienia mogą zostać domyślne.
Podczas tworzenia biblioteki opierałem się na dokumentacji od Adafruit napisanej dla tego wyświetlacza pod SPI dla Arduino.
Poniżej znajdują się funkcje uruchamiające oraz ustawiające
W celu wysłania komendy wywołuje się funkcje:
Działa ona za pomocą FSMC, który opisałem podczas przygotowywania projektu w CubeMx.
Przesłanie danych wygląda następująco:
Funkcja odpowiedzialna za określenie pozycji ekranu:
W tej części znajdują się funkcje odpowiedzialne za wykonywanie operacji na wyświetlaczu, czyli rysowanie kwadratów, trójkątów, kół czy linii oraz pozostałe operacje.
Uzupełnienie ekranu podanym kolorem:
Wyrysowanie prostokąta o podanym kolorze:
Rysowanie pojedynczego piksela na ekranie:
Najpierw sprawdzane są podawane parametry do funkcji. Potem ustawiany jest adres. na wybrany piksel. Kolejnym elementem jest wysłanie komendy zapisu danych po czym przesyłany jest wyświetlany kolor po 1 bajcie.
Teraz funkcja odpowiedzialna za rysowanie prostej linii na ekranie:
Do tej funkcji podaje się punkty startowe oraz końcowe linii. Ostatnim parametrem jest kolor linii.
Rysowanie pustego prostokąta na ekranie:
Tutaj podobnie jak poprzednio podawane są parametry początkowe oraz końcowe, czyli kąta przeciwległego. Ostatnim elementem jest kolor linii. Funkcja odwołuje się do wcześniej przedstawionej funkcji rysującej linie na wyświetlaczu.
Teraz funkcja odpowiedzialna za rysowanie okręgu na ekranie:
Ta część zawiera funkcje potrzebne do wyświetlenia tekstu na ekranie.
Biblioteki z czcionkami wybierzemy z plików jakie zostały udostępnione przez firmę ST. Dla przykładów dla mikrokontrolera F4 (STM32Cube_FW_F4_V1.16.0).
Czcionki można albo czytać z karty SD czy Pendriva którego podłączymy pod płytkę ST bądź przez dołączenie plików do projektu. Pliki z końcówką *.c należy dołączyć do projektu jak się będzie z nich korzystać. W przypadku pamięci zewnętrznej wgrywa się pliki z końcówką *.bin.
W tym przypadku czcionki będą zapisane na karcie SD:
Ładowanie czcionek jest dosyć proste. Otwiera się plik. Po czym przypisuje się odpowiedni składnik do struktury.
Wypisywanie pojedynczego znaku na ekranie wygląda następująco:
Na samym początku ustalane są parametry czcionki. Na końcu w pętli for następuje wyrysowanie znaku wykorzystując komendę odpowiedzialną za wypisanie pojedynczego piksela na ekranie.
Wypisanie ciągu znaków wykonuje następująca komenda:
Bibliotekę do wyświetlacza można pobrać z dysku Google pod tym linkiem. W folderze STM32 a następnie STM32F4_Discovery.
[Źródło: http://www.st.com/en/evaluation-tools/stm32f4discovery.html]
CubeMx:
Tutaj do uruchomienia jest kilka elementów. Najważniejszy jest interfejs wyświetlacza FSMC (ang. Flexible Static Memory Controler). Głównie pozwala on na obsługę pamięci Flash oraz SRAM. Natomiast dodatkowo można wybrać obsługę wyświetlacza w programie konfiguracyjnym:
Kolejnym obowiązkowym elementem jest zegar RCC:
Wraz z odpowiednią konfiguracją:
Dodatkowo można uruchomić kartę SD czy Pendriva. Pozwoli to na dostęp do czcionek oraz obrazów do wyświetlania. Na końcu można uruchomić USART1. Co pozwoli na debugowanie w czasie normalnej pracy urządzenia. Jego ustawienia mogą zostać domyślne.
Inicjalizacja:
Podczas tworzenia biblioteki opierałem się na dokumentacji od Adafruit napisanej dla tego wyświetlacza pod SPI dla Arduino.
Poniżej znajdują się funkcje uruchamiające oraz ustawiające
- void tftDisp9341_set(void)
- {
- /* Reset screen */
- tftDisp9341_reset();
- HAL_Delay(1000);
- dtt = tftDisp9341_regi_red(0xD3);
- /* Software reset */
- tftDisp9341_command(ILI_DISP_SWRES);
- delayus(1);
- /* Power Control A */
- tftDisp9341_command(ILI_DISP_POWCONA);
- tftDisp9341_data(0x39);
- tftDisp9341_data(0x2C);
- tftDisp9341_data(0x00);
- tftDisp9341_data(0x34);
- tftDisp9341_data(0x02);
- delayus(1);
- /* Power Control B */
- tftDisp9341_command(ILI_DISP_POWCONB);
- tftDisp9341_data(0x00);
- tftDisp9341_data(0xC1);
- tftDisp9341_data(0x30);
- delayus(1);
- /* Driver timing control A */
- tftDisp9341_command(ILI_DISP_DTIMCOA);
- tftDisp9341_data(0x85);
- tftDisp9341_data(0x00);
- tftDisp9341_data(0x78);
- delayus(1);
- /* Driver timing control B */
- tftDisp9341_command(ILI_DISP_DTIMCOB);
- tftDisp9341_data(0x00);
- tftDisp9341_data(0x00);
- delayus(1);
- /* Power on Sequence control */
- tftDisp9341_command(ILI_DISP_PSEQCON);
- tftDisp9341_data(0x64);
- tftDisp9341_data(0x03);
- tftDisp9341_data(0x12);
- tftDisp9341_data(0x81);
- delayus(1);
- /* Pump ratio control */
- tftDisp9341_command(ILI_DISP_PUMRAT);
- tftDisp9341_data(0x20);
- delayus(1);
- /* Power Control 1 */
- tftDisp9341_command(ILI_DISP_PWCTR1);
- tftDisp9341_data(0x10);
- delayus(1);
- /* Power Control 2 */
- tftDisp9341_command(ILI_DISP_PWCTR2);
- tftDisp9341_data(0x10);
- delayus(1);
- /* VCOM Control 1 */
- tftDisp9341_command(ILI_DISP_VMCTR1);
- tftDisp9341_data(0x3E);
- tftDisp9341_data(0x28);
- delayus(1);
- /* VCOM Control 2 */
- tftDisp9341_command(ILI_DISP_VMCTR2);
- tftDisp9341_data(0x86);
- delayus(1);
- /* Set screen rotation */
- tftDisp9341_rotation(0);
- delayus(1);
- /* Pixel format set for 16 bit */
- tftDisp9341_command(ILI_DISP_PIXFMT);
- tftDisp9341_data(0x55);
- delayus(1);
- /* Frame rate control */
- tftDisp9341_command(ILI_DISP_FRMCTR1);
- tftDisp9341_data(0x00);
- tftDisp9341_data(0x18);
- delayus(1);
- /* Display function control for 320 pages */
- tftDisp9341_command(ILI_DISP_DFUNCTR);
- tftDisp9341_data(0x08);
- tftDisp9341_data(0x82);
- tftDisp9341_data(0x27);
- delayus(1);
- /* Disable 3 Gamma Function */
- tftDisp9341_command(ILI_DISP_3GAMMA);
- tftDisp9341_data(0x00);
- delayus(1);
- /* Gamma set, gamma curve G2.2 */
- tftDisp9341_command(ILI_DISP_GAMMASET);
- tftDisp9341_data(0x01);
- delayus(1);
- /* Positive Gamma Correction */
- tftDisp9341_command(ILI_DISP_GMCTRP1);
- tftDisp9341_data(0x0F);
- tftDisp9341_data(0x31);
- tftDisp9341_data(0x2B);
- tftDisp9341_data(0x0C);
- tftDisp9341_data(0x0E);
- tftDisp9341_data(0x08);
- tftDisp9341_data(0x4E);
- tftDisp9341_data(0xF1);
- tftDisp9341_data(0x37);
- tftDisp9341_data(0x07);
- tftDisp9341_data(0x10);
- tftDisp9341_data(0x03);
- tftDisp9341_data(0x0E);
- tftDisp9341_data(0x09);
- tftDisp9341_data(0x00);
- delayus(1);
- /* Negative Gamma Correction */
- tftDisp9341_command(ILI_DISP_GMCTRN1);
- tftDisp9341_data(0x00);
- tftDisp9341_data(0x0E);
- tftDisp9341_data(0x14);
- tftDisp9341_data(0x03);
- tftDisp9341_data(0x11);
- tftDisp9341_data(0x07);
- tftDisp9341_data(0x31);
- tftDisp9341_data(0xC1);
- tftDisp9341_data(0x48);
- tftDisp9341_data(0x08);
- tftDisp9341_data(0x0F);
- tftDisp9341_data(0x0C);
- tftDisp9341_data(0x31);
- tftDisp9341_data(0x36);
- tftDisp9341_data(0x0F);
- delayus(1);
- /* Exit sleep */
- tftDisp9341_command(ILI_DISP_SLPOUT);
- HAL_Delay(150);
- /* Enable display */
- tftDisp9341_command(ILI_DISP_DISPON);
- tftDisp9341_data(0x2C);
- HAL_Delay(150);
- /* Set fonts */
- tftDisp9341_setfonts();
- }
W celu wysłania komendy wywołuje się funkcje:
- static void tftDisp9341_command(uint8_t command)
- {
- ADDR_CMD = command;
- }
Działa ona za pomocą FSMC, który opisałem podczas przygotowywania projektu w CubeMx.
Przesłanie danych wygląda następująco:
- static void tftDisp9341_data(uint8_t data)
- {
- ADDR_DATA = data;
- tftDisp9341_delay(1);
- }
Funkcja odpowiedzialna za określenie pozycji ekranu:
- void tftDisp9341_rotation(uint8_t rotat)
- {
- tftDisp9341_command(0x36);
- if(rotat == 0)
- {
- tftDisp9341_data(0x48);
- X_SIZE = ILI9341_TFT_WIDTH;
- Y_SIZE = ILI9341_TFT_HEIGHT;
- }
- else if(rotat == 1)
- {
- tftDisp9341_data(0x28);
- X_SIZE = ILI9341_TFT_HEIGHT;
- Y_SIZE = ILI9341_TFT_WIDTH;
- }
- else if(rotat == 2)
- {
- tftDisp9341_data(0x88);
- X_SIZE = ILI9341_TFT_WIDTH;
- Y_SIZE = ILI9341_TFT_HEIGHT;
- }
- else if(rotat == 3)
- {
- tftDisp9341_data(0xE8);
- X_SIZE = ILI9341_TFT_HEIGHT;
- Y_SIZE = ILI9341_TFT_WIDTH;
- }
- }
Funkcje obsługujące:
W tej części znajdują się funkcje odpowiedzialne za wykonywanie operacji na wyświetlaczu, czyli rysowanie kwadratów, trójkątów, kół czy linii oraz pozostałe operacje.
Uzupełnienie ekranu podanym kolorem:
- void tftDisp9341_fillScr(uint16_t color)
- {
- tftDisp9341_addrWindow(0,0,X_SIZE-1,Y_SIZE-1);
- tftDisp9341_fill(color,(long)X_SIZE*(long)Y_SIZE);
- }
Wyrysowanie prostokąta o podanym kolorze:
- void tftDisp9341_fillRectangle(uint16_t color, uint16_t startX, uint16_t startY,
- uint16_t endX, uint16_t endY)
- {
- tftDisp9341_addrWindow(startX,startY,endX,endY);
- tftDisp9341_fill(color,(uint16_t)(endX-startX+1)*(uint16_t)(endY-startY+1));
- }
Rysowanie pojedynczego piksela na ekranie:
- void tftDisp9341_pixel(uint16_t axisX, uint16_t axisY, uint16_t color)
- {
- /* Check passed data */
- if(axisX>=X_SIZE) || (axisY>=Y_SIZE))
- {
- return;
- }
- tftDisp9341_addrWindow(axisX,axisY,axisX,axisY);
- tftDisp9341_command(ILI_DISP_RAMWR);
- tftDisp9341_data(color >> 8);
- tftDisp9341_data(color & 0xFF);
- }
Najpierw sprawdzane są podawane parametry do funkcji. Potem ustawiany jest adres. na wybrany piksel. Kolejnym elementem jest wysłanie komendy zapisu danych po czym przesyłany jest wyświetlany kolor po 1 bajcie.
Teraz funkcja odpowiedzialna za rysowanie prostej linii na ekranie:
- void tftDisp9341_drawLine(uint16_t startX, uint16_t startY,
- uint16_t endX, uint16_t endY, uint16_t color)
- {
- int16_t steep = abs(endY-startY)>abs(endX-startX);
- int16_t deltaX = 0;
- int16_t deltaY = 0;
- int16_t errCalculate = 0;
- int16_t stepY = 0;
- if(steep != 0)
- {
- SWAP_VALUES(startX,startY);
- SWAP_VALUES(endX,endY);
- }
- if(x1>x2)
- {
- SWAP_VALUES(startX,endX);
- SWAP_VALUES(startY,endY);
- }
- deltaX=endX-startX;
- deltaY=abs(endY-startY);
- errCalculate=deltaX/2;
- if(startY<endY)
- {
- stepY=1;
- }
- else
- {
- stepY=-1;
- }
- while(startX<=endX)
- {
- if(steep)
- {
- tftDisp9341_pixel(startY,startX,color);
- }
- else
- {
- tftDisp9341_pixel(startX,startY,color);
- }
- errCalculate-=deltaY;
- if(errCalculate<0)
- {
- startY += stepY;
- errCalculate=deltaX;
- }
- startX++;
- }
- }
Do tej funkcji podaje się punkty startowe oraz końcowe linii. Ostatnim parametrem jest kolor linii.
Rysowanie pustego prostokąta na ekranie:
- void tftDisp9341_drawRectangle(uint16_t startX, uint16_t startY,
- uint16_t endX, uint16_t endY, uint16_t color)
- {
- tftDisp9341_drawLine(startX, startY, endX, startY, color);
- tftDisp9341_drawLine(endX, startY, endX, endY, color);
- tftDisp9341_drawLine(startX, startY, startX, endY, color);
- tftDisp9341_drawLine(startX, endY, endX, endY, color);
- }
Tutaj podobnie jak poprzednio podawane są parametry początkowe oraz końcowe, czyli kąta przeciwległego. Ostatnim elementem jest kolor linii. Funkcja odwołuje się do wcześniej przedstawionej funkcji rysującej linie na wyświetlaczu.
Teraz funkcja odpowiedzialna za rysowanie okręgu na ekranie:
- void tftDisp9341_drawCircle(uint16_t startX, uint16_t endY, uint16_t radius, uint16_t color)
- {
- int f = 1-radius;
- int ddF_x = 1;
- int ddF_y = -2*radius;
- int x = 0;
- int y = radius;
- tftDisp9341_pixel(startX,endY+radius,color);
- tftDisp9341_pixel(startX,endY-radius,color);
- tftDisp9341_pixel(startX+radius,endY,color);
- tftDisp9341_pixel(startX-radius,endY,color);
- while(x<y)
- {
- if(f>=0)
- {
- y--;
- ddF_y+=2;
- f+=ddF_y;
- }
- x++;
- ddF_x+=2;
- f+=ddF_x;
- tftDisp9341_pixel(startX+x, endY+y, color);
- tftDisp9341_pixel(startX-x, endY+y, color);
- tftDisp9341_pixel(startX+x, endY-y, color);
- tftDisp9341_pixel(startX-x, endY-y, color);
- tftDisp9341_pixel(startX+y, endY+x, color);
- tftDisp9341_pixel(startX-y, endY+x, color);
- tftDisp9341_pixel(startX+y, endY-x, color);
- tftDisp9341_pixel(startX-y, endY-x, color);
- }
- }
Czcionka:
Ta część zawiera funkcje potrzebne do wyświetlenia tekstu na ekranie.
Biblioteki z czcionkami wybierzemy z plików jakie zostały udostępnione przez firmę ST. Dla przykładów dla mikrokontrolera F4 (STM32Cube_FW_F4_V1.16.0).
Czcionki można albo czytać z karty SD czy Pendriva którego podłączymy pod płytkę ST bądź przez dołączenie plików do projektu. Pliki z końcówką *.c należy dołączyć do projektu jak się będzie z nich korzystać. W przypadku pamięci zewnętrznej wgrywa się pliki z końcówką *.bin.
W tym przypadku czcionki będą zapisane na karcie SD:
- void tftDisp9341_setFont(selectFont_en fontSel)
- {
- f_close(&MyFile);
- if(fontSel == se_Font8)
- {
- if(f_open(&MyFile,"font8.bin",FA_READ)!=FR_OK)
- {
- Error_Handler();
- }
- else
- {
- tftStruct.pFont=&Font8;
- }
- }
- else if(fontSel == se_Font12)
- {
- if(f_open(&MyFile,"font12.bin",FA_READ)!=FR_OK)
- {
- Error_Handler();
- }
- else
- {
- tftStruct.pFont=&Font12;
- }
- }
- else if(fontSel == se_Font16)
- {
- if(f_open(&MyFile,"font16.bin",FA_READ)!=FR_OK)
- {
- Error_Handler();
- }
- else
- {
- tftStruct.pFont=&Font16;
- }
- }
- else if(fontSel == se_Font20)
- {
- if(f_open(&MyFile,"font20.bin",FA_READ)!=FR_OK)
- {
- Error_Handler();
- }
- else
- {
- tftStruct.pFont=&Font20;
- }
- }
- else if(fontSel == se_Font24)
- {
- if(f_open(&MyFile,"font24.bin",FA_READ)!=FR_OK)
- {
- Error_Handler();
- }
- else
- {
- tftStruct.pFont=&Font24;
- }
- }
- }
Ładowanie czcionek jest dosyć proste. Otwiera się plik. Po czym przypisuje się odpowiedni składnik do struktury.
Wypisywanie pojedynczego znaku na ekranie wygląda następująco:
- void tftDisp9341_drawChar(uint16_t posX, uint16_t posY, uint8_t charac)
- {
- FRESULT res;
- uint32_t readBytes = 0;
- uint8_t charbuf[100] = {0};
- uint32_t i = 0;
- uint32_t j = 0;
- uint32_t height = 0;
- uint32_t width = 0;
- uint16_t fontsize = 0; /* Number of bytes for selected char */
- uint32_t line = 0;
- uint8_t offset = 0;
- uint16_t offsetfile = 0; /* Place in file where character definition starts */
- width = tftStruct.pFont->Width;
- height = tftStruct.pFont->Height;
- if((posX+width)>=X_SIZE)
- {
- return;
- }
- if((posY+height)>=Y_SIZE)
- {
- return;
- }
- if(height == 8)
- {
- offset = 3;
- fontsize = 8;
- offsetfile = (charac - ' ') * fontsize;
- f_lseek(&MyFile, offsetfile);
- res = f_read(&MyFile, charbuf, 8,(void*)&readBytes);
- if((readBytes==0)||(res!=FR_OK))
- {
- Error_Handler();
- }
- }
- else if(height == 12)
- {
- offset = 1;
- fontsize = 12;
- offsetfile = (charac - ' ') * fontsize;
- f_lseek(&MyFile,offsetfile);
- res = f_read(&MyFile, charbuf, 12,(void*)&readBytes);
- if((readBytes==0)||(res!=FR_OK))
- {
- Error_Handler();
- }
- }
- else if(height == 16)
- {
- offset = 5;
- fontsize = 32;
- offsetfile = (charac - ' ') * fontsize;
- f_lseek(&MyFile, offsetfile);
- res = f_read(&MyFile, charbuf, 32,(void*)&readBytes);
- if((readBytes==0)||(res!=FR_OK))
- {
- Error_Handler();
- }
- }
- else if(height == 20)
- {
- offset = 2;
- fontsize = 40;
- offsetfile = (charac - ' ') * fontsize;
- f_lseek(&MyFile,offsetfile);
- res = f_read(&MyFile, charbuf, 40,(void*)&readBytes);
- if((readBytes==0)||(res!=FR_OK))
- {
- Error_Handler();
- }
- }
- else if(height == 24)
- {
- offset = 7;
- fontsize = 72;
- offsetfile = (charac - ' ') * fontsize;
- f_lseek(&MyFile,offsetfile);
- res = f_read(&MyFile, charbuf, 72,(void*)&readBytes);
- if((readBytes==0)||(res!=FR_OK))
- {
- Error_Handler();
- }
- }
- for(i = 0; i < height; i++)
- {
- if((height == 8)||(height == 12))
- {
- line=charbuf[i];
- }
- else if((height == 16)||(height == 20))
- {
- line=(charbuf[i*2]<<8)|(charbuf[i*2+1]);
- }
- else
- {
- line=(charbuf[i*3]<<16)|(charbuf[i*3+1]<<8)|(charbuf[i*3+2]);
- }
- for(j=0; j<width; j++)
- {
- if(line & (1<<(width-j + offset - 1)))
- {
- tftDisp9341_pixel(posX + j, posY, tftStruct.TextColor);
- }
- else
- {
- tftDisp9341_pixel(posX + j, posY, tftStruct.BackColor);
- }
- }
- y++;
- }
- }
Na samym początku ustalane są parametry czcionki. Na końcu w pętli for następuje wyrysowanie znaku wykorzystując komendę odpowiedzialną za wypisanie pojedynczego piksela na ekranie.
Wypisanie ciągu znaków wykonuje następująca komenda:
- void tftDisp9341_string(uint16_t startX, uint16_t startY, char *str)
- {
- while(*str)
- {
- tftDisp9341_drawChar(startX,startY,str[0]);
- startX += tftStruct.pFont->Width;
- (void)*str++;
- }
- }
Bibliotekę do wyświetlacza można pobrać z dysku Google pod tym linkiem. W folderze STM32 a następnie STM32F4_Discovery.