GD32 TLI显示控制器Zephyr驱动移植详解
摘要
本文详细介绍如何将GD32F527的TLI(Touch LCD Interface)显示控制器移植到Zephyr RTOS中,实现一键配置功能。通过Zephyr的MEMC子系统实现SDRAM自动初始化,DISPLAY子系统实现TLI自动初始化,让用户无需编写任何初始化代码即可使用LCD显示功能。
一、概述
1.1 背景
GD32F527I-EVAL开发板板载480x272分辨率RGB LCD显示屏,需要通过TLI(Touch LCD Interface)接口驱动。以往的裸机开发需要用户手动编写大量初始化代码,包括:
- EXMC SDRAM初始化- TLI GPIO配置- PLLSAI像素时钟配置- TLI时序参数配置- 帧缓冲区内存管理
本文的目标是将这些初始化工作移植到Zephyr驱动层,实现自动初始化,用户只需在配置文件中指定LCD参数即可。
1.2 系统架构
二、驱动设计
2.1 设备树绑定
首先需要定义TLI设备的设备树绑定(Device Tree Binding),让Zephyr能够识别并配置TLI硬件。
创建文件 `dts/bindings/display/gd,gd32-tli.yaml`:
在GD32F5xx SoC设备树中添加TLI节点:
修改 `dts/arm/gd/gd32f5xx/gd32f5xx.dtsi`:
在GD32F527-EVAL开发板设备树中启用TLI:
修改 `boards/gd/gd32f527_eval/gd32f527_eval.dts`:
3.1 Kconfig配置
创建 `drivers/display/Kconfig.gd32_tli`:
3.2 驱动源代码
创建 `drivers/display/display_gd32_tli.c`:
修改 `drivers/display/CMakeLists.txt`,添加条件编译:
修改 `drivers/display/Kconfig`:
四、应用示例
4.1 项目配置
用户只需在 `prj.conf` 中启用相关配置:
4.2 图形库使用
图形库头文件定义了基本的宏和API:
5.1 驱动初始化时机
使用 `DEVICE_DEFINE` 宏注册设备,配合 `POST_KERNEL` 初始化级别,确保驱动在其他系统组件初始化之前完成初始化:
DEVICE_DEFINE(gd32_tli, "GD32_TLI",
帧缓冲区直接映射到SDRAM地址 `0xC0000000`,这是EXMC映射的起始地址。TLI控制器会自动从这个地址读取像素数据并发送到LCD显示。
5.3 像素时钟配置
TLI需要特定的像素时钟频率。使用PLLSAI产生30MHz像素时钟:/* PLLSAI = 240MHz, DIV8 = 30MHz */
通过本文的移植方案,实现了以下目标:
1. 一键配置:用户只需在prj.conf中配置相关宏即可,无需编写初始化代码2. 自动初始化:SDRAM和TLI均由Zephyr驱动自动初始化3. 统一接口:通过Zephyr标准DISPLAY子系统接口访问4. 易于扩展:驱动架构清晰,便于后续添加新功能
该方案已成功在GD32F527I-EVAL开发板上验证,LCD可正常显示图形内容。
## 参考资料
- Zephyr Project Documentation: https://docs.zephyrproject.org/- GD32F5xx User Manual- GD32F5xx Firmware Library
摘要
本文详细介绍如何将GD32F527的TLI(Touch LCD Interface)显示控制器移植到Zephyr RTOS中,实现一键配置功能。通过Zephyr的MEMC子系统实现SDRAM自动初始化,DISPLAY子系统实现TLI自动初始化,让用户无需编写任何初始化代码即可使用LCD显示功能。
一、概述
1.1 背景
GD32F527I-EVAL开发板板载480x272分辨率RGB LCD显示屏,需要通过TLI(Touch LCD Interface)接口驱动。以往的裸机开发需要用户手动编写大量初始化代码,包括:
- EXMC SDRAM初始化- TLI GPIO配置- PLLSAI像素时钟配置- TLI时序参数配置- 帧缓冲区内存管理
本文的目标是将这些初始化工作移植到Zephyr驱动层,实现自动初始化,用户只需在配置文件中指定LCD参数即可。
1.2 系统架构
二、驱动设计2.1 设备树绑定
首先需要定义TLI设备的设备树绑定(Device Tree Binding),让Zephyr能够识别并配置TLI硬件。
创建文件 `dts/bindings/display/gd,gd32-tli.yaml`:
# SPDX-License-Identifier: Apache-2.0 description: GD32 TLI (Touch LCD Interface) Display Controller include: base.yaml properties: compatible: const: "gd,gd32-tli" reg: description: TLI registers base address required: true interrupts: description: TLI interrupt required: true clocks: description: TLI clock source required: true # Display parameters width: type: int required: true description: Display width in pixels height: type: int required: true description: Display height in pixels hsync: type: int required: false default: 41 description: Horizontal sync pulse width hbp: type: int required: false default: 2 description: Horizontal back porch hfp: type: int required: false default: 2 description: Horizontal front porch vsync: type: int required: false default: 10 description: Vertical sync pulse width vbp: type: int required: false default: 2 description: Vertical back porch vfp: type: int required: false default: 2 description: Vertical front porch framebuffer: type: phandle required: false description: Framebuffer memory location (typically SDRAM)2.2 SoC设备树
在GD32F5xx SoC设备树中添加TLI节点:
修改 `dts/arm/gd/gd32f5xx/gd32f5xx.dtsi`:
tli: tli@40017000 {
compatible = "gd,gd32-tli";
reg = <0x40017000 0x400>;
interrupts = <84 0>;
clocks = <&cctl GD32_CLOCK_TLI>;
status = "disabled";
}; 2.3 板级设备树在GD32F527-EVAL开发板设备树中启用TLI:
修改 `boards/gd/gd32f527_eval/gd32f527_eval.dts`:
&tli {
status = "okay";
compatible = "gd,gd32-tli";
reg = <0x40017000 0x400>;
interrupts = <84 0>;
width = <480>;
height = <272>;
hsync = <41>;
hbp = <2>;
hfp = <2>;
vsync = <10>;
vbp = <2>;
vfp = <2>;
framebuffer = <0xC0000000>;
}; 三、驱动实现3.1 Kconfig配置
创建 `drivers/display/Kconfig.gd32_tli`:
# SPDX-License-Identifier: Apache-2.0 config DISPLAY_GD32_TLI bool "GD32 TLI Display Controller support" depends on DISPLAY && GPIO help Enable support for GD32 TLI (Touch LCD Interface) display controller. This driver configures the TLI peripheral for RGB LCD displays and provides framebuffer access.
3.2 驱动源代码
创建 `drivers/display/display_gd32_tli.c`:
/*
* GD32 TLI (Touch LCD Interface) Display Driver
* Simplified version - uses fixed configuration
*/
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/display.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/logging/log.h>
#include <gd32f5xx_tli.h>
#include <gd32f5xx_rcu.h>
#include <gd32f5xx_gpio.h>
LOG_MODULE_REGISTER(display_gd32_tli, CONFIG_DISPLAY_LOG_LEVEL);
/* SystemCoreClock is defined in GD32 HAL/startup code */
extern uint32_t SystemCoreClock;
/* LCD Configuration - matches GD32F527I-EVAL with 480x272 display */
#define LCD_WIDTH 480
#define LCD_HEIGHT 272
#define LCD_HSYNC 41
#define LCD_HBP 2
#define LCD_HFP 2
#define LCD_VSYNC 10
#define LCD_VBP 2
#define LCD_VFP 2
/* Framebuffer at SDRAM base */
#define FRAMEBUFFER_ADDR 0xC0000000
struct display_gd32_tli_data {
uint8_t *frame_buffer;
enum display_pixel_format current_format;
uint8_t pixel_size;
};
struct display_gd32_tli_config {
uint32_t base_addr;
};
static int gd32_tli_init(const struct device *dev)
{
struct display_gd32_tli_data *data = (struct display_gd32_tli_data *)dev->data;
LOG_INF("GD32 TLI Display Driver initializing...");
LOG_INF("Display size: %dx%d", LCD_WIDTH, LCD_HEIGHT);
LOG_INF("Framebuffer: 0x%08X", FRAMEBUFFER_ADDR);
/* Enable TLI clock */
rcu_periph_clock_enable(RCU_TLI);
/* Configure GPIO for TLI */
/* GPIOB - Backlight control */
rcu_periph_clock_enable(RCU_GPIOB);
gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_PIN_15);
gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_15);
gpio_bit_set(GPIOB, GPIO_PIN_15);
/* GPIOE - TLI signals */
rcu_periph_clock_enable(RCU_GPIOE);
gpio_af_set(GPIOE, GPIO_AF_14, GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6);
gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6);
gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6);
/* GPIOH - TLI data bus */
rcu_periph_clock_enable(RCU_GPIOH);
gpio_af_set(GPIOH, GPIO_AF_14, GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_8 | GPIO_PIN_9 |
GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15);
gpio_mode_set(GPIOH, GPIO_MODE_AF, GPIO_PUPD_NONE,
GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_8 | GPIO_PIN_9 |
GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15);
gpio_output_options_set(GPIOH, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,
GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_8 | GPIO_PIN_9 |
GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15);
/* GPIOI - TLI data bus */
rcu_periph_clock_enable(RCU_GPIOI);
gpio_af_set(GPIOI, GPIO_AF_14, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_4 |
GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_9 | GPIO_PIN_10);
gpio_mode_set(GPIOI, GPIO_MODE_AF, GPIO_PUPD_NONE,
GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_4 |
GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_9 | GPIO_PIN_10);
gpio_output_options_set(GPIOI, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,
GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_4 |
GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_9 | GPIO_PIN_10);
/* GPIOG - TLI signals */
rcu_periph_clock_enable(RCU_GPIOG);
gpio_af_set(GPIOG, GPIO_AF_14, GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12);
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12);
/* GPIOF - TLI clock */
rcu_periph_clock_enable(RCU_GPIOF);
gpio_af_set(GPIOF, GPIO_AF_14, GPIO_PIN_10);
gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_10);
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
LOG_INF("TLI GPIO configured");
/* Configure PLLSAI for pixel clock */
/* PLLSAI = 240MHz, DIV8 = 30MHz pixel clock for 480x272 */
if (ERROR == rcu_pllsai_r_config(240, 2)) {
LOG_ERR("Failed to configure PLLSAI!");
return -EIO;
}
rcu_tli_clock_div_config(RCU_PLLSAIR_DIV8);
rcu_osci_on(RCU_PLLSAI_CK);
if (ERROR == rcu_osci_stab_wait(RCU_PLLSAI_CK)) {
LOG_ERR("PLLSAI not stable!");
return -EIO;
}
LOG_INF("TLI clock configured");
/* Configure TLI */
tli_parameter_struct tli_init_struct;
tli_layer_parameter_struct tli_layer_init_struct;
/* Signal polarity */
tli_init_struct.signalpolarity_hs = TLI_HSYN_ACTLIVE_LOW;
tli_init_struct.signalpolarity_vs = TLI_VSYN_ACTLIVE_LOW;
tli_init_struct.signalpolarity_de = TLI_DE_ACTLIVE_LOW;
tli_init_struct.signalpolarity_pixelck = TLI_PIXEL_CLOCK_TLI;
/* Timing */
tli_init_struct.synpsz_hpsz = LCD_HSYNC - 1;
tli_init_struct.synpsz_vpsz = LCD_VSYNC - 1;
tli_init_struct.backpsz_hbpsz = LCD_HSYNC + LCD_HBP - 1;
tli_init_struct.backpsz_vbpsz = LCD_VSYNC + LCD_VBP - 1;
tli_init_struct.activesz_hasz = LCD_HSYNC + LCD_HBP + LCD_WIDTH - 1;
tli_init_struct.activesz_vasz = LCD_VSYNC + LCD_VBP + LCD_HEIGHT - 1;
tli_init_struct.totalsz_htsz = LCD_HSYNC + LCD_HBP + LCD_WIDTH + LCD_HFP - 1;
tli_init_struct.totalsz_vtsz = LCD_VSYNC + LCD_VBP + LCD_HEIGHT + LCD_VFP - 1;
/* Background color */
tli_init_struct.backcolor_red = 0;
tli_init_struct.backcolor_green = 0;
tli_init_struct.backcolor_blue = 0;
tli_init(&tli_init_struct);
/* Layer configuration */
tli_layer_init_struct.layer_window_leftpos = LCD_HSYNC + LCD_HBP;
tli_layer_init_struct.layer_window_rightpos = LCD_HSYNC + LCD_HBP + LCD_WIDTH - 1;
tli_layer_init_struct.layer_window_toppos = LCD_VSYNC + LCD_VBP;
tli_layer_init_struct.layer_window_bottompos = LCD_VSYNC + LCD_VBP + LCD_HEIGHT - 1;
tli_layer_init_struct.layer_ppf = LAYER_PPF_RGB565;
tli_layer_init_struct.layer_sa = 0xFF;
tli_layer_init_struct.layer_default_blue = 0;
tli_layer_init_struct.layer_default_green = 0;
tli_layer_init_struct.layer_default_red = 0;
tli_layer_init_struct.layer_default_alpha = 0;
tli_layer_init_struct.layer_acf1 = LAYER_ACF1_PASA;
tli_layer_init_struct.layer_acf2 = LAYER_ACF2_PASA;
tli_layer_init_struct.layer_frame_bufaddr = FRAMEBUFFER_ADDR;
tli_layer_init_struct.layer_frame_line_length = ((LCD_WIDTH * 2) + 3);
tli_layer_init_struct.layer_frame_buf_stride_offset = (LCD_WIDTH * 2);
tli_layer_init_struct.layer_frame_total_line_number = LCD_HEIGHT;
tli_layer_init(LAYER0, &tli_layer_init_struct);
/* Enable layer */
tli_layer_enable(LAYER0);
/* Reload configuration */
tli_reload_config(TLI_FRAME_BLANK_RELOAD_EN);
/* Enable TLI */
tli_enable();
/* Store framebuffer info */
data->frame_buffer = (uint8_t *)FRAMEBUFFER_ADDR;
data->current_format = PIXEL_FORMAT_RGB_565;
data->pixel_size = 2;
LOG_INF("GD32 TLI initialized successfully");
return 0;
}
/* Display driver API implementation */
static void gd32_tli_get_capabilities(const struct device *dev,
struct display_capabilities *capabilities)
{
ARG_UNUSED(dev);
capabilities->x_resolution = LCD_WIDTH;
capabilities->y_resolution = LCD_HEIGHT;
capabilities->supported_pixel_formats = BIT(PIXEL_FORMAT_RGB_565);
capabilities->current_pixel_format = PIXEL_FORMAT_RGB_565;
capabilities->screen_info = 0;
}
static void *gd32_tli_get_framebuffer(const struct device *dev)
{
struct display_gd32_tli_data *data = (struct display_gd32_tli_data *)dev->data;
return data->frame_buffer;
}
/* Device instantiation */
DEVICE_DEFINE(gd32_tli, "GD32_TLI",
gd32_tli_init,
NULL,
&display_gd32_tli_data,
&display_gd32_tli_config,
POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY,
&gd32_tli_api);3.3 CMakeLists.txt更新修改 `drivers/display/CMakeLists.txt`,添加条件编译:
zephyr_library_sources_ifdef(CONFIG_DISPLAY_GD32_TLI display_gd32_tli.c)
修改 `drivers/display/Kconfig`:
source "drivers/display/Kconfig.gd32_tli"
四、应用示例
4.1 项目配置
用户只需在 `prj.conf` 中启用相关配置:
# Enable Display subsystem CONFIG_DISPLAY=y CONFIG_DISPLAY_GD32_TLI=y # Enable MEMC for SDRAM CONFIG_MEMC=y CONFIG_MEMC_GD32_EXMC=y # LCD Configuration CONFIG_LCD_WIDTH=480 CONFIG_LCD_HEIGHT=272 CONFIG_LCD_FRAMEBUFFER_ADDR=0xC0000000
4.2 图形库使用
图形库头文件定义了基本的宏和API:
#include "graphics.h" /* LCD配置(从Kconfig获取) */ #define LCD_WIDTH CONFIG_LCD_WIDTH #define LCD_HEIGHT CONFIG_LCD_HEIGHT #define FRAMEBUFFER_ADDR CONFIG_LCD_FRAMEBUFFER_ADDR /* 颜色定义 */ #define COLOR_BLACK 0x0000 #define COLOR_WHITE 0xFFFF #define COLOR_RED 0xF800 #define COLOR_GREEN 0x07E0 #define COLOR_BLUE 0x001F图形API:
// 填充屏幕 void fill_screen(uint16_t color); // 画点 void draw_pixel(uint16_t x, uint16_t y, uint16_t color); // 画线 void draw_line(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color); // 画圆 void draw_circle(uint16_t x0, uint16_t y0, uint16_t radius, uint16_t color); // 画填充圆 void draw_filled_circle(uint16_t x0, uint16_t y0, uint16_t radius, uint16_t color); // 字符显示 void draw_string(uint16_t x, uint16_t y, const char *str, uint16_t fg_color, uint16_t bg_color);4.3 主程序示例
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include "graphics.h"
extern uint32_t SystemCoreClock;
int main(void)
{
printk("GD32F527 LCD Demo\r\n");
/* LCD和SDRAM已由驱动自动初始化 */
while (1) {
/* 测试画点 */
fill_screen(COLOR_BLACK);
for (int i = 0; i < 100; i++) {
draw_pixel(50 + i, 50, COLOR_RED);
}
k_msleep(2000);
/* 测试画线 */
fill_screen(COLOR_BLACK);
draw_line(10, 10, 400, 200, COLOR_GREEN);
k_msleep(2000);
/* 测试画圆 */
fill_screen(COLOR_BLACK);
draw_circle(240, 136, 80, COLOR_BLUE);
draw_filled_circle(240, 136, 50, COLOR_RED);
k_msleep(2000);
}
} 五、关键实现点5.1 驱动初始化时机
使用 `DEVICE_DEFINE` 宏注册设备,配合 `POST_KERNEL` 初始化级别,确保驱动在其他系统组件初始化之前完成初始化:
DEVICE_DEFINE(gd32_tli, "GD32_TLI",
gd32_tli_init, NULL, &display_gd32_tli_data, &display_gd32_tli_config, POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, &gd32_tli_api);5.2 帧缓冲区管理
帧缓冲区直接映射到SDRAM地址 `0xC0000000`,这是EXMC映射的起始地址。TLI控制器会自动从这个地址读取像素数据并发送到LCD显示。
5.3 像素时钟配置
TLI需要特定的像素时钟频率。使用PLLSAI产生30MHz像素时钟:/* PLLSAI = 240MHz, DIV8 = 30MHz */
rcu_pllsai_r_config(240, 2); rcu_tli_clock_div_config(RCU_PLLSAIR_DIV8);六、总结
通过本文的移植方案,实现了以下目标:
1. 一键配置:用户只需在prj.conf中配置相关宏即可,无需编写初始化代码2. 自动初始化:SDRAM和TLI均由Zephyr驱动自动初始化3. 统一接口:通过Zephyr标准DISPLAY子系统接口访问4. 易于扩展:驱动架构清晰,便于后续添加新功能
该方案已成功在GD32F527I-EVAL开发板上验证,LCD可正常显示图形内容。
## 参考资料
- Zephyr Project Documentation: https://docs.zephyrproject.org/- GD32F5xx User Manual- GD32F5xx Firmware Library
附工程源码:
我要赚赏金
