czwartek, 22 kwietnia 2021

LPC1769 - Wyświetlacz 128x64 ze sterownikiem ST7565R

W tym poście chciałbym opisać sposób generowania czcionek na wyświetlacz 128x64 ze sterownikiem ST7565R.

[Źródło: http://www.artronic.eu/pl/p/LCD-AG-C128064CF-FGN-NO-E6/2146]

Wyświetlacze można zakupić np w tych sklepach:


Domyślna czcionka:


Udało mi się znaleźć jedną, pełną, poprawnie działającą czcionkę na wspomnianym wyświetlaczu. Można ją pobrać z serwisu Github lub z dysku Google. Link na końcu postu.

Do tej czcionki wygenerowałem zestaw małych liter z polskimi znakami:

  1. uint8_t font_5_7_PL[] = {
  2.         0x04, 0x2A, 0x2A, 0x2B, 0x1E, /* ą */
  3.         0x1C, 0x22, 0x62, 0xA2, 0x04, /* ć */
  4.         0x1C, 0x2A, 0x2A, 0x2B, 0x18, /* ę */
  5.         0x00, 0x8A, 0xFE, 0x22, 0x00, /* ł */
  6.         0x3E, 0x10, 0x60, 0xA0, 0x1E, /* ń */
  7.         0x1C, 0x22, 0x62, 0xA2, 0x1C, /* ó */
  8.         0x12, 0x2A, 0x6A, 0xAA, 0x04, /* ś */
  9.         0x22, 0x26, 0xAA, 0x32, 0x22, /* ż */
  10.         0x22, 0x26, 0x6A, 0xB2, 0x22, /* ź */
  11. };

Każdej z liter została przypisana wartość numeryczna tak aby można było ją szybko odnaleźć w buforze.

  1. #define ST7565_PL_A     1   // 'ą'
  2. #define ST7565_PL_C     2   // 'ć'
  3. #define ST7565_PL_E     3   // 'ę'
  4. #define ST7565_PL_L     4   // 'ł'
  5. #define ST7565_PL_N     5   // 'ń'
  6. #define ST7565_PL_O     6   // 'ó'
  7. #define ST7565_PL_S     7   // 'ś'
  8. #define ST7565_PL_ZY    8   // 'ż'
  9. #define ST7565_PL_ZI    9   // 'ź'
  10.  
  11. #define ST7565_PL_A_BUF_POS     0   // 'ą'
  12. #define ST7565_PL_C_BUF_POS     5   // 'ć'
  13. #define ST7565_PL_E_BUF_POS     10   // 'ę'
  14. #define ST7565_PL_L_BUF_POS     15   // 'ł'
  15. #define ST7565_PL_N_BUF_POS     20   // 'ń'
  16. #define ST7565_PL_O_BUF_POS     25   // 'ó'
  17. #define ST7565_PL_S_BUF_POS     30   // 'ś'
  18. #define ST7565_PL_ZY_BUF_POS    35   // 'ż'
  19. #define ST7565_PL_ZI_BUF_POS    40   // 'ź'

Generowanie czcionek:


Do wygenerowania czcionek wykorzystałem program The Dot Factory v0.14.


Program przyjmuje wszystkie wprowadzone znaki. Włącznie z polskimi znakami.

Ustawienia generowanych danych wyglądają następująco: 


Wybrałem generowanie czcionki o stałej szerokości ponieważ łatwiej jest wykonać procedurę obracania znaku. Co niestety w późniejszym etapie będzie konieczne. 

Obsługa wygenerowanych czcionek:


Wygenerowane czcionki w programie The Dot Factory należy jeszcze obrócić o 90 stopni. To zadanie wykonuje następująca funkcja:

  1. void rotateChar(uint8_t *buf, uint8_t font_size, uint8_t font_width)
  2. {
  3.     uint8_t dataBuffer[256];
  4.     uint16_t i = 0;
  5.     uint16_t j = 0;
  6.     uint16_t k = 0;
  7.    
  8.     for(i=0; i<256; i++) { dataBuffer[i] = 0; }
  9.  
  10.     for(i=0; i<font_size; i++)
  11.     {
  12.         for ( j=0; j<font_width; j++ )
  13.         {
  14.             for (k=0; k<8; k++)
  15.             {
  16.                 dataBuffer[i*font_width * 8 + j * 8 + k] = ((*(buf + i * font_width + j) >> (7-k)) & 0x01);
  17.             }
  18.         }
  19.     }
  20.  
  21.     for (i=0; i<32; i++) { *(buf + i) = 0; }
  22.  
  23.     for (j=0;j<font_size;j++)
  24.     {
  25.         for(i=0;i<(font_width*8);i++)
  26.         {
  27.             *(buf + (j/8) * (font_width*8) + i) |= dataBuffer[j* (font_width*8) + i] << (7-(j%8));
  28.         }
  29.     }
  30. }

Sprawdza się ona najlepiej dla czcionek w rozmiarze 18pt o szerokości 12 pikseli.

Poniżej przykładowa cyfra przed obróceniem:

  1. uint8_t TNR_18pt_big_1 [] =
  2. {
  3.         // @32 '1' (12 pixels wide)
  4.         0x02, 0x00, //       #
  5.         0x0E, 0x00, //     ###
  6.         0x3E, 0x00, //   #####
  7.         0x7E, 0x00, //  ######
  8.         0x0E, 0x00, //     ###
  9.         0x0E, 0x00, //     ###
  10.         0x0E, 0x00, //     ###
  11.         0x0E, 0x00, //     ###
  12.         0x0E, 0x00, //     ###
  13.         0x0E, 0x00, //     ###
  14.         0x0E, 0x00, //     ###
  15.         0x0E, 0x00, //     ###
  16.         0x0E, 0x00, //     ###
  17.         0x0E, 0x00, //     ###
  18.         0x0E, 0x00, //     ###
  19.         0x7F, 0x80, //  ########
  20. };

Teraz ta sama cyfra z wykonanym obracaniem czcionki:

  1. uint8_t TNR_18pt_big_1 [] = {   
  2.     0x00, 0x10,
  3.     0x30, 0x30,
  4.     0x7f, 0x7f,
  5.     0xff, 0x00,
  6.     0x00, 0x00,
  7.     0x00, 0x00,
  8.     0x00, 0x00,
  9.     0x00, 0x00,
  10.     0x00, 0x00,
  11.     0x00, 0x00,
  12.     0xf8, 0xf8,
  13.     0xf8, 0x00,
  14.     0x00, 0x00,
  15.     0x00, 0x00,
  16.     0x00, 0x00,
  17.     0x00, 0x00
  18. };

Odwracanie czcionki w programie może być lekko kłopotliwe. Dlatego wykonałem prosty program w C który obraca podane znaki pojedynczo.

  1. #include <stdint.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4.  
  5. uint8_t fontBigger[] = {
  6.         0x00, 0x00, //
  7.         0x03, 0x00, //       ##
  8.         0x07, 0x00, //      ###
  9.         0x0B, 0x00, //     # ##
  10.         0x13, 0x00, //    #  ##
  11.         0x23, 0x00, //   #   ##
  12.         0x3F, 0x80, //   #######
  13.         0x03, 0x00, //       ##
  14.         0x03, 0x00, //       ##
  15.         0x03, 0x00, //       ##
  16.         0x00, 0x00, //
  17.         0x00, 0x00, //
  18.         0x00, 0x00, //
  19. };
  20.  
  21. void rotateChar(uint8_t *buf, uint8_t font_size, uint8_t font_width)
  22. {
  23.     uint8_t dataBuffer[256];
  24.     uint16_t i = 0;
  25.     uint16_t j = 0;
  26.     uint16_t k = 0;
  27.    
  28.     for(i=0; i<256; i++) { dataBuffer[i] = 0; }
  29.  
  30.     for(i=0; i<font_size; i++)
  31.     {
  32.         for ( j=0; j<font_width; j++ )
  33.         {
  34.             for (k=0; k<8; k++)
  35.             {
  36.                 dataBuffer[i*font_width * 8 + j * 8 + k] = ((*(buf + i * font_width + j) >> (7-k)) & 0x01);
  37.             }
  38.         }
  39.     }
  40.  
  41.     for (i=0; i<32; i++) { *(buf + i) = 0; }
  42.  
  43.     for (j=0;j<font_size;j++)
  44.     {
  45.         for(i=0;i<(font_width*8);i++)
  46.         {
  47.             *(buf + (j/8) * (font_width*8) + i) |= dataBuffer[j* (font_width*8) + i] << (7-(j%8));
  48.         }
  49.     }
  50. }
  51.  
  52. int main()
  53. {
  54.     change_char_size(fontBigger, 13, 2, 0);
  55.    
  56.     for(uint8_t i = 0; i<sizeof(fontBigger); i++)
  57.     {
  58.         printf("0x%.2x, ", fontBigger[i]);
  59.     }
  60.     printf("\r\nEND\r\n");
  61.  
  62.     return 0;
  63. }

Powyższa funkcja zwraca wprowadzony znak w następujący sposób:

  1. 0x00, 0x00, 0x06, 0x0a, 0x12, 0x22, 0x7f, 0x7f, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xc0, 0x00, 0x00,
  2. END

Dodatkowo należy pamiętać o szerokości znaków. Pomimo tego, że czcionka została wygenerowana przy zachowaniu stałej szerokości znaku to jej faktyczny rozmiar pozostał bez zmian. Co w przypadku wyświetlania ciągów znaków będzie wyglądał bardzo słabo (pozostaną dość duże przerwy przy niektórych znakach). Z tego powodu po wypisywaniu tekstu należy pamiętać o faktycznej szerokości znaku. Należy także uwzględnić dodatkowe przerwy pomiędzy znakami, które dobrałem doświadczalnie.

Funkcja odpowiedzialna za wypisanie znaku na ekranie wygląda następująco:

  1. static void display_char(uint8_t *displayBufferPtr, uint8_t x, uint8_t line, uint8_t *charData)
  2. {
  3.     uint8_t *charDataPtr = NULL;
  4.     charDataPtr = (uint8_t*)charData;
  5.    
  6.     for (uint8_t i=0; i<13; i++) {
  7.             displayBufferPtr[x + ((line - 1)*LCDWIDTH) + i + ST7565R_OFFSET_LINE] |= (*(charDataPtr + 0*16 + i) >> 4) & 0x0F;
  8.             displayBufferPtr[x + (line * LCDWIDTH) + i + ST7565R_OFFSET_LINE] |= (*(charDataPtr + 0*16 + i) << 4) & 0xF0;
  9.             displayBufferPtr[x + (line * LCDWIDTH) + i + ST7565R_OFFSET_LINE] |= (*(charDataPtr + 1*16 + i) >> 4) & 0x0F;
  10.             displayBufferPtr[x + ((line + 1)*LCDWIDTH) + i + ST7565R_OFFSET_LINE] |= (*(charDataPtr + 1*16 + i) << 4) & 0xF0;
  11.         }
  12.     }
  13. }

Procedura czyszczenia pojedynczego znaku na wyświetlaczu wygląda następująco:

  1. static void clear_char(uint8_t *buff, uint8_t x, uint8_t line)
  2. {
  3.     for (uint8_t i=0; i<13; i++) {
  4.         buff[x + ((line - 1) * LCDWIDTH) + i + ST7565R_OFFSET_LINE] &= 0x00;
  5.         buff[x + (line * LCDWIDTH) + i + ST7565R_OFFSET_LINE] &= 0x00;
  6.         buff[x + (line * LCDWIDTH) + i + ST7565R_OFFSET_LINE] &= 0x00;
  7.         buff[x + (line + 1 * LCDWIDTH) + i + ST7565R_OFFSET_LINE] &= 0x00;
  8.     }
  9. }

Przykładową czcionkę można pobrać z dysku Google pod tym linkiem.