sdk例程:st7789屏移植lvgl,以及按键模拟touch功能 - minichao9901/TangNano-20k-Zynq-7020 GitHub Wiki

介绍

  • st7789, 1.3寸屏,240x240分辨率, spi-mode=3接口
  • 按键采用小梅哥的扩展板,一共有5个按键,可以设置5个坐标
  • 测试发现,用ps_spi不能驱动这个屏,可能是时序不兼容。用axi_spi可以驱动
  • 小梅哥的库非常好用:包括gpio,spi, i2c, scu_timer的使用,非常方便的API函数
  • 吐槽一下:axi_spi实在太垃圾,它虽然支持block_transfer,但是传输的个数增大一倍,block与block之间的冷却时间也需要增大一倍,对提速没有帮助。

硬件设计

  • 很简单,即使一个axi_spi,以及许多个emio image
#dcx
#rst_n
#mosi
#sck

# PS_SPI
set_property -dict {PACKAGE_PIN F16 IOSTANDARD LVCMOS33} [get_ports GPIO_0_0_tri_io[0]]
set_property -dict {PACKAGE_PIN E17 IOSTANDARD LVCMOS33} [get_ports GPIO_0_0_tri_io[1]]
set_property -dict {PACKAGE_PIN E18 IOSTANDARD LVCMOS33} [get_ports SPI_0_0_io0_io]
set_property -dict {PACKAGE_PIN G17 IOSTANDARD LVCMOS33} [get_ports SPI_0_0_sck_io]

#AXI_SPI
set_property -dict {PACKAGE_PIN F17 IOSTANDARD LVCMOS33} [get_ports GPIO_0_0_tri_io[2]]
set_property -dict {PACKAGE_PIN D18 IOSTANDARD LVCMOS33} [get_ports GPIO_0_0_tri_io[3]]
set_property -dict {PACKAGE_PIN E19 IOSTANDARD LVCMOS33} [get_ports spi_rtl_0_io0_io]
set_property -dict {PACKAGE_PIN G18 IOSTANDARD LVCMOS33} [get_ports spi_rtl_0_sck_io]

#key
set_property -dict {PACKAGE_PIN W16 IOSTANDARD LVCMOS33} [get_ports GPIO_0_0_tri_io[4]]
set_property -dict {PACKAGE_PIN V15 IOSTANDARD LVCMOS33} [get_ports GPIO_0_0_tri_io[5]]
set_property -dict {PACKAGE_PIN W15 IOSTANDARD LVCMOS33} [get_ports GPIO_0_0_tri_io[6]]
set_property -dict {PACKAGE_PIN U14 IOSTANDARD LVCMOS33} [get_ports GPIO_0_0_tri_io[7]]
set_property -dict {PACKAGE_PIN U15 IOSTANDARD LVCMOS33} [get_ports GPIO_0_0_tri_io[8]]

#其余不用的输入/输出可以不分配引脚,并避免报错
set_property IOSTANDARD LVCMOS33 [get_ports *]
set_property SEVERITY {Warning} [get_drc_checks NSTD-1]
set_property SEVERITY {Warning} [get_drc_checks RTSTAT-1]
set_property SEVERITY {Warning} [get_drc_checks UCIO-1]

程序

main.c

#include "ACZ702_Lib/COMMON.h"
#include "lvgl/lvgl.h"
#include "lv_examples/lv_examples.h"
#include "lvgl/port/lv_port_disp.h"
#include "lvgl/port/lv_port_indev.h"

//GPIO2, V1扩展板
#define PS_DCX (54+0)
#define PS_RSTN (54+1)
#define PL_DCX (54+2)
#define PL_RSTN (54+3)

//GPIO1,小梅哥扩展板
#define PS_KEY1 (54+4)
#define PS_KEY2 (54+5)
#define PS_KEY3 (54+6)
#define PS_KEY4 (54+7)
#define PS_KEY5 (54+8)

int main_ps_emio(void)
{
    PS_GPIO_Init(); //初始化PS端MIO和EMIO
    PS_GPIO_SetMode(PS_DCX, OUTPUT, 1);
    PS_GPIO_SetMode(PS_RSTN, OUTPUT, 1); 
    PS_GPIO_SetMode(PL_DCX, OUTPUT, 1);
    PS_GPIO_SetMode(PL_RSTN, OUTPUT, 1); 

    PS_GPIO_SetMode(PS_KEY1, INPUT, 1);
    PS_GPIO_SetMode(PS_KEY2, INPUT, 1);
    PS_GPIO_SetMode(PS_KEY3, INPUT, 1);
    PS_GPIO_SetMode(PS_KEY4, INPUT, 1);
    PS_GPIO_SetMode(PS_KEY5, INPUT, 1);
}

#define KEY1_PRES 	1  	//KEY1按下后返回值
#define KEY2_PRES	2	//KEY2按下后返回值
#define KEY3_PRES	3	//KEY3按下后返回值
#define KEY4_PRES   4	//KEY4按下后返回值
#define KEY5_PRES   5	//KEY5按下后返回值

#define KEY1 PS_GPIO_GetPort(PS_KEY1)
#define KEY2 PS_GPIO_GetPort(PS_KEY2)
#define KEY3 PS_GPIO_GetPort(PS_KEY3)
#define KEY4 PS_GPIO_GetPort(PS_KEY4)
#define KEY5 PS_GPIO_GetPort(PS_KEY5)

u8 KEY_Scan(u8 mode)
{
	static u8 key_up = 1; // 按键松开标志
	if (mode == 1)
		key_up = 1; // 支持连按
	if (key_up && (KEY1 == 0 || KEY2 == 0 || KEY3 == 0 || KEY4 == 0 || KEY5 == 0))
	{
		usleep(50 * 1000);
		key_up = 0;
		if (KEY1 == 0)
			return KEY1_PRES;
		else if (KEY2 == 0)
			return KEY2_PRES;
		else if (KEY3 == 0)
			return KEY3_PRES;
		else if (KEY4 == 0)
			return KEY4_PRES;
		else if (KEY5 == 0)
			return KEY5_PRES;
	}
	else if (KEY1 == 1 && KEY2 == 1 && KEY3 == 1 && KEY4 == 1 && KEY5 == 1)
		key_up = 1;
	return 0; // 无按键按下
}

void st7789_rst()
{
	PS_GPIO_SetPort(PL_RSTN,0);
	usleep(100*1000);
	PS_GPIO_SetPort(PL_RSTN,1);
	usleep(100*1000);
}

void write_command(u8 cmd)
{
	PS_GPIO_SetPort(PL_DCX,0);
	AXI_SPI_Transfer(&AXI_SPI0, 0, NULL, &cmd, 1);
}

void write_command_data(u8 cmd, u8 *pdata, u32 length)
{
	PS_GPIO_SetPort(PL_DCX,0);
	AXI_SPI_Transfer(&AXI_SPI0, 0, NULL, &cmd, 1);
	usleep(1);

	PS_GPIO_SetPort(PL_DCX,1);
	AXI_SPI_Transfer(&AXI_SPI0, 0, NULL, pdata, length);	
}

void write_data(u8 *pdata, u32 length)
{
	PS_GPIO_SetPort(PL_DCX,1);
	AXI_SPI_Transfer(&AXI_SPI0, 0, NULL, pdata, length);
}

void lcd_init()
{
	write_command(0x11);
	usleep(100*1000);
	write_command(0x21);

	u8 data=0x55;
	write_command_data(0x3a, &data, 1);

//	data=0x8;
//	write_command_data(0x36, &data, 1);

	write_command(0x29);
}

#define LCD_X_SIZE 240
#define LCD_Y_SIZE 240

void lcdqspi_fill_block(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1, uint32_t color)
{
	u8 xwin_list[]={(x0>>8)&0xff, x0&0xff, (x1>>8)&0xff, x1&0xff};
	u8 ywin_list[]={(y0>>8)&0xff, y0&0xff, (y1>>8)&0xff, y1&0xff};
	write_command_data(0x2a, xwin_list, 4);
	write_command_data(0x2b, ywin_list, 4);

	u32 length=(x1-x0)*(y1-y0);
	write_command(0x2c);
	while(length--){
		u8 color_list[]={(color>>8)&0xff, color&0xff};
		write_data(color_list,2);
	}
}

void lcdqspi_clear(uint32_t color)
{
    lcdqspi_fill_block(0, 0, LCD_X_SIZE - 1, LCD_Y_SIZE - 1, color);
}


u8 frame_cache[240*240*2];
void lcdqspi_fill_pcolor(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1, uint16_t *pcolor)
{
	u8 xwin_list[]={(x0>>8)&0xff, x0&0xff, (x1>>8)&0xff, x1&0xff};
	u8 ywin_list[]={(y0>>8)&0xff, y0&0xff, (y1>>8)&0xff, y1&0xff};
	write_command_data(0x2a, xwin_list, 4);
	write_command_data(0x2b, ywin_list, 4);

	u32 length=(x1-x0)*(y1-y0);
	write_command(0x2c);

	while(length--){
		u8 color_list[]={(*pcolor>>8)&0xff, *pcolor&0xff};
		write_data(color_list,2);
		pcolor++;
	}

//	u32 remain=length;
//	u32 BLOCK_SIZE=2;
//	while(remain>=BLOCK_SIZE){
//		u32 index=0;
//		for(int i=0; i<BLOCK_SIZE; i++){
//			frame_cache[index++]=(*pcolor>>8)&0xff;
//			frame_cache[index++]=*pcolor&0xff;
//			pcolor++;
//
//			write_data(frame_cache,BLOCK_SIZE*2);
//		}
//		remain-=BLOCK_SIZE;
//		index+=BLOCK_SIZE*2;
//	}
//
//	if(remain>0){
//		u32 index=0;
//		for(int i=0; i<remain; i++){
//			frame_cache[index++]=(*pcolor>>8)&0xff;
//			frame_cache[index++]=*pcolor&0xff;
//			pcolor++;
//
//			write_data(frame_cache,remain*2);
//		}
//	}


}

u16 frame_buff[240][240];
void fill_color(u16 color){
	for(int i=0; i<240; i++){
		for(int j=0; j<240; j++){
			frame_buff[i][j]=color;
		}
	}
}

//static void ScuTimer_InterruptHandler(void *data)
//{
//	XScuTimer *timer = (XScuTimer *)data;
//	lv_tick_inc(1);
//	XScuTimer_ClearInterruptStatus(timer);
//	//xil_printf("1\r\n");
//}

void ScuTimer_IRQ_Handler(void *CallBackRef)
{
	/* ↓↓↓用户处理↓↓↓ */
	lv_tick_inc(1);
	/* ↑↑↑结束处理↑↑↑ */
    XScuTimer_ClearInterruptStatus(&ScuTimer);
}


int main(void)
{
	XScuGic gic;
	XScuGic_Config *gic_cfg;
	XScuTimer timer;
	XScuTimer_Config *timer_cfg;

	//初始化通用中断控制器
	Xil_DCacheDisable();
	ScuGic_Init();


//		/* 私有定时器初始化 */
//	gic_cfg = XScuGic_LookupConfig(XPAR_SCUGIC_0_DEVICE_ID);
//	XScuGic_CfgInitialize(&gic, gic_cfg, gic_cfg->CpuBaseAddress);
//	timer_cfg = XScuTimer_LookupConfig(XPAR_XSCUTIMER_0_DEVICE_ID);
//	XScuTimer_CfgInitialize(&timer, timer_cfg, timer_cfg->BaseAddr);
//
//	XScuTimer_LoadTimer(&timer, XPAR_PS7_CORTEXA9_0_CPU_CLK_FREQ_HZ / 2000 - 1);	// 1ms
//	XScuTimer_EnableAutoReload(&timer);
//
//	Xil_ExceptionInit();
//	Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_IRQ_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, &gic);
//	Xil_ExceptionEnable();
//
//	XScuGic_Connect(&gic, XPAR_SCUTIMER_INTR, (Xil_ExceptionHandler)ScuTimer_InterruptHandler, (void *)&timer);
//	XScuGic_Enable(&gic, XPAR_SCUTIMER_INTR);
//
//	XScuTimer_EnableInterrupt(&timer);
//	XScuTimer_Start(&timer);


	ScuGic_Init();
	ScuTimer_Int_Init(1000);


	//初始化SPI0,设为主机模式,64分频
	AXI_SPI_Init(&AXI_SPI0, XPAR_SPI_0_DEVICE_ID, XSP_MASTER_OPTION|XSP_CLK_ACTIVE_LOW_OPTION|XSP_CLK_PHASE_1_OPTION);

	main_ps_emio();
	st7789_rst();
	lcd_init();


		/* lvgl初始化 */
	lv_init();
	lv_port_disp_init();
	lv_port_indev_init();

	/* 创建demo */
	//lv_demo_printer();
	lv_demo_keypad_encoder();
	//lv_demo_widgets();
	//lv_demo_stress();

	/* 死循环 */
	for ( ; ; ) {
		lv_task_handler();
		usleep(5 * 1000);
	}
	
	return 0;
}

lv_port_disp.c

/**
 * @file lv_port_disp_templ.c
 *
 */

 /*Copy this file as "lv_port_disp.c" and set this value to "1" to enable content*/
#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_disp.h"

/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/

static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);
#if LV_USE_GPU
static void gpu_blend(lv_disp_drv_t * disp_drv, lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa);
static void gpu_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
        const lv_area_t * fill_area, lv_color_t color);
#endif

/**********************
 *  STATIC VARIABLES
 **********************/

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

#define  LCD_DOUBLE_BUFFER_ADDR		0x10100000
#define  LCD_DOUBLE_BUFFER_ADDR2	0x10200000

void lv_port_disp_init(void)
{
    static lv_disp_buf_t disp_buf;
    lv_disp_buf_init(&disp_buf, (void *)LCD_DOUBLE_BUFFER_ADDR,
    		(void *)LCD_DOUBLE_BUFFER_ADDR2,
			LV_HOR_RES_MAX * LV_VER_RES_MAX);   /*Initialize the display buffer*/

    /*-----------------------------------
     * Register the display in LVGL
     *----------------------------------*/

    lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/

    /*Set up the functions to access to your display*/

    /*Set the resolution of the display*/
    disp_drv.hor_res = LV_HOR_RES_MAX;
    disp_drv.ver_res = LV_VER_RES_MAX;

    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = disp_flush;

    /*Set a display buffer*/
    disp_drv.buffer = &disp_buf;

#if LV_USE_GPU
    /*Optionally add functions to access the GPU. (Only in buffered mode, LV_VDB_SIZE != 0)*/

    /*Blend two color array using opacity*/
    disp_drv.gpu_blend_cb = gpu_blend;

    /*Fill a memory array with a color*/
    disp_drv.gpu_fill_cb = gpu_fill;
#endif

    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

/* Flush the content of the internal buffer the specific area on the display
 * You can use DMA or any hardware acceleration to do this operation in the background but
 * 'lv_disp_flush_ready()' has to be called when finished. */
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
    int16_t x;
    int16_t y;
    uint8_t *ptrBase;
    uint8_t *ptr;

    /* 优化后的结果 */
    // ptrBase = (area->y1 * LV_HOR_RES_MAX + area->x1) * 2 + (uint8_t *)frame_buffer_addr;
    // for(y = area->y1; y <= area->y2; y++, ptrBase += (LV_HOR_RES_MAX * 2)) {

    //     for(x = area->x1, ptr = ptrBase; x <= area->x2; x++, ptr += 2) {

    //     	*(uint16_t *)ptr = color_p->full;
    //         color_p++;
    //     }
    // }

    xil_printf("x0=%d, x1=%d, y0=%d, y1=%d\r\n", area->x1,area->x2,area->y1,area->y2);
    extern void lcdqspi_fill_pcolor(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1, uint16_t *pcolor);
    lcdqspi_fill_pcolor(area->x1,area->y1,area->x2,area->y2, color_p);
    

    /* IMPORTANT!!!
     * Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
}


/*OPTIONAL: GPU INTERFACE*/
#if LV_USE_GPU

/* If your MCU has hardware accelerator (GPU) then you can use it to blend to memories using opacity
 * It can be used only in buffered mode (LV_VDB_SIZE != 0 in lv_conf.h)*/
static void gpu_blend(lv_disp_drv_t * disp_drv, lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa)
{
    /*It's an example code which should be done by your GPU*/
    uint32_t i;
    for(i = 0; i < length; i++) {
        dest[i] = lv_color_mix(dest[i], src[i], opa);
    }
}

/* If your MCU has hardware accelerator (GPU) then you can use it to fill a memory with a color
 * It can be used only in buffered mode (LV_VDB_SIZE != 0 in lv_conf.h)*/
static void gpu_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
                    const lv_area_t * fill_area, lv_color_t color)
{
    /*It's an example code which should be done by your GPU*/
    int32_t x, y;
    dest_buf += dest_width * fill_area->y1; /*Go to the first line*/

    for(y = fill_area->y1; y <= fill_area->y2; y++) {
        for(x = fill_area->x1; x <= fill_area->x2; x++) {
            dest_buf[x] = color;
        }
        dest_buf+=dest_width;    /*Go to the next line*/
    }
}

#endif  /*LV_USE_GPU*/

#else /* Enable this file at the top */

/* This dummy typedef exists purely to silence -Wpedantic. */
typedef int keep_pedantic_happy;
#endif

lv_port_indev.c

/**
 * @file lv_port_indev_templ.c
 *
 */

 /*Copy this file as "lv_port_indev.c" and set this value to "1" to enable content*/
#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_indev.h"
// #include "../../touch/ft5x26.h"
// #include "../../touch/gt9xx.h"
// #include "../../TOUCH_NEW/ft5206.h"
// #include "../../TOUCH_NEW/gt9147.h"
#include "xil_printf.h"

extern unsigned int lcd_id;

/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/

//static bool gt9xx_touch_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
//static bool ft5x26_touch_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static bool key_touch_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);

/**********************
 *  STATIC VARIABLES
 **********************/
lv_indev_t * indev_touchpad;


/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

typedef bool (*read_cb)(struct _lv_indev_drv_t *, lv_indev_data_t *);

void lv_port_indev_init(void)
{
    /* Here you will find example implementation of input devices supported by LittelvGL:
     *  - Touchpad
     *  - Mouse (with cursor support)
     *  - Keypad (supports GUI usage only with key)
     *  - Encoder (supports GUI usage only with: left, right, push)
     *  - Button (external buttons to press points on the screen)
     *
     *  The `..._read()` function are only examples.
     *  You should shape them according to your hardware
     */


    lv_indev_drv_t indev_drv;
    read_cb cb_ptr;

    /*------------------
     * Touchpad
     * -----------------*/
	// emio_init();
    
    // /*Initialize your touchpad if you have*/
	// switch(lcd_id) {
	// case 0:
	// case 1:
	// case 5: {
	// 	GT9147_Init();
	// 	cb_ptr = gt9xx_touch_read;
	// }
	// 	break;
	// case 2:
	// case 4:
	// default: {
	// 	FT5206_Init();
	// 	cb_ptr = ft5x26_touch_read;
	// }
	// 	break;
	// }

    cb_ptr=key_touch_read;

    /*Register a touchpad input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = cb_ptr;
    indev_touchpad = lv_indev_drv_register(&indev_drv);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/



/*------------------
 * Touchpad
 * -----------------*/
/* Will be called by the library to read the touchpad */
static bool key_touch_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    static lv_coord_t last_x = 0;
    static lv_coord_t last_y = 0;

    /*Save the pressed coordinates and the state*/
    extern u8 KEY_Scan(u8 mode);
    u8 key = KEY_Scan(0);
    if (key != 0)
    {
        data->state = LV_INDEV_STATE_PR;
        switch (key)
        {
        case 1:
            last_x = 60, last_y = 60; break;
        case 2:
            last_x = 180, last_y = 60; break;
        case 3:
            last_x = 60, last_y = 180; break;
        case 4:
            last_x = 180, last_y = 180; break;
        case 5:
            last_x = 120, last_y = 120; break;
        }
    }
    else
        data->state = LV_INDEV_STATE_REL;

    /*Set the last pressed coordinates*/
    xil_printf("touch state=%d, x=%d, y=%d, key=%d\r\n", data->state, last_x, last_y, key);
    data->point.x = last_x;
    data->point.y = last_y;

    /*Return `false` because we are not buffering and no more data to read*/
    return false;
}

#else /* Enable this file at the top */

/* This dummy typedef exists purely to silence -Wpedantic. */
typedef int keep_pedantic_happy;
#endif

效果

image image image

补充说明:关于frame_buff大小的设置

如果设置为10行,那么刷图时,需要调用disp_flush函数很多次

image image

⚠️ **GitHub.com Fallback** ⚠️