prettyprint

2024年7月19日 星期五

[Raspberry Pi Pico (c-sdk)] LVGL Graphics Library & Pico PIO TFT display driver(Serial or Parallel)

 本文章介紹LVGL繪圖函式庫如何結合本網站先前介紹的Raspberry Pi Pico 利用PIO製作的驅動程式。

  1. [Raspberry Pi Pico (c-sdk)] Display: Ep 5 :TFT LCD 4-lines Serial(SPI) Driver
  2. [Raspberry Pi Pico (c-sdk)] Display: Ep 4 : ILI9341 TFT LCD 8-bit parallel
本文章以LVGL 8.4.0為例:

一、LVGL Graphic library所需要的檔案

  1. src資料夾、
  2. lvgl.h 
  3. lv_conf.h(由lv_conf_template.h複製)
修改lv_conf.h的內容:
  1. /* clang-format off */
    #if 1 /*Set it to "1" to enable content*/
  2. #define LV_COLOR_DEPTH 16
    #define LV_COLOR_16_SWAP 1
CMakeLists.txt內容:

二、週期性呼叫 lv_tick_inc(x)

bool lv_inc_timer_cb(repeating_timer_t *rt) {
lv_tick_inc(*((uint8_t*)rt->user_data));
return true;
}

void pico_lvgl_tick_inc_timer_init(uint8_t tick_inc) {
static repeating_timer_t rt;
static uint8_t _tick_inc;
_tick_inc = tick_inc;
add_repeating_timer_ms(tick_inc, lv_inc_timer_cb, (void*) (&_tick_inc), &rt);
}


三、呼叫 lv_init()

四、製作draw buffer flush callback function, 將一個block資料寫到TFT Display memory中
void tft_lvgl_draw_bitmap(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint8_t *bitmap)
{
uint32_t total_pixels = (x2-x1+1) * (y2-y1+1)*2;

tft_set_address_window (x1, y1, x2, y2);
tft_cmd_dma(TFT_MEMORYWRITE, total_pixels, bitmap);
}

void tft_lvgl_disp_flush(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p)
{
tft_lvgl_draw_bitmap(
(uint16_t)(area->x1),
(uint16_t)(area->y1),
(uint16_t)(area->x2),
(uint16_t)(area->y2), (uint8_t*)color_p
);
lv_disp_flush_ready(disp); /* Indicate you are ready with the flushing*/
}
其中
tft_lvgl_draw_bitmap(...)為前篇文章介紹PIO TFT Display Driver的function。

五、以製作一組音樂播放器為展示範例



六、程式碼:

  • 更改 pico_tft.h:
/*=== according to TFT===*/
#define TFT_WIDTH           320 
#define TFT_HEIGHT          480 

#define PICO_TFT_SERIAL     1
#define PICO_TFT_PARALLEL   0
#define PICO_TFT_DMA        1

#define TFT_ST7735          0
#define TFT_ST7796          1
#define TFT_ILI9342         0
#define TFT_ILI9341         0
/*=== according to TFT===*/
以符合硬體

  • 更改 xpt2046.h:
//== Set the following values ​​according to the hardware ==
#define XPT2046_IRQ_GPIO    11 
#define XPT2046_MOSI        15 
#define XPT2046_MISO        12 
#define XPT2046_CS          13 
#define XPT2046_CLK         14 
#define XPT2046_SPI         spi1

#define XPT2046_MIN_RAW_X 1350  
#define XPT2046_MAX_RAW_X 31000 
#define XPT2046_MIN_RAW_Y 2050  
#define XPT2046_MAX_RAW_Y 31500 
//== Set the following values ​​according to the hardware ==
以符合硬體

  1. lvgl
CmakeList.txt
option(LV_LVGL_H_INCLUDE_SIMPLE
       "Use #include \"lvgl.h\" instead of #include \"../../lvgl.h\"" ON)

# Option to define LV_CONF_INCLUDE_SIMPLE, default: ON
option(LV_CONF_INCLUDE_SIMPLE
       "Use #include \"lv_conf.h\" instead of #include \"../../lv_conf.h\"" ON)


add_library(lvgl INTERFACE)


file(GLOB_RECURSE SOURCES src/*.c src/*.S)

target_sources(lvgl INTERFACE
    ${SOURCES}
)

target_include_directories(lvgl INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
    ${CMAKE_CURRENT_LIST_DIR}/..
)

  
  • pico_tft
CMakeLists.txt
add_library(pico_tft INTERFACE)
pico_generate_pio_header(pico_tft ${CMAKE_CURRENT_LIST_DIR}/pico_tft.pio)

target_sources(pico_tft INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/pico_tft.c
    ${CMAKE_CURRENT_LIST_DIR}/pico_tft_lvgl.c

)

target_include_directories(pico_tft INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
    ${CMAKE_CURRENT_LIST_DIR}/..

)

target_link_libraries(pico_tft INTERFACE
        hardware_pio
        hardware_dma
)

  

pico_tft.h
#ifndef  _TFT_H_
#define _TFT_H_

/*=== according to TFT===*/
#define TFT_WIDTH           320 
#define TFT_HEIGHT          480 

#define PICO_TFT_SERIAL     1
#define PICO_TFT_PARALLEL   0
#define PICO_TFT_DMA        1

#define TFT_ST7735          0
#define TFT_ST7796          1
#define TFT_ILI9342         0
#define TFT_ILI9341         0
/*=== according to TFT===*/

enum {
    TFT_ORIENTATION_PORTRAIT=0,
    TFT_ORIENTATION_LANDSCAPE,
    TFT_ORIENTATION_PORTRAIT_MIRROR,
    TFT_ORIENTATION_LANDSCAPE_MIRROR,
};     

#include "pico/stdlib.h"

#include "hardware/pio.h"
#include "pico_tft_lvgl.h"


void tft_init(PIO pio, uint sm, uint din_base, uint csx_dcx_sck_side_base_pin);
void tft_init_config();
void tft_draw_pixel(uint16_t x, uint16_t y, uint16_t color);
void tft_set_address_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
void tft_cmd(uint32_t cmd, uint32_t count,  uint8_t *param);
void tft_cmd_dma(uint32_t cmd, uint32_t count,  uint8_t *param);
uint16_t tft_color_565RGB(uint8_t R, uint8_t G, uint8_t B);
//void tft_lv_draw_bitmap(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t *bitmap);
void tft_pio_cmd_init(PIO pio, uint sm, uint out_base,  uint set_base, uint32_t freq);

uint16_t tft_get_width();
uint16_t tft_get_height();
uint8_t tft_get_orientation();
void tft_set_orientation(uint8_t orientation);

void tft_fill_rect(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color);

#endif

pico_tft.c
#include "stdio.h"
#include "stdlib.h"
#include "pico/stdlib.h"
#include "hardware/clocks.h"
#include "string.h"

#include "registers.h"
#include "pico_tft.pio.h"
#include "pico_tft.h"
#include "hardware/dma.h"

#define MAX_BYTE_TRANS (TFT_WIDTH*TFT_HEIGHT*2)

// MADCTL register: 			MY,MX,MV,ML,BGR,MH,x,x
#if TFT_ST7735
static uint8_t TFT_MADCTL_PORTRAIT  		=	0b11001000;
static uint8_t TFT_MADCTL_LANDSCAPE  		=	0b10101000;
static uint8_t TFT_MADCTL_PORTRAIT_MIRROR  =	0b00001000;
static uint8_t TFT_MADCTL_LANDSCAPE_MIRROR = 	0b01101000;
#endif

#if TFT_ST7796 
static uint8_t TFT_MADCTL_PORTRAIT  		=	0b01001000;
static uint8_t TFT_MADCTL_LANDSCAPE  		=	0b00101000;
static uint8_t TFT_MADCTL_PORTRAIT_MIRROR  =	0b10001000;
static uint8_t TFT_MADCTL_LANDSCAPE_MIRROR = 	0b11101000;
#endif 

#if TFT_ILI9341
// MADCTL register: 			MY,MX,MV,ML,BGR,MH,x,x
static uint8_t TFT_MADCTL_PORTRAIT  		=	0b00000000;
static uint8_t TFT_MADCTL_LANDSCAPE  		=	0b01100000;
static uint8_t TFT_MADCTL_PORTRAIT_MIRROR  =	0b11000000;
static uint8_t TFT_MADCTL_LANDSCAPE_MIRROR = 	0b10100000;
#endif

#if TFT_ILI9342
// MADCTL register: 			MY,MX,MV,ML,BGR,MH,x,x
static uint8_t TFT_MADCTL_PORTRAIT  		=	0b00000000;
static uint8_t TFT_MADCTL_LANDSCAPE  		=	0b01100000;
static uint8_t TFT_MADCTL_PORTRAIT_MIRROR  =	0b11000000;
static uint8_t TFT_MADCTL_LANDSCAPE_MIRROR = 	0b10100000;

#endif

static uint16_t tft_width;
static uint16_t tft_height;
static uint8_t tft_orientation;

PIO tft_pio = pio1;
uint tft_sm=0;
uint in_out_base_pin=4;
uint set_base_pin=12;
uint sideset_base_pin=16; 
uint s_in_out_base_pin=19; 

int tft_dma_channel;

void tft_cmd(uint32_t cmd, uint32_t count, uint8_t *param)
{
    #if PICO_TFT_SERIAL
    pio_sm_put_blocking(tft_pio, tft_sm, cmd << 24);
    #endif
    #if PICO_TFT_PARALLEL
    pio_sm_put_blocking(tft_pio, tft_sm, cmd);
    #endif
    pio_sm_put_blocking(tft_pio, tft_sm, count);
    for (int i = 0; i < count; i++)
    {
        #if PICO_TFT_SERIAL
        pio_sm_put_blocking(tft_pio, tft_sm, param[i]<<24);
        #endif
        #if PICO_TFT_PARALLEL
        pio_sm_put_blocking(tft_pio, tft_sm, param[i]);
        #endif
    }
}
//#ifdef PICO_TFT_DMA
void tft_cmd_dma(uint32_t cmd, uint32_t count, uint8_t *param)
{
    #if PICO_TFT_SERIAL
    tft_cmd(cmd, count, param);
    return;
    #endif 
    #if PICO_TFT_PARALLEL
    #if PICO_TFT_DMA
    pio_sm_put_blocking(tft_pio, tft_sm, cmd);
    pio_sm_put_blocking(tft_pio, tft_sm, count);
    dma_channel_set_trans_count(tft_dma_channel, count >> DMA_SIZE_8, false);
    dma_channel_set_read_addr(tft_dma_channel, param, false);
    dma_channel_start(tft_dma_channel);
    dma_channel_wait_for_finish_blocking(tft_dma_channel);
    #else
    tft_cmd(cmd, count, param);
    #endif
    #endif
    
}
//#endif
void tft_pio_cmd_init(PIO pio, uint sm, uint in_out_base,  uint set_sideset, uint32_t freq) {
    uint offset=0;
    pio_sm_config c;
    #if PICO_TFT_PARALLEL
    offset = pio_add_program(pio, &tft_pio_parallel_program);
    c = tft_pio_parallel_program_get_default_config(offset);
    for (int i=0; i < 8; i++) pio_gpio_init(pio, in_out_base+i);
    for (int i=0; i < 4; i++) pio_gpio_init(pio, set_sideset+i);
    pio_sm_set_consecutive_pindirs(pio, sm, in_out_base, 8, true);
    pio_sm_set_consecutive_pindirs(pio, sm, set_sideset, 4, true);
    sm_config_set_in_pins(&c, in_out_base);
    sm_config_set_out_pins(&c, in_out_base, 8);
    sm_config_set_set_pins(&c, set_sideset, 4);
    sm_config_set_out_shift(&c, true, false, 8);
    sm_config_set_in_shift(&c, false, false, 8);
    #endif
    #if PICO_TFT_SERIAL
    offset = pio_add_program(pio, &tft_pio_serial_program);
    c = tft_pio_serial_program_get_default_config(offset);
    pio_gpio_init(pio, in_out_base);
    for (int i=0; i < 3; i++) pio_gpio_init(pio, set_sideset+i);
    pio_sm_set_consecutive_pindirs(pio, sm, in_out_base, 1, true);
    pio_sm_set_consecutive_pindirs(pio, sm, set_sideset, 3, true);
    sm_config_set_in_pins(&c, in_out_base);
    sm_config_set_out_pins(&c, in_out_base, 1);
    sm_config_set_sideset_pins(&c, set_sideset);
    sm_config_set_out_shift(&c, false, false, 8);
    sm_config_set_in_shift(&c, true, false, 8);
    #endif
       
    //sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
    
    float div = (float)clock_get_hz(clk_sys)/freq;
    sm_config_set_clkdiv(&c, div);
    //sm_config_set_clkdiv(&c, 1.25);
    
    #if PICO_TFT_DMA
    /*   DMA  */
    tft_dma_channel = dma_claim_unused_channel(true);
    dma_channel_config dc = dma_channel_get_default_config(tft_dma_channel);
    channel_config_set_write_increment(&dc, false);
    channel_config_set_read_increment(&dc, true);
    channel_config_set_dreq(&dc, pio_get_dreq(pio, sm, true));
    channel_config_set_transfer_data_size(&dc, DMA_SIZE_8); //DMA_SIZE_8,16,32
    
    uint32_t pio_base, tx_offset;
    pio_base = (pio == pio0) ? PIO0_BASE: PIO1_BASE;
    tx_offset = PIO_TXF0_OFFSET + sm*4;

    dma_channel_configure(tft_dma_channel, &dc, (void*) (pio_base + tx_offset), 
             NULL, MAX_BYTE_TRANS>> DMA_SIZE_8, false); //DMA_SIZE_8 or 16 or 32
    /*  DMA */
    #endif 
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

/* tft draw functions*/
uint16_t tft_color_565RGB(uint8_t R, uint8_t G, uint8_t B) {
    uint16_t c;
    c = (((uint16_t)R)>>3)<<11 | (((uint16_t)G)>>2) << 5 | ((uint16_t)B)>>3;
    return c;
}
void tft_memory_write_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);
    tft_cmd(TFT_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);
	tft_cmd(TFT_PAGEADDRSET, 4,   addr );

    tft_cmd(TFT_MEMORYWRITE, 0, NULL);
}

void tft_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);
    tft_cmd(TFT_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);
	tft_cmd(TFT_PAGEADDRSET, 4,  addr );
}

/* put color at point*/
void tft_draw_pixel(uint16_t x, uint16_t y, uint16_t color)
{
    if ( x < 0 || x > TFT_WIDTH-1 || y < 0 || y > TFT_HEIGHT-1) {
        printf("over range,x,y\n");
        return;
    }
	tft_set_address_window(x,y,x,y);
    tft_cmd(TFT_MEMORYWRITE, 2,  (uint8_t[2]){(uint8_t)(color >> 8), (uint8_t)color});
}

uint16_t tft_get_width() {
    return tft_width;
}

uint16_t tft_get_height() {
    return tft_height;
}

uint8_t tft_get_orientation() {
    return tft_orientation;
}
void tft_set_orientation(uint8_t orientation) {
    tft_orientation = orientation;
    switch (orientation) {
        case TFT_ORIENTATION_PORTRAIT:
            tft_cmd(TFT_MADCTL, 1,  (uint8_t[1]){TFT_MADCTL_PORTRAIT});
            tft_width = TFT_WIDTH;
            tft_height = TFT_HEIGHT;
        break;
        case TFT_ORIENTATION_PORTRAIT_MIRROR:
            tft_cmd(TFT_MADCTL, 1,  (uint8_t[1]){TFT_MADCTL_PORTRAIT_MIRROR});
            tft_width = TFT_WIDTH;
            tft_height = TFT_HEIGHT;
        break;
        case TFT_ORIENTATION_LANDSCAPE:
            tft_cmd(TFT_MADCTL, 1,  (uint8_t[1]){TFT_MADCTL_LANDSCAPE});
            tft_width = TFT_HEIGHT;
            tft_height = TFT_WIDTH;
        break;
        case TFT_ORIENTATION_LANDSCAPE_MIRROR:
            tft_cmd(TFT_MADCTL, 1,  (uint8_t[1]){TFT_MADCTL_LANDSCAPE_MIRROR});
            tft_width = TFT_HEIGHT;
            tft_height = TFT_WIDTH;
        break;
    }

}

#if TFT_ST7735
void tft_init_config() {
	tft_cmd(TFT_SOFTRESET, 0,  NULL);
    sleep_ms(150);
    tft_cmd(TFT_DISPLAYOFF, 0,  NULL);
    sleep_ms(150);
    tft_cmd(TFT_PIXELFORMAT, 1,  (uint8_t[1]){0x55}); //0x55
    tft_cmd(TFT_POWERCONTROL1, 1,  (uint8_t[1]){0x05}); // 0x05 :3.3V
    tft_cmd(TFT_POWERCONTROL2, 1,  (uint8_t[1]){0x10});
    tft_cmd(TFT_VCOMCONTROL1, 2,  (uint8_t[2]){0x3E, 0x28});
    tft_cmd(TFT_VCOMCONTROL2, 1,  (uint8_t[1]){0x86});
    //tft_cmd(TFT_MADCTL, 1,  (uint8_t[1]){0x08}); //MY,MX,MV,ML,BRG,MH,0,0(40)
    tft_set_orientation(TFT_ORIENTATION_PORTRAIT);
    tft_cmd(TFT_FRAMECONTROL, 2,  (uint8_t[2]){0b00, 0x1B}); // Default 70Hz:0x1B
    tft_cmd(TFT_DISPLAYFUNC, 4,  (uint8_t[4]){0x0A, 0x82, 0x27, 0x04}); //0a,a2,27,04
    tft_cmd(TFT_GAMMASET, 1,  (uint8_t[1]){0x01});
  
  	//tft_cmd(tft_PGAMCOR, 15, (uint8_t[15]){ 0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1, 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00 });
    //tft_cmd(tft_NGAMCOR,  15,0xFF, (uint8_t[15]){ 0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1, 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f }); 
    
    tft_cmd(TFT_SLEEPOUT, 0,  NULL);
    sleep_ms(150);
    tft_cmd(TFT_DISPLAYON, 0,  NULL);
    sleep_ms(500);
    
}
#endif

#if TFT_ST7796
void tft_init_config() {
	//tft_cmd(TFT_SOFTRESET, 1,  NULL);
    tft_cmd(TFT_SOFTRESET, 0,  NULL);
    sleep_ms(120);
    tft_cmd(TFT_SLEEPOUT, 0,  NULL);
    sleep_ms(120);

    tft_cmd(TFT_COMMANDSET, 1,  (uint8_t[1]){0xC3}); //enable part 1
    tft_cmd(TFT_COMMANDSET, 1,  (uint8_t[1]){0x96}); //enable part 2
    //tft_cmd(TFT_MADCTL, 1,  (uint8_t[1]){0x88}); //MY,MX,MV,ML,BRG,MH,0,0(24), 0:RGB
    tft_set_orientation(TFT_ORIENTATION_PORTRAIT);
    tft_cmd(TFT_PIXELFORMAT, 1,  (uint8_t[1]){0x05}); //0x05:RGB565, 0x06 RGB666
    tft_cmd(TFT_DSIPLAY_INVER, 1,  (uint8_t[1]){0x01}); // 1-dot
    #if TFT_ILI9342
    tft_cmd(TFT_DISPLAYFUNC, 3,  (uint8_t[3]){0x0A, 0x82, 0x27});  // ILI9342
    #endif
    #if TFT_ST7796
    tft_cmd(TFT_DISPLAYFUNC, 3,  (uint8_t[3]){0x80, 0x02, 0x3B}); // ST7796
    #endif
    tft_cmd(TFT_DISP_OUTPUT_CTRL_ADJUST, 8,  (uint8_t[8])
                {0x40,
                 0x8A,
                 0x00,
                 0x00,
                 0x29,  //Source eqaulizing period time= 22.5 us
                 0x19,  //Timing for "Gate start"=25 (Tclk)
                 0xA5,  //Timing for "Gate End"=37 (Tclk), Gate driver EQ function ON
                 0x33}); // ST7796

    tft_cmd(TFT_POWERCONTROL2, 1,  (uint8_t[1]){0x06}); // 0x05 :3.3V
    tft_cmd(TFT_POWERCONTROL3, 1,  (uint8_t[1]){0xA7});
    tft_cmd(TFT_VCOMCONTROL1, 1,  (uint8_t[1]){0x18});
    sleep_ms(120);
    tft_cmd(TFT_PGAMCOR, 14, (uint8_t[14]){ 0xf0, 0x09, 0x0b, 0x06, 0x04, 0x15, 0x2f, 0x54, 0x42, 0x3c, 0x17, 0x14, 0x18, 0x1b});
    tft_cmd(TFT_NGAMCOR, 14, (uint8_t[14]){ 0xe0, 0x09, 0x0b, 0x06, 0x04, 0x03, 0x2b, 0x43, 0x42, 0x3b, 0x16, 0x14, 0x17, 0x1b}); 
    sleep_ms(120);
    tft_cmd(TFT_COMMANDSET, 1,  (uint8_t[1]){0x3C}); // disable part 1
    tft_cmd(TFT_COMMANDSET, 1,  (uint8_t[1]){0x69}); // disable part 2

    tft_cmd(TFT_DISPLAYOFF, 0,  NULL);
    sleep_ms(120);    
    tft_cmd(TFT_DISPLAYON, 0,  NULL);
    sleep_ms(500);
    
}
#endif 

#if TFT_ILI9342
void tft_init_config() {
    tft_cmd(TFT_SOFTRESET, 0, NULL);
    sleep_ms(150);
    tft_cmd(TFT_DISPLAYOFF, 0, NULL);
    sleep_ms(150);
    tft_cmd(TFT_PIXELFORMAT, 1, (uint8_t[1]){0x55}); //0x55
    tft_cmd(TFT_POWERCONTROL1, 1, (uint8_t[1]){0x05}); // 0x05 :3.3V
    tft_cmd(TFT_POWERCONTROL2, 1, (uint8_t[1]){0x10});
    tft_cmd(TFT_VCOMCONTROL1, 2, (uint8_t[2]){0x3E, 0x28});
    tft_cmd(TFT_VCOMCONTROL2, 1, (uint8_t[1]){0x86});
    tft_cmd(TFT_MADCTL, 1, (uint8_t[1]){0x60}); //MY,MX,MV,ML,BRG,MH,0,0(40)
    tft_set_orientation(TFT_ORIENTATION_PORTRAIT);
    tft_cmd(TFT_FRAMECONTROL, 2, (uint8_t[2]){0x00, 0x1B}); // Default 70Hz
    tft_cmd(TFT_DISPLAYFUNC, 4, (uint8_t[4]){0x0A, 0x82, 0x27, 0x04}); //0a,a2,27,04
    tft_cmd(TFT_GAMMASET, 1, (uint8_t[1]){0x01});
  
  	tft_cmd(TFT_PGAMCOR, 15, (uint8_t[15]){ 0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1, 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00 });
    tft_cmd(TFT_NGAMCOR, 15, (uint8_t[15]){ 0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1, 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f }); 
    
    tft_cmd(TFT_SLEEPOUT, 0, NULL);
    sleep_ms(150);
    tft_cmd(TFT_DISPLAYON, 0, NULL);
    sleep_ms(500);
    
    
}
#endif

#if TFT_ILI9341
void tft_init_config() {
    
    tft_cmd(TFT_SOFTRESET, 0, NULL);
    sleep_ms(50);
    tft_cmd(TFT_DISPLAYOFF, 0, NULL);

    tft_cmd(TFT_POWERCONTROL1, 1, (uint8_t[1]){0x23});
    tft_cmd(TFT_POWERCONTROL2, 1, (uint8_t[1]){0x10});
    tft_cmd(TFT_VCOMCONTROL1, 2, (uint8_t[2]){0x2B, 0x2B});
    tft_cmd(TFT_VCOMCONTROL2, 1, (uint8_t[1]){0xC0});
    tft_cmd(TFT_MADCTL, 1, (uint8_t[1]){0x88}); //MY,MX,MV,ML,BRG,MH,0,0(40)
    tft_set_orientation(TFT_ORIENTATION_PORTRAIT);
    tft_cmd(TFT_PIXELFORMAT, 1, (uint8_t[1]){0x55});
    tft_cmd(TFT_FRAMECONTROL, 2, (uint8_t[2]){0x00, 0x1B});

    tft_cmd(TFT_ENTRYMODE, 1, (uint8_t[1]){0x07});
    //tft_cmd(TFT_DISPLAYFUNC, 4, (uint8_t[4]){0x0A, 0x82, 0x27, 0x00});

    tft_cmd(TFT_SLEEPOUT, 0, NULL);
    sleep_ms(150);
    tft_cmd(TFT_DISPLAYON, 0, NULL);
    sleep_ms(500);
}
#endif

void tft_init(PIO pio, uint sm, uint din_base, uint csx_dcx_sck_side_base_pin) {
    tft_pio = pio;
    tft_sm = sm;
    #if PICO_TFT_PARALLEL
        in_out_base_pin = din_base;
        set_base_pin = csx_dcx_sck_side_base_pin;
        tft_pio_cmd_init(tft_pio, tft_sm, in_out_base_pin, set_base_pin, 70000000/*70000000*/);  //pio freq
    #endif
    #if PICO_TFT_SERIAL
        s_in_out_base_pin = din_base;
        sideset_base_pin = csx_dcx_sck_side_base_pin;
        tft_pio_cmd_init(tft_pio, tft_sm, s_in_out_base_pin, sideset_base_pin, 90000000/* 62.5M baud rate for SPI*/);  //pio freq  
    #endif 

    tft_init_config();

    
}
  

pico_tft_lvgl.h
#ifndef __PICO_TFT_LVGL__
#define __PICO_TFT_LVGL__
#include "pico_tft.h"
#include "lvgl.h"

void tft_lvgl_draw_bitmap(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t *bitmap);
void tft_lvgl_disp_flush(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p);
uint32_t pico_lvgl_get_tick_cb();
#endif
  

pico_tft_lvgl.c
#include "pico_tft.h"
#include "pico_tft_lvgl.h"
#include "registers.h"
#include "stdio.h"

void tft_lvgl_draw_bitmap(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint8_t *bitmap)
{
	uint32_t total_pixels = (x2-x1+1) * (y2-y1+1)*2;

	tft_set_address_window (x1, y1, x2, y2);
    tft_cmd_dma(TFT_MEMORYWRITE, total_pixels,  bitmap);
   
}

void tft_lvgl_disp_flush(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p)
{
    tft_lvgl_draw_bitmap(
            (uint16_t)(area->x1), 
            (uint16_t)(area->y1), 
            (uint16_t)(area->x2), 
            (uint16_t)(area->y2), (uint8_t*)color_p
            );
 
    lv_disp_flush_ready(disp);         /* Indicate you are ready with the flushing*/
}

/*  tell how many milliseconds have elapsed since start up
    for lvgl 9.x.x
*/
uint32_t pico_lvgl_get_tick_cb() 
{
    return (time_us_32()/1000);    
}

  

pico_tft.pio
.program tft_pio_parallel
.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
jmp start
.wrap


.program tft_pio_serial
; CSX, D/CX(A0), SCL --> gpio 13, 12, 11  (side-set pins)
.side_set 3
.wrap_target
start:
pull                        side 0b100
mov x, osr                  side 0b000           ;command code, if 0x0, command nop, only data
jmp !x param                side 0b000
set y,7                     side 0b000
cmd_bit_loop:
out pins, 1                 side 0b000  [1]
jmp y--, cmd_bit_loop       side 0b001  [1]

param:
pull                        side 0b010
mov x, osr                  side 0b010            ;how many parameters
jmp !x, start               side 0b010           ;no parameter return start
jmp x--, param_data         side 0b010        
param_data:
pull                        side 0b010                ; write data
set y,7                     side 0b010
data_bit_loop:
out pins, 1                 side 0b010  [1]    
jmp y--, data_bit_loop      side 0b011  [1]
jmp x--, param_data         side 0b010
jmp start                   side 0b100
  • registers.h
#define TFT_NOP             0x00
#define TFT_SOFTRESET       0x01
#define TFT_SLEEPIN         0x10
#define TFT_SLEEPOUT        0x11
#define TFT_NORMALDISP      0x13
#define TFT_INVERTOFF       0x20
#define TFT_INVERTON        0x21
#define TFT_GAMMASET        0x26
#define TFT_DISPLAYOFF      0x28
#define TFT_DISPLAYON       0x29
#define TFT_COLADDRSET      0x2A
#define TFT_PAGEADDRSET     0x2B
#define TFT_MEMORYWRITE     0x2C
#define TFT_MEMORYREAD      0x2E
#define TFT_PIXELFORMAT     0x3A
#define TFT_MEMORYWRITECONT  0x3C
#define TFT_MEMORYREADCONT  0x3E
#define TFT_FRAMECONTROL    0xB1
#define TFT_DSIPLAY_INVER   0xB4
#define TFT_DISPLAYFUNC     0xB6
#define TFT_ENTRYMODE       0xB7
#define TFT_POWERCONTROL1   0xC0
#define TFT_POWERCONTROL2   0xC1
#define TFT_POWERCONTROL3   0xC2
#define TFT_VCOMCONTROL1    0xC5
#define TFT_VCOMCONTROL2    0xC7
#define TFT_COMMANDSET      0xF0
#define TFT_VSCROLLDEF      0x33
#define TFT_VSCROLLADDR     0x37
#define TFT_MEMCONTROL      0x36
#define TFT_MADCTL          0x36

#define TFT_PGAMCOR         0xE0
#define TFT_NGAMCOR         0xE1
#define TFT_DISP_OUTPUT_CTRL_ADJUST                 0xE8

#define TFT_MADCTL_MY       0x80
#define TFT_MADCTL_MX       0x40
#define TFT_MADCTL_MV       0x20
#define TFT_MADCTL_ML       0x10
#define TFT_MADCTL_RGB      0x00
#define TFT_MADCTL_BGR      0x08
#define TFT_MADCTL_MH       0x04
  • XPT2046_touch
CMakeLists.txt
add_library(xpt2046_touch INTERFACE)
target_sources(xpt2046_touch INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/xpt2046.c
)

target_include_directories(xpt2046_touch INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
    ${CMAKE_CURRENT_LIST_DIR}/..
)

target_link_libraries(xpt2046_touch INTERFACE
        hardware_spi
        hardware_dma
)
  

xpt2046.h
#ifndef _XPT2046_H_
#define _XPT2046_H_
#include "pico/stdlib.h"
#include "lvgl.h"

//== Set the following values ​​according to the hardware ==
#define XPT2046_IRQ_GPIO    11 
#define XPT2046_MOSI        15 
#define XPT2046_MISO        12 
#define XPT2046_CS          13 
#define XPT2046_CLK         14 
#define XPT2046_SPI         spi1

#define XPT2046_MIN_RAW_X 1350  
#define XPT2046_MAX_RAW_X 31000 
#define XPT2046_MIN_RAW_Y 2050  
#define XPT2046_MAX_RAW_Y 31500 
//== Set the following values ​​according to the hardware ==


void xpt2046_init();
bool xpt2046_getXY(uint16_t *x, uint16_t *y);
bool xpt2046_TouchPressed();
void xpt2046_lvgl_read_cb(struct _lv_indev_drv_t * indev, lv_indev_data_t* data);

#endif
  

xpt2046.c
#include "stdio.h"
#include "stdlib.h"
#include "xpt2046.h"
#include "hardware/spi.h"
#include "hardware/gpio.h"
#include "pico/stdlib.h"
#include "pico_tft.h"

uint32_t xpt2046_event=0;
uint8_t READ_X = 0xD0;
uint8_t READ_Y = 0x90;
#define XPT_WIDTH  320
#define XPT_HEIGHT 480

bool xpt2046_getXY(uint16_t *x, uint16_t *y) {
   
    uint8_t temp[2];
    uint16_t raw_x, raw_y;
    uint16_t est_raw_x, est_raw_y;
    uint32_t avg_x = 0;
    uint32_t avg_y = 0;
     uint8_t nsamples = 0;
    uint8_t SAMPLES=10;

     gpio_put(XPT2046_CS, false);  
     if(gpio_get(XPT2046_IRQ_GPIO)) return false; 
     busy_wait_ms(10);
    //first pass
    SAMPLES=20; // get first average;
    for(uint8_t i = 0; i < SAMPLES; i++, nsamples++)
    {
        if(gpio_get(XPT2046_IRQ_GPIO)) {
            break;
        }
        spi_write_blocking(XPT2046_SPI, &READ_X, 1);
        spi_read_blocking(XPT2046_SPI, 0x00, temp, 2);
        raw_x = ((uint16_t)temp[0]) << 8 | (uint16_t)temp[1];

        spi_write_blocking(XPT2046_SPI, &READ_Y, 1);
        spi_read_blocking(XPT2046_SPI, 0x00, temp, 2);
        raw_y = ((uint16_t)temp[0]) << 8 | (uint16_t)temp[1];

        avg_x += raw_x;
        avg_y += raw_y;
    }  

    if(nsamples < SAMPLES)
        return false;

    gpio_put(XPT2046_CS, true);
    raw_x = (avg_x / SAMPLES);
    raw_y = (avg_y / SAMPLES);

    if(raw_x < XPT2046_MIN_RAW_X || raw_x > XPT2046_MAX_RAW_X) return false;
    if(raw_y < XPT2046_MIN_RAW_Y || raw_y > XPT2046_MAX_RAW_Y)  return false;  
    
    uint16_t tx,ty;
    tx = (raw_x - XPT2046_MIN_RAW_X) * XPT_WIDTH  / (XPT2046_MAX_RAW_X - XPT2046_MIN_RAW_X);
    ty = (raw_y - XPT2046_MIN_RAW_Y) * XPT_HEIGHT / (XPT2046_MAX_RAW_Y - XPT2046_MIN_RAW_Y);
   
    // adjust for TFT orientation
    uint8_t lot = tft_get_orientation();
		switch (lot)
		{
		case TFT_ORIENTATION_PORTRAIT:
			*x=tx;
			*y=TFT_HEIGHT-ty;
			break;
		case TFT_ORIENTATION_LANDSCAPE:
			*x=TFT_HEIGHT-ty;
			*y=TFT_WIDTH-tx;
					break;
		case TFT_ORIENTATION_PORTRAIT_MIRROR:
			*x=TFT_WIDTH-tx;
			*y=ty;
					break;
		case TFT_ORIENTATION_LANDSCAPE_MIRROR:
			*x=ty;
			*y=tx;
			break;
		}
    return true;   
}


bool xpt2046_TouchPressed()
{
    return !gpio_get(XPT2046_IRQ_GPIO);
}

void xpt2046_init() {
    gpio_init(XPT2046_IRQ_GPIO);
    gpio_init(XPT2046_MISO);
    gpio_init(XPT2046_MOSI);
    gpio_init(XPT2046_CLK);
    gpio_init(XPT2046_CS);
    gpio_set_dir(XPT2046_CS, GPIO_OUT);
    gpio_set_dir(XPT2046_IRQ_GPIO, GPIO_OUT);
    gpio_set_function(XPT2046_CLK,GPIO_FUNC_SPI);
    gpio_set_function(XPT2046_CS,GPIO_FUNC_SIO);
    gpio_set_function(XPT2046_MOSI,GPIO_FUNC_SPI);
    gpio_set_function(XPT2046_MISO,GPIO_FUNC_SPI);
    //spi_init(XPT2046_SPI,250000);
    spi_init(XPT2046_SPI,3125000); //3125000

}

extern lv_obj_t *mouse_cursor;
void xpt2046_lvgl_read_cb(struct _lv_indev_drv_t * indev, lv_indev_data_t* data) {
    uint16_t x,y;
    
    if (xpt2046_TouchPressed()) { 
        if (xpt2046_getXY(&x,&y)) {
            data->point.x = x;
            data->point.y = y;
            data->state = LV_INDEV_STATE_PRESSED; 
        } else {
            data->state = LV_INDEV_STATE_RELEASED;
        }          
    }  else {
        data->state = LV_INDEV_STATE_RELEASED;
    }
}
  • pico_lvgl
CMakeLists.txt

add_library(pico_lvgl INTERFACE)

target_sources(pico_lvgl INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/pico_lvgl.c
)

add_subdirectory(pico_tft)
add_subdirectory(xpt2046_touch)
add_subdirectory(lvgl)

target_include_directories(pico_lvgl INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
    ${CMAKE_CURRENT_LIST_DIR}/pico_tft
    ${CMAKE_CURRENT_LIST_DIR}/xpt2046_touch
    ${CMAKE_CURRENT_LIST_DIR}/lvgl
)

target_link_libraries(pico_lvgl INTERFACE
        hardware_pio
        hardware_dma
        pico_tft
        xpt2046_touch
        lvgl
)

pico_lvgl.c

#include "pico_lvgl.h"

bool lv_inc_timer_cb(repeating_timer_t *rt) {
    lv_tick_inc(*((uint8_t*)rt->user_data));
    return true;
}

/*
TFT_SERIAL:
@param sdi_gpio: SDI(MOSI)
@param csx_dcx_sck_gpio: csx:GPIO_n+2,dcx:GPIO_n+1, sck:GPIO_n 

TFT_PARALLEL:
@param sdi_gpio: LCD_D0(DB0): GPIO_dn, LCD_D1(DB1):GPIO_dn+1,...LCD_D7(DB7):GPIO_dn+7 
@param csx_dcx_sck_gpio: CS:GPIO_n+3,RS:GPIO_n+2, WR:GPIO_n+1, RD:GPIO_n 
*/
void pico_lvgl_tft_init(PIO pio, uint sm, uint sdi_gpio, uint csx_dcx_sck_gpio) {

    tft_init(pio , sm, sdi_gpio, csx_dcx_sck_gpio);


}


void pico_lvgl_tick_inc_timer_init(uint8_t tick_inc) {
    static repeating_timer_t rt;
    static uint8_t _tick_inc;
    _tick_inc = tick_inc;
    add_repeating_timer_ms(tick_inc, lv_inc_timer_cb, (void*) (&_tick_inc), &rt);
}

/*
tick_inc: to call the lv_tick_inc(x) function periodically in (x) milliseconds.
*/
void pico_lvgl_display_init(uint8_t tick_inc) {
    // init timer
    if (tick_inc > 10) tick_inc = 10;
    if (tick_inc < 1) tick_inc = 1;
    pico_lvgl_tick_inc_timer_init(tick_inc);

    // lv_init
    lv_init();

    // set draw buffer
    static lv_disp_draw_buf_t draw_buf;
    static lv_color_t buf1[TFT_WIDTH * TFT_HEIGHT / 10];                        /*Declare a buffer for 1/10 screen size*/
    lv_disp_draw_buf_init(&draw_buf, buf1, NULL, TFT_WIDTH * TFT_HEIGHT / 10);  /*Initialize the display buffer.*/
    
    //setup display
    static lv_disp_drv_t disp_drv;        /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);          /*Basic initialization*/
    disp_drv.flush_cb = tft_lvgl_disp_flush;    /*Set your driver function*/
    disp_drv.draw_buf = &draw_buf;        /*Assign the buffer to the display*/
    disp_drv.hor_res = tft_get_width();   /*Set the horizontal resolution of the display*/
    disp_drv.ver_res = tft_get_height();   /*Set the vertical resolution of the display*/
    lv_disp_drv_register(&disp_drv);      /*Finally register the driver*/

}


void pico_lvgl_xpt2046_init() {
    // init xpt2046 hardware
    xpt2046_init();

    // setup touch screen input device
    static lv_indev_drv_t indev_drv;           /*Descriptor of a input device driver*/
    lv_indev_drv_init(&indev_drv);             /*Basic initialization*/
    indev_drv.type = LV_INDEV_TYPE_POINTER;    /*Touch pad is a pointer-like device*/
    indev_drv.read_cb = xpt2046_lvgl_read_cb;      /*Set your driver function*/
    lv_indev_drv_register(&indev_drv);         /*Finally register the driver*/
}


lv_indev_t *encoder_indev;
lv_group_t *encoder_group;
void pico_lvgl_encoder_init(bool pio_mode) {
    // init rotary encoder hardware
    if (pio_mode) {
        lvgl_pio_encoder_init();
    } else 
    {
        gpio_encoder_init();
    }

    // setup rotary encoder input device
    static lv_indev_drv_t indev_drv;           /*Descriptor of a input device driver*/
    lv_indev_drv_init(&indev_drv);             /*Basic initialization*/
    indev_drv.type = LV_INDEV_TYPE_ENCODER;    /*Rotary Encoder device*/
    if (pio_mode) {
        indev_drv.read_cb = lvgl_encoder_read_cb;      /*Set your driver function*/
    
    } else {
        indev_drv.read_cb = gpio_encoder_read_cb;
    }
    //indev_drv.long_press_time=1000;
    indev_drv.long_press_repeat_time=2000;
    encoder_indev = lv_indev_drv_register(&indev_drv);         /*Finally register the driver*/
    encoder_group = lv_group_create();
    
    //lv_group_set_default(kg);
    lv_indev_set_group(encoder_indev, encoder_group);

}

    
    
    
     
    
pico_lvgl.h

#ifndef __PICO_LVGL_H__
#define __PICO_LVGL_H__
#include "stdio.h"
#include "pico/stdio.h"
#include "hardware/pio.h"
#include "pico_tft.h"
#include "xpt2046.h"
#include "lvgl.h"

void pico_lvgl_tft_init(PIO pio, uint sm, uint sdi_gpio, uint csx_dcx_sck_gpio);
void pico_lvgl_xpt2046_init();

void pico_lvgl_display_init(uint8_t tick_inc);



#endif

  • root 
CMakeLists.txt

add_subdirectory(pico_lvgl)
target_link_libraries(project_name
        pico_lvgl
)