一、概述
对于分辨率要求不高的设备,为了节省内存,通常会采用 Universal mode(SPI/I2C/UART)类型接口的屏幕。在这种情况下,需要适配的主要是 LCD 驱动程序(LCD driver)。
二、openvela LCD 接口
openvela LCD 框架对上层用户提供了标准的 VFS 文件操作接口,用户可以通过以下方式操作 /dev/lcd0 设备:
- open:打开 LCD 设备。
- ioctl:执行 LCD 控制命令。
- close:关闭 LCD 设备。
通过这些接口,用户可以实现图形绘制和 LCD 的控制操作。
1、驱动层接口
在驱动层,openvela LCD 框架提供了以下三个函数,驱动开发者需要实现这些接口以适配具体的 LCD 硬件:
#ifdef CONFIG_LCD
struct lcd_dev_s; /* Forward reference */
int board_lcd_initialize(void);
FAR struct lcd_dev_s *board_lcd_getdev(int lcddev);
void board_lcd_uninitialize(void);
#endif
-
board_lcd_initialize
用于初始化 LCD 芯片,包括 SPI 初始化、LCD 寄存器配置等硬件相关操作。
-
board_lcd_uninitialize
用于销毁 LCD 相关资源,例如关闭电源、释放内存等。
-
board_lcd_getdev
获取 LCD 设备实例,并实现 struct lcd_dev_s 定义的一系列方法。该函数是 LCD 驱动的核心,开发者需要实现 struct lcd_dev_s 中定义的所有方法。
2、struct lcd_dev_s 结构
struct lcd_dev_s 是 LCD 驱动的核心结构,封装了与 LCD 控制器交互的接口方法。以下是 struct lcd_dev_s 的部分定义示例:
struct lcd_dev_s
{
//获取lcd controller的配置信息
int (*getvideoinfo)(FAR struct lcd_dev_s *dev,
FAR struct fb_videoinfo_s *vinfo);
int (*getplaneinfo)(FAR struct lcd_dev_s *dev, unsigned int planeno,
FAR struct lcd_planeinfo_s *pinfo);
#ifdef CONFIG_FB_CMAP
//调色表
int (*getcmap)(FAR struct lcd_dev_s *dev, FAR struct fb_cmap_s *cmap);
int (*putcmap)(FAR struct lcd_dev_s *dev,
FAR const struct fb_cmap_s *cmap);
#endif
#ifdef CONFIG_FB_HWCURSOR
//硬件光标
int (*getcursor)(FAR struct lcd_dev_s *dev,
FAR struct fb_cursorattrib_s *attrib);
int (*setcursor)(FAR struct lcd_dev_s *dev,
FAR struct fb_setcursor_s *settings);
#endif
//lcd 特有的控制接口
//获得lcd的电源状态(0: full off - CONFIG_LCD_MAXPOWER: full on). 对于有背光的lcd,这个值一般是背光亮度值
int (*getpower)(struct lcd_dev_s *dev);
//设置lcd的电源状态(0: full off - CONFIG_LCD_MAXPOWER: full on). 对于有背光的lcd,这个值一般是背光亮度值
int (*setpower)(struct lcd_dev_s *dev, int power);
//获取当前对比度 (0-CONFIG_LCD_MAXCONTRAST)
int (*getcontrast)(struct lcd_dev_s *dev);
//设置当前对比度 (0-CONFIG_LCD_MAXCONTRAST)
int (*setcontrast)(struct lcd_dev_s *dev, unsigned int contrast);
};
开发者可以参考以下文件中的实现:
- boards/arm/stm32/stm32f4discovery/src/stm32_st7789.c
该文件展示了如何实现 struct lcd_dev_s 的方法,并适配具体的 LCD 控制器。
三、启用 openvela LCD
在使用 openvela LCD 功能时,需要启用相关的编译选项,并在系统启动阶段完成初始化和注册操作。以下是具体步骤:
1、启用以下编译选项
在配置文件中,确保以下选项已启用:
- CONFIG_LCD:启用 LCD 支持。
- CONFIG_LCD_DEV:启用 LCD 设备支持。
2、系统启动阶段的调用方式
在系统启动阶段,需要调用以下函数完成 LCD 的初始化和注册:
示例代码
#ifdef CONFIG_LCD
// Initialize the LCD board
ret = board_lcd_initialize();
if (ret < 0)
{
syslog(LOG_ERR, "ERROR: board_lcd_initialize() failed: %d\n", ret);
}
#ifdef CONFIG_LCD_DEV
// Register the LCD device
ret = lcddev_register(0);
if (ret < 0)
{
syslog(LOG_ERR, "ERROR: lcddev_register() failed: %d\n", ret);
}
#endif /* CONFIG_LCD_DEV */
#endif /* CONFIG_LCD */
代码说明
- board_lcd_initialize
- 用于初始化 LCD 硬件,例如 SPI 接口、LCD 控制器寄存器等。
- 如果初始化失败,会返回负值并记录错误日志。
- lcddev_register
- 注册 LCD 设备实例,通常用于将 LCD 设备挂载到 /dev/lcd0。
- 如果注册失败,会返回负值并记录错误日志。
3、struct lcd_planeinfo_s 结构
struct lcd_planeinfo_s 是 LCD 驱动的重要结构,定义了与 LCD 数据传输和颜色特性相关的接口和属性。
示例代码
struct lcd_planeinfo_s
{
/* LCD Data Transfer */
/* 对某一行写入npixels个数据 */
int (*putrun)(fb_coord_t row, fb_coord_t col, FAR const uint8_t *buffer,
size_t npixels);
/* 更新矩形区域 */
int (*putarea)(fb_coord_t row_start, fb_coord_t row_end,
fb_coord_t col_start, fb_coord_t col_end,
FAR const uint8_t *buffer);
/* 读取某一行npixels个数据 */
int (*getrun)(fb_coord_t row, fb_coord_t col, FAR uint8_t *buffer,
size_t npixels);
/* 读取一个矩形区域的数据 */
int (*getarea)(fb_coord_t row_start, fb_coord_t row_end,
fb_coord_t col_start, fb_coord_t col_end,
FAR uint8_t *buffer);
/* Plane color characteristics */
/* 工作区,每个lcd 设备一个,多个layer共享一个buffer。至少存储一行的数据(bpp * xres/8),要和像素格式对齐 */
uint8_t *buffer;
/* 一个像素占用的位数 */
uint8_t bpp;
};
代码说明
数据传输接口
- putrun
- 向某一行写入指定数量(npixels)的像素数据。
- putarea
- 向指定的矩形区域写入像素数据。
- getrun
- 从某一行读取指定数量(npixels)的像素数据。
- getarea
- 从指定的矩形区域读取像素数据。
Plane 颜色特性
- buffer
- 功能:工作区缓冲区,每个 LCD 设备一个,多个图层(layer)共享一个缓冲区。
- 要求:缓冲区至少能够存储一行的数据(bpp * xres / 8),并与像素格式对齐。
- bpp
- 功能:表示每个像素占用的位数(bits per pixel)。
四、LCD Framebuffer 模式
LCD Framebuffer 是 openvela 对 LCD 驱动程序(LCD driver)的一层 Framebuffer 封装。在启用 LCD Framebuffer 模式后,应用层可以通过 /dev/fb0 访问和控制 LCD 设备。需要注意的是,该模式会分配一帧图形缓存(Framebuffer),因此会消耗额外的内存空间。
参考文件:drivers/lcd/lcd_framebuffer.c
1、LCD Framebuffer 模式的核心接口
根据 Framebuffer Driver 的描述,LCD Framebuffer 驱动实现了以下三个核心接口:
- up_fbinitialize:初始化 Framebuffer 和 LCD 驱动。
- up_fbgetvplane:获取 Video Plane 信息。
- up_fbuninitialize:释放 Framebuffer 和相关资源。
在 up_fbinitialize 函数中,完成了对 LCD 驱动的初始化调用。
2、up_fbinitialize 函数实现
以下是 up_fbinitialize 函数的实现逻辑。
示例代码
int up_fbinitialize(int display)
{
FAR struct lcdfb_dev_s *priv;
FAR struct lcd_dev_s *lcd;
struct fb_videoinfo_s vinfo;
struct fb_area_s area;
int ret;
lcdinfo("display=%d\n", display);
DEBUGASSERT((unsigned)display < UINT8_MAX);
/* Allocate the framebuffer state structure */
priv = (FAR struct lcdfb_dev_s *)kmm_zalloc(sizeof(struct lcdfb_dev_s));
if (priv == NULL)
{
lcderr("ERROR: Failed to allocate state structure\n");
return -ENOMEM;
}
/* Initialize the LCD-independent fields of the state structure */
priv->display = display;
priv->vtable.getvideoinfo = lcdfb_getvideoinfo,
priv->vtable.getplaneinfo = lcdfb_getplaneinfo,
#ifdef CONFIG_FB_CMAP
priv->vtable.getcmap = lcdfb_getcmap,
priv->vtable.putcmap = lcdfb_putcmap,
#endif
#ifdef CONFIG_FB_HWCURSOR
priv->vtable.getcursor = lcdfb_getcursor,
priv->vtable.setcursor = lcdfb_setcursor,
#endif
priv->vtable.updatearea = lcdfb_updateearea,
#ifdef CONFIG_LCD_EXTERNINIT
/* Use external graphics driver initialization */
lcd = board_graphics_setup(display);
if (lcd == NULL)
{
gerr("ERROR: board_graphics_setup failed, devno=%d\n", display);
ret = -ENODEV;
goto errout_with_state;
}
#else
/* Initialize the LCD device */
ret = board_lcd_initialize();
if (ret < 0)
{
lcderr("ERROR: board_lcd_initialize() failed: %d\n", ret);
goto errout_with_state;
}
/* Get the device instance */
lcd = board_lcd_getdev(display);
if (lcd == NULL)
{
lcderr("ERROR: board_lcd_getdev failed, devno=%d\n", display);
ret = -ENODEV;
goto errout_with_lcd;
}
#endif
priv->lcd = lcd;
/* Initialize the LCD-dependent fields of the state structure */
DEBUGASSERT(lcd->getvideoinfo != NULL);
ret = lcd->getvideoinfo(lcd, &vinfo);
if (ret < 0)
{
lcderr("ERROR: LCD getvideoinfo() failed: %d\n", ret);
goto errout_with_lcd;
}
priv->xres = vinfo.xres;
priv->yres = vinfo.yres;
DEBUGASSERT(lcd->getplaneinfo != NULL);
ret = lcd->getplaneinfo(lcd, VIDEO_PLANE, &priv->pinfo);
if (ret < 0)
{
lcderr("ERROR: LCD getplaneinfo() failed: %d\n", ret);
goto errout_with_lcd;
}
/* Allocate (and clear) the framebuffer */
priv->stride = ((size_t)priv->xres * priv->pinfo.bpp + 7) >> 3;
priv->fblen = priv->stride * priv->yres;
priv->fbmem = (FAR uint8_t *)kmm_zalloc(priv->fblen);
if (priv->fbmem == NULL)
{
lcderr("ERROR: Failed to allocate frame buffer memory\n");
ret = -ENOMEM;
goto errout_with_lcd;
}
/* Add the state structure to the list of framebuffer interfaces */
priv->flink = g_lcdfb;
g_lcdfb = priv;
/* Write the entire framebuffer to the LCD */
area.x = 0;
area.y = 0;
area.w = priv->xres;
area.h = priv->yres;
ret = lcdfb_updateearea(&priv->vtable, &area);
if (ret < 0)
{
lcderr("FB update failed: %d\n", ret);
}
/* Turn the LCD on at 75% power */
priv->lcd->setpower(priv->lcd, ((3*CONFIG_LCD_MAXPOWER + 3) / 4));
return OK;
errout_with_lcd:
#ifndef CONFIG_LCD_EXTERNINIT
board_lcd_uninitialize();
#endif
errout_with_state:
kmm_free(priv);
return ret;
}
3、配置选项
在 LCD Framebuffer 模式下,需要启用以下编译选项:
- CONFIG_LCD:启用 LCD 支持。
- CONFIG_VIDEO_FB:启用 Framebuffer 支持。
- CONFIG_LCD_FRAMEBUFFER:启用 LCD 的 Framebuffer 支持。
注意:在该模式下,无需启用 CONFIG_LCD_DEV 选项。