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


评论
  • 在当今竞争激烈的市场环境中,企业不仅需要优化成本,还需积极响应国家的能源政策,减少对环境的影响。提升工业能源效率正是实现这一双重目标的关键。中国近年来大力推进“双碳”目标(碳达峰、碳中和),并出台了一系列政策鼓励企业节能减排。通过宏集CODRA的Panorama解决方案,企业可以获得专为这一目标设计的SCADA工具,实时监控和调整所有工业设备的能耗。特别是其中的能源管理模块,能够有效分析数据,预防故障,避免能源浪费。Panorama的优化技术宏集CODRA提供的解决方案,尤其是Panorama
    宏集科技 2025-03-06 11:25 115浏览
  • ASL6328芯片支持高达 6.0 Gbps 运行速率的交流和直流耦合输入T-MDS 信号,具备可编程均衡和抖动清理功能。ASL6328 是一款单端口 HDMI/DVI 电平转换 / 中继器,具有重新定时功能。它包含 TypeC双模式 DP 线缆适配器寄存器,可用于识别线缆适配器的性能。抖动清理 PLL(锁相环)能够消除输入抖动,并完全重置系统抖动容限,因此能更好地满足更高数据速率下 HDMI 抖动合规性要求。设备的运行和配置可通过引脚设置或 I2C 总线实现。自动断电和静噪功能提供了灵活的电
    QQ1540182856 2025-03-06 14:26 84浏览
  • 1. 背景在汽车电子系统测试中,CANoe作为主流的仿真测试工具,常需与云端服务器、第三方软件或物联网设备进行交互。随着CANoe与外部软件、服务器或设备交互越来越多,直接使用Socket进行通信往往不能满足使用需求,依托于CANoe 的连接功能集(Connectivity Feature Set),以及Distributed Object(DO)功能,可以仿真HTTP节点,实现设备与服务器等之间的通信,保证数据处理的可靠性和便捷性。本文详细解析如何利用CANoe搭建HTTP测试环境,并提供典型
    北汇信息 2025-03-05 11:56 86浏览
  • 多人同时共享相同无线网络,以下场景是否是您熟悉的日常?姐姐:「妈~我在房间在线上课,影音一直断断续续的怎么上课啊!」奶奶:「媳妇啊~我在在线追剧,影片一直卡卡的,实在让人生气!」除此之外,同时间有老公在跟客户开在线会议,还有弟弟在玩在线游戏,而妈妈自己其实也在客厅追剧,同时间加总起来,共有五个人同时使用这个网络!我们不论是在家里、咖啡厅、餐厅、商场或是公司,都会面临到周遭充斥着非常多的无线路由器(AP),若同时间每位使用者透过手机、平板或是笔电连接到相同的一个网络,可想而知网络上的壅塞及相互干扰
    百佳泰测试实验室 2025-03-06 16:50 32浏览
  • 文/Leon编辑/cc孙聪颖2025年全国两会进行时,作为“十四五”规划收官之年,本届两会释放出坚定目标、稳中求进、以进促稳等信号。其中,企业家们的建议备受关注,关系到民营经济在2025年的走向。作为国内科技制造业的“老兵”,全国人大代表、TCL集团创始人及董事长李东生在本届两会中提出三份代表建议,包括《关于优化中国科技制造业融资环境的建议》、《关于加强AI深度伪造欺诈管理的建议》和《关于降低灵活就业人员社会保险参保门槛的建议》,表现出对科技制造、AI发展和劳动者保障方面的关注。会后,李东生接受
    华尔街科技眼 2025-03-06 19:41 31浏览
  • 随着自动驾驶技术的迅猛发展,构建高保真、动态的仿真场景成为了行业的迫切需求。传统的三维重建方法在处理复杂场景时常常面临效率和精度的挑战。在此背景下,3D高斯点阵渲染(3DGS)技术应运而生,成为自动驾驶仿真场景重建的关键突破。一、3DGS技术概述与原理1、3DGS的技术概述3DGS是一种基于3D高斯分布的三维场景表示方法。通过将场景中的对象转化为多个3D高斯点,每个点包含位置、协方差矩阵和不透明度等信息,3DGS能够精确地表达复杂场景的几何形状和光照特性。与传统的神经辐射场(NeRF)方法相比,
    康谋 2025-03-06 13:17 119浏览
  • 产品质量合格率偏低会引起质量成本(也称“劣质成本”)的大幅增加。质量成本通常分为内部损失成本和外部损失成本两部分。内部损失成本是指产品交付前因质量不合格造成的损失,包括返工、报废等;外部损失成本是指产品交付后因质量问题导致的损失,如退货、召回等。此外,质量问题还会影响生产效率,带来额外人工和停工损失。下面分别介绍各类损失的具体计算方法和公式。直接成本损失(内部故障成本)直接成本是由于产品在出厂前质量不合格所造成的看得见的损失。常见的直接损失包括返工、报废以及由此产生的额外原材料消耗等。返工成本:
    优思学院 2025-03-05 15:25 77浏览
  • 在六西格玛项目中,团队的选择往往决定了最终的成败。合适的团队成员不仅能推动项目顺利进行,更能确保最终成果符合预期。因此,组建六西格玛团队时,必须挑选最合适的人才,确保他们具备必要的能力和特质。团队主管的关键特质每个精益六西格玛项目都需要一位主管来带领团队。他们不仅需要具备领导力,还要能够分析数据、制定策略,并与管理层和团队成员高效沟通。团队主管的核心职责包括:领导团队行动:能够激励成员,确保团队朝着既定目标前进。数据分析能力:精通数据处理和分析,能基于数据做出决策。沟通协调:能够在管理层和团队之
    优思学院 2025-03-06 12:51 92浏览
  • 文/Leon编辑/侯煜‍2008至2021年间,创维以高举高打的凌厉之势,果断进行投资,一度成为中国市场大屏OLED产业的旗手,引领着显示技术的发展方向。但近年来,创维在 OLED 领域的发展轨迹却逐渐模糊,态度陷入暧昧不明的混沌状态。究其根源,一方面,创维对过往的押注难以割舍,在技术革新与市场变化的浪潮中,不愿轻易推翻曾经的战略布局;另一方面,早期在大屏OLED 技术研发、市场推广等环节投入的巨额资金,已然形成沉没成本,极大地限制了创维在显示技术路线上的重新抉择。但市场瞬息万变,为适应激烈的行
    华尔街科技眼 2025-03-05 20:03 144浏览
  • 服务器应用环境与客户需求PCIe 5.0高速接口技术的成熟驱动着生成式AI与高效能运算等相关应用蓬勃发展。在随着企业对服务器性能的要求日益严苛,服务器更新换代的周期也持续加快。在此背景下,白牌与DIY(Do It Yourself)服务器市场迎来了新的发展契机,但同时也面临着更趋复杂的技术挑战。传统上,白牌与DIY服务器以其高度客制化与成本效益优势受到市场青睐。然而,随着PCIe 5.0等高速技术的导入,服务器系统的复杂度大幅提升,对组装技术与组件兼容性也就提出更高的要求。举个简单的例子来说,P
    百佳泰测试实验室 2025-03-06 17:00 37浏览
  • 案例1 2008款保时捷卡宴车行驶中发动机偶发熄火故障现象 一辆2008款保时捷卡宴车,搭载4.8 L 自然吸气发动机,累计行驶里程约为21万km。车主反映,该车行驶中发动机偶发熄火;重新起动,发动机能够起动着机,只是起动时间延长,且组合仪表上的发动机故障灯异常点亮。 故障诊断接车后试车,发动机起动及怠速运转正常。用故障检测仪检测,发动机控制单元(DME)中存储有故障代码“P0335 曲轴位置传感器A电路”,由此怀疑曲轴位置传感器信号偶尔异常,导致发动机熄火。用虹科Pico汽车示波器测
    虹科Pico汽车示波器 2025-03-05 11:00 62浏览
  • 概述随着工业4.0的深入推进,制造业对自动化和智能化的需求日益增长。传统生产线面临空间不足、效率低下、灵活性差等问题,尤其在现有工厂改造项目中,如何在有限空间内实现高效自动化成为一大挑战。此次项目的客户需要在现有工厂基础上进行改造,空间有限。为此,客户选择了SCARA型线性轴机器人作为执行设备。然而,SCARA机器人的高效运行离不开强大的控制系统支持。宏集凭借其先进的智能控制系统,为客户提供了高效、灵活的自动化解决方案,确保SCARA机器人在有限空间内发挥最大效能。一、客户需求在此次改造项目中,
    宏集科技 2025-03-06 11:27 120浏览
  • 引言嘿,各位电动汽车的爱好者们!咱们今儿个就来聊聊电动汽车里那些“看不见,摸不着”,但又至关重要的零部件。要说电动汽车这玩意儿,那可真是科技含量满满,各种高精尖的技术都往里堆。但要让这些高科技玩意儿协同工作,稳定可靠地运转,那就得靠一些幕后英雄,比如说——电容器。你可能会想,电容器?这不就是电子电路里常见的元件嘛,能有多重要? 哎,你可别小瞧了这小小的电容器。在电动汽车的心脏地带——高压直流转换器(DC-DC转换器)里,车规级的电容器那可是扮演着举足轻重的角色。 今天,咱们就聚焦分析三星电机车规
    贞光科技 2025-03-05 17:02 90浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦