一. 前言
前面系列文章我们分享了ST7789和GC9A01A的驱动,并移植了LVGL和emWin跑了Demo。LVGL本身支持多显示器,刚好手里也有多块基于GC9A01A的1.28寸圆屏,这一篇我们就在之前的基础上演示LVGL的多屏显示。
二. 驱动修改
前面我们分享LVGL移植时可以看到,显示驱动移植就是根据模板实现用户自己的显示函数即可,然后注册对应的驱动。对于显示器驱动可能用的一套底层驱动使用id区分不同显示器,也可能是完全不同的驱动,不管是什么方式,对于LVGL只需要注册多个驱动即可,一个驱动对应一个显示器。
我们的1.28寸屏使用SPI驱动,为了减少引脚使用所以两个屏幕共用DI,CLK和DC三个引脚,CS和RST两个屏幕独立使用,所以一共7个引脚,采用分时刷屏方式。所以驱动也只需要在原来的基础上稍微修改,接口中增加一个ID参数用于区分显示器即可。
gc9a01a_itf.h/c相应的接口中,增加ID参数用于区分显示屏,相应的增加gc9a01a设备实例,和CS,RST等控制接口区分,修改后内容如下
gc9a01a_itf.h
extern "C"{
/**
* \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);
}
gc9a01a_itf.c
/******************************************************************************
* 以下是底层适配
*
******************************************************************************/
/* 使用IO模拟SPI方式 */
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);
}
/* 使用硬件SPI方式 */
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);
}
/******************************************************************************
* 以下是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)
{
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++;
}
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
extern "C"{
int gc9a01a_test(void);
}
gc9a01a_test.c
static void rgb_test(void)
{
for(int x=0;x<GC9A01A_HSIZE;x++)
{
for(int y=0;y<GC9A01A_VSIZE;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<GC9A01A_HSIZE;x++)
{
for(int y=0;y<GC9A01A_VSIZE;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<GC9A01A_HSIZE;x++)
{
for(int y=0;y<GC9A01A_VSIZE;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
/**
* \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(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;
}
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(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;
}
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(dev == (gc9a01a_dev_st*)0)
{
return -1;
}
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(dev == (gc9a01a_dev_st*)0)
{
return -1;
}
/* @todo 添加IO等解除初始化配置 */
if(dev->deinit != 0)
{
dev->deinit();
}
return 0;
}
gc9a01a.h
extern "C"{
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); /**< 解除初始化接口 */
/**
* \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);
}
三. 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*/
/*********************
* INCLUDES
*********************/
/*********************
* DEFINES
*********************/
/**********************
* 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*/
// }
//}
/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
/**
* @file lv_port_disp_templ.h
*
*/
/*Copy this file as "lv_port_disp.h" and set this value to "1" to enable content*/
extern "C" {
/*********************
* INCLUDES
*********************/
/*********************
* 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
**********************/
} /*extern "C"*/
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*/
/*********************
* INCLUDES
*********************/
/*********************
* DEFINES
*********************/
/**********************
* 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*/
// }
//}
/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
/**
* @file lv_port_disp_templ.h
*
*/
/*Copy this file as "lv_port_disp.h" and set this value to "1" to enable content*/
extern "C" {
/*********************
* INCLUDES
*********************/
/*********************
* 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
**********************/
} /*extern "C"*/
四.测试
分别在两个屏幕各创建一个按钮显示不同内容
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
*
*/
extern "C" {
/*********************
* INCLUDES
*********************/
/*********************
* DEFINES
*********************/
/**********************
* 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
**********************/
} /* extern "C" */
lv_demo_music.c改为如下
/**
* @file lv_demo_music.c
*
*/
/*********************
* INCLUDES
*********************/
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void auto_step_cb(lv_timer_t * timer);
/**********************
* 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,
};
static lv_timer_t * auto_step_timer[2];
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));
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);
}
void lv_demo_music_close(lv_disp_t* disp1, lv_disp_t* disp2)
{
/*Delete all aniamtions*/
lv_anim_del(NULL, NULL);
lv_timer_del(auto_step_timer[0]);
lv_timer_del(auto_step_timer[1]);
_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
**********************/
static 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;
const lv_font_t * font_small = &lv_font_montserrat_22;
const lv_font_t * font_large = &lv_font_montserrat_32;
const lv_font_t * font_small = &lv_font_montserrat_12;
const lv_font_t * font_large = &lv_font_montserrat_16;
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;
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;
case 12:
lv_obj_scroll_by(ctrl[id], 0, -LV_VER_RES, LV_ANIM_ON);
break;
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;
case 20:
lv_obj_scroll_by(ctrl[id], 0, LV_VER_RES, LV_ANIM_ON);
break;
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);
lv_label_set_text_fmt(num, "%ld", lv_refr_get_fps_avg());
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);
lv_label_set_text(attr, "Copyright 2020 LVGL Kft.\nwww.lvgl.io | lvgl@lvgl.io");
lv_label_set_text(attr, "Copyright 2020 LVGL Kft. | www.lvgl.io | lvgl@lvgl.io");
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++;
}
执行如下
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* 来操作对象位于哪个显示器。