LISA Display 双屏异显示例
源码位置: samples/drivers/devices/lisa_display/dual_display 查看源码
功能说明
演示如何使用 LISA Display 驱动同时控制两个独立的显示屏,实现多屏异显功能。每个显示屏可以使用不同的 Panel 驱动和总线接口,独立显示不同的内容。
示例配置
Display0: ST7789P3 面板 + SPI 4-Wire 总线(240x240)
Display1: AXS15231B 面板 + QSPI 总线(176x560)
核心特性
多 Panel 驱动: 同时启用多个 Panel 驱动(ST7789P3 + AXS15231B)
多总线类型: 支持不同总线接口(SPI 4-Wire + QSPI)
独立控制: 每个显示屏独立配置亮度、颜色、刷新频率
异步显示: 两个屏幕显示不同内容,互不干扰
线程安全: 驱动内部使用双层锁机制,支持多线程并发访问
智能锁管理: 自动识别共享总线并管理锁资源,无需用户干预
硬件连接
Display0: ST7789P3 (SPI 4-Wire)
信号 |
ARCS-D 引脚 |
说明 |
|---|---|---|
VCC |
3.3V |
电源 |
GND |
GND |
地 |
CS |
GPIOB_5 |
片选 |
DC |
GPIOB_0 |
数据/命令选择 |
RESET |
GPIOA_1 |
复位 |
SCK |
GPIOB_3 (SPI1_CLK) |
SPI 时钟 |
MOSI |
GPIOB_1 (SPI1_MOSI) |
SPI 数据 |
BL |
PWM0_CH0 (GPIOA_0) |
背光 PWM |
注意: 此配置基于 ARCS EVB 板型,对应代码中的宏定义:
LCD0_RST_PIN = 1(GPIOA)LCD0_CS_PIN = 5(GPIOB)LCD0_DC_PIN = 0(GPIOB)LCD0_SPI_CLK_PIN = 3(GPIOB, 复用为 SPI1_CLK)LCD0_SPI_DATA_PIN = 1(GPIOB, 复用为 SPI1_MOSI)LCD0_BL_PWM_CHANNEL = 0
Display1: AXS15231B (QSPI)
信号 |
ARCS-D 引脚 |
说明 |
|---|---|---|
VCC |
3.3V |
电源 |
GND |
GND |
地 |
CS |
GPIOA_19 |
片选 |
RESET |
GPIOA_2 |
复位 |
QSPI_CLK |
QSPI_CLK |
QSPI 时钟 |
QSPI_D0 |
QSPI_D0 |
QSPI 数据 0 |
QSPI_D1 |
QSPI_D1 |
QSPI 数据 1 |
QSPI_D2 |
QSPI_D2 |
QSPI 数据 2 |
QSPI_D3 |
QSPI_D3 |
QSPI 数据 3 |
BL |
PWM0_CH1 (GPIOA_x) |
背光 PWM |
注意: 此配置基于 ARCS EVB 板型,对应代码中的宏定义:
LCD1_RST_PIN = 2(GPIOA)LCD1_CS_PIN = 19(GPIOA)LCD1_BL_PWM_CHANNEL = 1
Kconfig 配置
在 prj.conf 中的关键配置:
# 启用双显示屏支持
CONFIG_LISA_DUAL_DISPLAY=y
# Display 驱动
CONFIG_LISA_DISPLAY_DEVICE=y
# Panel 驱动(支持多选)
CONFIG_LISA_DISPLAY_PANEL_ST7789P3=y
CONFIG_PANEL_ST7789P3_WIDTH=240
CONFIG_PANEL_ST7789P3_HEIGHT=320
CONFIG_LISA_DISPLAY_PANEL_AXS15231B=y
CONFIG_PANEL_AXS15231B_WIDTH=176
CONFIG_PANEL_AXS15231B_HEIGHT=560
# 总线驱动
CONFIG_LISA_DISPLAY_BUS_SPI_4WIRE=y
CONFIG_LISA_DISPLAY_BUS_QSPI=y
# 背光控制
CONFIG_LISA_DISPLAY_BACKLIGHT_PWM=y
# PSRAM 配置(双屏需要更大内存)
CONFIG_PSRAM_HEAP_SIZE=0x200000 # 2MB
示例流程
初始化 Display0 (ST7789P3)
获取 display 设备和相关外设(GPIO、SPI、PWM)
配置 SPI 4-Wire 总线参数
设置 Panel 设备名称为 “st7789p3”
分配帧缓冲区
设置亮度为 80%
初始化 Display1 (AXS15231B)
获取 display1 设备和相关外设(GPIO、QSPI、PWM)
配置 QSPI 总线参数
设置 Panel 设备名称为 “axs15231b”
分配帧缓冲区
设置亮度为 60%
循环显示
Display0 和 Display1 显示不同颜色
每 2 秒切换到下一个颜色
颜色序列:红 → 绿 → 蓝 → 黄 → 青 → 品红 → 白
编译
# 使用提供的脚本
./build.sh
# 或者手动编译
lisa zep build -b csk6011a_nano -B build
重要提示:在编译前,请先确认您使用的开发板型号。SDK 目前支持以下开发板:
arcs_evb - ARCS EVB 评估板
arcs_mini - ARCS Mini 开发板
根据您的开发板型号,选择对应的编译命令:
在 SDK 根目录执行编译:
# 使用 arcs_evb 开发板
./build.sh -C -S samples/<示例路径> -DBOARD=arcs_evb
# 或使用 arcs_mini 开发板
./build.sh -C -S samples/<示例路径> -DBOARD=arcs_mini
Note
确保已安装对应的工具链。
预期输出
终端输出:
Dual Display sample started
Initializing Display0 (ST7789P3)...
Display0 initialized: 240x240
Initializing Display1 (AXS15231B)...
Display1 initialized: 176x560
Both displays initialized successfully
Display0: color index 0
Display1: color index 3
Display0: color index 1
Display1: color index 4
...
显示效果:
Display0 (240x240): 以 80% 亮度显示,颜色从红色开始循环
Display1 (176x560): 以 60% 亮度显示,颜色从黄色开始循环
两个屏幕同步切换,但显示不同颜色
核心代码
Display0 配置
lisa_display_config_t config0 = {
.panel_name = "st7789p3", // Panel 设备名称
.bus_type = LISA_DISPLAY_BUS_SPI_4WIRE,
.bus_instance = 0,
.bus_config = {
.spi_4wire = {
.spi_dev = spi1,
.cs_gpio = gpiob,
.cs_pin = LCD0_CS_PIN,
.dc_gpio = gpiob,
.dc_pin = LCD0_DC_PIN,
.spi_freq = 50 * 1000 * 1000, // 50MHz
}
},
.backlight = {
.type = LISA_DISPLAY_BACKLIGHT_TYPE_PWM,
.config.pwm = {
.dev = pwm0,
.channel = 0, // PWM 通道 0
.freq = 2000,
}
},
.rst_gpio = gpioa,
.rst_pin = LCD0_RST_PIN,
};
Display1 配置
lisa_display_config_t config1 = {
.panel_name = "axs15231b", // Panel 设备名称
.bus_type = LISA_DISPLAY_BUS_QSPI,
.bus_instance = 0,
.bus_config = {
.qspi = {
.qspi_dev = qspi,
.qspi_freq = 50 * 1000 * 1000, // 50MHz
.cs_gpio = gpioa,
.cs_pin = LCD1_CS_PIN,
}
},
.backlight = {
.type = LISA_DISPLAY_BACKLIGHT_TYPE_PWM,
.config.pwm = {
.dev = pwm0,
.channel = 1, // PWM 通道 1(不同于 Display0)
.freq = 2000,
}
},
.rst_gpio = gpioa,
.rst_pin = LCD1_RST_PIN,
};
关键配置差异
配置项 |
Display0 |
Display1 |
|---|---|---|
Panel 设备名称 |
“st7789p3” |
“axs15231b” |
总线类型 |
SPI 4-Wire |
QSPI |
设备名称 |
display |
display1 |
PWM 通道 |
0 |
1 |
复位引脚 |
GPIOA_1 |
GPIOA_2 |
核心 API
API |
说明 |
|---|---|
|
获取 Display0 设备实例 |
|
获取 Display1 设备实例 |
|
关联总线配置到显示设备 |
|
获取显示屏分辨率等参数 |
|
开启显示 |
|
写入帧数据到显示屏 |
|
设置背光亮度(0-100) |
故障排查
问题 1: Display0 或 Display1 初始化失败
可能原因:
Panel 驱动未启用(检查 Kconfig)
设备名称配置错误(panel_name 与驱动注册名称不一致)
总线设备未找到(检查设备注册)
解决方法:
// 检查设备是否存在
lisa_device_t *dev = lisa_device_get("display");
if (!dev) {
LISA_LOGE(LOG_TAG, "display not found!");
}
// 检查 Panel 设备是否存在
lisa_device_t *panel = lisa_device_get("st7789p3");
if (!panel) {
LISA_LOGE(LOG_TAG, "st7789p3 panel not found!");
}
问题 2: 屏幕无显示
可能原因:
硬件连接错误
引脚配置不正确(检查 pinmux)
背光未开启或 PWM 通道冲突
解决方法:
检查硬件连线,确保电源、地、信号线正确
确认
lisa_display_blanking_off()被调用检查两个显示屏使用不同的 PWM 通道
验证背光 PWM 配置正确
问题 3: 内存分配失败
可能原因:
PSRAM 配置不足(双屏需要更多内存)
解决方法:
增加
CONFIG_PSRAM_HEAP_SIZE(建议至少 2MB)检查帧缓冲区大小计算是否正确
问题 4: 颜色显示异常
可能原因:
需要启用颜色反转
像素格式配置错误
解决方法:
尝试开启/关闭
CONFIG_LISA_DISPLAY_COLOR_INVERT检查 Panel 配置中的像素格式设置
线程安全特性
本示例演示的多屏异显功能具备完整的线程安全保护:
1. 双层锁机制
驱动内部使用两层互斥锁保护:
总线级锁(Bus Mutex): 保护硬件总线访问
如果两个 Display 使用相同的总线设备(如共享 SPI),会自动共享同一个锁
如果使用独立总线(如本示例:Display0 用 SPI,Display1 用 QSPI),则各自使用独立的锁
零配置:驱动自动判断并管理,无需用户干预
资源级锁(Rotate Mutex): 保护全局旋转缓冲区
所有使用 90°/270° 旋转的 Display 共享全局旋转资源
自动互斥访问,防止数据竞争
2. SPI 一主多从场景
如果两个显示屏共享同一个 SPI 总线(只是 CS 引脚不同):
// 两个屏幕都使用 spi1,但 CS 引脚不同
lisa_display_config_t config0 = {
.panel_name = "st7789p3",
.bus_type = LISA_DISPLAY_BUS_SPI_4WIRE,
.bus_config.spi_4wire = {
.spi_dev = spi1, // 相同的 SPI 设备
.cs_pin = 1, // 不同的 CS 引脚
// ...
},
};
lisa_display_config_t config1 = {
.panel_name = "axs15231b",
.bus_type = LISA_DISPLAY_BUS_SPI_4WIRE,
.bus_config.spi_4wire = {
.spi_dev = spi1, // 相同的 SPI 设备
.cs_pin = 2, // 不同的 CS 引脚
// ...
},
};
驱动会自动:
检测到两个 Display 使用相同的
bus_dev指针共享同一个总线锁,确保不会同时访问
无需用户添加任何同步代码
3. 多线程使用示例
// 线程 1:更新 Display0
void thread1(void *arg)
{
while (1) {
// 安全调用,驱动内部有锁保护
lisa_display_write(display0, 0, 0, &desc0, buffer0);
lisa_thread_mdelay(100);
}
}
// 线程 2:更新 Display1
void thread2(void *arg)
{
while (1) {
// 安全调用,即使与线程 1 并发执行也不会冲突
lisa_display_write(display1, 0, 0, &desc1, buffer1);
lisa_thread_mdelay(100);
}
}
线程安全保证:
每个 Display 设备有独立的设备级锁
共享总线的 Display 会互斥访问总线
使用旋转功能时会互斥访问全局旋转缓冲区
所有错误路径都正确释放锁,不会死锁
注意事项
设备名称:
panel_name必须与 Panel 驱动注册时使用的设备名称一致ST7789P3 驱动注册为 “st7789p3”
AXS15231B 驱动注册为 “axs15231b”
资源独立性: 确保两个显示屏使用不同的资源
不同的 PWM 通道(避免冲突)
不同的 GPIO 引脚(RST、CS、DC)
独立的帧缓冲区
内存管理: 双屏需要分配两个帧缓冲区,确保 PSRAM 足够大
Display0: 240×240×2 = 115,200 字节
Display1: 176×560×2 = 197,120 字节
总计约 312KB,建议配置 2MB PSRAM
总线频率: 根据实际硬件调整 SPI/QSPI 频率,过高可能导致显示异常
引脚复用: 示例中重写了 pinmux 函数,确保引脚配置符合硬件设计
线程安全: 驱动完全线程安全,可以在多个线程中同时操作不同的 Display
共享总线时会自动互斥,保证总线访问安全
使用旋转功能时会自动互斥全局旋转缓冲区
所有同步机制对用户透明,无需额外代码
扩展应用
显示不同内容
// Display0 显示图像
void display0_show_image(const uint16_t *image_data, uint16_t width, uint16_t height)
{
lisa_display_buffer_desc_t desc = {
.width = width,
.height = height,
.buf_size = width * height * sizeof(uint16_t),
};
lisa_display_write(display0_ctx.device, 0, 0, &desc, image_data);
}
// Display1 显示文本(需配合 GUI 库)
void display1_show_text(const char *text)
{
// 使用 LVGL 或其他 GUI 库渲染文本到 framebuffer
// ...
lisa_display_buffer_desc_t desc = {
.width = display1_ctx.width,
.height = display1_ctx.height,
.buf_size = display1_ctx.width * display1_ctx.height * sizeof(uint16_t),
};
lisa_display_write(display1_ctx.device, 0, 0, &desc, display1_ctx.framebuffer);
}
动态调整亮度
// 根据环境光调整亮度
void adjust_brightness(uint8_t brightness0, uint8_t brightness1)
{
lisa_display_set_brightness(display0_ctx.device, brightness0);
lisa_display_set_brightness(display1_ctx.device, brightness1);
}
文件说明
src/main.c- 示例主程序prj.conf- Kconfig 配置CMakeLists.txt- 构建配置sample.yaml- 测试配置build.sh- 构建脚本README.md- 本文档
性能优化
1. 无锁开销
本示例中 Display0 和 Display1 使用不同总线(SPI vs QSPI)
两个 Display 各自使用独立的总线锁,互不影响
无锁竞争,无性能损失
2. CPU/DMA 并行
旋转操作使用 Ping-Pong 缓冲
CPU 旋转下一块数据时,DMA 传输当前数据
最大化硬件利用率
3. CPDMA 加速(可选)
# 启用硬件 DMA 加速旋转
CONFIG_LISA_DISPLAY_CPDMA_ROTATE=y
CONFIG_LISA_DISPLAY_CPDMA_CH=2