Popis použití OLED displeje SSD1306 v projektech na Raspberry Pi Pico a Pico-SDK. Monochromatický OLED displej komunikuje pomocí I2c sběrnice, má velikost 128 bodů na šířku a 32 bodů na výšku. Obvykle se prodává v barvě bílé, žluté a modré.

C knihovna pro OLED diplej umí psát písmenka v kódování UTF-8. K dispozici jsou fonty:

  • Spleen 5x8

  • Spleen 6x12

  • Spleen 8x16 (983 znaků — latinka, alfabeta, azbuka, braille)

  • Spleen 12x24

  • Spleen 16x32

  • Spleen 32x64

  • Tahoma 10

  • Tahoma 12

  • Tahoma 16

  • Font 9x15

  • Font 10x20 (5205 znaků).

Jeden displej (SDA 4, SCL 5)

IMG 20260306 150044

Jeden displej (SDA 4, SCL 5)

IMG 20260306 150203

Dva displeje (1. SDA 4, SCL 5; 2. SDA 18, SCL 19)

IMG 20260329 191346

Použití knihovny

Varování: Knihovna určitě obsahuje chyby, ale je jich méně než v předchozích verzích.

cmake_minimum_required(VERSION 3.13)
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)

project(ssd1306_test C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(ssd1306_test
    main.c
    lib/ssd1306.c
    lib/utf8.c
    # všechny použité fonty
    lib/font_tahoma_10.c
    lib/font_spleen_16x32.c
)
target_link_libraries(
    ssd1306_test
    hardware_i2c
    pico_stdlib
    )
pico_enable_stdio_usb(ssd1306_test 0)
pico_enable_stdio_uart(ssd1306_test 0)

pico_add_extra_outputs(ssd1306_test)
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "lib/ssd1306.h"
#include "lib/font.h"
#include <stdio.h>
#include <string.h>

// pro OLED displej SSD1306
#define DISPLAY_WIDTH   128			// šířka displeje
#define DISPLAY_HEIGHT  32			// výška displeje
#define I2C_ADDRESS     0x3C		// i2c adresa
#define I2C_FREQ        1000000		// rychlost i2c sběrnice
#define SDA_PIN         4			// datový pin
#define SDC_PIN         5			// hodinový pin
#define I2C             i2c0		// i2c hardware

// struktura pro OLED displej včetně framebufferu
static ssd1306_t disp;

// všechny použité fonty
extern bitmapFONT font_tahoma_10;
extern bitmapFONT font_spleen_16x32;

// nastavení I2C sběrnice
void setup_i2c(void)
{
    i2c_init(I2C, I2C_FREQ);
    gpio_set_function(SDA_PIN, GPIO_FUNC_I2C);
    gpio_set_function(SDC_PIN, GPIO_FUNC_I2C);
    gpio_pull_up(SDA_PIN);
    gpio_pull_up(SDC_PIN);
}

void zapnuti_displeje( void )
{
    ssd1306_init(&disp, DISPLAY_WIDTH, DISPLAY_HEIGHT, I2C_ADDRESS, I2C); // inicializace
    ssd1306_poweron(&disp); // zapnutí displeje
    ssd1306_clear(&disp);   // vymazání displeje
    ssd1306_contrast(&disp,0x50);
    ssd1306_show(&disp); // zobrazíme na displeji
}

int main()
{

	// nastavení displeje SSD1306
    setup_i2c();
    zapnuti_displeje();
    sleep_ms(10);
	// vymazání framebufferu
	ssd1306_clear(&disp);
	// nakreslení obdélníka z horního levého x=0,y=0
	// do dolního levého rohu x=127,y=31
    ssd1306_draw_empty_square(&disp, 0, 0, 127, 31);
    ssd1306_draw_empty_square(&disp, 1, 1, 18, 31);
    // řecké písmeno fi fontem spleen 16x32 (horní roh znaku x=3, y=3)
    ssd1306_drawutf8_char_with_font(&disp, 3, 3, &font_spleen_16x32, 421 );
    // text fontem spleen 6x12 z bodu x=26, y=2
    ssd1306_draw_utf8_string(&disp, 26, 2, &font_tahoma_10, "Maličký ježeček");
    // text fontem spleen 6x12 z bodu x=26, y=16
    ssd1306_draw_utf8_string(&disp, 26, 16, &font_tahoma_10, "žere žlutá jablíčka.");
    ssd1306_show(&disp); // zobrazíme písmenka na displeji
    // ukončení práce s displejem (uvolnění paměti framebufferu)
    ssd1306_deinit(&disp);
}

Zdrojové kódy

Níže uvedený příklad je pro dva displeje najednou (1. na i2c0 pinech 4 a 5, 2. na i2c1 pinech 18 a 19).

Chcete-li jenom jeden displej, odstraňte následující:

static ssd1306_t disp1;
// nastavení I2C sběrnice 1
void setup_i2c1(void)
{
    // nelze použít i2c_default -- aplikace bude chodit nekorektně (zajímavá chyba)
    i2c_init(i2c1, I2C_FREQ);
    gpio_set_function(18, GPIO_FUNC_I2C);
    gpio_set_function(19, GPIO_FUNC_I2C);
    // Pull-up rezistory jsou v displeji, netřeba nastavovat
    gpio_pull_up(18);
    gpio_pull_up(19);
}

// zapnutí druhého displeje
void zapnuti_displeje1( void )
{
    ssd1306_init(&disp1, DISPLAY_WIDTH, DISPLAY_HEIGHT, I2C_ADDRESS, i2c1); // inicializace
    ssd1306_poweron(&disp1); // zapnutí displeje
    ssd1306_clear(&disp1);   // vymazání displeje
    ssd1306_contrast(&disp1,0x50);
    ssd1306_show(&disp1); // zobrazíme písmenka na displeji
}

// kreslení na druhý displej
void screen4( const char *str )
{
    ssd1306_clear(&disp1);
    ssd1306_draw_utf8_string(&disp1, 0, 0, &font_spleen_8x16, str);
    ssd1306_show(&disp1);
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)

project(ssd1306_utf8 C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(ssd1306_utf8
    main.c
    lib/ssd1306.c
    lib/utf8.c
    lib/font_spleen_6x12.c
    lib/font_spleen_8x16.c
    lib/font_spleen_16x32.c
    lib/font_10x20.c
    lib/font_9x15.c
)
target_link_libraries(
    ssd1306_utf8
    hardware_i2c
    pico_stdlib
    )
pico_enable_stdio_usb(ssd1306_utf8 0)
pico_enable_stdio_uart(ssd1306_utf8 0)

pico_add_extra_outputs(ssd1306_utf8)
Příklad použití main.c
/* ssd1306_utf8.c verze 0.3
 * (c) Jirka Chráska 2026, <jirka@lixis.cz>
 * BSD 3 clause licence
 */
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "lib/ssd1306.h"
#include "lib/font.h"
#include <stdio.h>
#include <string.h>

// https://www.youtube.com/watch?v=vpSkBV5vydg
// https://github.com/zahash/utf8.c


// pro OLED displej SSD1306
#define DISPLAY_WIDTH   128
#define DISPLAY_HEIGHT  32
#define I2C_ADDRESS     0x3C
#define I2C_FREQ        1000000
#define SDA_PIN         4
#define SDC_PIN         5
#define I2C             i2c0

static ssd1306_t disp;
static ssd1306_t disp1;

extern bitmapFONT font_spleen_6x12;
extern bitmapFONT font_spleen_8x16;
extern bitmapFONT font_spleen_16x32;
extern bitmapFONT font_10x20;
extern bitmapFONT font_9x15;

// nastavení I2C sběrnice
void setup_i2c(void)
{
    // nelze použít i2c_default -- aplikace bude chodit nekorektně (zajímavá chyba)
    i2c_init(I2C, I2C_FREQ);
    gpio_set_function(SDA_PIN, GPIO_FUNC_I2C);
    gpio_set_function(SDC_PIN, GPIO_FUNC_I2C);
    gpio_pull_up(SDA_PIN);
    gpio_pull_up(SDC_PIN);
}

// nastavení I2C sběrnice 1
void setup_i2c1(void)
{
    // nelze použít i2c_default -- aplikace bude chodit nekorektně (zajímavá chyba)
    i2c_init(i2c1, I2C_FREQ);
    gpio_set_function(18, GPIO_FUNC_I2C);
    gpio_set_function(19, GPIO_FUNC_I2C);
    gpio_pull_up(18);
    gpio_pull_up(19);
}
void zapnuti_displeje( void )
{
    ssd1306_init(&disp, DISPLAY_WIDTH, DISPLAY_HEIGHT, I2C_ADDRESS, I2C); // inicializace
    ssd1306_poweron(&disp); // zapnutí displeje
    ssd1306_clear(&disp);   // vymazání displeje
    ssd1306_contrast(&disp,0x50);
    ssd1306_show(&disp); // zobrazíme písmenka na displeji
}

void zapnuti_displeje1( void )
{
    ssd1306_init(&disp1, DISPLAY_WIDTH, DISPLAY_HEIGHT, I2C_ADDRESS, i2c1); // inicializace
    ssd1306_poweron(&disp1); // zapnutí displeje
    ssd1306_clear(&disp1);   // vymazání displeje
    ssd1306_contrast(&disp1,0x50);
    ssd1306_show(&disp1); // zobrazíme písmenka na displeji
}

void screen1( void )
{
    ssd1306_clear(&disp);
    ssd1306_draw_utf8_string(&disp, 0, 0, &font_spleen_8x16, "Maličký ježeček");
    ssd1306_draw_utf8_string(&disp, 0, 16, &font_spleen_8x16, "Příliš žluťoučký");
    ssd1306_show(&disp); 
}

void screen2( void )
{
    ssd1306_clear(&disp);
    ssd1306_draw_utf8_string(&disp, 0, 0, &font_spleen_8x16, "ΑBΓΔEΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ");
    ssd1306_show(&disp);     
}

void screen3( void )
{
    ssd1306_clear(&disp);
    ssd1306_draw_utf8_string(&disp, 0, 0, &font_spleen_8x16, "Здравствуйте.");
    ssd1306_draw_utf8_string(&disp, 0, 16, &font_spleen_8x16, "Χριστός ανέστη!" /*"Χριστὸς ἀνέστη"*/);
    ssd1306_show(&disp); 
}


void screen( const char *str )
{
    ssd1306_clear(&disp);
    ssd1306_draw_utf8_string(&disp, 0, 0, &font_spleen_8x16, str);
    ssd1306_show(&disp);     
}

void screen4( const char *str )
{
    ssd1306_clear(&disp1);
    ssd1306_draw_utf8_string(&disp1, 0, 0, &font_spleen_8x16, str);
    ssd1306_show(&disp1);     
}

void screen_10x20( const char *str )
{
    ssd1306_clear(&disp);
    ssd1306_draw_utf8_string(&disp, 0, 0, &font_10x20, str);
    ssd1306_show(&disp);     
}
int main()
{
char buf1[128];
char buf2[128];
    memset(buf1,'\0',128);
    memset(buf2,'\0',128);
    sleep_ms(2000);
// nastavení displeje SSD1306
    setup_i2c();
    zapnuti_displeje();
    sleep_ms(10);
    setup_i2c1();
    zapnuti_displeje1();
    sleep_ms(10);

// výstup na displej
    
    screen1();
    sleep_ms(1000);
    screen2();
    sleep_ms(1000);    
    screen3();
    sleep_ms(1000);
    screen_10x20("Χριστὸς νικά");
    sleep_ms(1000);
    
    
// testování znaků fontu Spleen 8x16    
    while (1) {
        screen4("Všechna písmena Spleen 16x32");
        // sleep_ms(1000);
        for( int j = 0; j < font_spleen_16x32.Chars; j++  ) {
            sprintf(buf1, "Index:     %d", j);
            sprintf(buf2, "Codepoint: %x", font_spleen_16x32.Index[j] );
            ssd1306_clear(&disp);
            ssd1306_draw_empty_square(&disp, 0, 0, 127, 31);
            ssd1306_draw_empty_square(&disp, 1, 1, 18, 31);
            ssd1306_drawutf8_char_with_font(&disp, 3, 3, &font_spleen_16x32, j );
            ssd1306_draw_utf8_string(&disp, 26, 2, &font_spleen_6x12, buf1);
            ssd1306_draw_utf8_string(&disp, 26, 16, &font_spleen_6x12, buf2);
            ssd1306_show(&disp); // zobrazíme písmenka na displeji
            sleep_ms(200);
        }
        screen4("Všechna písmena Font 9x15");
        // sleep_ms(1000);
        for( int j = 0; j < font_9x15.Chars; j++  ) {
            sprintf(buf1, "Index:     %d", j);
            sprintf(buf2, "Codepoint: %x", font_9x15.Index[j] );
            ssd1306_clear(&disp);
            ssd1306_draw_empty_square(&disp, 0, 0, 127, 31);
            ssd1306_draw_empty_square(&disp, 1, 1, 15, 24);
            ssd1306_drawutf8_char_with_font(&disp, 3, 3, &font_9x15, j );
            ssd1306_draw_utf8_string(&disp, 26, 2, &font_spleen_6x12, buf1);
            ssd1306_draw_utf8_string(&disp, 26, 16, &font_spleen_6x12, buf2);
            ssd1306_show(&disp); // zobrazíme písmenka na displeji
            sleep_ms(200);
        }
        screen4("Všechna písmena Font 10x20");
        // sleep_ms(1000);
        for( int j = 0; j < font_10x20.Chars; j++  ) {
            sprintf(buf1, "Index:     %d", j);
            sprintf(buf2, "Codepoint: %x", font_10x20.Index[j] );
            ssd1306_clear(&disp);
            ssd1306_draw_empty_square(&disp, 0, 0, 127, 31);
            ssd1306_draw_empty_square(&disp, 1, 1, 15, 24);
            ssd1306_drawutf8_char_with_font(&disp, 3, 3, &font_10x20, j );
            ssd1306_draw_utf8_string(&disp, 26, 2, &font_spleen_6x12, buf1);
            ssd1306_draw_utf8_string(&disp, 26, 16, &font_spleen_6x12, buf2);
            ssd1306_show(&disp); // zobrazíme písmenka na displeji
            sleep_ms(200);
        }
        screen4("Všechna písmena Spleen 8x16");
        // sleep_ms(1000);
        for( int j = 0; j < font_spleen_8x16.Chars; j++  ) {
            sprintf(buf1, "Index:     %d", j);
            sprintf(buf2, "Codepoint: %x", font_spleen_8x16.Index[j] );
            ssd1306_clear(&disp);
            ssd1306_draw_empty_square(&disp, 0, 0, 127, 31);
            ssd1306_draw_empty_square(&disp, 1, 1, 12, 19);
            ssd1306_drawutf8_char_with_font(&disp, 3, 3, &font_spleen_8x16, j );
            ssd1306_draw_utf8_string(&disp, 26, 2, &font_spleen_6x12, buf1);
            ssd1306_draw_utf8_string(&disp, 26, 16, &font_spleen_6x12, buf2);
            ssd1306_show(&disp); // zobrazíme písmenka na displeji
            sleep_ms(200);
        }
        // alfabeta
        screen4("Řečtina font spleen 8x16");
        // sleep_ms(1000);
        for( int i = 385; i < 446; i++  ) {
            sprintf(buf1, "Index:     %d", i);
            sprintf(buf2, "Codepoint: %x", font_spleen_8x16.Index[i] );
            ssd1306_clear(&disp);
            ssd1306_draw_empty_square(&disp, 0, 0, 16, 26);
            ssd1306_drawutf8_char_with_font(&disp, 0, 0, &font_spleen_8x16, i /*font_spleen_8x16.Index[i]*/);
            ssd1306_draw_utf8_string(&disp, 26,  2, &font_spleen_6x12, buf1);
            ssd1306_draw_utf8_string(&disp, 26, 16, &font_spleen_6x12, buf2);
            ssd1306_show(&disp); // zobrazíme písmenka na displeji
            sleep_ms(200);
        }
        screen1();
        sleep_ms(1000);
        screen3();
        sleep_ms(1000);
        screen4("Ruština");
        // sleep_ms(1000);
        // alfabeta
        for( int i = 446; i < 521; i++  ) {
            sprintf(buf1, "Index: %d", i);
            sprintf(buf2, "Codepoint: %x", font_spleen_8x16.Index[i] );
            ssd1306_clear(&disp);
            ssd1306_drawutf8_char_with_font(&disp, 0, 0, &font_spleen_8x16, i /*font_spleen_8x16.Index[i]*/);
            ssd1306_draw_utf8_string(&disp, 26,  2, &font_spleen_6x12, buf1);
            ssd1306_draw_utf8_string(&disp, 26, 16, &font_spleen_6x12, buf2);
            ssd1306_show(&disp); // zobrazíme písmenka na displeji
            sleep_ms(500);
        }
    }
    ssd1306_deinit(&disp);
    ssd1306_deinit(&disp1);
}
Hlavičkový soubor pro displej SSD1306 lib/ssd1306.h
/*
MIT License

Copyright (c) 2021 David Schramm

UTF-8 fonty
(c) Jirka Chráska 2026, jirka@lixis.cz 


Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

/** 
* @file ssd1306.h
* 
* simple driver for ssd1306 displays
*/

#ifndef _inc_ssd1306
#define _inc_ssd1306
#include <pico/stdlib.h>
#include <hardware/i2c.h>
#include "font.h"

/**
*	@brief defines commands used in ssd1306
*/
typedef enum {
    SET_CONTRAST = 0x81,
    SET_ENTIRE_ON = 0xA4,
    SET_NORM_INV = 0xA6,
    SET_DISP = 0xAE,
    SET_MEM_ADDR = 0x20,
    SET_COL_ADDR = 0x21,
    SET_PAGE_ADDR = 0x22,
    SET_DISP_START_LINE = 0x40,
    SET_SEG_REMAP = 0xA0,
    SET_MUX_RATIO = 0xA8,
    SET_COM_OUT_DIR = 0xC0,
    SET_DISP_OFFSET = 0xD3,
    SET_COM_PIN_CFG = 0xDA,
    SET_DISP_CLK_DIV = 0xD5,
    SET_PRECHARGE = 0xD9,
    SET_VCOM_DESEL = 0xDB,
    SET_CHARGE_PUMP = 0x8D
} ssd1306_command_t;

/**
*	@brief holds the configuration
*/
typedef struct {
    uint8_t width; 		/**< width of display */
    uint8_t height; 	/**< height of display */
    uint8_t pages;		/**< stores pages of display (calculated on initialization*/
    uint8_t address; 	/**< i2c address of display*/
    i2c_inst_t *i2c_i; 	/**< i2c connection instance */
    bool external_vcc; 	/**< whether display uses external vcc */ 
    uint8_t *buffer;	/**< display buffer */
    size_t bufsize;		/**< buffer size */
} ssd1306_t;

/**
*	@brief initialize display
*
*	@param[in] p : pointer to instance of ssd1306_t
*	@param[in] width : width of display
*	@param[in] height : heigth of display
*	@param[in] address : i2c address of display
*	@param[in] i2c_instance : instance of i2c connection
*	
* 	@return bool.
*	@retval true for Success
*	@retval false if initialization failed
*/
bool ssd1306_init(ssd1306_t *p, uint16_t width, uint16_t height, uint8_t address, i2c_inst_t *i2c_instance);

/**
*	@brief deinitialize display
*
*	@param[in] p : instance of display
*
*/
void ssd1306_deinit(ssd1306_t *p);

/**
*	@brief turn off display
*
*	@param[in] p : instance of display
*
*/
void ssd1306_poweroff(ssd1306_t *p);

/**
	@brief turn on display

	@param[in] p : instance of display

*/
void ssd1306_poweron(ssd1306_t *p);

/**
	@brief set contrast of display

	@param[in] p : instance of display
	@param[in] val : contrast

*/
void ssd1306_contrast(ssd1306_t *p, uint8_t val);

/**
	@brief set invert display

	@param[in] p : instance of display
	@param[in] inv : inv==0: disable inverting, inv!=0: invert

*/
void ssd1306_invert(ssd1306_t *p, uint8_t inv);

/**
	@brief display buffer, should be called on change

	@param[in] p : instance of display

*/
void ssd1306_show(ssd1306_t *p);

/**
	@brief clear display buffer

	@param[in] p : instance of display

*/
void ssd1306_clear(ssd1306_t *p);

/**
	@brief clear pixel on buffer

	@param[in] p : instance of display
	@param[in] x : x position
	@param[in] y : y position
*/
void ssd1306_clear_pixel(ssd1306_t *p, uint32_t x, uint32_t y);

/**
	@brief draw pixel on buffer

	@param[in] p : instance of display
	@param[in] x : x position
	@param[in] y : y position
*/
void ssd1306_draw_pixel(ssd1306_t *p, uint32_t x, uint32_t y);

/**
	@brief draw line on buffer

	@param[in] p : instance of display
	@param[in] x1 : x position of starting point
	@param[in] y1 : y position of starting point
	@param[in] x2 : x position of end point
	@param[in] y2 : y position of end point
*/
void ssd1306_draw_line(ssd1306_t *p, int32_t x1, int32_t y1, int32_t x2, int32_t y2);

/**
	@brief clear square at given position with given size

	@param[in] p : instance of display
	@param[in] x : x position of starting point
	@param[in] y : y position of starting point
	@param[in] width : width of square
	@param[in] height : height of square
*/
void ssd1306_clear_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height);

/**
	@brief draw filled square at given position with given size

	@param[in] p : instance of display
	@param[in] x : x position of starting point
	@param[in] y : y position of starting point
	@param[in] width : width of square
	@param[in] height : height of square
*/
void ssd1306_draw_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height);

/**
	@brief draw empty square at given position with given size

	@param[in] p : instance of display
	@param[in] x : x position of starting point
	@param[in] y : y position of starting point
	@param[in] width : width of square
	@param[in] height : height of square
*/
void ssd1306_draw_empty_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height);

/**
	@brief draw monochrome bitmap with offset

	@param[in] p : instance of display
	@param[in] data : image data (whole file)
	@param[in] size : size of image data in bytes
	@param[in] x_offset : offset of horizontal coordinate
	@param[in] y_offset : offset of vertical coordinate
*/
void ssd1306_bmp_show_image_with_offset(ssd1306_t *p, const uint8_t *data, const long size, uint32_t x_offset, uint32_t y_offset);

/**
	@brief draw monochrome bitmap

	@param[in] p : instance of display
	@param[in] data : image data (whole file)
	@param[in] size : size of image data in bytes
*/
void ssd1306_bmp_show_image(ssd1306_t *p, const uint8_t *data, const long size);

/**
	@brief draw char with given font

	@param[in] p : instance of display
	@param[in] x : x starting position of char
	@param[in] y : y starting position of char
	@param[in] scale : scale font to n times of original size (default should be 1)
	@param[in] font : pointer to font
	@param[in] c : character to draw
*/

/**
function: Show UTF8 characters
parameter(s):
    p                : struktura ssd1306_t
    x                : X coordinate
    y                : Y coordinate
    Font             :A structure pointer that displays a character size
    Index            : index do fontu
return:     width of the character;
*/
uint16_t ssd1306_drawutf8_char_with_font(ssd1306_t *p, uint32_t x, uint32_t y, bitmapFONT *Font, uint16_t Index );

/**
function:	Display the string in UTF8
parameter:
    p                : struktura ssd1306_t
    Xstart           :X coordinate
    Ystart           :Y coordinate
    Font             :A structure pointer that displays a character size
    pString          :The first address of the English string to be displayed
returns:
    Width of the whole string
*/
uint16_t ssd1306_draw_utf8_string(ssd1306_t *p, uint32_t Xstart, uint32_t Ystart, bitmapFONT *Font, const char *pString );

#endif
Implementace pro displej SSD1306 lib/ssd1306.c
/*

MIT License

Copyright (c) 2021 David Schramm

UTF-8 fonty
(c) Jirka Chráska 2026, jirka@lixis.cz 

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include <pico/stdlib.h>
#include <hardware/i2c.h>
#include <pico/binary_info.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "ssd1306.h"
#include "font.h"
#include "utf8.h"

inline static void swap(int32_t *a, int32_t *b) 
{
    int32_t *t=a;
    *a=*b;
    *b=*t;
}

inline static void fancy_write(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, char *name) 
{
    switch(i2c_write_blocking(i2c, addr, src, len, false)) {
    case PICO_ERROR_GENERIC:
        printf("[%s] addr not acknowledged!\n", name);
        break;
    case PICO_ERROR_TIMEOUT:
        printf("[%s] timeout!\n", name);
        break;
    default:
        //printf("[%s] wrote successfully %lu bytes!\n", name, len);
        break;
    }
}

inline static void ssd1306_write(ssd1306_t *p, uint8_t val) 
{
    uint8_t d[2]= {0x00, val};
    fancy_write(p->i2c_i, p->address, d, 2, "ssd1306_write");
}

bool ssd1306_init(ssd1306_t *p, uint16_t width, uint16_t height, uint8_t address, i2c_inst_t *i2c_instance) 
{
    p->width=width;
    p->height=height;
    p->pages=height/8;
    p->address=address;

    p->i2c_i=i2c_instance;


    p->bufsize=(p->pages)*(p->width);
    if((p->buffer=malloc(p->bufsize+1))==NULL) {
        p->bufsize=0;
        return false;
    }

    ++(p->buffer);

    // from https://github.com/makerportal/rpi-pico-ssd1306
    uint8_t cmds[]= {
        SET_DISP,
        // timing and driving scheme
        SET_DISP_CLK_DIV,
        0x80,
        SET_MUX_RATIO,
        height - 1,
        SET_DISP_OFFSET,
        0x00,
        // resolution and layout
        SET_DISP_START_LINE,
        // charge pump
        SET_CHARGE_PUMP,
        p->external_vcc?0x10:0x14,
        SET_SEG_REMAP | 0x01,           // column addr 127 mapped to SEG0
        SET_COM_OUT_DIR | 0x08,         // scan from COM[N] to COM0
        SET_COM_PIN_CFG,
        width>2*height?0x02:0x12,
        // display
        SET_CONTRAST,
        0xff,
        SET_PRECHARGE,
        p->external_vcc?0x22:0xF1,
        SET_VCOM_DESEL,
        0x30,                           // or 0x40?
        SET_ENTIRE_ON,                  // output follows RAM contents
        SET_NORM_INV,                   // not inverted
        SET_DISP | 0x01,
        // address setting
        SET_MEM_ADDR,
        0x00,  // horizontal
    };

    for(size_t i=0; i<sizeof(cmds); ++i)
        ssd1306_write(p, cmds[i]);

    return true;
}

inline void ssd1306_deinit(ssd1306_t *p) 
{
    free(p->buffer-1);
}

inline void ssd1306_poweroff(ssd1306_t *p) 
{
    ssd1306_write(p, SET_DISP|0x00);
}

inline void ssd1306_poweron(ssd1306_t *p) 
{
    ssd1306_write(p, SET_DISP|0x01);
}

inline void ssd1306_contrast(ssd1306_t *p, uint8_t val) 
{
    ssd1306_write(p, SET_CONTRAST);
    ssd1306_write(p, val);
}

inline void ssd1306_invert(ssd1306_t *p, uint8_t inv) 
{
    ssd1306_write(p, SET_NORM_INV | (inv & 1));
}

inline void ssd1306_clear(ssd1306_t *p) 
{
    memset(p->buffer, 0, p->bufsize);
}

void ssd1306_clear_pixel(ssd1306_t *p, uint32_t x, uint32_t y) 
{
    if(x>=p->width || y>=p->height) return;

    p->buffer[x+p->width*(y>>3)]&=~(0x1<<(y&0x07));
}

void ssd1306_draw_pixel(ssd1306_t *p, uint32_t x, uint32_t y) 
{
    if(x>=p->width || y>=p->height) return;

    p->buffer[x+p->width*(y>>3)]|=0x1<<(y&0x07); // y>>3==y/8 && y&0x7==y%8
}

void ssd1306_draw_line(ssd1306_t *p, int32_t x1, int32_t y1, int32_t x2, int32_t y2) 
{
    if(x1>x2) {
        swap(&x1, &x2);
        swap(&y1, &y2);
    }

    if(x1==x2) {
        if(y1>y2)
            swap(&y1, &y2);
        for(int32_t i=y1; i<=y2; ++i)
            ssd1306_draw_pixel(p, x1, i);
        return;
    }

    float m=(float) (y2-y1) / (float) (x2-x1);

    for(int32_t i=x1; i<=x2; ++i) {
        float y=m*(float) (i-x1)+(float) y1;
        ssd1306_draw_pixel(p, i, (uint32_t) y);
    }
}

void ssd1306_clear_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height) 
{
    for(uint32_t i=0; i<width; ++i)
        for(uint32_t j=0; j<height; ++j)
            ssd1306_clear_pixel(p, x+i, y+j);
}

void ssd1306_draw_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height) 
{
    for(uint32_t i=0; i<width; ++i)
        for(uint32_t j=0; j<height; ++j)
            ssd1306_draw_pixel(p, x+i, y+j);
}

void ssd1306_draw_empty_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height) 
{
    ssd1306_draw_line(p, x, y, x+width, y);
    ssd1306_draw_line(p, x, y+height, x+width, y+height);
    ssd1306_draw_line(p, x, y, x, y+height);
    ssd1306_draw_line(p, x+width, y, x+width, y+height);
}

/******************************************************************************
function: Show UTF8 characters
parameter(s):
    p                : struktura ssd1306_t
    x                : X coordinate
    y                : Y coordinate
    Font             :A structure pointer that displays a character size
    Index            : index do fontu
return:     width of the character;
******************************************************************************/
uint16_t ssd1306_drawutf8_char_with_font(ssd1306_t *p, uint32_t x, uint32_t y, bitmapFONT *Font, uint16_t Index )
{
uint8_t row, column, color;

    if (x > p->width || y > p->height) {
        return 0;
    }
    
    uint8_t  col    = Font->Width/8; // sloupce v bytech
    uint8_t  fcol   = col;
    uint8_t  zb     = Font->Width%8;
    if( zb != 0) {
        col ++;
    }
    uint8_t chwidth = Font->Widths[Index]; // šířka znaku
    uint16_t chsize = Font->Height * col;
    uint32_t bindex = Index*chsize;
    for (row = 0; row < Font->Height; row++ ) {
        // lezeme po bytech (po osmičkách)
        uint8_t chw = chwidth;
        for (column = 0; column < col; column++, chw-- ) {
            for( int j = 0; j<8; j++ ) {
                if( chw == 0 ) break;
                color = ((uint8_t) Font->Bitmap[bindex+row*col+column]) & (0x80 >> j);
                if( color ) {
                    ssd1306_draw_pixel(p, x + column*8 + j, y + row);
                }
            }
        } 
    }
return chwidth;
}

/**
function:	Display the UTF-8 string  
parameter:
    p                : struktura ssd1306_t
    Xstart           :X coordinate
    Ystart           :Y coordinate
    Font             :A structure pointer that displays a character size
    pString          :The first address of the English string to be displayed
returns:
    Width of the whole string
*/
uint16_t ssd1306_draw_utf8_string(ssd1306_t *p, uint32_t Xstart, uint32_t Ystart, bitmapFONT * Font, const char * pString )
{
uint32_t Xpoint = Xstart;
uint32_t Ypoint = Ystart;
uint16_t index; 
uint16_t str_width  = 0;
uint16_t char_width = 0;

    if (Xstart > p->width || Ystart > p->height) {
        return 0;
    }

    utf8_string    ustr = make_utf8_string(pString);
    utf8_char_iter iter = make_utf8_char_iter(ustr);
    utf8_char c;
    uint32_t codepoint;
    while ((c = next_utf8_char(&iter)).byte_len > 0 ) {
        codepoint = unicode_code_point(c);
        index = 0;
        for(int i=0; i < Font->Chars; i++) { 
            if(Font->Index[i] == codepoint ) {
                index = i;
                //printf("index=%d ", index);
                break;
                }
            }
        if((Xpoint + Font->Width ) > p->width ) {
            Xpoint = Xstart;
            Ypoint += Font->Height;
        }
        if ((Ypoint  + Font->Height ) > p->height ) {
            Xpoint = Xstart;
            Ypoint = Ystart;
        }
        char_width = ssd1306_drawutf8_char_with_font(p, Xpoint, Ypoint, Font, index);
        Xpoint    += char_width;
        str_width += char_width;
    }
return str_width;
}


static inline uint32_t ssd1306_bmp_get_val(const uint8_t *data, const size_t offset, uint8_t size) 
{
    switch(size) {
    case 1:
        return data[offset];
    case 2:
        return data[offset]|(data[offset+1]<<8);
    case 4:
        return data[offset]|(data[offset+1]<<8)|(data[offset+2]<<16)|(data[offset+3]<<24);
    default:
        __builtin_unreachable();
    }
    __builtin_unreachable();
}

void ssd1306_bmp_show_image_with_offset(ssd1306_t *p, const uint8_t *data, const long size, uint32_t x_offset, uint32_t y_offset) 
{
    if(size<54) // data smaller than header
        return;

    const uint32_t bfOffBits=ssd1306_bmp_get_val(data, 10, 4);
    const uint32_t biSize=ssd1306_bmp_get_val(data, 14, 4);
    const uint32_t biWidth=ssd1306_bmp_get_val(data, 18, 4);
    const int32_t  biHeight=(int32_t) ssd1306_bmp_get_val(data, 22, 4);
    const uint16_t biBitCount=(uint16_t) ssd1306_bmp_get_val(data, 28, 2);
    const uint32_t biCompression=ssd1306_bmp_get_val(data, 30, 4);

    if(biBitCount!=1) // image not monochrome
        return;

    if(biCompression!=0) // image compressed
        return;

    const int table_start=14+biSize;
    uint8_t color_val=0;

    for(uint8_t i=0; i<2; ++i) {
        if(!((data[table_start+i*4]<<16)|(data[table_start+i*4+1]<<8)|data[table_start+i*4+2])) {
            color_val=i;
            break;
        }
    }

    uint32_t bytes_per_line=(biWidth/8)+(biWidth&7?1:0);
    if(bytes_per_line&3)
        bytes_per_line=(bytes_per_line^(bytes_per_line&3))+4;

    const uint8_t *img_data=data+bfOffBits;

    int32_t step=biHeight>0?-1:1;
    int32_t border=biHeight>0?-1:-biHeight;

    for(uint32_t y=biHeight>0?biHeight-1:0; y!=(uint32_t)border; y+=step) {
        for(uint32_t x=0; x<biWidth; ++x) {
            if(((img_data[x>>3]>>(7-(x&7)))&1)==color_val)
                ssd1306_draw_pixel(p, x_offset+x, y_offset+y);
        }
        img_data+=bytes_per_line;
    }
}

inline void ssd1306_bmp_show_image(ssd1306_t *p, const uint8_t *data, const long size) 
{
    ssd1306_bmp_show_image_with_offset(p, data, size, 0, 0);
}

void ssd1306_show(ssd1306_t *p) 
{
    uint8_t payload[]= {SET_COL_ADDR, 0, p->width-1, SET_PAGE_ADDR, 0, p->pages-1};
    if(p->width==64) {
        payload[1]+=32;
        payload[2]+=32;
    }

    for(size_t i=0; i<sizeof(payload); ++i)
        ssd1306_write(p, payload[i]);

    *(p->buffer-1)=0x40;

    fancy_write(p->i2c_i, p->address, p->buffer-1, p->bufsize+1, "ssd1306_show");
}
Podpora pro UTF-8 lib/utf8.h
/**
 * @file utf8.h
 * @brief simple library for working with UTF-8 encoded strings
 *
 * @code
 * #include "utf8.h"
 * #include <stdio.h>
 *
 * int main() {
 *     const char* str = "Hello, こんにちは, Здравствуйте";
 *     utf8_string ustr = make_utf8_string(str);
 *     utf8_string_slice slice = make_utf8_string_slice(ustr, 2, 11);
 *     utf8_char_iter iter = make_utf8_char_iter(ustr);
 *
 *     printf("string: %s\n", ustr.str);
 *     printf("slice: %.*s\n", (int)slice.byte_len, slice.str);
 *
 *     utf8_char ch;
 *     while ((ch = next_utf8_char(&iter)).byte_len > 0) {
 *         printf("character: %.*s\t", (int)ch.byte_len, ch.str);
 *         printf("unicode code point: U+%04X\n", unicode_code_point(ch));
 *     }
 *
 *     return 0;
 * }
 * @endcode
 */

#ifndef ZAHASH_UTF8_H
#define ZAHASH_UTF8_H

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

/**
 * @brief Represents the validity of a UTF-8 encoded string.
 *
 * @details The `utf8_validity` struct indicates whether a given UTF-8 encoded string is valid or not,
 * along with the position up to which it is valid.
 *
 * - Invalid case: "hello\xC0\xC0" => { .valid = false, .valid_upto = 5  }
 * - Valid case:   "hello world"   => { .valid = true,  .valid_upto = 11 }
 */
typedef struct {
    bool valid;          ///< Flag indicating the validity of the UTF-8 string.
    size_t valid_upto;   ///< The position up to which the string is valid.
} utf8_validity;

/**
 * @brief Represents a non-owning UTF-8 encoded string. (just a wrapper type).
 *
 * @details The `utf8_string` struct holds a pointer to a UTF-8 encoded string along with its byte length,
 */
typedef struct {
    const char* str;     ///< Pointer to the UTF-8 encoded string.
    size_t byte_len;     ///< Byte length of the UTF-8 string ('\0' not counted).
} utf8_string;

/**
 * @brief Represents a UTF-8 encoded string that fully owns its data.
 *
 * @details The `owned_utf8_string` struct holds a pointer to a UTF-8 encoded string that is dynamically allocated
 *          and therefore is owned by the struct, which means the caller is responsible for freeing the memory when
 *          it is no longer needed using the `free_owned_utf8_string` function.
 */
typedef struct {
    char* str;          ///< Pointer to the UTF-8 encoded string (owned). This memory is dynamically allocated.
    size_t byte_len;    ///< Byte length of the UTF-8 string ('\0' not counted).
} owned_utf8_string;

/**
 * @brief Represents an iterator for traversing UTF-8 characters in a string.
 *
 * @details The `utf8_char_iter` struct serves as an iterator for traversing UTF-8 characters
 * within a UTF-8 encoded string.
 */
typedef struct {
    const char* str;     ///< Pointer to the current position of the iterator.
} utf8_char_iter;

/**
 * @brief Represents a UTF-8 character.
 *
 * @details The `utf8_char` struct encapsulates a UTF-8 character, including its pointer and byte length.
 * The byte length represents the number of bytes occupied by the UTF-8 character.
 */
typedef struct {
    const char* str;     ///< Pointer to the UTF-8 character.
    uint8_t byte_len;    ///< Byte length of the UTF-8 character.
} utf8_char;

/**
 * @brief Validates whether a given string is UTF-8 compliant in O(n) time.
 *
 * @param str The input string to validate.
 * @return The validity of the UTF-8 string along with the position up to which it is valid.
 */
utf8_validity validate_utf8(const char* str);

/**
 * @brief Wraps a C-style string in a UTF-8 string structure after verifying its UTF-8 compliance.
 *
 * @param str The input C-style string to wrap.
 * @return A UTF-8 string structure containing the wrapped string if valid; otherwise, a structure with NULL string pointer.
 *
 * @code
 * // Example usage:
 * const char *str = "definitely utf8 string こんにちは नमस्ते Здравствуйте";
 * utf8_string ustr = make_utf8_string(str);
 * assert( ustr.str != NULL );
 *
 * const char *s = "non-utf8 sequence \xC0\xC0";
 * utf8_string ustr = make_utf8_string(str);
 * assert( ustr.str == NULL );
 * @endcode
 */
utf8_string make_utf8_string(const char* str);

/**
 * @brief Converts a C-style string to a UTF-8 string, replacing invalid sequences with U+FFFD REPLACEMENT CHARACTER (�).
 *
 * @details It takes a C-style string as input and converts it to a UTF-8 encoded string.
 *          Any invalid UTF-8 sequences in the input string are replaced with the U+FFFD REPLACEMENT CHARACTER (�) to ensure
 *          that the resulting string is valid UTF-8. The resulting string is dynamically allocated and the caller
 *          is responsible for freeing the memory when no longer needed using `free_owned_utf8_string`.
 *
 * @param str The input C-style string to convert. The string can contain invalid UTF-8 sequences.
 * @return An `owned_utf8_string` structure containing the resulting UTF-8 string. If memory allocation fails, the structure
 *         will contain a `NULL` pointer and a `byte_len` of 0.
 *
 * @code
 * // Example usage:
 * const char* str = "hello\xC0\xC0 world!";
 * owned_utf8_string owned_ustr = make_utf8_string_lossy(str);
 * @endcode
 */
owned_utf8_string make_utf8_string_lossy(const char* str);

/**
 * @brief Creates the non-owning UTF-8 encoded string `utf8_string` from an `owned_utf8_string`.
 *
 * @details The resulting `utf8_string` will point to the same underlying string without taking ownership.
 *          The caller must ensure the original `owned_utf8_string` remains valid as long as the reference is used.
 *
 * @param owned_str The owned UTF-8 string from which to create a non-owning reference.
 * @return utf8_string A non-owning UTF-8 string reference (`utf8_string`) pointing to the same data.
 *
 * @note This function does not free or transfer ownership of the `owned_utf8_string`.
 *       The caller is responsible for managing the lifetime of the owned string.
 */
utf8_string as_utf8_string(const owned_utf8_string* owned_str);

/**
 * @brief Frees the memory allocated for an `owned_utf8_string`.
 *
 * @details The `free_owned_utf8_string` function deallocates the memory used by an `owned_utf8_string`
 *          and sets the `str` pointer to `NULL` and `byte_len` to 0.
 *
 * @param owned_str A pointer to the `owned_utf8_string` structure to be freed.
 *
 * @code
 * // Example usage:
 * owned_utf8_string owned_ustr = make_utf8_string_lossy("hello\xC0\xC0 world!");
 * free_owned_utf8_string(&owned_ustr);
 * @endcode
 */
void free_owned_utf8_string(owned_utf8_string* owned_str);

/**
 * @brief Creates a UTF-8 string slice from a specified range of bytes in the original string.
 *
 * @param ustr The original UTF-8 string.
 * @param byte_index The starting byte index of the slice.
 * @param byte_len The byte length of the slice.
 * @return A UTF-8 string representing the specified byte range [offset, offset + byte_len) if valid (range between UTF-8 char boundaries);
 * otherwise { .str = NULL, .byte_len = 0 }
 *
 * @note if `byte_index` >= strlen(ustr.str) then returns terminating '\0' of ustr.str { .str = '\0', .byte_len = 0 }
 * @note if `byte_index` + `byte_len` >= strlen(ustr.str) then only chars till terminating '\0' are considered.
 */
utf8_string slice_utf8_string(utf8_string ustr, size_t byte_index, size_t byte_len);

/**
 * @brief Creates an iterator for traversing UTF-8 characters within a string. (see next_utf8_char( .. ) for traversal)
 *
 * @param ustr The UTF-8 string to iterate over.
 * @return An iterator structure initialized to the start of the string.
 */
utf8_char_iter make_utf8_char_iter(utf8_string ustr);

/**
 * @brief Retrieves the next UTF-8 character from the iterator.
 *
 * @param iter Pointer to the UTF-8 character iterator.
 * @return The next UTF-8 character from the iterator.
 * @note If the iterator reaches the end, it keeps returning terminating '\0' of iter.str { .str = '\0', .byte_len = 0 }
 */
utf8_char next_utf8_char(utf8_char_iter* iter);

/**
 * @brief Retrieves the UTF-8 character at the specified character index within a UTF-8 string in O(n) time.
 *
 * @details The `nth_utf8_char` function returns the UTF-8 character located at the specified character index
 * within the given UTF-8 string. The character index is zero-based, indicating the position of
 * the character in the string. If the index is out of bounds or invalid, the function returns
 * { .str = NULL, .byte_len = 0 }
 *
 * @param ustr The UTF-8 string from which to retrieve the character.
 * @param char_index The zero-based index of the character to retrieve.
 * @return The UTF-8 character at the specified index within the string.
 *
 * @code
 * // Example usage:
 * utf8_string str = make_utf8_string("Hello Здравствуйте こんにちは");
 * utf8_char char_at_index = nth_utf8_char(str, 7);    // д
 * @endcode
 */
utf8_char nth_utf8_char(utf8_string ustr, size_t char_index);

/**
 * @brief Counts the number of UTF-8 characters in the given utf8_string.
 *
 * @param ustr The UTF-8 string whose characters are to be counted.
 * @return The total number of characters in the UTF-8 string.
 */
size_t utf8_char_count(utf8_string ustr);

/**
 * @brief Checks if a given byte is the start of a UTF-8 character. ('\0' is also a valid character boundary)
 *
 * @param str Pointer to the byte to check.
 * @return `true` if the byte is the start of a UTF-8 character; otherwise, `false`.
 */
bool is_utf8_char_boundary(const char* str);

/**
 * @brief Converts a UTF-8 character to its corresponding Unicode code point (which is the same as a UTF-32 value).
 *
 * @param uchar The UTF-8 character to convert.
 * @return The Unicode code point.
 */
uint32_t unicode_code_point(utf8_char uchar);

#endif
Implementace UTF-8 lib/utf8.c
#include "utf8.h"

#include <stdlib.h>
#include <string.h>

typedef struct {
    bool valid;
    size_t next_offset;
} utf8_char_validity;

utf8_char_validity validate_utf8_char(const char* str, size_t offset) {
    // Single-byte UTF-8 characters have the form 0xxxxxxx
    if (((uint8_t)str[offset] & 0b10000000) == 0b00000000)
        return (utf8_char_validity) { .valid = true, .next_offset = offset + 1 };

    // Two-byte UTF-8 characters have the form 110xxxxx 10xxxxxx
    if (((uint8_t)str[offset + 0] & 0b11100000) == 0b11000000 &&
        ((uint8_t)str[offset + 1] & 0b11000000) == 0b10000000) {

        // Check for overlong encoding
        // 0(xxxxxxx)
        // 0(1111111)
        // 110(xxxxx) 10(xxxxxx)
        // 110(00001) 10(111111)
        // 110(00010) 10(000000)
        if (((uint8_t)str[offset] & 0b00011111) < 0b00000010)
            return (utf8_char_validity) { .valid = false, .next_offset = offset };

        return (utf8_char_validity) { .valid = true, .next_offset = offset + 2 };
    }

    // Three-byte UTF-8 characters have the form 1110xxxx 10xxxxxx 10xxxxxx
    if (((uint8_t)str[offset + 0] & 0b11110000) == 0b11100000 &&
        ((uint8_t)str[offset + 1] & 0b11000000) == 0b10000000 &&
        ((uint8_t)str[offset + 2] & 0b11000000) == 0b10000000) {

        // Check for overlong encoding
        // 110(xxxxx) 10(xxxxxx)
        // 110(11111) 10(111111)
        // 1110(xxxx) 10(xxxxxx) 10(xxxxxx)
        // 1110(0000) 10(011111) 10(111111)
        // 1110(0000) 10(100000) 10(000000)
        if (((uint8_t)str[offset + 0] & 0b00001111) == 0b00000000 &&
            ((uint8_t)str[offset + 1] & 0b00111111) < 0b00100000)
            return (utf8_char_validity) { .valid = false, .next_offset = offset };

        // Reject UTF-16 surrogates
        // U+D800 to U+DFFF
        // 1110(1101) 10(100000) 10(000000) ED A0 80 to 1110(1101) 10(111111) 10(111111) ED BF BF
        if ((uint8_t)str[offset + 0] == 0b11101101 &&
            (uint8_t)str[offset + 1] >= 0b10100000 &&
            (uint8_t)str[offset + 1] <= 0b10111111)
            return (utf8_char_validity) { .valid = false, .next_offset = offset };

        return (utf8_char_validity) { .valid = true, .next_offset = offset + 3 };
    }

    // Four-byte UTF-8 characters have the form 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
    if (((uint8_t)str[offset + 0] & 0b11111000) == 0b11110000 &&
        ((uint8_t)str[offset + 1] & 0b11000000) == 0b10000000 &&
        ((uint8_t)str[offset + 2] & 0b11000000) == 0b10000000 &&
        ((uint8_t)str[offset + 3] & 0b11000000) == 0b10000000) {

        // Check for overlong encoding
        // 1110(xxxx) 10(xxxxxx) 10(xxxxxx)
        // 1110(1111) 10(111111) 10(111111)
        // 11110(xxx) 10(xxxxxx) 10(xxxxxx) 10(xxxxxx)
        // 11110(000) 10(001111) 10(111111) 10(111111)
        // 11110(000) 10(010000) 10(000000) 10(000000)
        if (((uint8_t)str[offset + 0] & 0b00000111) == 0b00000000 &&
            ((uint8_t)str[offset + 1] & 0b00111111) < 0b00010000)
            return (utf8_char_validity) { .valid = false, .next_offset = offset };

        return (utf8_char_validity) { .valid = true, .next_offset = offset + 4 };
    }

    return (utf8_char_validity) { .valid = false, .next_offset = offset };
}

utf8_validity validate_utf8(const char* str) {
    if (str == NULL) return (utf8_validity) { .valid = false, .valid_upto = 0 };

    size_t offset = 0;
    utf8_char_validity char_validity;

    while (str[offset] != '\0') {
        char_validity = validate_utf8_char(str, offset);
        if (char_validity.valid) offset = char_validity.next_offset;
        else return (utf8_validity) { .valid = false, .valid_upto = offset };
    }

    return (utf8_validity) { .valid = true, .valid_upto = offset };
}

utf8_string make_utf8_string(const char* str) {
    utf8_validity validity = validate_utf8(str);
    if (validity.valid) return (utf8_string) { .str = str, .byte_len = validity.valid_upto };
    return (utf8_string) { .str = NULL, .byte_len = 0 };
}

owned_utf8_string make_utf8_string_lossy(const char* str) {
    if (str == NULL) return (owned_utf8_string) { .str = NULL, .byte_len = 0 };

    size_t len = strlen(str);

    // Worst case scenario: every byte is invalid and is replaced with 3 bytes for U+FFFD
    size_t worst_case_size = len * 3 + 1;

    // Allocate buffer for the lossy UTF-8 string
    char* buffer = (char*)malloc(worst_case_size);
    if (!buffer) return (owned_utf8_string) { .str = NULL, .byte_len = 0 }; // failed allocation

    size_t buffer_offset = 0;
    size_t offset = 0;
    utf8_char_validity char_validity;

    while (offset < len) {
        char_validity = validate_utf8_char(str, offset);

        if (char_validity.valid) {
            // Copy valid UTF-8 character sequence to the buffer
            size_t char_len = char_validity.next_offset - offset;
            memcpy(buffer + buffer_offset, str + offset, char_len);
            buffer_offset += char_len;
            offset = char_validity.next_offset;
        } else {
            // Insert the UTF-8 bytes for U+FFFD (�)
            // FFFD = 1111111111111101
            //      = (1111) (111111) (111101)
            //      = 1110(1111) 10(111111) 10(111101)
            //      = EF BF BD
            buffer[buffer_offset++] = 0xEF;
            buffer[buffer_offset++] = 0xBF;
            buffer[buffer_offset++] = 0xBD;
            offset++;
        }
    }

    buffer[buffer_offset] = '\0';

    return (owned_utf8_string) { .str = buffer, .byte_len = buffer_offset };
}

utf8_string as_utf8_string(const owned_utf8_string* owned_str) {
    return (utf8_string) { .str = owned_str->str, .byte_len = owned_str->byte_len };
}

void free_owned_utf8_string(owned_utf8_string* owned_str) {
    if (owned_str->str) {
        free(owned_str->str);
        owned_str->str = NULL;
        owned_str->byte_len = 0;
    }
}

utf8_char_iter make_utf8_char_iter(utf8_string ustr) {
    return (utf8_char_iter) { .str = ustr.str };
}

bool is_utf8_char_boundary(const char* str) {
    return (uint8_t)*str <= 0b01111111 || (uint8_t)*str >= 0b11000000;
}

utf8_string slice_utf8_string(utf8_string ustr, size_t start_byte_index, size_t byte_len) {
    if (start_byte_index > ustr.byte_len) start_byte_index = ustr.byte_len;

    size_t excl_end_byte_index = start_byte_index + byte_len;
    if (excl_end_byte_index > ustr.byte_len) excl_end_byte_index = ustr.byte_len;

    if (is_utf8_char_boundary(ustr.str + start_byte_index) && is_utf8_char_boundary(ustr.str + excl_end_byte_index))
        return (utf8_string) { .str = ustr.str + start_byte_index, .byte_len = excl_end_byte_index - start_byte_index };

    return (utf8_string) { .str = NULL, .byte_len = 0 };
}

utf8_char next_utf8_char(utf8_char_iter* iter) {
    if (*iter->str == '\0') return (utf8_char) { .str = iter->str, .byte_len = 0 };

    // iter->str is at the current char's starting byte (char boundary).
    const char* curr_boundary = iter->str;

    iter->str++;
    uint8_t byte_len = 1;

    // find the next char's starting byte (next char boundary) and set the iter->str to that.
    while (!is_utf8_char_boundary(iter->str)) {
        iter->str++;
        byte_len++;
    }

    return (utf8_char) { .str = curr_boundary, .byte_len = byte_len };
}

utf8_char nth_utf8_char(utf8_string ustr, size_t char_index) {
    utf8_char_iter iter = make_utf8_char_iter(ustr);

    utf8_char ch;
    while ((ch = next_utf8_char(&iter)).byte_len != 0 && char_index-- != 0) {}

    if (ch.byte_len == 0) return (utf8_char) { .str = NULL, .byte_len = 0 };
    return ch;
}

size_t utf8_char_count(utf8_string ustr) {
    utf8_char_iter iter = make_utf8_char_iter(ustr);

    size_t count = 0;
    while (next_utf8_char(&iter).byte_len > 0) count++;
    return count;
}

uint32_t unicode_code_point(utf8_char uchar) {
    switch (uchar.byte_len) {
    case 1: return uchar.str[0] & 0b01111111;
    case 2: return
        (uchar.str[0] & 0b00011111) << 6 |
        (uchar.str[1] & 0b00111111);
    case 3: return
        (uchar.str[0] & 0b00001111) << 12 |
        (uchar.str[1] & 0b00111111) << 6 |
        (uchar.str[2] & 0b00111111);
    case 4: return
        (uchar.str[0] & 0b00000111) << 18 |
        (uchar.str[1] & 0b00111111) << 12 |
        (uchar.str[2] & 0b00111111) << 6 |
        (uchar.str[3] & 0b00111111);
    }

    return 0; // unreachable
}
ASCII font lib/font.h
#ifndef __font_h__
#define __font_h__

#include "pico/stdlib.h"


// struktura bitmapového fontu
typedef struct bitmap_font {
	unsigned char        Width;     // maximální sířka znaku
	unsigned char        Height;    // výška znaku
	unsigned int         Chars;     // počet znaků ve fontu
	const unsigned char *Widths;    // ukazatel na pole šířek jednotlivých znaků
	const uint32_t      *Index;     // pole UTF-8 kódu znaků
	const unsigned char *Bitmap;    // pole bitmap jednotlivých znaků
} bitmapFONT;

// aby to hezky vypadalo v kódu
#define ________ 0x00
#define _______X 0x01
#define ______X_ 0x02
#define ______XX 0x03
#define _____X__ 0x04
#define _____X_X 0x05
#define _____XX_ 0x06
#define _____XXX 0x07
#define ____X___ 0x08
#define ____X__X 0x09
#define ____X_X_ 0x0A
#define ____X_XX 0x0B
#define ____XX__ 0x0C
#define ____XX_X 0x0D
#define ____XXX_ 0x0E
#define ____XXXX 0x0F
#define ___X____ 0x10
#define ___X___X 0x11
#define ___X__X_ 0x12
#define ___X__XX 0x13
#define ___X_X__ 0x14
#define ___X_X_X 0x15
#define ___X_XX_ 0x16
#define ___X_XXX 0x17
#define ___XX___ 0x18
#define ___XX__X 0x19
#define ___XX_X_ 0x1A
#define ___XX_XX 0x1B
#define ___XXX__ 0x1C
#define ___XXX_X 0x1D
#define ___XXXX_ 0x1E
#define ___XXXXX 0x1F
#define __X_____ 0x20
#define __X____X 0x21
#define __X___X_ 0x22
#define __X___XX 0x23
#define __X__X__ 0x24
#define __X__X_X 0x25
#define __X__XX_ 0x26
#define __X__XXX 0x27
#define __X_X___ 0x28
#define __X_X__X 0x29
#define __X_X_X_ 0x2A
#define __X_X_XX 0x2B
#define __X_XX__ 0x2C
#define __X_XX_X 0x2D
#define __X_XXX_ 0x2E
#define __X_XXXX 0x2F
#define __XX____ 0x30
#define __XX___X 0x31
#define __XX__X_ 0x32
#define __XX__XX 0x33
#define __XX_X__ 0x34
#define __XX_X_X 0x35
#define __XX_XX_ 0x36
#define __XX_XXX 0x37
#define __XXX___ 0x38
#define __XXX__X 0x39
#define __XXX_X_ 0x3A
#define __XXX_XX 0x3B
#define __XXXX__ 0x3C
#define __XXXX_X 0x3D
#define __XXXXX_ 0x3E
#define __XXXXXX 0x3F
#define _X______ 0x40
#define _X_____X 0x41
#define _X____X_ 0x42
#define _X____XX 0x43
#define _X___X__ 0x44
#define _X___X_X 0x45
#define _X___XX_ 0x46
#define _X___XXX 0x47
#define _X__X___ 0x48
#define _X__X__X 0x49
#define _X__X_X_ 0x4A
#define _X__X_XX 0x4B
#define _X__XX__ 0x4C
#define _X__XX_X 0x4D
#define _X__XXX_ 0x4E
#define _X__XXXX 0x4F
#define _X_X____ 0x50
#define _X_X___X 0x51
#define _X_X__X_ 0x52
#define _X_X__XX 0x53
#define _X_X_X__ 0x54
#define _X_X_X_X 0x55
#define _X_X_XX_ 0x56
#define _X_X_XXX 0x57
#define _X_XX___ 0x58
#define _X_XX__X 0x59
#define _X_XX_X_ 0x5A
#define _X_XX_XX 0x5B
#define _X_XXX__ 0x5C
#define _X_XXX_X 0x5D
#define _X_XXXX_ 0x5E
#define _X_XXXXX 0x5F
#define _XX_____ 0x60
#define _XX____X 0x61
#define _XX___X_ 0x62
#define _XX___XX 0x63
#define _XX__X__ 0x64
#define _XX__X_X 0x65
#define _XX__XX_ 0x66
#define _XX__XXX 0x67
#define _XX_X___ 0x68
#define _XX_X__X 0x69
#define _XX_X_X_ 0x6A
#define _XX_X_XX 0x6B
#define _XX_XX__ 0x6C
#define _XX_XX_X 0x6D
#define _XX_XXX_ 0x6E
#define _XX_XXXX 0x6F
#define _XXX____ 0x70
#define _XXX___X 0x71
#define _XXX__X_ 0x72
#define _XXX__XX 0x73
#define _XXX_X__ 0x74
#define _XXX_X_X 0x75
#define _XXX_XX_ 0x76
#define _XXX_XXX 0x77
#define _XXXX___ 0x78
#define _XXXX__X 0x79
#define _XXXX_X_ 0x7A
#define _XXXX_XX 0x7B
#define _XXXXX__ 0x7C
#define _XXXXX_X 0x7D
#define _XXXXXX_ 0x7E
#define _XXXXXXX 0x7F
#define X_______ 0x80
#define X______X 0x81
#define X_____X_ 0x82
#define X_____XX 0x83
#define X____X__ 0x84
#define X____X_X 0x85
#define X____XX_ 0x86
#define X____XXX 0x87
#define X___X___ 0x88
#define X___X__X 0x89
#define X___X_X_ 0x8A
#define X___X_XX 0x8B
#define X___XX__ 0x8C
#define X___XX_X 0x8D
#define X___XXX_ 0x8E
#define X___XXXX 0x8F
#define X__X____ 0x90
#define X__X___X 0x91
#define X__X__X_ 0x92
#define X__X__XX 0x93
#define X__X_X__ 0x94
#define X__X_X_X 0x95
#define X__X_XX_ 0x96
#define X__X_XXX 0x97
#define X__XX___ 0x98
#define X__XX__X 0x99
#define X__XX_X_ 0x9A
#define X__XX_XX 0x9B
#define X__XXX__ 0x9C
#define X__XXX_X 0x9D
#define X__XXXX_ 0x9E
#define X__XXXXX 0x9F
#define X_X_____ 0xA0
#define X_X____X 0xA1
#define X_X___X_ 0xA2
#define X_X___XX 0xA3
#define X_X__X__ 0xA4
#define X_X__X_X 0xA5
#define X_X__XX_ 0xA6
#define X_X__XXX 0xA7
#define X_X_X___ 0xA8
#define X_X_X__X 0xA9
#define X_X_X_X_ 0xAA
#define X_X_X_XX 0xAB
#define X_X_XX__ 0xAC
#define X_X_XX_X 0xAD
#define X_X_XXX_ 0xAE
#define X_X_XXXX 0xAF
#define X_XX____ 0xB0
#define X_XX___X 0xB1
#define X_XX__X_ 0xB2
#define X_XX__XX 0xB3
#define X_XX_X__ 0xB4
#define X_XX_X_X 0xB5
#define X_XX_XX_ 0xB6
#define X_XX_XXX 0xB7
#define X_XXX___ 0xB8
#define X_XXX__X 0xB9
#define X_XXX_X_ 0xBA
#define X_XXX_XX 0xBB
#define X_XXXX__ 0xBC
#define X_XXXX_X 0xBD
#define X_XXXXX_ 0xBE
#define X_XXXXXX 0xBF
#define XX______ 0xC0
#define XX_____X 0xC1
#define XX____X_ 0xC2
#define XX____XX 0xC3
#define XX___X__ 0xC4
#define XX___X_X 0xC5
#define XX___XX_ 0xC6
#define XX___XXX 0xC7
#define XX__X___ 0xC8
#define XX__X__X 0xC9
#define XX__X_X_ 0xCA
#define XX__X_XX 0xCB
#define XX__XX__ 0xCC
#define XX__XX_X 0xCD
#define XX__XXX_ 0xCE
#define XX__XXXX 0xCF
#define XX_X____ 0xD0
#define XX_X___X 0xD1
#define XX_X__X_ 0xD2
#define XX_X__XX 0xD3
#define XX_X_X__ 0xD4
#define XX_X_X_X 0xD5
#define XX_X_XX_ 0xD6
#define XX_X_XXX 0xD7
#define XX_XX___ 0xD8
#define XX_XX__X 0xD9
#define XX_XX_X_ 0xDA
#define XX_XX_XX 0xDB
#define XX_XXX__ 0xDC
#define XX_XXX_X 0xDD
#define XX_XXXX_ 0xDE
#define XX_XXXXX 0xDF
#define XXX_____ 0xE0
#define XXX____X 0xE1
#define XXX___X_ 0xE2
#define XXX___XX 0xE3
#define XXX__X__ 0xE4
#define XXX__X_X 0xE5
#define XXX__XX_ 0xE6
#define XXX__XXX 0xE7
#define XXX_X___ 0xE8
#define XXX_X__X 0xE9
#define XXX_X_X_ 0xEA
#define XXX_X_XX 0xEB
#define XXX_XX__ 0xEC
#define XXX_XX_X 0xED
#define XXX_XXX_ 0xEE
#define XXX_XXXX 0xEF
#define XXXX____ 0xF0
#define XXXX___X 0xF1
#define XXXX__X_ 0xF2
#define XXXX__XX 0xF3
#define XXXX_X__ 0xF4
#define XXXX_X_X 0xF5
#define XXXX_XX_ 0xF6
#define XXXX_XXX 0xF7
#define XXXXX___ 0xF8
#define XXXXX__X 0xF9
#define XXXXX_X_ 0xFA
#define XXXXX_XX 0xFB
#define XXXXXX__ 0xFC
#define XXXXXX_X 0xFD
#define XXXXXXX_ 0xFE
#define XXXXXXXX 0xFF

#endif
UTF-8 Fonty

lib/font_spleen_5x8.c

lib/font_spleen_8x16.c (ručně upravovaný font potřebuje index)

Celý projekt ke stažení: ../ssd1306_utf8_v0.3.tar.gz

Přidání dalších fontů

Konverze z formátu BDF do C
./bdf2c -b -O -C font_spleen_16x32.h < spleen-16x32.bdf >font_spleen_16x32.c

Potom je potřeba upravit soubory .c a .h takto:

Přejmenovat

	/// bitmap font structure
const struct bitmap_font font = {
	.Width = 16, .Height = 32,
	.Chars = 969,
	.Widths = __font_widths__,
	.Index  = __font_index__,
	.Bitmap = __font_bitmap__,
};

na:

	/// bitmap font structure
const struct bitmap_font font_spleen_16x32 = {
	.Width = 16, .Height = 32,
	.Chars = 969,
	.Widths = __font_widths__,
	.Index  = __font_index__,
	.Bitmap = __font_bitmap__,
};

Zdroje a odkazy

https://github.com/zahash/utf8.c C knihovna pro práci s UTF-8