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* 来操作对象位于哪个显示器。


评论
  • 物联网(IoT)的快速发展彻底改变了从智能家居到工业自动化等各个行业。由于物联网系统需要高效、可靠且紧凑的组件来处理众多传感器、执行器和通信设备,国产固态继电器(SSR)已成为满足中国这些需求的关键解决方案。本文探讨了国产SSR如何满足物联网应用的需求,重点介绍了它们的优势、技术能力以及在现实场景中的应用。了解物联网中的固态继电器固态继电器是一种电子开关设备,它使用半导体而不是机械触点来控制负载。与传统的机械继电器不同,固态继电器具有以下优势:快速切换:确保精确快速的响应,这对于实时物联网系统至
    克里雅半导体科技 2025-01-03 16:11 176浏览
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 97浏览
  •     为控制片内设备并且查询其工作状态,MCU内部总是有一组特殊功能寄存器(SFR,Special Function Register)。    使用Eclipse环境调试MCU程序时,可以利用 Peripheral Registers Viewer来查看SFR。这个小工具是怎样知道某个型号的MCU有怎样的寄存器定义呢?它使用一种描述性的文本文件——SVD文件。这个文件存储在下面红色字体的路径下。    例:南京沁恒  &n
    电子知识打边炉 2025-01-04 20:04 79浏览
  • 随着市场需求不断的变化,各行各业对CPU的要求越来越高,特别是近几年流行的 AIOT,为了有更好的用户体验,CPU的算力就要求更高了。今天为大家推荐由米尔基于瑞芯微RK3576处理器推出的MYC-LR3576核心板及开发板。关于RK3576处理器国产CPU,是这些年的骄傲,华为手机全国产化,国人一片呼声,再也不用卡脖子了。RK3576处理器,就是一款由国产是厂商瑞芯微,今年第二季推出的全新通用型的高性能SOC芯片,这款CPU到底有多么的高性能,下面看看它的几个特性:8核心6 TOPS超强算力双千
    米尔电子嵌入式 2025-01-03 17:04 48浏览
  • 在快速发展的能源领域,发电厂是发电的支柱,效率和安全性至关重要。在这种背景下,国产数字隔离器已成为现代化和优化发电厂运营的重要组成部分。本文探讨了这些设备在提高性能方面的重要性,同时展示了中国在生产可靠且具有成本效益的数字隔离器方面的进步。什么是数字隔离器?数字隔离器充当屏障,在电气上将系统的不同部分隔离开来,同时允许无缝数据传输。在发电厂中,它们保护敏感的控制电路免受高压尖峰的影响,确保准确的信号处理,并在恶劣条件下保持系统完整性。中国国产数字隔离器经历了重大创新,在许多方面达到甚至超过了全球
    克里雅半导体科技 2025-01-03 16:10 122浏览
  • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
    知白 2025-01-06 12:04 110浏览
  • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
    优思学院 2025-01-06 12:03 76浏览
  • PLC组态方式主要有三种,每种都有其独特的特点和适用场景。下面来简单说说: 1. 硬件组态   定义:硬件组态指的是选择适合的PLC型号、I/O模块、通信模块等硬件组件,并按照实际需求进行连接和配置。    灵活性:这种方式允许用户根据项目需求自由搭配硬件组件,具有较高的灵活性。    成本:可能需要额外的硬件购买成本,适用于对系统性能和扩展性有较高要求的场合。 2. 软件组态   定义:软件组态主要是通过PLC
    丙丁先生 2025-01-06 09:23 71浏览
  • 光耦合器,也称为光隔离器,是一种利用光在两个隔离电路之间传输电信号的组件。在医疗领域,确保患者安全和设备可靠性至关重要。在众多有助于医疗设备安全性和效率的组件中,光耦合器起着至关重要的作用。这些紧凑型设备经常被忽视,但对于隔离高压和防止敏感医疗设备中的电气危害却是必不可少的。本文深入探讨了光耦合器的功能、其在医疗应用中的重要性以及其实际使用示例。什么是光耦合器?它通常由以下部分组成:LED(发光二极管):将电信号转换为光。光电探测器(例如光电晶体管):检测光并将其转换回电信号。这种布置确保输入和
    腾恩科技-彭工 2025-01-03 16:27 171浏览
  • 本文介绍Linux系统更换开机logo方法教程,通用RK3566、RK3568、RK3588、RK3576等开发板,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。制作图片开机logo图片制作注意事项(1)图片必须为bmp格式;(2)图片大小不能大于4MB;(3)BMP位深最大是32,建议设置为8;(4)图片名称为logo.bmp和logo_kernel.bmp;开机
    Industio_触觉智能 2025-01-06 10:43 72浏览
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 92浏览
  • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
    GIRtina 2025-01-06 11:10 87浏览
  • 自动化已成为现代制造业的基石,而驱动隔离器作为关键组件,在提升效率、精度和可靠性方面起到了不可或缺的作用。随着工业技术不断革新,驱动隔离器正助力自动化生产设备适应新兴趋势,并推动行业未来的发展。本文将探讨自动化的核心趋势及驱动隔离器在其中的重要角色。自动化领域的新兴趋势智能工厂的崛起智能工厂已成为自动化生产的新标杆。通过结合物联网(IoT)、人工智能(AI)和机器学习(ML),智能工厂实现了实时监控和动态决策。驱动隔离器在其中至关重要,它确保了传感器、执行器和控制单元之间的信号完整性,同时提供高
    腾恩科技-彭工 2025-01-03 16:28 166浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦