LVGL多显示-基于GC9A01A的1.28寸圆屏

原创 嵌入式Lee 2024-07-10 11:56

一. 前言

前面系列文章我们分享了ST7789GC9A01A的驱动,并移植了LVGLemWin跑了DemoLVGL本身支持多显示器,刚好手里也有多块基于GC9A01A1.28寸圆屏,这一篇我们就在之前的基础上演示LVGL的多屏显示。

二. 驱动修改

前面我们分享LVGL移植时可以看到,显示驱动移植就是根据模板实现用户自己的显示函数即可,然后注册对应的驱动。对于显示器驱动可能用的一套底层驱动使用id区分不同显示器,也可能是完全不同的驱动,不管是什么方式,对于LVGL只需要注册多个驱动即可,一个驱动对应一个显示器。

我们的1.28寸屏使用SPI驱动,为了减少引脚使用所以两个屏幕共用DICLKDC三个引脚,CSRST两个屏幕独立使用,所以一共7个引脚,采用分时刷屏方式。所以驱动也只需要在原来的基础上稍微修改,接口中增加一个ID参数用于区分显示器即可。

gc9a01a_itf.h/c相应的接口中,增加ID参数用于区分显示屏,相应的增加gc9a01a设备实例,和CSRST等控制接口区分,修改后内容如下

gc9a01a_itf.h

#ifndef GC9A01A_ITF_H#define GC9A01A_ITF_H
#ifdef __cplusplus extern "C"{#endif
#include
#define GC9A01A_ITF_SPI_IO 0 /**< 配置为1使用IO模拟, 配置为0使用硬件SPI */
#define GC9A01A_HSIZE 240#define GC9A01A_VSIZE 240
/** * \fn gc9a01a_itf_init * 初始化 * \retval 0 成功 * \retval 其他值 失败*/int gc9a01a_itf_init(void);
/** * \fn gc9a01a_itf_deinit * 解除初始化 * \retval 0 成功 * \retval 其他值 失败*/int gc9a01a_itf_deinit(void);
/** * \fn gc9a01a_itf_sync * 刷新显示 * \param[in] id 设备索引 * \retval 0 成功 * \retval 其他值 失败*/int gc9a01a_itf_sync(int id);
/** * \fn gc9a01a_itf_set_pixel * 写点 * \param[in] id 设备索引 * \param[in] x x坐标位置 * \param[in] y y坐标位置 * \param[in] rgb565 颜色*/void gc9a01a_itf_set_pixel(int id, uint16_t x, uint16_t y, uint16_t rgb565);
/** * \fn gc9a01a_itf_get_pixel * 读点 * \param[in] id 设备索引 * \param[in] x x坐标位置 * \param[in] y y坐标位置 * \return rgb565颜色*/uint16_t gc9a01a_itf_get_pixel(int id, uint16_t x, uint16_t y);
#ifdef __cplusplus }#endif
#endif

gc9a01a_itf.c

#include #include "gc9a01a_itf.h"#include "gc9a01a.h"
#define USE_SPI_PORT SPI_PORT1 #define DC_PIN GPIO_20 #define SCL_PIN GPIO_29 #define SDA_PIN GPIO_39
#define CS1_PIN GPIO_30 #define CS2_PIN GPIO_38 #define RST1_PIN GPIO_31#define RST2_PIN GPIO_27
/****************************************************************************** * 以下是底层适配 * ******************************************************************************/
#if GC9A01A_ITF_SPI_IO/* 使用IO模拟SPI方式 */ #include "io_spi.h"
static void port_io_spi_cs_write(uint8_t val){ (void)val;}
static void port_gc9a01a_spi_enable1(uint8_t val){ if(val) { gpio_write(CS1_PIN,0); } else { gpio_write(CS1_PIN,1); }}
static void port_gc9a01a_spi_enable2(uint8_t val){ if(val) { gpio_write(CS2_PIN,0); } else { gpio_write(CS2_PIN,1); }}
static void port_io_spi_sck_write(uint8_t val){ gpio_write(SCL_PIN,val);}
static void port_io_spi_mosi_write(uint8_t val){ gpio_write(SDA_PIN,val);}
static uint8_t port_io_spi_miso_read(void){ return 0;}
static void port_io_spi_init(void){ gpio_open(CS1_PIN, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(CS1_PIN, GPIO_PULL_UP); gpio_write(CS1_PIN,1);
gpio_open(CS2_PIN, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(CS2_PIN, GPIO_PULL_UP); gpio_write(CS2_PIN,1);
gpio_open(SCL_PIN, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(SCL_PIN, GPIO_PULL_UP); gpio_write(SCL_PIN,1); gpio_open(SDA_PIN, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(SDA_PIN, GPIO_PULL_UP); gpio_write(SDA_PIN,1); if(RST1_PIN < GPIO_INVALID) { gpio_open(RST1_PIN, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(RST1_PIN, GPIO_PULL_UP); gpio_write(RST1_PIN,1); } if(RST2_PIN < GPIO_INVALID) { gpio_open(RST2_PIN, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(RST2_PIN, GPIO_PULL_UP); gpio_write(RST2_PIN,1); }}
static void port_io_spi_deinit(void){ gpio_close(CS1_PIN); gpio_close(CS2_PIN); gpio_close(SCL_PIN); gpio_close(SDA_PIN); }
/* IO模拟SPI设备实例 */static io_spi_dev_st s_io_spi_dev ={ .cs_write = port_io_spi_cs_write, .sck_write = port_io_spi_sck_write, .mosi_write = port_io_spi_mosi_write, .miso_read = port_io_spi_miso_read, .delay_pf = 0, .init = port_io_spi_init, .deinit = port_io_spi_deinit, .delayns = 1, .mode = 0, .msb = 1,};
static void port_gc9a01a_set_dcx(uint8_t val){ gpio_write(DC_PIN, val);}
static void port_gc9a01a_set_reset1(uint8_t val){ (void)val; if(RST1_PIN < GPIO_INVALID) { gpio_write(RST1_PIN, val); }}
static void port_gc9a01a_set_reset2(uint8_t val){ (void)val; if(RST2_PIN < GPIO_INVALID) { gpio_write(RST2_PIN, val); }}
static void port_gc9a01a_spi_write(uint8_t* buffer, uint32_t len){ io_spi_trans(&s_io_spi_dev, buffer, 0, len);}
static void port_gc9a01a_delay_ms(uint32_t t){ if(t > 0) { os_delay(t); }}
static void port_gc9a01a_init(void){ gpio_open(DC_PIN, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(DC_PIN, GPIO_PULL_UP); gpio_write(DC_PIN,1);
io_spi_init(&s_io_spi_dev);}
static void port_gc9a01a_deinit(void){ gpio_close(DC_PIN);
io_spi_deinit(&s_io_spi_dev);}
#else/* 使用硬件SPI方式 */#include "spi.h"
static void port_gc9a01a_set_dcx(uint8_t val){ gpio_write(DC_PIN, val);}
static void port_gc9a01a_set_reset1(uint8_t val){ (void)val; if(RST1_PIN < GPIO_INVALID) { gpio_write(RST1_PIN, val); }}
static void port_gc9a01a_set_reset2(uint8_t val){ (void)val; if(RST2_PIN < GPIO_INVALID) { gpio_write(RST2_PIN, val); }}
volatile uint32_t s_gc9a01a_spi_busy_flag = 0;
static void spi_dma_cb(void){ //printf("spi done\r\n"); s_gc9a01a_spi_busy_flag = 0; //timer_delay_us(2);}
static void port_gc9a01a_spi_write(uint8_t* buffer, uint32_t len){ RET ret; int timeout; if(len < 100) { timeout = 2; /* 数据比较少时,是在发命令,最多等2ms */ } else { timeout = 200; /* 数据比较多时肯定是在刷屏,最多等200mS */ } s_gc9a01a_spi_busy_flag = 1; if(RET_OK != (ret = spi_dma_trans_direct(USE_SPI_PORT, buffer, 0, len, spi_dma_cb))) { printf("spi err %d\r\n", ret); s_gc9a01a_spi_busy_flag = 0; return; } /* 等待传输完成,由于一个SPI多CS分时,所以必须阻塞等一次传完,才能进行下一次 */ while(s_gc9a01a_spi_busy_flag != 0) { os_delay(1); /* 释放CPU */ timeout--; if(timeout <= 0) { s_gc9a01a_spi_busy_flag = 0; printf("spi busy\r\n"); return; } } }
static void port_gc9a01a_spi_enable1(uint8_t val){ if(val) { gpio_write(CS1_PIN,0); } else { gpio_write(CS1_PIN,1); }}
static void port_gc9a01a_spi_enable2(uint8_t val){ if(val) { gpio_write(CS2_PIN,0); } else { gpio_write(CS2_PIN,1); }}
static void port_gc9a01a_delay_ms(uint32_t t){ os_delay(t);}
static void port_gc9a01a_init(void){ static int s_port_gc9a01a_init_flag = 0; if(s_port_gc9a01a_init_flag == 0) { /* 共用,底层接口只需要初始化一次 */ s_port_gc9a01a_init_flag = 1; gpio_open(DC_PIN, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(DC_PIN, GPIO_PULL_UP); gpio_write(DC_PIN,1);
gpio_open(GPIO_PMM00, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(GPIO_PMM00, GPIO_PULL_UP); gpio_write(GPIO_PMM00,1); gpio_open(GPIO_PMM01, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(GPIO_PMM01, GPIO_PULL_UP); gpio_write(GPIO_PMM01,1);
gpio_open(CS1_PIN, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(CS1_PIN, GPIO_PULL_UP); gpio_write(CS1_PIN,1);
gpio_open(CS2_PIN, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(CS2_PIN, GPIO_PULL_UP); gpio_write(CS2_PIN,1);
if(RST1_PIN < GPIO_INVALID) { gpio_open(RST1_PIN, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(RST1_PIN, GPIO_PULL_UP); gpio_write(RST1_PIN,1); } if(RST2_PIN < GPIO_INVALID) { gpio_open(RST2_PIN, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(RST2_PIN, GPIO_PULL_UP); gpio_write(RST2_PIN,1); } printf("io init\r\n"); /* SPI配置 */ spi_cfg_t spi_cfg; spi_gpio_cfg_t gpio_cfg;
spi_cfg.frequency = 80ul * 1000ul * 1000ul; // SPI时钟源是350M,在此基础上再分频。本TFT支持最大100M. 设置60M实际是350/6=58.3MHz gpio_cfg.auto_cs = false; gpio_cfg.cs = GPIO_INVALID; gpio_cfg.clk = SCL_PIN; gpio_cfg.miso = GPIO_INVALID; gpio_cfg.mosi = SDA_PIN;
//if((gpio_cfg.auto_cs == false) && (gpio_cfg.cs < GPIO_INVALID)) //{ // gpio_open(gpio_cfg.cs, GPIO_DIRECTION_OUTPUT); // gpio_set_pull_mode(gpio_cfg.cs, GPIO_PULL_UP); //}
RET ret; if(RET_OK != (ret = spi_init(USE_SPI_PORT))) { printf("spi init err %d\r\n", ret); } if(RET_OK != (ret = spi_open(USE_SPI_PORT, &spi_cfg, &gpio_cfg))) { printf("spi open err %d\r\n", ret); } spi_modify_CPOL_CPHA(USE_SPI_PORT, SPI_CLK_MODE_0);
printf("spi init\r\n"); }}
static void port_gc9a01a_deinit(void){ gpio_close(DC_PIN); if(RST1_PIN < GPIO_INVALID) { gpio_close(RST1_PIN); } if(RST2_PIN < GPIO_INVALID) { gpio_close(RST2_PIN); } spi_close(USE_SPI_PORT);} #endif
/****************************************************************************** * 以下是GC9A01A设备实例 * ******************************************************************************/
static uint16_t s_gc9a01a_itf_buffer[2][GC9A01A_HSIZE][GC9A01A_VSIZE]; /**< 显存 */
/* 设备实例 */static gc9a01a_dev_st s_gc9a01a_itf_dev1 ={ .set_dcx = port_gc9a01a_set_dcx, .set_reset = port_gc9a01a_set_reset1, .write = port_gc9a01a_spi_write, .enable = port_gc9a01a_spi_enable1, .delay = port_gc9a01a_delay_ms, .init = port_gc9a01a_init, .deinit = port_gc9a01a_deinit,
.buffer = (uint16_t*)(s_gc9a01a_itf_buffer[0]),};
/* 设备实例 */static gc9a01a_dev_st s_gc9a01a_itf_dev2 ={ .set_dcx = port_gc9a01a_set_dcx, .set_reset = port_gc9a01a_set_reset2, .write = port_gc9a01a_spi_write, .enable = port_gc9a01a_spi_enable2, .delay = port_gc9a01a_delay_ms, .init = port_gc9a01a_init, .deinit = port_gc9a01a_deinit,
.buffer = (uint16_t*)(s_gc9a01a_itf_buffer[1]),};
/****************************************************************************** * 以下是对外操作接口 * ******************************************************************************/

/** * \fn gc9a01a_itf_init * 初始化 * \retval 0 成功 * \retval 其他值 失败*/int gc9a01a_itf_init(void){#if 0 gpio_open(DC_PIN, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(DC_PIN, GPIO_PULL_UP); gpio_write(DC_PIN,1); gpio_open(CS_PIN, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(CS_PIN, GPIO_PULL_UP); gpio_write(CS_PIN,1); gpio_open(SCL_PIN, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(SCL_PIN, GPIO_PULL_UP); gpio_write(SCL_PIN,1); gpio_open(SDA_PIN, GPIO_DIRECTION_OUTPUT); gpio_set_pull_mode(SDA_PIN, GPIO_PULL_UP); gpio_write(SDA_PIN,1); while(1) { static int s_cnt = 0; if(s_cnt%1 == 0) { gpio_toggle(DC_PIN); } if(s_cnt%2 == 1) { gpio_toggle(CS_PIN); } if(s_cnt%4 == 2) { gpio_toggle(SCL_PIN); } if(s_cnt%8 == 3) { gpio_toggle(SDA_PIN); } os_delay(1); s_cnt++; }#endif static int s_gc9a01a_init_flag = 0; if(s_gc9a01a_init_flag == 0) { s_gc9a01a_init_flag = 1; /* 只初始化一次 */ gc9a01a_init(&s_gc9a01a_itf_dev1); gc9a01a_init(&s_gc9a01a_itf_dev2); } return 0;}
/** * \fn gc9a01a_itf_deinit * 解除初始化 * \retval 0 成功 * \retval 其他值 失败*/int gc9a01a_itf_deinit(void){ gc9a01a_deinit(&s_gc9a01a_itf_dev1); gc9a01a_deinit(&s_gc9a01a_itf_dev2); return 0;}
/** * \fn gc9a01a_itf_sync * 刷新显示 * \param[in] id 设备索引 * \retval 0 成功 * \retval 其他值 失败*/int gc9a01a_itf_sync(int id){ if(id == 0) { gc9a01a_sync(&s_gc9a01a_itf_dev1, 0, GC9A01A_HSIZE-1, 0, GC9A01A_VSIZE-1, s_gc9a01a_itf_dev1.buffer, GC9A01A_HSIZE*GC9A01A_VSIZE*2); return 0; } else if(id == 1) { gc9a01a_sync(&s_gc9a01a_itf_dev2, 0, GC9A01A_HSIZE-1, 0, GC9A01A_VSIZE-1, s_gc9a01a_itf_dev2.buffer, GC9A01A_HSIZE*GC9A01A_VSIZE*2); return 0; }
return -1;}
/** * \fn gc9a01a_itf_set_pixel * 写点 * \param[in] id 设备索引 * \param[in] x x坐标位置 * \param[in] y y坐标位置 * \param[in] rgb565 颜色*/void gc9a01a_itf_set_pixel(int id, uint16_t x, uint16_t y, uint16_t rgb565){ //if(x >= GC9A01A_HSIZE) //{ // return -1; //} //if(y >= GC9A01A_VSIZE) //{ // return -1; //} if(id == 0) { s_gc9a01a_itf_dev1.buffer[y*GC9A01A_HSIZE + x] = (uint16_t)((rgb565>>8)&0xFF) | (uint16_t)((rgb565<<8) & 0xFF00); } else if(id == 1) { s_gc9a01a_itf_dev2.buffer[y*GC9A01A_HSIZE + x] = (uint16_t)((rgb565>>8)&0xFF) | (uint16_t)((rgb565<<8) & 0xFF00); }}
/** * \fn gc9a01a_itf_get_pixel * 读点 * \param[in] id 设备索引 * \param[in] x x坐标位置 * \param[in] y y坐标位置 * \return rgb565颜色*/uint16_t gc9a01a_itf_get_pixel(int id, uint16_t x, uint16_t y){ if(id == 0) { uint16_t color = s_gc9a01a_itf_dev1.buffer[y*GC9A01A_HSIZE + x]; return ((uint16_t)(color>>8) | (uint16_t)(color<<8)); } else if(id == 1) { uint16_t color = s_gc9a01a_itf_dev2.buffer[y*GC9A01A_HSIZE + x]; return ((uint16_t)(color>>8) | (uint16_t)(color<<8)); } return 0;}

gc9a01a_test.h/c也相应的增加两个屏幕的测试,修改后内容如下

gc9a01a_test.h

#ifndef GC9A01A_TEST_H#define GC9A01A_TEST_H
#ifdef __cplusplus extern "C"{#endif
#include
int gc9a01a_test(void);
#ifdef __cplusplus }#endif
#endif

gc9a01a_test.c


#include #include #include #include "timer.h"#include "gc9a01a_itf.h"#include "gc9a01a_test.h"
static void rgb_test(void){ for(int x=0;x { for(int y=0;y { gc9a01a_itf_set_pixel(0,x, y, 0xF800); gc9a01a_itf_set_pixel(1,x, y, 0xF800); } } gc9a01a_itf_sync(0); gc9a01a_itf_sync(1); os_delay(1000);
for(int x=0;x { for(int y=0;y { gc9a01a_itf_set_pixel(0,x, y, 0x07E0); gc9a01a_itf_set_pixel(1,x, y, 0x07E0); } } gc9a01a_itf_sync(0); gc9a01a_itf_sync(1); os_delay(1000);
for(int x=0;x { for(int y=0;y { gc9a01a_itf_set_pixel(0,x, y, 0x001F); gc9a01a_itf_set_pixel(1,x, y, 0x001F); } } gc9a01a_itf_sync(0); gc9a01a_itf_sync(1); os_delay(1000);}
int gc9a01a_test(void){ printf("gc9a01a test\r\n"); gc9a01a_itf_init(); rgb_test(); uint32_t start; uint32_t end; uint32_t ftime = 0; while(0) { start = timer_get_time(); for(int i=0;i<10;i++) { gc9a01a_itf_sync(0); gc9a01a_itf_sync(1); } end = timer_get_time(); ftime = (end - start);
uint32_t fps = (ftime*2+100)/(100*2); /* 刷新一次的时间uS */ if(fps > 0) { printf("FPS:%d\r\n",1000000/fps); } else { printf("FPS:%d\r\n",0); } } return 0;}

Gc9a01a.c/h不需要变化,内容如下

gc9a01a.c

#include "gc9a01a.h"
/** * \struct gc9a01a_cmd_st * 命令结构体*/typedef struct{ uint8_t cmd; /**< 命令 */ uint8_t data[12]; /**< 参数,最多5个参数 */ uint8_t datalen; /**< 参数长度 */ uint16_t delay; /**< 延时时间 */} gc9a01a_cmd_st;
static gc9a01a_cmd_st s_gc9a01a_cmd_init_list[]={ ///{0xEF,{0},0,0}, ///{0xEB,{0x14},1,0},
/* 很多寄存器访问都需要Inter_command为高(默认为低)所以先发FE和EF配置Inter_command为高 */ {GC9A01A_CMD_IRE1,{0},0,0}, {GC9A01A_CMD_IRE2,{0},0,0}, {0xEB,{0x14},1,0},
{0x84,{0x40},1,0}, {0x85,{0xFF},1,0}, {0x86,{0xFF},1,0}, {0x87,{0xFF},1,0}, {0x88,{0x0A},1,0}, {0x89,{0x21},1,0}, {0x8A,{0x00},1,0}, {0x8B,{0x80},1,0}, {0x8C,{0x01},1,0}, {0x8D,{0x01},1,0}, {0x8E,{0xFF},1,0}, {0x8F,{0xFF},1,0},
/* 设置GS SS * 第一个参数为0,第二个参数有效 * GS bit6 0:G1->G32 1:G32->G1 * SS bit5 0:S1->S360 1:S360->S1 */ {GC9A01A_CMD_DFC,{0x00,0x20},2,0}, /** * Memory Access Control * 7 6 5 4 3 2 1 0 * MY MX MV ML BGR MH 0 0 * Y反转 X反转 XY交换 垂直刷新方向 0-RGB 水平刷新方向 * 1-BGR */ {GC9A01A_CMD_MADCTL,{0x08},1,0}, /* Pixel Format Set *7 【6 5 4】 3 【2 1 0】 * DPI DBI * RGB接口 MCU接口 * 101 16位 011 12位 * 110 18位 101 16位 * 110 18位 */ {GC9A01A_CMD_COLMOD,{0x55},1,0},
{0x90,{0x08,0x08,0x08,0x08},4,0}, {0xBD,{0x06},1,0}, {0xBC,{0x00},1,0},
{0xFF,{0x60,0x01,0x04},3,0},
/* 电源控制 */ {GC9A01A_CMD_PC2,{0x13},1,0}, //vbp {GC9A01A_CMD_PC3,{0x13},1,0}, //vbn {GC9A01A_CMD_PC4,{0x22},1,0}, //vrh
{0xBE,{0x11},1,0}, {0xE1,{0x10,0x0E},2,0}, {0xDF,{0x21,0x0C,0x02},3,0}, /* 设置gamma曲线 */ {GC9A01A_CMD_SETGAMMA1,{0x45,0x09,0x08,0x08,0x26,0x2A},6,0}, //默认值 80 03 08 06 05 2B {GC9A01A_CMD_SETGAMMA2,{0x43,0x70,0x72,0x36,0x37,0x6F},6,0}, //默认值 41 97 98 13 17 CD {GC9A01A_CMD_SETGAMMA3,{0x45,0x09,0x08,0x08,0x26,0x2A},6,0}, //默认值 40 03 08 0B 08 2E {GC9A01A_CMD_SETGAMMA3,{0x43,0x70,0x72,0x36,0x37,0x6F},6,0}, //默认值 3F 98 B4 14 18 CD
{0xED,{0x1B,0x0B},2,0}, {0xAE,{0x77},1,0}, {0xED,{0x1B,0x0B},2,0}, {0xCD,{0x63},1,0},
{0x70,{0x07,0x07,0x04,0x0E,0x0F,0x09,0x07,0x08,0x03},9,0}, {0xEB,{0x34},1,0}, {0x62,{0x18,0x0D,0x71,0xED,0x70,0x70,0x18,0x0F,0x71,0xEF,0x70,0x70},12,0}, {0x63,{0x18,0x11,0x71,0xF1,0x70,0x70,0x18,0x13,0x71,0xF3,0x70,0x70},12,0}, {0x64,{0x28,0x29,0xF1,0x01,0xF1,0x00,0x07},7,0}, {0x66,{0x3C,0x00,0xCD,0x67,0x45,0x45,0x10,0x00,0x00,0x00},10,0}, {0x67,{0x00,0x3C,0x00,0x00,0x00,0x01,0x54,0x10,0x32,0x98},10,0}, {0x74,{0x10,0x85,0x80,0x00,0x00,0x4E,0x00},7,0},
{0x98,{0x3E,0x07},2,0},
{GC9A01A_CMD_TELON,{0},0,0}, /* Tearing Effect Line ON */ {GC9A01A_CMD_INVON, {0x00},0,0}, {GC9A01A_CMD_SLPOUT,{0 },0,120}, /**< SLPOUT (11h): Sleep Out */ {GC9A01A_CMD_DISPON,{0}, 0,20}, /**< DISPON (29h): Display On */};
/** * \fn gc9a01a_write_cmd * 写命令 * \param[in] dev \ref gc9a01a_dev_st * \param[in] cmd 命令字节 * \retval 0 成功 * \retval 其他值 失败*/static int gc9a01a_write_cmd(gc9a01a_dev_st* dev,uint8_t cmd){ uint8_t tmp;#if GC9A01A_CHECK_PARAM if(dev == (gc9a01a_dev_st*)0) { return -1; } if(dev->set_dcx == (gc9a01a_set_dcx_pf)0) { return -1; } if(dev->write == (gc9a01a_spi_write_pf)0) { return -1; }#endif tmp = cmd; dev->enable(1); dev->set_dcx(0); dev->write(&tmp,1); dev->enable(0); return 0;}
/** * \fn gc9a01a_write_data * 写数据 * \param[in] dev \ref gc9a01a_dev_st * \param[in] data 待写入数据 * \param[in] len 待写入数据长度 * \retval 0 成功 * \retval 其他值 失败*/static int gc9a01a_write_data(gc9a01a_dev_st* dev,uint8_t* data, uint32_t len){#if GC9A01A_CHECK_PARAM if(dev == (gc9a01a_dev_st*)0) { return -1; } if(dev->set_dcx == (gc9a01a_set_dcx_pf)0) { return -1; } if(dev->write == (gc9a01a_spi_write_pf)0) { return -1; }#endif dev->enable(1); dev->set_dcx(1); dev->write(data,len); dev->enable(0); return 0;}
/** * \fn gc9a01a_set_windows * 设置窗口范围(行列地址) * \param[in] dev \ref gc9a01a_dev_st * \param[in] data 待写入数据 * \param[in] len 待写入数据长度 * \retval 0 成功 * \retval 其他值 失败*/static int gc9a01a_set_windows(gc9a01a_dev_st* dev, uint16_t x0, uint16_t x1, uint16_t y0, uint16_t y1){ uint8_t data[4]; gc9a01a_write_cmd(dev, GC9A01A_CMD_CASET); data[0] = (x0>>8) & 0xFF; /* 列开始地址 大端 */ data[1] = x0 & 0xFF; data[2] = (x1>>8) & 0xFF; /* 列结束地址 大端 */ data[3] = x1 & 0xFF; gc9a01a_write_data(dev, data, 4);
gc9a01a_write_cmd(dev, GC9A01A_CMD_RASET); data[0] = (y0>>8) & 0xFF; /* 行开始地址 大端 */ data[1] = y0 & 0xFF; data[2] = (y1>>8) & 0xFF; /* 行结束地址 大端 */ data[3] = y1 & 0xFF; gc9a01a_write_data(dev, data, 4);
return 0;}
/** * \fn gc9a01a_sync * 现存写入gc9a01a * \param[in] dev \ref gc9a01a_dev_st * \paran[in] x0 列开始地址 * \paran[in] x1 列结束地址 * \paran[in] y0 行开始地址 * \paran[in] y1 行结束地址 * \paran[in] buffer 待写入数据 * \paran[in] len 待写入数据长度 * \retval 0 成功 * \retval 其他值 失败*/int gc9a01a_sync(gc9a01a_dev_st* dev, uint16_t x0, uint16_t x1, uint16_t y0, uint16_t y1, uint16_t* buffer, uint32_t len){ (void)dev; gc9a01a_set_windows(dev, x0, x1, y0, y1); gc9a01a_write_cmd(dev,GC9A01A_CMD_RAMWR); gc9a01a_write_data(dev, (uint8_t*)buffer, len); return 0;}
/** * \fn gc9a01a_init * 初始化 * \param[in] dev \ref gc9a01a_dev_st * \retval 0 成功 * \retval 其他值 失败*/int gc9a01a_init(gc9a01a_dev_st* dev){#if GC9A01A_CHECK_PARAM if(dev == (gc9a01a_dev_st*)0) { return -1; }#endif if(dev->init_flag != 0) { return 0; } dev->init_flag = 1; if(dev->init != 0) { dev->init(); } dev->set_reset(1); dev->delay(120); dev->set_reset(0); dev->delay(120); dev->set_reset(1); dev->delay(120);
/* 初始化序列 */ for(uint32_t i=0; i<sizeof(s_gc9a01a_cmd_init_list)/sizeof(s_gc9a01a_cmd_init_list[0]); i++) { gc9a01a_write_cmd(dev, s_gc9a01a_cmd_init_list[i].cmd); if(s_gc9a01a_cmd_init_list[i].datalen > 0) { gc9a01a_write_data(dev, s_gc9a01a_cmd_init_list[i].data,s_gc9a01a_cmd_init_list[i].datalen); if(s_gc9a01a_cmd_init_list[i].delay > 0) { dev->delay(s_gc9a01a_cmd_init_list[i].delay); } } }
return 0;}
/** * \fn gc9a01a_deinit * 解除初始化 * \param[in] dev \ref gc9a01a_dev_st * \return 总是返回0*/int gc9a01a_deinit(gc9a01a_dev_st* dev){#if GC9A01A_CHECK_PARAM if(dev == (gc9a01a_dev_st*)0) { return -1; }#endif
/* @todo 添加IO等解除初始化配置 */
if(dev->deinit != 0) { dev->deinit(); } return 0;}

gc9a01a.h

#ifndef GC9A01A_H#define GC9A01A_H
#ifdef __cplusplus extern "C"{#endif
#include
#define GC9A01A_CHECK_PARAM 1
typedef void (*gc9a01a_set_dcx_pf)(uint8_t val); /**< DCX引脚操作接口,val=1为数据和参数, val=0为命令 */typedef void (*gc9a01a_set_reset_pf)(uint8_t val); /**< 复位引脚操作,val=1输出高,val=0输出低 */typedef void (*gc9a01a_spi_write_pf)(uint8_t* buffer, uint32_t len); /**< MOSI写接口接口,buffer为待写数据,len为待写长度 */typedef void (*gc9a01a_spi_enable_pf)(uint8_t val); /**< 使能接口 */typedef void (*gc9a01a_spi_delay_ms_pf)(uint32_t t); /**< 延时接口 */typedef void (*gc9a01a_init_pf)(void); /**< 初始化接口 */typedef void (*gc9a01a_deinit_pf)(void); /**< 解除初始化接口 */
#define GC9A01A_CMD_SLPOUT 0x11 /* 退出SLEEP模式 */#define GC9A01A_CMD_INVON 0x21 /* 显示反转 */#define GC9A01A_CMD_DISPON 0x29 /* 打开显示 */#define GC9A01A_CMD_CASET 0x2A /* 设置列地址 */#define GC9A01A_CMD_RASET 0x2B /* 设置行地址 */#define GC9A01A_CMD_RAMWR 0x2C /* 写数据 */#define GC9A01A_CMD_MADCTL 0x36 /* 显存访问控制 */#define GC9A01A_CMD_COLMOD 0x3A /* 点格式 */#define GC9A01A_CMD_IRE1 0xFE /* Inter Register Enable1 */#define GC9A01A_CMD_IRE2 0xEF /* Inter Register Enable2 */#define GC9A01A_CMD_DFC 0xB6 /* Display Function Control */#define GC9A01A_CMD_PC2 0xC3 /* Power Control 2 */#define GC9A01A_CMD_PC3 0xC4 /* Power Control 3 */#define GC9A01A_CMD_PC4 0xC9 /* Power Control 4 */#define GC9A01A_CMD_TELON 0x35 /* Tearing Effect Line ON */#define GC9A01A_CMD_SETGAMMA1 0xF0 /*SET_GAMMA1 */#define GC9A01A_CMD_SETGAMMA2 0xF1 /*SET_GAMMA2 */#define GC9A01A_CMD_SETGAMMA3 0xF2 /*SET_GAMMA3 */#define GC9A01A_CMD_SETGAMMA4 0xF3 /*SET_GAMMA4 */
/** * \struct gc9a01a_dev_st * 设备接口结构体*/typedef struct{ gc9a01a_set_dcx_pf set_dcx; /**< DCX写接口 */ gc9a01a_set_reset_pf set_reset; /**< RESET写接口 */ gc9a01a_spi_write_pf write; /**< 数据写接口 */ gc9a01a_spi_enable_pf enable; /**< 使能接口 */ gc9a01a_spi_delay_ms_pf delay; /**< 延时接口 */ gc9a01a_init_pf init; /**< 初始化接口 */ gc9a01a_deinit_pf deinit; /**< 解除初始化接口 */
uint16_t* buffer; /**< 显存,用户分配 */ int init_flag; /**< 初始化标志 */ } gc9a01a_dev_st;
/** * \fn gc9a01a_sync * 现存写入gc9a01a * \param[in] dev \ref gc9a01a_dev_st * \paran[in] x0 列开始地址 * \paran[in] x1 列结束地址 * \paran[in] y0 行开始地址 * \paran[in] y1 行结束地址 * \paran[in] buffer 待写入数据 * \paran[in] len 待写入数据长度 * \retval 0 成功 * \retval 其他值 失败*/int gc9a01a_sync(gc9a01a_dev_st* dev, uint16_t x0, uint16_t x1, uint16_t y0, uint16_t y1, uint16_t* buffer, uint32_t len);
/** * \fn gc9a01a_init * 初始化 * \param[in] dev \ref gc9a01a_dev_st * \retval 0 成功 * \retval 其他值 失败*/int gc9a01a_init(gc9a01a_dev_st* dev);
/** * \fn gc9a01a_deinit * 解除初始化 * \param[in] dev \ref gc9a01a_dev_st * \return 总是返回0*/int gc9a01a_deinit(gc9a01a_dev_st* dev);
#ifdef __cplusplus }#endif
#endif

三. LVGL驱动注册

LVGL是通过数据结构lv_disp_t来绑定显示器的,

在调用lv_disp_drv_register注册驱动时会返回lv_disp_t*

此时用户就可以使用该返回的lv_disp_t*,作为参数, 在创建obj时指定obj绑定到该lv_disp_t

所以我们需要将原来的

void  lv_port_disp_init(void);

改为

lv_disp_t* lv_port_disp_init(void);

以返回lv_disp_t*给用户使用。

所以在原来一份驱动的基础上

lv_port_disp.c

lv_port_disp.h

改为2份驱动

lv_port_disp1.c

lv_port_disp1.h

lv_port_disp2.c

lv_port_disp2.h

分别提供初始化函数给用户调用

lv_disp_t* lv_port_disp1_init(void);

lv_disp_t* lv_port_disp2_init(void);

因为使用的是同样的屏幕

接口lcd_itf_set_pixel增加一个ID参数即可。

对应的

lv_port_disp1.c

lv_port_disp1.h

内容如下

/** * @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 "lcd_itf.h"#include "lv_port_disp1.h"#include #define MY_DISP_HOR_RES LCD_ITF_H_SIZE#define MY_DISP_VER_RES LCD_ITF_V_SIZE
/********************* * DEFINES *********************/#ifndef MY_DISP_HOR_RES #warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen width, default value 320 is used for now. #define MY_DISP_HOR_RES 320#endif
#ifndef MY_DISP_VER_RES #warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen height, default value 240 is used for now. #define MY_DISP_VER_RES 240#endif
/********************** * TYPEDEFS **********************/
/********************** * STATIC PROTOTYPES **********************/static void disp_init(void);
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);//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);
/********************** * STATIC VARIABLES **********************/
/********************** * MACROS **********************/
/********************** * GLOBAL FUNCTIONS **********************/
lv_disp_t* lv_port_disp1_init(void){ /*------------------------- * Initialize your display * -----------------------*/ disp_init();
/*----------------------------- * Create a buffer for drawing *----------------------------*/
/** * LVGL requires a buffer where it internally draws the widgets. * Later this buffer will passed to your display driver's `flush_cb` to copy its content to your display. * The buffer has to be greater than 1 display row * * There are 3 buffering configurations: * 1. Create ONE buffer: * LVGL will draw the display's content here and writes it to your display * * 2. Create TWO buffer: * LVGL will draw the display's content to a buffer and writes it your display. * You should use DMA to write the buffer's content to the display. * It will enable LVGL to draw the next part of the screen to the other buffer while * the data is being sent form the first buffer. It makes rendering and flushing parallel. * * 3. Double buffering * Set 2 screens sized buffers and set disp_drv.full_refresh = 1. * This way LVGL will always provide the whole rendered screen in `flush_cb` * and you only need to change the frame buffer's address. */
/* Example for 1) */ static lv_disp_draw_buf_t draw_buf_dsc_1; static lv_color_t buf_1[MY_DISP_HOR_RES * 10]; /*A buffer for 10 rows*/ lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*/
/* Example for 2) */ //static lv_disp_draw_buf_t draw_buf_dsc_2; //static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10]; /*A buffer for 10 rows*/ //static lv_color_t buf_2_2[MY_DISP_HOR_RES * 10]; /*An other buffer for 10 rows*/ //lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*/
/* Example for 3) also set disp_drv.full_refresh = 1 below*/ //static lv_disp_draw_buf_t draw_buf_dsc_3; //static lv_color_t buf_3_1[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*A screen sized buffer*/ //static lv_color_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*Another screen sized buffer*/ //lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2, // MY_DISP_VER_RES * LV_VER_RES_MAX); /*Initialize the display buffer*/
/*----------------------------------- * Register the display in LVGL *----------------------------------*/
static 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 = MY_DISP_HOR_RES; disp_drv.ver_res = MY_DISP_VER_RES;
/*Used to copy the buffer's content to the display*/ disp_drv.flush_cb = disp_flush;
/*Set a display buffer*/ disp_drv.draw_buf = &draw_buf_dsc_1;
/*Required for Example 3)*/ //disp_drv.full_refresh = 1;
/* Fill a memory array with a color if you have GPU. * Note that, in lv_conf.h you can enable GPUs that has built-in support in LVGL. * But if you have a different GPU you can use with this callback.*/ //disp_drv.gpu_fill_cb = gpu_fill;
/*Finally register the driver*/ return lv_disp_drv_register(&disp_drv);}
/********************** * STATIC FUNCTIONS **********************/
/*Initialize your display and the required peripherals.*/static void disp_init(void){ lcd_itf_init(); /*You code here*/}
volatile bool disp1_flush_enabled = true;
/* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL */void disp1_enable_update(void){ disp1_flush_enabled = true;}
/* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL */void disp1_disable_update(void){ disp1_flush_enabled = false;}
/*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){ if(disp1_flush_enabled) { /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
int32_t x; int32_t y; for(y = area->y1; y <= area->y2; y++) { for(x = area->x1; x <= area->x2; x++) { /*Put a pixel to the display. For example:*/ /*put_px(x, y, *color_p)*/ lcd_itf_set_pixel(0,x, y, *((uint16_t*)color_p)); color_p++; } } }
/*IMPORTANT!!! *Inform the graphics library that you are ready with the flushing*/ lv_disp_flush_ready(disp_drv);}
/*OPTIONAL: GPU INTERFACE*/
/*If your MCU has hardware accelerator (GPU) then you can use it to fill a memory with a color*///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*/// }//}
#else /*Enable this file at the top*/
/*This dummy typedef exists purely to silence -Wpedantic.*/typedef int keep_pedantic_happy;#endif
/** * @file lv_port_disp_templ.h * */
/*Copy this file as "lv_port_disp.h" and set this value to "1" to enable content*/#if 1
#ifndef LV_PORT_DISP1_TEMPL_H#define LV_PORT_DISP1_TEMPL_H
#ifdef __cplusplusextern "C" {#endif
/********************* * INCLUDES *********************/#if defined(LV_LVGL_H_INCLUDE_SIMPLE)#include "lvgl.h"#else#include "lvgl/lvgl.h"#endif
/********************* * DEFINES *********************/
/********************** * TYPEDEFS **********************/
/********************** * GLOBAL PROTOTYPES **********************//* Initialize low level display driver */lv_disp_t* lv_port_disp1_init(void);
/* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL */void disp1_enable_update(void);
/* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL */void disp1_disable_update(void);
/********************** * MACROS **********************/
#ifdef __cplusplus} /*extern "C"*/#endif
#endif /*LV_PORT_DISP_TEMPL_H*/
#endif /*Disable/Enable content*/

lv_port_disp2.c

lv_port_disp2.h

内容如下

/** * @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 "lcd_itf.h"#include "lv_port_disp2.h"#include #define MY_DISP_HOR_RES LCD_ITF_H_SIZE#define MY_DISP_VER_RES LCD_ITF_V_SIZE
/********************* * DEFINES *********************/#ifndef MY_DISP_HOR_RES #warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen width, default value 320 is used for now. #define MY_DISP_HOR_RES 320#endif
#ifndef MY_DISP_VER_RES #warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen height, default value 240 is used for now. #define MY_DISP_VER_RES 240#endif
/********************** * TYPEDEFS **********************/
/********************** * STATIC PROTOTYPES **********************/static void disp_init(void);
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);//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);
/********************** * STATIC VARIABLES **********************/
/********************** * MACROS **********************/
/********************** * GLOBAL FUNCTIONS **********************/
lv_disp_t* lv_port_disp2_init(void){ /*------------------------- * Initialize your display * -----------------------*/ disp_init();
/*----------------------------- * Create a buffer for drawing *----------------------------*/
/** * LVGL requires a buffer where it internally draws the widgets. * Later this buffer will passed to your display driver's `flush_cb` to copy its content to your display. * The buffer has to be greater than 1 display row * * There are 3 buffering configurations: * 1. Create ONE buffer: * LVGL will draw the display's content here and writes it to your display * * 2. Create TWO buffer: * LVGL will draw the display's content to a buffer and writes it your display. * You should use DMA to write the buffer's content to the display. * It will enable LVGL to draw the next part of the screen to the other buffer while * the data is being sent form the first buffer. It makes rendering and flushing parallel. * * 3. Double buffering * Set 2 screens sized buffers and set disp_drv.full_refresh = 1. * This way LVGL will always provide the whole rendered screen in `flush_cb` * and you only need to change the frame buffer's address. */
/* Example for 1) */ static lv_disp_draw_buf_t draw_buf_dsc_1; static lv_color_t buf_1[MY_DISP_HOR_RES * 10]; /*A buffer for 10 rows*/ lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*/
/* Example for 2) */ //static lv_disp_draw_buf_t draw_buf_dsc_2; //static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10]; /*A buffer for 10 rows*/ //static lv_color_t buf_2_2[MY_DISP_HOR_RES * 10]; /*An other buffer for 10 rows*/ //lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*/
/* Example for 3) also set disp_drv.full_refresh = 1 below*/ //static lv_disp_draw_buf_t draw_buf_dsc_3; //static lv_color_t buf_3_1[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*A screen sized buffer*/ //static lv_color_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*Another screen sized buffer*/ //lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2, // MY_DISP_VER_RES * LV_VER_RES_MAX); /*Initialize the display buffer*/
/*----------------------------------- * Register the display in LVGL *----------------------------------*/
static 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 = MY_DISP_HOR_RES; disp_drv.ver_res = MY_DISP_VER_RES;
/*Used to copy the buffer's content to the display*/ disp_drv.flush_cb = disp_flush;
/*Set a display buffer*/ disp_drv.draw_buf = &draw_buf_dsc_1;
/*Required for Example 3)*/ //disp_drv.full_refresh = 1;
/* Fill a memory array with a color if you have GPU. * Note that, in lv_conf.h you can enable GPUs that has built-in support in LVGL. * But if you have a different GPU you can use with this callback.*/ //disp_drv.gpu_fill_cb = gpu_fill;
/*Finally register the driver*/ return lv_disp_drv_register(&disp_drv);}
/********************** * STATIC FUNCTIONS **********************/
/*Initialize your display and the required peripherals.*/static void disp_init(void){ lcd_itf_init(); /*You code here*/}
volatile bool disp2_flush_enabled = true;
/* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL */void disp2_enable_update(void){ disp2_flush_enabled = true;}
/* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL */void disp2_disable_update(void){ disp2_flush_enabled = false;}
/*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){ if(disp2_flush_enabled) { /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
int32_t x; int32_t y; for(y = area->y1; y <= area->y2; y++) { for(x = area->x1; x <= area->x2; x++) { /*Put a pixel to the display. For example:*/ /*put_px(x, y, *color_p)*/ lcd_itf_set_pixel(1,x, y, *((uint16_t*)color_p)); color_p++; } } }
/*IMPORTANT!!! *Inform the graphics library that you are ready with the flushing*/ lv_disp_flush_ready(disp_drv);}
/*OPTIONAL: GPU INTERFACE*/
/*If your MCU has hardware accelerator (GPU) then you can use it to fill a memory with a color*///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*/// }//}
#else /*Enable this file at the top*/
/*This dummy typedef exists purely to silence -Wpedantic.*/typedef int keep_pedantic_happy;#endif
/** * @file lv_port_disp_templ.h * */
/*Copy this file as "lv_port_disp.h" and set this value to "1" to enable content*/#if 1
#ifndef LV_PORT_DISP2_TEMPL_H#define LV_PORT_DISP2_TEMPL_H
#ifdef __cplusplusextern "C" {#endif
/********************* * INCLUDES *********************/#if defined(LV_LVGL_H_INCLUDE_SIMPLE)#include "lvgl.h"#else#include "lvgl/lvgl.h"#endif
/********************* * DEFINES *********************/
/********************** * TYPEDEFS **********************/
/********************** * GLOBAL PROTOTYPES **********************//* Initialize low level display driver */lv_disp_t* lv_port_disp2_init(void);
/* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL */void disp2_enable_update(void);
/* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL */void disp2_disable_update(void);
/********************** * MACROS **********************/
#ifdef __cplusplus} /*extern "C"*/#endif
#endif /*LV_PORT_DISP_TEMPL_H*/
#endif /*Disable/Enable content*/

.测试

分别在两个屏幕各创建一个按钮显示不同内容


static void btn_event_cb(lv_event_t * e){    lv_event_code_t code = lv_event_get_code(e);    lv_obj_t * btn = lv_event_get_target(e);    if(code == LV_EVENT_CLICKED) {        static uint8_t cnt = 0;        cnt++;
        /*Get the first child of the button which is the label and change its text*/        lv_obj_t * label = lv_obj_get_child(btn, 0);        lv_label_set_text_fmt(label, "Button: %d", cnt);    }}
/** * Create a button with a label and react on click event. */static void lv_example_get_started_1(lv_disp_t* disp1, lv_disp_t* disp2){    lv_obj_t * btn1 = lv_btn_create(lv_disp_get_scr_act(disp1));     /*Add a button the current screen*/    lv_obj_set_pos(btn1, (LCD_ITF_H_SIZE-120)/2, (LCD_ITF_V_SIZE-50)/2);                            /*Set its position*/    lv_obj_set_size(btn1, 120, 50);                          /*Set its size*/    lv_obj_add_event_cb(btn1, btn_event_cb, LV_EVENT_ALL, NULL);           /*Assign a callback to the button*/
    lv_obj_t * label1 = lv_label_create(btn1);          /*Add a label to the button*/    lv_label_set_text(label1, "This is disp1!");                     /*Set the labels text*/    lv_obj_center(label1);
    lv_obj_t * btn2 = lv_btn_create(lv_disp_get_scr_act(disp2));     /*Add a button the current screen*/    lv_obj_set_pos(btn2, (LCD_ITF_H_SIZE-120)/2, (LCD_ITF_V_SIZE-50)/2);                            /*Set its position*/    lv_obj_set_size(btn2, 120, 50);                     /*Set its size*/    lv_obj_add_event_cb(btn2, btn_event_cb, LV_EVENT_ALL, NULL);           /*Assign a callback to the button*/
    lv_obj_t * label2 = lv_label_create(btn2);          /*Add a label to the button*/    lv_label_set_text(label2, "This is disp2!");                     /*Set the labels text*/    lv_obj_center(label2);}
static lv_disp_t* s_disp1;static lv_disp_t* s_disp2;

执行

while(1)    {        lv_log_register_print_cb(my_print);
        lv_init();        s_disp1 = lv_port_disp1_init();        if(s_disp1 == (lv_disp_t*)0)        {            printf("disp1 init err\r\n");        }        s_disp2 = lv_port_disp2_init();        if(s_disp2 == (lv_disp_t*)0)        {            printf("disp2 init err\r\n");        }        lv_example_get_started_1(s_disp1,s_disp2);        while(1)        {                   uint32_t delay = lv_timer_handler();            if (delay < 1) delay = 1;            os_delay(delay);        }        lv_deinit();    }

效果如下

五. MusicDemo

Demo中接口增加lv_disp_t参数由用户传入以绑定不同显示器,同时对应的list等全局变量也增加一份,根据不同显示器区分索引。

lv_demo_music.h改为如下

/** * @file lv_demo_music.h * */
#ifndef LV_DEMO_MUSIC_H#define LV_DEMO_MUSIC_H
#ifdef __cplusplusextern "C" {#endif
/********************* * INCLUDES *********************/#include "../lv_demos.h"
#if LV_USE_DEMO_MUSIC
/********************* * DEFINES *********************/
#if LV_DEMO_MUSIC_LARGE# define LV_DEMO_MUSIC_HANDLE_SIZE 40#else# define LV_DEMO_MUSIC_HANDLE_SIZE 20#endif
/********************** * TYPEDEFS **********************/
/********************** * GLOBAL PROTOTYPES **********************/
void lv_demo_music(lv_disp_t* disp1, lv_disp_t* disp2);void lv_demo_music_close(lv_disp_t* disp1, lv_disp_t* disp2);
const char * _lv_demo_music_get_title(uint32_t track_id);const char * _lv_demo_music_get_artist(uint32_t track_id);const char * _lv_demo_music_get_genre(uint32_t track_id);uint32_t _lv_demo_music_get_track_length(uint32_t track_id);
/********************** * MACROS **********************/
#endif /*LV_USE_DEMO_MUSIC*/
#ifdef __cplusplus} /* extern "C" */#endif
#endif /*LV_DEMO_MUSIC_H*/

lv_demo_music.c改为如下

/** * @file lv_demo_music.c * */
/********************* * INCLUDES *********************/#include "lv_demo_music.h"
#if LV_USE_DEMO_MUSIC
#include "lv_demo_music_main.h"#include "lv_demo_music_list.h"
/********************* * DEFINES *********************/
/********************** * TYPEDEFS **********************/
/********************** * STATIC PROTOTYPES **********************/#if LV_DEMO_MUSIC_AUTO_PLAY static void auto_step_cb(lv_timer_t * timer);#endif
/********************** * STATIC VARIABLES **********************/static lv_obj_t * ctrl[2];static lv_obj_t * list[2];
static const char * title_list[] = { "Waiting for true love", "Need a Better Future", "Vibrations", "Why now?", "Never Look Back", "It happened Yesterday", "Feeling so High", "Go Deeper", "Find You There", "Until the End", "Unknown", "Unknown", "Unknown", "Unknown",};
static const char * artist_list[] = { "The John Smith Band", "My True Name", "Robotics", "John Smith", "My True Name", "Robotics", "Robotics", "Unknown artist", "Unknown artist", "Unknown artist", "Unknown artist", "Unknown artist", "Unknown artist", "Unknown artist", "Unknown artist",};
static const char * genre_list[] = { "Rock - 1997", "Drum'n bass - 2016", "Psy trance - 2020", "Metal - 2015", "Metal - 2015", "Metal - 2015", "Metal - 2015", "Metal - 2015", "Metal - 2015", "Metal - 2015", "Metal - 2015", "Metal - 2015", "Metal - 2015", "Metal - 2015",};
static const uint32_t time_list[] = { 1 * 60 + 14, 2 * 60 + 26, 1 * 60 + 54, 2 * 60 + 24, 2 * 60 + 37, 3 * 60 + 33, 1 * 60 + 56, 3 * 60 + 31, 2 * 60 + 20, 2 * 60 + 19, 2 * 60 + 20, 2 * 60 + 19, 2 * 60 + 20, 2 * 60 + 19,};
#if LV_DEMO_MUSIC_AUTO_PLAY static lv_timer_t * auto_step_timer[2]; #endif
static lv_color_t original_screen_bg_color[2];
/********************** * MACROS **********************/
/********************** * GLOBAL FUNCTIONS **********************/
void lv_demo_music(lv_disp_t* disp1, lv_disp_t* disp2){ original_screen_bg_color[0] = lv_obj_get_style_bg_color(lv_disp_get_scr_act(disp1), 0); lv_obj_set_style_bg_color(lv_disp_get_scr_act(disp1), lv_color_hex(0x343247), 0); original_screen_bg_color[1] = lv_obj_get_style_bg_color(lv_disp_get_scr_act(disp2), 0); lv_obj_set_style_bg_color(lv_disp_get_scr_act(disp2), lv_color_hex(0x343247), 0);
list[0] = _lv_demo_music_list_create(lv_disp_get_scr_act(disp1)); list[1] = _lv_demo_music_list_create(lv_disp_get_scr_act(disp2)); ctrl[0] = _lv_demo_music_main_create(lv_disp_get_scr_act(disp1)); ctrl[1] = _lv_demo_music_main_create(lv_disp_get_scr_act(disp2));#if LV_DEMO_MUSIC_AUTO_PLAY auto_step_timer[0] = lv_timer_create(auto_step_cb, 1000, (void*)0); auto_step_timer[1] = lv_timer_create(auto_step_cb, 1000, (void*)1);#endif}
void lv_demo_music_close(lv_disp_t* disp1, lv_disp_t* disp2){ /*Delete all aniamtions*/ lv_anim_del(NULL, NULL);
#if LV_DEMO_MUSIC_AUTO_PLAY lv_timer_del(auto_step_timer[0]); lv_timer_del(auto_step_timer[1]);#endif _lv_demo_music_list_close(); _lv_demo_music_main_close();
lv_obj_clean(lv_disp_get_scr_act(disp1)); lv_obj_clean(lv_disp_get_scr_act(disp2));
lv_obj_set_style_bg_color(lv_disp_get_scr_act(disp1), original_screen_bg_color[0], 0); lv_obj_set_style_bg_color(lv_disp_get_scr_act(disp2), original_screen_bg_color[1], 0);}
const char * _lv_demo_music_get_title(uint32_t track_id){ if(track_id >= sizeof(title_list) / sizeof(title_list[0])) return NULL; return title_list[track_id];}
const char * _lv_demo_music_get_artist(uint32_t track_id){ if(track_id >= sizeof(artist_list) / sizeof(artist_list[0])) return NULL; return artist_list[track_id];}
const char * _lv_demo_music_get_genre(uint32_t track_id){ if(track_id >= sizeof(genre_list) / sizeof(genre_list[0])) return NULL; return genre_list[track_id];}
uint32_t _lv_demo_music_get_track_length(uint32_t track_id){ if(track_id >= sizeof(time_list) / sizeof(time_list[0])) return 0; return time_list[track_id];}
/********************** * STATIC FUNCTIONS **********************/
#if LV_DEMO_MUSIC_AUTO_PLAYstatic void auto_step_cb(lv_timer_t * t){ void* user_data = t->user_data; int id = (int)user_data; LV_UNUSED(t); static uint32_t state = 0;
#if LV_DEMO_MUSIC_LARGE const lv_font_t * font_small = &lv_font_montserrat_22; const lv_font_t * font_large = &lv_font_montserrat_32;#else const lv_font_t * font_small = &lv_font_montserrat_12; const lv_font_t * font_large = &lv_font_montserrat_16;#endif
switch(state) { case 5: _lv_demo_music_album_next(true); break;
case 6: _lv_demo_music_album_next(true); break; case 7: _lv_demo_music_album_next(true); break; case 8: _lv_demo_music_play(0); break;#if LV_DEMO_MUSIC_SQUARE || LV_DEMO_MUSIC_ROUND case 11: lv_obj_scroll_by(ctrl[id], 0, -LV_VER_RES, LV_ANIM_ON); break; case 13: lv_obj_scroll_by(ctrl[id], 0, -LV_VER_RES, LV_ANIM_ON); break;#else case 12: lv_obj_scroll_by(ctrl[id], 0, -LV_VER_RES, LV_ANIM_ON); break;#endif case 15: lv_obj_scroll_by(list[id], 0, -300, LV_ANIM_ON); break; case 16: lv_obj_scroll_by(list[id], 0, 300, LV_ANIM_ON); break; case 18: _lv_demo_music_play(1); break; case 19: lv_obj_scroll_by(ctrl[id], 0, LV_VER_RES, LV_ANIM_ON); break;#if LV_DEMO_MUSIC_SQUARE || LV_DEMO_MUSIC_ROUND case 20: lv_obj_scroll_by(ctrl[id], 0, LV_VER_RES, LV_ANIM_ON); break;#endif case 30: _lv_demo_music_play(2); break; case 40: { lv_obj_t * bg = lv_layer_top(); lv_obj_set_style_bg_color(bg, lv_color_hex(0x6f8af6), 0); lv_obj_set_style_text_color(bg, lv_color_white(), 0); lv_obj_set_style_bg_opa(bg, LV_OPA_COVER, 0); lv_obj_fade_in(bg, 400, 0); lv_obj_t * dsc = lv_label_create(bg); lv_obj_set_style_text_font(dsc, font_small, 0); lv_label_set_text(dsc, "The average FPS is"); lv_obj_align(dsc, LV_ALIGN_TOP_MID, 0, 90);
lv_obj_t * num = lv_label_create(bg); lv_obj_set_style_text_font(num, font_large, 0);#if LV_USE_PERF_MONITOR lv_label_set_text_fmt(num, "%ld", lv_refr_get_fps_avg());#endif lv_obj_align(num, LV_ALIGN_TOP_MID, 0, 120);
lv_obj_t * attr = lv_label_create(bg); lv_obj_set_style_text_align(attr, LV_TEXT_ALIGN_CENTER, 0); lv_obj_set_style_text_font(attr, font_small, 0);#if LV_DEMO_MUSIC_SQUARE || LV_DEMO_MUSIC_ROUND lv_label_set_text(attr, "Copyright 2020 LVGL Kft.\nwww.lvgl.io | lvgl@lvgl.io");#else lv_label_set_text(attr, "Copyright 2020 LVGL Kft. | www.lvgl.io | lvgl@lvgl.io");#endif lv_obj_align(attr, LV_ALIGN_BOTTOM_MID, 0, -10); break; } case 41: lv_scr_load(lv_obj_create(NULL)); _lv_demo_music_pause(); break; } state++;}
#endif /*LV_DEMO_MUSIC_AUTO_PLAY*/
#endif /*LV_USE_DEMO_MUSIC*/

执行如下

while(1)    {        lv_log_register_print_cb(my_print);
        lv_init();        s_disp1 = lv_port_disp1_init();        if(s_disp1 == (lv_disp_t*)0)        {            printf("disp1 init err\r\n");        }        s_disp2 = lv_port_disp2_init();        if(s_disp2 == (lv_disp_t*)0)        {            printf("disp2 init err\r\n");        }        lv_demo_music(s_disp1,s_disp2);        while(1)        {                   uint32_t delay = lv_timer_handler();            if (delay < 1) delay = 1;            os_delay(delay);        }        lv_deinit();    }

两份实例,所以lcd_conf.h中堆要增大,原来48k改为128k

#define LV_MEM_SIZE (128 * 1024U) /*[bytes]*/

效果见视频,

这里实际并没有修改完整,demo中用到的obj对象实例,都要根据两个显示器准备两份,绑定到不同的显示器,这里只作为演示就不花时间去全部修改了。

六. 总结

LVGL的多显示支持比较简单,只需要注册多个驱动,根据注册返回的lv_disp_t*, 用户即可根据该lv_disp_t* 来操作对象位于哪个显示器。


评论
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 68浏览
  • 学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&
    youyeye 2024-11-30 14:30 78浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 119浏览
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 108浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 101浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 98浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 122浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 120浏览
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 71浏览
  • TOF多区传感器: ND06   ND06是一款微型多区高集成度ToF测距传感器,其支持24个区域(6 x 4)同步测距,测距范围远达5m,具有测距范围广、精度高、测距稳定等特点。适用于投影仪的无感自动对焦和梯形校正、AIoT、手势识别、智能面板和智能灯具等多种场景。                 如果用ND06进行手势识别,只需要经过三个步骤: 第一步&
    esad0 2024-12-04 11:20 50浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 84浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 106浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 100浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦