W tym poście chciałbym opisać sposób generowania czcionek na wyświetlacz 128x64 ze sterownikiem ST7565R.
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:
- uint8_t font_5_7_PL[] = {
- 0x04, 0x2A, 0x2A, 0x2B, 0x1E, /* ą */
- 0x1C, 0x22, 0x62, 0xA2, 0x04, /* ć */
- 0x1C, 0x2A, 0x2A, 0x2B, 0x18, /* ę */
- 0x00, 0x8A, 0xFE, 0x22, 0x00, /* ł */
- 0x3E, 0x10, 0x60, 0xA0, 0x1E, /* ń */
- 0x1C, 0x22, 0x62, 0xA2, 0x1C, /* ó */
- 0x12, 0x2A, 0x6A, 0xAA, 0x04, /* ś */
- 0x22, 0x26, 0xAA, 0x32, 0x22, /* ż */
- 0x22, 0x26, 0x6A, 0xB2, 0x22, /* ź */
- };
Każdej z liter została przypisana wartość numeryczna tak aby można było ją szybko odnaleźć w buforze.
- #define ST7565_PL_A 1 // 'ą'
- #define ST7565_PL_C 2 // 'ć'
- #define ST7565_PL_E 3 // 'ę'
- #define ST7565_PL_L 4 // 'ł'
- #define ST7565_PL_N 5 // 'ń'
- #define ST7565_PL_O 6 // 'ó'
- #define ST7565_PL_S 7 // 'ś'
- #define ST7565_PL_ZY 8 // 'ż'
- #define ST7565_PL_ZI 9 // 'ź'
- #define ST7565_PL_A_BUF_POS 0 // 'ą'
- #define ST7565_PL_C_BUF_POS 5 // 'ć'
- #define ST7565_PL_E_BUF_POS 10 // 'ę'
- #define ST7565_PL_L_BUF_POS 15 // 'ł'
- #define ST7565_PL_N_BUF_POS 20 // 'ń'
- #define ST7565_PL_O_BUF_POS 25 // 'ó'
- #define ST7565_PL_S_BUF_POS 30 // 'ś'
- #define ST7565_PL_ZY_BUF_POS 35 // 'ż'
- #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:
- void rotateChar(uint8_t *buf, uint8_t font_size, uint8_t font_width)
- {
- uint8_t dataBuffer[256];
- uint16_t i = 0;
- uint16_t j = 0;
- uint16_t k = 0;
- for(i=0; i<256; i++) { dataBuffer[i] = 0; }
- for(i=0; i<font_size; i++)
- {
- for ( j=0; j<font_width; j++ )
- {
- for (k=0; k<8; k++)
- {
- dataBuffer[i*font_width * 8 + j * 8 + k] = ((*(buf + i * font_width + j) >> (7-k)) & 0x01);
- }
- }
- }
- for (i=0; i<32; i++) { *(buf + i) = 0; }
- for (j=0;j<font_size;j++)
- {
- for(i=0;i<(font_width*8);i++)
- {
- *(buf + (j/8) * (font_width*8) + i) |= dataBuffer[j* (font_width*8) + i] << (7-(j%8));
- }
- }
- }
Sprawdza się ona najlepiej dla czcionek w rozmiarze 18pt o szerokości 12 pikseli.
Poniżej przykładowa cyfra przed obróceniem:
- uint8_t TNR_18pt_big_1 [] =
- {
- // @32 '1' (12 pixels wide)
- 0x02, 0x00, // #
- 0x0E, 0x00, // ###
- 0x3E, 0x00, // #####
- 0x7E, 0x00, // ######
- 0x0E, 0x00, // ###
- 0x0E, 0x00, // ###
- 0x0E, 0x00, // ###
- 0x0E, 0x00, // ###
- 0x0E, 0x00, // ###
- 0x0E, 0x00, // ###
- 0x0E, 0x00, // ###
- 0x0E, 0x00, // ###
- 0x0E, 0x00, // ###
- 0x0E, 0x00, // ###
- 0x0E, 0x00, // ###
- 0x7F, 0x80, // ########
- };
Teraz ta sama cyfra z wykonanym obracaniem czcionki:
- uint8_t TNR_18pt_big_1 [] = {
- 0x00, 0x10,
- 0x30, 0x30,
- 0x7f, 0x7f,
- 0xff, 0x00,
- 0x00, 0x00,
- 0x00, 0x00,
- 0x00, 0x00,
- 0x00, 0x00,
- 0x00, 0x00,
- 0x00, 0x00,
- 0xf8, 0xf8,
- 0xf8, 0x00,
- 0x00, 0x00,
- 0x00, 0x00,
- 0x00, 0x00,
- 0x00, 0x00
- };
Odwracanie czcionki w programie może być lekko kłopotliwe. Dlatego wykonałem prosty program w C który obraca podane znaki pojedynczo.
- #include <stdint.h>
- #include <stdio.h>
- #include <string.h>
- uint8_t fontBigger[] = {
- 0x00, 0x00, //
- 0x03, 0x00, // ##
- 0x07, 0x00, // ###
- 0x0B, 0x00, // # ##
- 0x13, 0x00, // # ##
- 0x23, 0x00, // # ##
- 0x3F, 0x80, // #######
- 0x03, 0x00, // ##
- 0x03, 0x00, // ##
- 0x03, 0x00, // ##
- 0x00, 0x00, //
- 0x00, 0x00, //
- 0x00, 0x00, //
- };
- void rotateChar(uint8_t *buf, uint8_t font_size, uint8_t font_width)
- {
- uint8_t dataBuffer[256];
- uint16_t i = 0;
- uint16_t j = 0;
- uint16_t k = 0;
- for(i=0; i<256; i++) { dataBuffer[i] = 0; }
- for(i=0; i<font_size; i++)
- {
- for ( j=0; j<font_width; j++ )
- {
- for (k=0; k<8; k++)
- {
- dataBuffer[i*font_width * 8 + j * 8 + k] = ((*(buf + i * font_width + j) >> (7-k)) & 0x01);
- }
- }
- }
- for (i=0; i<32; i++) { *(buf + i) = 0; }
- for (j=0;j<font_size;j++)
- {
- for(i=0;i<(font_width*8);i++)
- {
- *(buf + (j/8) * (font_width*8) + i) |= dataBuffer[j* (font_width*8) + i] << (7-(j%8));
- }
- }
- }
- int main()
- {
- change_char_size(fontBigger, 13, 2, 0);
- for(uint8_t i = 0; i<sizeof(fontBigger); i++)
- {
- printf("0x%.2x, ", fontBigger[i]);
- }
- printf("\r\nEND\r\n");
- return 0;
- }
Powyższa funkcja zwraca wprowadzony znak w następujący sposób:
- 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,
- 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:
- static void display_char(uint8_t *displayBufferPtr, uint8_t x, uint8_t line, uint8_t *charData)
- {
- uint8_t *charDataPtr = NULL;
- charDataPtr = (uint8_t*)charData;
- for (uint8_t i=0; i<13; i++) {
- displayBufferPtr[x + ((line - 1)*LCDWIDTH) + i + ST7565R_OFFSET_LINE] |= (*(charDataPtr + 0*16 + i) >> 4) & 0x0F;
- displayBufferPtr[x + (line * LCDWIDTH) + i + ST7565R_OFFSET_LINE] |= (*(charDataPtr + 0*16 + i) << 4) & 0xF0;
- displayBufferPtr[x + (line * LCDWIDTH) + i + ST7565R_OFFSET_LINE] |= (*(charDataPtr + 1*16 + i) >> 4) & 0x0F;
- displayBufferPtr[x + ((line + 1)*LCDWIDTH) + i + ST7565R_OFFSET_LINE] |= (*(charDataPtr + 1*16 + i) << 4) & 0xF0;
- }
- }
- }
Procedura czyszczenia pojedynczego znaku na wyświetlaczu wygląda następująco:
- static void clear_char(uint8_t *buff, uint8_t x, uint8_t line)
- {
- for (uint8_t i=0; i<13; i++) {
- buff[x + ((line - 1) * LCDWIDTH) + i + ST7565R_OFFSET_LINE] &= 0x00;
- buff[x + (line * LCDWIDTH) + i + ST7565R_OFFSET_LINE] &= 0x00;
- buff[x + (line * LCDWIDTH) + i + ST7565R_OFFSET_LINE] &= 0x00;
- buff[x + (line + 1 * LCDWIDTH) + i + ST7565R_OFFSET_LINE] &= 0x00;
- }
- }