LCD TFT displej TF028 s paralelním 8 bitovým rozhraním původně určený pro Arduino se mi podařilo rozchodit na Raspberry Pi Pico. Funguje velmi rychle, ale má některé hardwarové chyby. Odporový touchscreen je totiž připojen na stejné piny jako ovládání TFT displeje. Toto je první verze, ke komunikaci používám PIO, zatím bez DMA.

Hardware

Displej musí být napájen z 5V, na 3.3V nefunguje.

Popis pinů na displeji

zapojeni pinu

Problém je v tom, že piny LCD_CS a LCD_RS jsou spojeny s piny touchscreenu X+ a Y+.

Zapojení pinů podrobněji

tft screen shield 2p8 pinout

Zapojení displeje k Picu

IMG 20250804 231820

Tabulka 1. Zapojení pinů
displej pin Pico pin

Power VCC (5V)

5V (na 3.3V displej nefunguje)

Power GOND

GND

Reset signal (LCD_RST)

3.3V (bez toho displej nefunguje)

Chip select (LCD CS)

GPIO15

Command/Data select (LCD_RS)

GPIO14

Write signal (LCD_WR)

GPIO13

Read signal (LCD_RD)

GPIO12

LCD Data bit 0 (LCD_D0)

GPIO4

LCD Data bit 1 (LCD_D1)

GPIO5

LCD Data bit 2 (LCD_D2)

GPIO6

LCD Data bit 3 (LCD_D3)

GPIO7

LCD Data bit 4 (LCD_D4)

GPIO8

LCD Data bit 5 (LCD_D5)

GPIO9

LCD Data bit 6 (LCD_D6)

GPIO10

LCD Data bit 7 (LCD_D7)

GPIO11

Software

Sestavovací soubor pro cmake.

CMakeLists.txt
# cmake version
cmake_minimum_required(VERSION 3.13)

# include the sdk.cmake file
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)

# give the project a name (anything you want)
project(ili_pio_test)

# initialize the sdk
pico_sdk_init()

#######

# name anything you want
add_executable(ili_pio_test)

# must match with pio filename and executable name from above
pico_generate_pio_header(ili_pio_test ${CMAKE_CURRENT_LIST_DIR}/ili9341.pio)

# must match with executable name and source file names
target_sources(ili_pio_test PRIVATE
	main.c
	ili9341.c
	logo.c
	pico.c
	)

# must match with executable name
target_link_libraries(ili_pio_test PRIVATE
	pico_stdlib
	hardware_pio
	hardware_dma
	hardware_irq
	)

# must match with executable name
pico_add_extra_outputs(ili_pio_test)
pico_enable_stdio_usb(ili_pio_test 1)
pico_enable_stdio_uart(ili_pio_test 0)
#add_compile_options(-Ofast)

Komunikace s displejem

Komunikaci s diplejem zajištuje PIO program.

Časová posloupnost zápisu na displej s odpovídajícím kódem Pio asembleru

Časová posloupnost zápis z datového listu str. 28 write sequence

PIO asembler ili9341.pio pio write sequence

.program ili9341_pio_cmd
; CSX, D/CX, WRX, RDX --> gpio 15, 14, 13, 12  (set pins), RESET připojit na 3.3V
.wrap_target
start:
pull
set pins, 0b0011
mov x, osr              ; command code, if 0x0, command nop, only data
jmp !x param
set pins, 0b0001
out pins, 8      [1]
set pins, 0b0011 [1]
param:
set pins, 0b0111
pull
mov x, osr              ; how many parameters
jmp !x, start           ; no parameter return start
jmp x--, param_data         
param_data:
pull                    ; write data
set pins, 0b0101 
out pins, 8      [1] 
set pins, 0b0111 [1]
jmp x--, param_data
set pins, 0b1111
.wrap

Pojmenování řídících registrů displeje.

registers.h
#define ILI9341_NOP                 0x00
#define ILI9341_SOFTRESET           0x01
#define ILI9341_SLEEPIN             0x10
#define ILI9341_SLEEPOUT            0x11
#define ILI9341_NORMALDISP          0x13
#define ILI9341_INVERTOFF           0x20
#define ILI9341_INVERTON            0x21
#define ILI9341_GAMMASET            0x26
#define ILI9341_DISPLAYOFF          0x28
#define ILI9341_DISPLAYON           0x29
#define ILI9341_COLADDRSET          0x2A
#define ILI9341_PAGEADDRSET         0x2B
#define ILI9341_MEMORYWRITE         0x2C
#define ILI9341_MEMORYREAD          0x2E
#define ILI9341_PIXELFORMAT         0x3A
#define ILI9341_MEMORYWRITECONT     0x3C
#define ILI9341_MEMORYREADCONT      0x3E
#define ILI9341_FRAMECONTROL        0xB1
#define ILI9341_DISPLAYFUNC         0xB6
#define ILI9341_ENTRYMODE           0xB7
#define ILI9341_POWERCONTROL1       0xC0
#define ILI9341_POWERCONTROL2       0xC1
#define ILI9341_VCOMCONTROL1        0xC5
#define ILI9341_VCOMCONTROL2        0xC7
#define ILI9341_VSCROLLDEF          0x33
#define ILI9341_VSCROLLADDR         0x37
#define ILI9341_MEMCONTROL          0x36
#define ILI9341_MADCTL              0x36

#define ILI9341_PGAMCOR             0xE0
#define ILI9341_NGAMCOR             0xE1

#define ILI9341_MADCTL_MY           0x80
#define ILI9341_MADCTL_MX           0x40
#define ILI9341_MADCTL_MV           0x20
#define ILI9341_MADCTL_ML           0x10
#define ILI9341_MADCTL_RGB          0x00
#define ILI9341_MADCTL_BGR          0x08
#define ILI9341_MADCTL_MH           0x04

Zápis na displej a grafické funkce.

ili9341.c
#include "stdio.h"
#include "stdlib.h"
#include "ili9341.pio.h"
#include "pico/stdlib.h"
#include "hardware/clocks.h"
#include "string.h"
#include "registers.h"
#include "bitmap_typedefs.h"
//#include "ili9341.h"
#define SCREEN_WIDTH   240
#define SCREEN_HEIGHT  320




PIO  ili9341_pio     = pio1;
uint ili9341_sm      = 0;
uint out_base_pin    = 4;
uint in_base_pin     = 4;
uint set_base_pin    = 12;

//TFT width and height default global variables
uint16_t ili_tftwidth  = 320;
uint16_t ili_tftheight = 240;


static uint8_t  p_count = 0;
static uint8_t  d_count = 0;
static uint8_t  data_recv = 0;
static uint8_t  params[8];
static uint32_t data[8];


// poslání příkazu displeji
void ili9341_cmd(uint8_t cmd, uint32_t count, uint8_t *param) 
{
    pio_sm_restart(ili9341_pio,ili9341_sm);
    pio_sm_put_blocking(ili9341_pio, ili9341_sm, cmd);   // command code
    pio_sm_put_blocking(ili9341_pio, ili9341_sm, count); // how many parameters
    for( int i = 0; i < count; i++) {
        pio_sm_put_blocking( ili9341_pio, ili9341_sm, param[i]);
    }
    
}

// inicializace PIO
void ili9431_pio_cmd_init(PIO pio, uint sm, uint out_base, uint set_base, uint32_t freq)
{
    uint offset = 0;
    pio_sm_config c;
    offset = pio_add_program(pio, &ili9341_pio_cmd_program);
    c = ili9341_pio_cmd_program_get_default_config(offset);

    for (int i = 0; i < 8; i++)
        pio_gpio_init(pio, out_base + i);
    for (int i = 0; i < 4; i++)
        pio_gpio_init(pio, set_base + i);

    pio_sm_set_consecutive_pindirs(pio, sm, out_base, 8, true);
    pio_sm_set_consecutive_pindirs(pio, sm, set_base, 4, true);

    sm_config_set_out_pins(&c, out_base, 8);
    sm_config_set_set_pins(&c, set_base, 4);

    sm_config_set_out_shift(&c, true, false, 32);

    float div = clock_get_hz(clk_sys) / freq;
    sm_config_set_clkdiv(&c, div);

    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

/* ili3941 kreslící funkce */
// ---------------------------------------------------------------------------------------
// převod z formátu 5R6G5B na uint16_t
uint16_t ili9341_color_565RGB(uint8_t R, uint8_t G, uint8_t B)
{
//    uint16_t c;
    return (((uint16_t)R) >> 3) << 11 | (((uint16_t)G) >> 2) << 5 | ((uint16_t)B) >> 3;
    //return c;
}

// nastaveni adresy displje

void ili9341_set_address_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
    uint8_t addr[4];
    addr[0] = (uint8_t)(x1 >> 8);
    addr[1] = (uint8_t)(x1 & 0xff);
    addr[2] = (uint8_t)(x2 >> 8);
    addr[3] = (uint8_t)(x2 & 0xff);
    ili9341_cmd(ILI9341_COLADDRSET, 4, addr);

    addr[0] = (uint8_t)(y1 >> 8);
    addr[1] = (uint8_t)(y1 & 0xff);
    addr[2] = (uint8_t)(y2 >> 8);
    addr[3] = (uint8_t)(y2 & 0xff);
    ili9341_cmd(ILI9341_PAGEADDRSET, 4, addr);

    ili9341_cmd(ILI9341_MEMORYWRITE, 0, NULL);
}

// nakresleni bodu na displeji

void ili9341_draw_pixel(uint16_t x, uint16_t y, uint16_t color)
{
    if (x < 0 || x > SCREEN_WIDTH || y < 0 || y > SCREEN_HEIGHT)
        return;
    ili9341_set_address_window(x, y, x, y);
    ili9341_cmd(ILI9341_NOP, 2, (uint8_t[2]){(uint8_t)(color), (uint8_t)(color>>8)});
}

// invertování barev dipleje
void ili9341_invert_display(bool invert)
{
    if (invert)
        ili9341_cmd(ILI9341_INVERTON, 0, NULL);
    else
        ili9341_cmd(ILI9341_INVERTOFF, 0, NULL);
}

// nakreslení vyplněného obdélníka
void ili9341_fill_rect(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color)
{
    if (x < 0)
        x = 0;
    if (y < 0)
        y = 0;
    if (x + width > SCREEN_WIDTH)
        width = SCREEN_WIDTH - x;
    if (y + height > SCREEN_HEIGHT)
        height = SCREEN_HEIGHT - y;
    ili9341_set_address_window(x, y, x + width, y + height);
    for (int j = y; j < y + height; j++)
        for (int i = x; i < x + width; i++)
            ili9341_cmd(ILI9341_NOP, 2, (uint8_t[2]){(uint8_t)(color >> 8), (uint8_t)(color & 0x00ff)});
}


// nakreslení čáry
void ili9341_draw_line(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color)
{
    /* algorithm from https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm */
    int dx = abs(x1 - x0);
    int sx = x0 < x1 ? 1 : -1;
    int dy = -abs(y1 - y0);
    int sy = y0 < y1 ? 1 : -1;
    int error = dx + dy;
    int e2;
    while (1)
    {
        // plot(x0, y0)
        ili9341_draw_pixel(x0, y0, color);
        if (x0 == x1 && y0 == y1)
            break;
        e2 = 2 * error;
        if (e2 >= dy)
        {
            if (x0 == x1)
                break;
            error = error + dy;
            x0 = x0 + sx;
        }
        if (e2 <= dx)
        {
            if (y0 == y1)
                break;
            error = error + dx;
            y0 = y0 + sy;
        }
    }
}


void ili9341_draw_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color) {
	int x;
	int y;
	int error;
	int old_error;

	x=0;
	y=-r;
	error=2-2*r;
	do{
		ili9341_draw_pixel(x0-x,  y0+y, color);
		ili9341_draw_pixel(x0-y,  y0-x, color);
		ili9341_draw_pixel(x0+x,  y0-y, color);
		ili9341_draw_pixel(x0+y,  y0+x, color);
		if ((old_error=error)<=x)	error+=++x*2+1;
		if (old_error>y || error>x) error+=++y*2+1;	 
	} while(y<0);
}

// Draw circle of filling
// x0: Central X coordinate
// y0: Central Y coordinate
// r: radius
// color:color
void ili9341_draw_fill_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color) {
	int x;
	int y;
	int error;
	int old_error;
	int ChangeX;

	x=0;
	y=-r;
	error=2-2*r;
	ChangeX=1;
	do{
		if(ChangeX) {
			ili9341_draw_line(x0-x, y0-y,x0-x, y0+y, color);
			ili9341_draw_line(x0+x, y0-y, x0+x, y0+y, color);
		} // endif
		ChangeX=(old_error=error)<=x;
		if (ChangeX)			error+=++x*2+1;
		if (old_error>y || error>x) error+=++y*2+1;
	} while(y<=0);
} 


// nakreslení bitmapy
void ili9341_draw_bitmap(uint16_t x, uint16_t y, const tImage *bitmap)
{
    uint16_t width = 0, height = 0;
    width = bitmap->width;
    height = bitmap->height;

    uint16_t total_pixels = width * height;

    ili9341_set_address_window(x, y, x + width - 1, y + height - 1);

    for (uint16_t pixels = 0; pixels < total_pixels; pixels++)
    {
        ili9341_cmd(ILI9341_NOP, 1, (uint8_t[1]){(uint8_t)(bitmap->data[2 * pixels])});
        ili9341_cmd(ILI9341_NOP, 1, (uint8_t[1]){(uint8_t)(bitmap->data[2 * pixels + 1])});
    }
}


void ili9431_init_config()
{
    ili9341_cmd(ILI9341_SOFTRESET, 0, NULL);
    sleep_ms(150);
    ili9341_cmd(ILI9341_DISPLAYOFF, 0, NULL);
    sleep_ms(150);
    ili9341_cmd(ILI9341_PIXELFORMAT,   1, (uint8_t[1]){0x55});
    ili9341_cmd(ILI9341_POWERCONTROL1, 1, (uint8_t[1]){0x05}); // 0x05 :3.3V
    ili9341_cmd(ILI9341_POWERCONTROL2, 1, (uint8_t[1]){0x10});
    ili9341_cmd(ILI9341_VCOMCONTROL1,  2, (uint8_t[2]){0x3E, 0x28});
    ili9341_cmd(ILI9341_VCOMCONTROL2,  1, (uint8_t[1]){0x86});
//    ili9341_cmd(ILI9341_MADCTL,        1, (uint8_t[1]){0x40});             // MY,MX,MV,ML,BRG,MH,0,0
    ili9341_cmd(ILI9341_MADCTL,        1, (uint8_t[1]){0x48});             // MY,MX,MV,ML,RGB,MH,0,0
    ili9341_cmd(ILI9341_FRAMECONTROL,  2, (uint8_t[2]){0x00, 0x1B}); // Default 70Hz
    ili9341_cmd(ILI9341_DISPLAYFUNC,   4, (uint8_t[4]){0x0A, 0xA2, 0x27, 0x04});
    ili9341_cmd(ILI9341_GAMMASET,      1, (uint8_t[1]){0x01});

    ili9341_cmd(ILI9341_PGAMCOR, 15, (uint8_t[15]){0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1, 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00});
    ili9341_cmd(ILI9341_NGAMCOR, 15, (uint8_t[15]){0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1, 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f});

    ili9341_cmd(ILI9341_SLEEPOUT, 0, NULL);
    sleep_ms(150);
    ili9341_cmd(ILI9341_DISPLAYON, 0, NULL);
    sleep_ms(500);
}

void ili9341_init()
{

    ili9431_pio_cmd_init(ili9341_pio, ili9341_sm, out_base_pin, set_base_pin, 70 * 1000000); // 70 MHz
    ili9431_init_config();
}


// kreslení znaků a řetězců

/*
 * Render a character glyph on the display. Called by `_ili_draw_string_main()`
 * User need NOT call it
 */
void _ili_render_glyph(uint16_t x, uint16_t y, uint16_t fore_color, uint16_t back_color, const tImage *glyph, uint8_t is_bg)
{
	uint16_t width = 0, height = 0;

	width = glyph->width;
	height = glyph->height;

	uint16_t temp_x = x;
	uint16_t temp_y = y;

	uint8_t mask = 0x80;
	uint8_t bit_counter = 0;

	const uint8_t *glyph_data_ptr = (const uint8_t *)(glyph->data);
	uint8_t glyph_data = 0;

	// font bitmaps are stored in column major order (scanned from left-to-right, not the conventional top-to-bottom)
	// as font glyphs have heigher height than width, this scanning saves some storage.
	// So, we also render in left-to-right manner.

	// Along x axis (width)
	for (int i = 0; i < width; i++)
	{
		// Along y axis (height)
		for (int j = 0; j < height; j++)
		{
			// load new data only when previous byte (or word, depends on glyph->dataSize) is completely traversed by the mask
			// bit_counter = 0 means glyph_data is completely traversed by the mask
			if (bit_counter == 0)
			{
				glyph_data = *glyph_data_ptr++;
				bit_counter = glyph->dataSize;
			}
			// Decrement bit counter
			bit_counter--;

			//If pixel is blank
			if (glyph_data & mask)
			{
				//Has background color (not transparent bg)
				if (is_bg)
				{
					ili9341_draw_pixel(temp_y, temp_x, back_color);
				}
			}

			//if pixel is not blank
			else
			{
				ili9341_draw_pixel(temp_y, temp_x, fore_color);
			}

			glyph_data <<= 1;
			temp_y++;
		}

		//New col starts. So, row is set to initial value and col is increased by one
		temp_y = y;
		temp_x++;

		//Reset the bit counter cause we're moving to next column, so we start with a new byte
		bit_counter = 0;
	}
}



/**
 * Renders a string by drawing each character glyph from the passed string.
 * Called by `ili_draw_string()` and `ili_draw_string_withbg()`.
 * Text is wrapped automatically if it hits the screen boundary.
 * x_padding and y_padding defines horizontal and vertical distance (in px) between two characters
 * is_bg=1 : Text will habe background color,   is_bg=0 : Text will have transparent background
 * User need NOT call it.
 */

void _ili_draw_string_main(uint16_t x, uint16_t y, char *str, uint16_t fore_color, uint16_t back_color, const tFont *font, uint8_t is_bg)
{
	uint16_t x_temp = x;
	uint16_t y_temp = y;

	uint8_t x_padding = 0;
	uint8_t y_padding = 0;
	const tImage *img = {0};
	uint16_t width = 0, height = 0;



	while (*str)
	{
		if (*str == '\n')
		{
			x_temp = x;					//go to first col
			y_temp += (font->chars[0].image->height + y_padding);	//go to next row (row height = height of space)
		}

		else if (*str == '\t')
		{
			x_temp += 4 * (font->chars[0].image->height + y_padding);	//Skip 4 spaces (width = width of space)
		}
		else
		{
			for (uint8_t i = 0; i < font->length; i++)
			{
				if (font->chars[i].code == *str)
				{
					img = font->chars[i].image;
					break;
				}
			}
			// No glyph (img) found, so return from this function
			if (img == NULL)
			{
				return;
			}

			width = img->width;
			height = img->height;

			if(y_temp + (height + y_padding) > ili_tftheight - 1)	//not enough space available at the bottom
				return;
			if (x_temp + (width + x_padding) > ili_tftwidth - 1)	//not enough space available at the right side
			{
				x_temp = x;					//go to first col
				y_temp += (height + y_padding);	//go to next row
			}


			if (is_bg)
				_ili_render_glyph(x_temp, y_temp, fore_color, back_color, img, 1);
			else
				_ili_render_glyph(x_temp, y_temp, fore_color, back_color, img, 0);
			x_temp += (width + x_padding);		//next char position
		}


		str++;
	}
}


/**
 * Draws a character at a given position, fore color, back color.
 * @param x Start col address
 * @param y Start row address
 * @param character the ASCII character to be drawn
 * @param fore_color foreground color
 * @param back_color background color
 * @param font Pointer to the font of the character
 * @param is_bg Defines if character has background or not (transparent)
 */
void ili_draw_char(uint16_t x, uint16_t y, char character, uint16_t fore_color, uint16_t back_color, const tFont *font, uint8_t is_bg)
{
	const tImage *img = NULL;
	for (uint8_t i = 0; i < font->length; i++)
	{
		if (font->chars[i].code == character)
		{
			img = font->chars[i].image;
			break;
		}
	}
	// No glyph (img) found, so return from this function
	if (img == NULL)
	{
		return;
	}
	if (is_bg)
		_ili_render_glyph(x, y, fore_color, back_color, img, 1);
	else
		_ili_render_glyph(x, y, fore_color, back_color, img, 0);
}


/**
 * Draws a string on the display with `font` and `color` at given position.
 * Background of this string is transparent
 * @param x Start col address
 * @param y Start y address
 * @param str pointer to the string to be drawn
 * @param color 16-bit RGB565 color of the string
 * @param font Pointer to the font of the string
 */
void ili9341_draw_string(uint16_t x, uint16_t y, char *str, uint16_t color, const tFont *font)
{
	_ili_draw_string_main(x, y, str, color, 0, font, 0);
}


/**
 * Draws a string on the display with `font`, `fore_color`, and `back_color` at given position.
 * The string has background color
 * @param x Start col address
 * @param y Start y address
 * @param str pointer to the string to be drawn
 * @param foe_color 16-bit RGB565 color of the string
 * @param back_color 16-bit RGB565 color of the string's background
 * @param font Pointer to the font of the string
 */
void ili9341_draw_string_withbg(uint16_t x, uint16_t y, char *str, uint16_t fore_color, uint16_t back_color, const tFont *font)
{
	_ili_draw_string_main(x, y, str, fore_color, back_color, font, 1);
}

Hlavičkový soubor ke grafickým funkcím

ili9341.h

#ifndef  _ILI9431_H_
#define _ILI9431_H_

#define SCREEN_WIDTH   240
#define SCREEN_HEIGHT  320

#include "pico/stdlib.h"
#include "bitmap_typedefs.h"
#include "hardware/pio.h"


void ili9341_init();
void ili9431_init_config();
void ili9341_draw_pixel(uint16_t x, uint16_t y, uint16_t color);
// uint16_t ili9341_read_pixel(uint16_t x, uint16_t y);
// void ili9341_set_address_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
// void ili9341_memory_write_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
void ili9341_cmd(uint8_t cmd, uint32_t count, uint8_t *param);
uint16_t ili9341_color_565RGB(uint8_t R, uint8_t G, uint8_t B);
void ili9341_fill_rect(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color);
void ili9341_invert_display(bool invert);
void ili9341_draw_line(uint16_t start_x, uint16_t start_y, uint16_t end_x, uint16_t end_y, uint16_t color);
void ili9341_draw_fill_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color);
void ili9341_draw_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color);
void ili9341_draw_string(uint16_t x, uint16_t y, uint8_t* str, uint16_t color, const tFont *font);
void ili9341_draw_string_withbg(uint16_t x, uint16_t y, char *str, uint16_t fore_color, uint16_t back_color, const tFont *font);
void ili9341_draw_bitmap(uint16_t x, uint16_t y, const tImage *bitmap);
void ili9431_pio_cmd_init(PIO pio, uint sm, uint out_base,  uint set_base, uint32_t freq);
// void ili9341_vertical_scroll_def(uint16_t top, uint16_t bottom);
// void ili9341_vertical_scrolling(uint16_t addr);
// void ili9341_read_area(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t * pixels);
// void ili9341_write_area(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t * pixels);
// void ili9341_draw_char(uint16_t x, uint16_t y, uint8_t character, uint16_t color, const tFont *font);
// void ili9341_popMessage(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t *message, uint8_t seconds, const tFont *font);
#endif

Typedefy pro obrázky a fonty. Písmenka se dávají na displej jako obrázky.

link:bitmap_typedefs.h
/*
MIT License

Copyright (c) 2019 Avra Mitra

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.
*/

#ifndef INC_BITMAP_TYPEDEFS_H_
#define INC_BITMAP_TYPEDEFS_H_

#include <stdint.h>
#include <inttypes.h>

 typedef struct {
     const uint8_t *data;
     uint16_t width;
     uint16_t height;
     uint8_t dataSize;
     } tImage;

 typedef struct {
	  const uint16_t *data;
	  uint16_t width;
	  uint16_t height;
	  uint8_t dataSize;
	  } tImage16bit;
 typedef struct {
     long int code;
     const tImage *image;
     } tChar;
 typedef struct {
     int length;
     const tChar *chars;
     } tFont;

#endif /* INC_BITMAP_TYPEDEFS_H_ */

Na výběr barev ve formátu RGB565 je dobrý web https://rgbcolorpicker.com/565

Testování možností displeje.

main.c
/*
 * TFT LCD displej ILI9341 s paralelním 8 bitovým rozhraním TF028, programovaní pomoci PIO
 * (c) Jirka Chráska 2025; <jirka@lixis.cz>
 * BSD 3 clause licence
 */ 

#include <stdio.h>
#include "stdlib.h"
#include "pico/stdlib.h"
#include "hardware/dma.h"
#include "hardware/pio.h"
#include "ili9341.h"
#include "string.h"
#include "logo.h"
#include "pico.h"
#include "font_ubuntu_mono_24.h"

// https://rgbcolorpicker.com/565

// obvyklé barvy
#define BLACK       0x0000      /*   0,   0,   0 */
#define NAVY        0x000F      /*   0,   0, 128 */
#define DARKGREEN   0x03E0      /*   0, 128,   0 */
#define DARKCYAN    0x03EF      /*   0, 128, 128 */
#define MAROON      0x7800      /* 128,   0,   0 */
#define PURPLE      0x780F      /* 128,   0, 128 */
#define OLIVE       0x7BE0      /* 128, 128,   0 */
#define LIGHTGREY   0xC618      /* 192, 192, 192 */
#define DARKGREY    0x7BEF      /* 128, 128, 128 */
#define BLUE        0x001F      /*   0,   0, 255 */
#define GREEN       0x07E0      /*   0, 255,   0 */
#define CYAN        0x07FF      /*   0, 255, 255 */
#define RED         0xF800      /* 255,   0,   0 */
#define MAGENTA     0xF81F      /* 255,   0, 255 */
#define YELLOW      0xFFE0      /* 255, 255,   0 */
#define WHITE       0xFFFF      /* 255, 255, 255 */
#define ORANGE      0xFD20      /* 255, 165,   0 */
#define GREENYELLOW 0xAFE5      /* 173, 255,  47 */
#define PINK        0xF81F

void demo() 
{
    ili9341_fill_rect(0,0, SCREEN_WIDTH, SCREEN_HEIGHT, NAVY);
    // for (int j = 0; j < 320; j+=5) ili9341_draw_line(0, 0, 239, j, ORANGE);
    // for (int i = 0; i < 240; i+=5) ili9341_draw_line(0, 0, i, 319, OLIVE);
    // for (int i = 10;i < 150; i+=10) {
    //     ili9341_fill_rect(i, i, 100, 100, ili9341_color_565RGB(0x70+i, 0x80+i*2, 0x40+i));
    // }
    ili9341_draw_bitmap(0,0, &pico);
    ili9341_draw_string_withbg(4, 108, "Programujeme ILI9341 8bit paralelni displej pomoci  PIO koprocesoru (bez DMA).", LIGHTGREY, DARKGREY, &font_ubuntu_mono_24);
    ili9341_draw_string_withbg(4, 184, "(c) Jirka Chraska 2025;   <jirka@lixis.cz>.", LIGHTGREY, DARKGREY, &font_ubuntu_mono_24);
}

void demo2()
{
    int r, g, b;
    char buf[60];
    for(r=0; r<32; r++) {
        for(g=0; g<64; g++) {
            for(b=0; b<32; b++) {
            ili9341_fill_rect(0,0, SCREEN_WIDTH, SCREEN_HEIGHT, ili9341_color_565RGB(r*8,g*4,b*8));
            sprintf(buf,"R=%03d G=%03d B=%03d",r,g,b);
            ili9341_draw_string_withbg(5,200,buf,0x0000, 0xffff, &font_ubuntu_mono_24);
            sleep_ms(20);
            }
        }
    }
}

#define ow 32
#define oh 30
void demo3()
{
    
    static uint16_t ctable[8][10] = {
        0x80c3, 0x8103, 0x8123, 0x8163, 0x81a3, 0x81c3, 0x8203, 0x8223, 0x8263, 0x82a3,
        0x82c3, 0x8303, 0x8323, 0x8363, 0x8383, 0x83c3, 0x8403, 0x8423, 0x7c23, 0x7423,
        0x6c23, 0x6423, 0x5c23, 0x5423, 0x4c23, 0x4423, 0x3c23, 0x3423, 0x2c23, 0x2423,
        0x1c23, 0x1c24, 0x1c25, 0x1c26, 0x1c27, 0x1c28, 0x1c29, 0x1c2a, 0x1c2b, 0x1c2c,
        0x1c2d, 0x1c2e, 0x1c2f, 0x1c30, 0x1bf0, 0x1bd0, 0x1b90, 0x1b50, 0x1b30, 0x1af0,
        0x1ab0, 0x1a90, 0x1a70, 0x1a30, 0x19f0, 0x19d0, 0x38d0, 0x40d0, 0x48d0, 0x50d0,
        0x58d0, 0x60d0, 0x68d0, 0x70d0, 0x70d8, 0x78d0, 0x80d0, 0x80cf, 0x80ce, 0x80cc, 
        0x80ca, 0x80cb, 0x80c8, 0x80c7, 0x80c6, 0x80c5, 0x80c4, 0x80c3, 0x0000, 0xffff
    };
    
    ili9341_fill_rect(0,0, SCREEN_WIDTH, SCREEN_HEIGHT, 0xffff);
    for( int i=0; i<10; i++) {
        for( int j=0; j<8; j++) {
            ili9341_fill_rect(j*30,i*32,30,32,ctable[j][i]);
        }
    }
    
}

int main()
{
bool LED_state = false ;
    gpio_init(25) ;	
    gpio_set_dir(25, GPIO_OUT) ;
    gpio_put(25, true);
    sleep_ms(250);

    stdio_init_all();
    sleep_ms(500);
    gpio_put(25, false);
    sleep_ms(250);
    printf("Testujeme ili9341 8bit paralelni.\n");
    gpio_put(25, true);
    sleep_ms(250);

    // inicializace displeje
    ili9341_init();
    // nastavení pozadí
    ili9341_fill_rect(0,0, SCREEN_WIDTH, SCREEN_HEIGHT, 0xf800);
    printf("Červený displej\n");
    // kreslení čar
    for (int j = 0; j < 320; j+=5) {
        ili9341_draw_line(0, 0, 239, j, WHITE);
        // printf("Čára 1\n");
        }
    sleep_ms(3000);
    for (int i = 0; i < 240; i+=5) {
        ili9341_draw_line(239, 319, 239-i, 0, BLACK);
        // printf("Čára 2\n");
    }
    sleep_ms(3000);
    // kreslení vyplněných obdélníků
    for (int i = 10;i < 150; i+=10) {
        ili9341_fill_rect(i, i, 100, 100, ili9341_color_565RGB(0x60+i, 0x70+i*2, 0x30+i));
        printf("Obdélník %d\n", ili9341_color_565RGB(0x60+i, 0x70+i*2, 0x30+i));
    }
    sleep_ms(3000);
    ili9341_fill_rect(0,0, SCREEN_WIDTH, SCREEN_HEIGHT, 0x0000);
    ili9341_draw_bitmap(62,62, &logo);
    sleep_ms(3000);
    // invertování barev
    ili9341_draw_string_withbg(62, 0, "Inverze displeje.", DARKGREY, BLACK, &font_ubuntu_mono_24);
    ili9341_invert_display(true);
    printf("Inverujeme.\n");
    
    sleep_ms(3000);
    ili9341_draw_string_withbg(62, 0, "Inverze zpet.    ", DARKGREY, BLACK, &font_ubuntu_mono_24);
    ili9341_invert_display(false);
    printf("Inverujeme zpět.\n");
    sleep_ms(3000);

    // psaní textu
    printf("Budeme psát:\n");
    ili9341_fill_rect(0,0, SCREEN_WIDTH, SCREEN_HEIGHT, BLACK);
    ili9341_draw_string_withbg(0, 10, "Pismenka: abcdefghijklmnopgrstuvwxyz", LIGHTGREY, BLACK, &font_ubuntu_mono_24);
    ili9341_draw_string_withbg(0, 80, "Font: Ubuntu mono 24px", LIGHTGREY, BLACK, &font_ubuntu_mono_24);
    
    // kreslení kruhů
    printf("Kruhy:\n");
    for (int i = 0;i < 130; i+=10) {
        ili9341_draw_fill_circle(160, 2*i+50, 50, ili9341_color_565RGB(250-i/2,28,100+i/2));
        }
    sleep_ms(6000);

    // demo a měření rychlosti vykreslování
    char buf[60];
    while (true) {
        absolute_time_t t1 = get_absolute_time();
        demo();
        absolute_time_t t2 = get_absolute_time();
        sprintf(buf, "%d ms", absolute_time_diff_us(t1,t2)/1000);
        ili9341_draw_string_withbg(232, 212, buf, LIGHTGREY, BLACK, &font_ubuntu_mono_24);
        LED_state = LED_state? false : true ;
        gpio_put(25, LED_state);
        sleep_ms(2500);
        
        t1 = get_absolute_time();
        demo3();
        t2 = get_absolute_time();
        sprintf(buf, "%d ms", absolute_time_diff_us(t1,t2)/1000);
        ili9341_draw_string_withbg(232, 212, buf, LIGHTGREY, BLACK, &font_ubuntu_mono_24);
        LED_state = LED_state? false : true ;
        gpio_put(25, LED_state);
        sleep_ms(2500);
    }
}

Výsledky

Špatné barevné podání (BGR místo RGB)
Otočení videa
ffmpeg -i VID_20250805_042539.mp4 -vf "transpose=1" -acodec copy ili9341_parallel1.mp4

Chyby

Displej má špatné barevné podání. Je to zpsobeno tím, že je špatně nastevené posílání bitů do displeje. Displej očekává 5 bitů modré, 6 bitů zelené a 5 bitů modré. My posíláme 5 bitů červené, 6 bitů zelené a 5 bitů modré barevné složky. Dá se to spravit počátečním nastavením displeje.

Způsob posílání barevných bitů do dipleje (datasheet str. 64)

posilani 8bitu

Počáteční nastavení je nutno upravit
void ili9431_init_config()
{
    ili9341_cmd(ILI9341_SOFTRESET, 0, NULL);
    sleep_ms(150);
    ili9341_cmd(ILI9341_DISPLAYOFF, 0, NULL);
    sleep_ms(150);
    ili9341_cmd(ILI9341_PIXELFORMAT,   1, (uint8_t[1]){0x55});
    ili9341_cmd(ILI9341_POWERCONTROL1, 1, (uint8_t[1]){0x05}); // 0x05 :3.3V
    ili9341_cmd(ILI9341_POWERCONTROL2, 1, (uint8_t[1]){0x10});
    ili9341_cmd(ILI9341_VCOMCONTROL1,  2, (uint8_t[2]){0x3E, 0x28});
    ili9341_cmd(ILI9341_VCOMCONTROL2,  1, (uint8_t[1]){0x86});
//    ili9341_cmd(ILI9341_MADCTL,        1, (uint8_t[1]){0x40});             // MY,MX,MV,ML,BGR,MH,0,0 (1)
    ili9341_cmd(ILI9341_MADCTL,        1, (uint8_t[1]){0x48});             // MY,MX,MV,ML,RGB,MH,0,0 (2)
    ili9341_cmd(ILI9341_FRAMECONTROL,  2, (uint8_t[2]){0x00, 0x1B}); // Default 70Hz
    ili9341_cmd(ILI9341_DISPLAYFUNC,   4, (uint8_t[4]){0x0A, 0xA2, 0x27, 0x04});
    ili9341_cmd(ILI9341_GAMMASET,      1, (uint8_t[1]){0x01});

    ili9341_cmd(ILI9341_PGAMCOR, 15, (uint8_t[15]){0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1, 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00});
    ili9341_cmd(ILI9341_NGAMCOR, 15, (uint8_t[15]){0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1, 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f});

    ili9341_cmd(ILI9341_SLEEPOUT, 0, NULL);
    sleep_ms(150);
    ili9341_cmd(ILI9341_DISPLAYON, 0, NULL);
    sleep_ms(500);
}
1 Nastavení displeje na BGR (špatně).
2 Nastavení displeje na RGB (správně).

Datasheet říká, že 3. bit D3 má být 0, praxe ukazuje, že pro RGB tam má být 1. Po změně MADCTL 3. bitu na 1 to funguje správně. Buďto je v datasheetu chyba a nebo si musím sehnat stádo ovcí, přestat programovat a raději pást ovečky.

Celý projekt ke stažení je zde: TFT_LCD2.8_ILI9341_parallel_v1.tar.gz

Zdroje a odkazy