做为首款MPU与MCU跨界处理器,支持LCD显示是必备的技能,这项本领就叫enhanced Liquid Crystal Display Interface (eLCDIF)。

拥有了eLCDIF, 我就可以从容支持大部分LCD面板,分辨率达1280×800,刷新率为60Hz的高清屏也可轻松拿下!

eLCDIF技能主要包括:

内部系统总线master访问能力,自主读取并刷新帧数据

8/16/18/24-bit LCD数据总线,可以支持多种色彩编码格式

可编程时序与参数,兼容众多RGB LCD接口

下面,我会以SDK中sd_jpeg工程为例,同时基于MIMXRT1050-EVKB开发板,向大家展示一个完整的MCU + LCD系统。

2,呈现色彩

我的eLCDIF技能支持最常用的并行LCD数据/控制总线 —— RGB接口,也叫DOTCLK LCD接口。那么,让我们先来学习一下,看看如何通过RGB规范接口,驱动LCD显示屏。


我的搭档,LCD(TFT)显示屏在一个时钟周期内可以按不同强度驱动一个像素点的三个组成部分:红,绿,蓝区域。配合背光,在屏幕上呈现万千色彩。


每个像素理论可呈现16777216种色彩


一个DOT时钟周期刷新一个像素数据


根据数据线的定义与连接,可以支持不同色彩深度。24线连接可以表示成24 bits per pixel (bpp),同样的还有18bpp, 16bpp, 15bpp, 8bpp等色彩格式。
(U = Unused bit)


RGB332 -> 8bpp (Red 3, Green 3, Blue 2),单字节存储 (RRR GGG BB)


RGB555 -> 16bpp (Red 5, Green 5, Blue 5),双字节存储 (U RRRRR GGGGG BBBBB)


RGB565 -> 16bpp (Red 5, Green 6, Blue 5),双字节存储 (RRRRR GGGGGG BBBBB)


RGB888 -> 24bpp (Red 8, Green 8, Blue 8), 四字节存储 (UUUUUUUU RRRRRRRR GGGGGGGG BBBBBBBB)


以下是一个24bpp色彩格式的单像素点的示意图:

<ignore_js_op>

ESXI 显卡驱动P40 esxi amd显卡驱动_数据


关于LCD的背光,一般不放在RGB接口总线中控制。不同的LCD屏需要不同的背光电路设计,我会提供一个GPIO管脚来作为背光的开关控制信号线。


下面划重点 对于RGB接口来说,每个像素时钟(也称为DOT Clock)的上升沿或者下降沿,我都会从帧数据缓存区中取出一个像素的数据,根据需求转换为LCD要求的数据格式(RGB888或者其他编码),然后驱动eLCDIF外部总线把数据输出给相连接的LCD面板,从而点亮相对应的像素点。



说起来轻松做起来难,面对各式各样的LCD显示屏,稍有一点时序配合差错,他们就会立即“黑脸”,或者胡乱“撕碎”一幅完整的图像!为此,我练就了灵活多样的沟通技能,根据LCD显示屏们的要求,提供可定制服务,从而满足他们的个性化需求。



俗话说,没有什么道理是一张图片说明不了的;如果有,那就两张!第一张图解释了我需要满足的LCD们的所有时序要求:



总体来说,我会按照从左至右,从上到下的顺序,逐一点亮各个像素,完成一帧图像的刷新,然后循环往复。



基于TFT液晶面板特性,在开始和结束一行像素的数据刷新时,需要有行同步脉冲信号和前后的延迟时间,让面板有时间对一整行像素的刷新进行必要的准备工作。

HSW、HBP和HFP都来自LCD面板的规定,可以在他们的手册里找到,而我会严格按照他们的需求,通过HSYNC线产生这些信号和延迟。

HSW、HBP和HFP的值,约定以像素时钟(DOT Clock)为单位,就是以相当于增加了多少的虚拟像素点来计算这些延迟时间。



同样,在开始和完成一帧数据的刷新时,也需要帧同步脉冲信号和前后的延迟时间。

VSW、VBP和VFP也都是来自LCD面板的规定,我会通过VSYNC线产生这些信号和延迟。他们的值,通常约定以行为单位,就是以相当于增加了多少的虚拟行来计算这些延迟时间。



由以上两点可知,为了准确的展示一帧图像,我和LCD面板需要额外的同步和准备时间,而这些都会计算在帧的刷新时间之内。您只看到图像在真实宽度(Active Width)和真实高度(Active Height)内呈现,其实,我已经在更广的区域内完成了刷新:

Total Width = HSW + HBP + Active Width + HFP

Total Height = VSW + VBP + Active Height + VFP



此外,我的eLCDIF的所有信号线,都可以根据LCD面板的要求,定义有效极性,低电平、高电平、上升沿或下降沿,总有一款能满足各式各样的LCD显示屏。



<ignore_js_op>

ESXI 显卡驱动P40 esxi amd显卡驱动_ESXI 显卡驱动P40_02


第二张图展示了一个具体LCD面板(480 × 272)的参数要求例子。



<ignore_js_op>

ESXI 显卡驱动P40 esxi amd显卡驱动_#define_03


下面的图,以数据帧的形式,展示了我处理、刷新一帧数据的流程和工作方式。

<ignore_js_op>

ESXI 显卡驱动P40 esxi amd显卡驱动_数据_04


3,系统协作



有了上面的背景知识,再来系统的了解我驱动LCD的技能就比较容易了。



一个基本的嵌入式显示系统,由微处理器(MCU)、帧数据缓存区(framebuffer)、显示控制模块(display controller)和显示面板(display glass)组成。



微处理器(MCU)负责在帧数据缓存区(framebuffer)中计算,解码显示图像。



帧数据缓存区(framebuffer)是一块内存区域,用来存储帧像素的数据,其大小取决于LCD屏的大小和色彩编码格式。

通常,需要双framebuffer,一个显示当前图像,另一个准备下一帧数据。



显示控制模块(display controller)持续不断,自动地从framebuffer中取出数据,以一定刷新率(通常为60Hz)驱动LCD面板。

作为一个高端跨界MCU,我集成了一个显示控制模块,那就是eLCDIF。



显示面板(display glass)与显示控制模块协同工作,最终呈现出来图像。LCD显示面板有如下属性:



显示屏尺寸

总像素数,也就是行,列像素数相乘。通常表示成类似480 × 272的形式。



色彩深度

色彩深度体现了一个像素点可以显示的不同颜色数目,用bpp(bits per pixel)表示。24bpp(也可表示为RGB888)的色彩深度说明一个像素可以显示出16777216种颜色。



刷新率(Hz)

刷新率指显示屏在一秒内可以完成多少帧数据的显示。人眼感觉比较舒服的刷新率是60Hz。



下图展示了一个完整嵌入式显示系统框架。

<ignore_js_op>

ESXI 显卡驱动P40 esxi amd显卡驱动_ide_05


对整个LCD显示系统来说,帧数据缓存区(framebuffer)是系统性能的关键点。



通常会有多个模块(MCU core, DMA, network等)需要联合处理framebuffer中的数据。



Framebuffer中数据的组织形式取决于色彩编码格式的选择。



Framebuffer大小用公式(行数 × 列数 × 色彩深度)来计算。例如一个采用16bpp色彩编码格式的800x600大小的LCD屏,其需要的单个framebuffer大小为:

                                                    800 columns × 600 rows × 2 bytes/pixel = 960000 bytes




下表展示了基于不同色彩编码格式的不同大小的显示屏,对framebuffer大小的基本要求。

<ignore_js_op>

ESXI 显卡驱动P40 esxi amd显卡驱动_ide_06


作为一个MCU,很自豪的说,我的显示系统可以支持HD高清屏,在60Hz的刷新率下稳定工作。



4,eLCDIF



我的显示控制模块叫eLCDIF,前边介绍了一些基本特性,这一节中,我将协助大家对eLCDIF做更加深入的了解。



下面是eLCDIF控制器的逻辑框图,大家无需急于弄懂每个子模块的工作原理,有个总体印象就可以了。详细的介绍和寄存器说明,可以从我的手册里找到,边做项目边查手册,是最快的学习方法。



<ignore_js_op>

ESXI 显卡驱动P40 esxi amd显卡驱动_数据_07


我要给大家介绍的,是eLCDIF模块的三个时钟域,这也是站在系统的高度理解eLCDIF工作原理的关键。



AHB时钟域



AHB时钟域中,eLCDIF的AXI Master与内部系统总线(AHB)配合,完成帧数据从内存到eLCDIF的FIFO的搬移工作。



APB时钟域

APB时钟域中,内核(Cortex-M7)通过控制总线(APB)配置、读取eLCDIF的寄存器。



Pixel时钟域

Pixel时钟域中,eLCDIF在输出管脚上产生时序信号。Pixel Clock (又称DOT Clock)是这个时钟域的基础时钟,其值根据LCD面板的大小和刷新率计算而来,然后由我内部的PLL输出提供。



eLCDIF控制器拥有总共29个典型电压3.3V的信号线:

<ignore_js_op>

ESXI 显卡驱动P40 esxi amd显卡驱动_数据_08


eLCDIF接口的RGB数据线不需要必须与LCD面板的RGB数据线一对一链接。



LCD面板拥有更多的RGB数据线,没有用到的可以接地



eLCDIF拥有更多的RGB数据线,没有用到的不需要特殊处理



下图展示了24根RGB数据线(24bpp)的eLCDIF,和只有18根RGB数据线(18bpp色彩格式)的LCD面板的连接方式。

<ignore_js_op>

ESXI 显卡驱动P40 esxi amd显卡驱动_ide_09


除了上述属于eLCDIF控制器的信号线,通常整个显示系统还需要额外的控制线,例如控制背光的GPIO,用于触摸屏的I2C或者SPI等。



此外,eLCDIF提供了一个特别的功能——Lookup table (LUT)。有两块256×24 bits的存储区域,可以用来建立LUT表,以适应更多种类的LCD应用。



有些显示系统,原始帧数据的色彩深度小于LCD面板的色彩深度。这时就可以通过LUT表把原始帧数据的色彩深度转换成LCD面板需要的色彩深度,来保证LCD面板的正常工作。



软件初始化LUT表后,接下来的转换工作由eLCDIF硬件自动完成,从而大大提高了系统性能,也降低了软件对core的占用率。



下表展示了一个LUT的应用案例,原始帧数据的色彩深度为3bpp,只能表示出8个颜色(如最后一列所示),而LCD面板是一个16bpp色彩深度(RGB565)的接口。为了正确显示出原始帧数据的颜色,软件需要建立一个3bpp到16bpp的LUT查找表,eLCDIF根据此表,可以得到正确的RGB565格式的数据,然后送往LCD面板显示。

<ignore_js_op>

ESXI 显卡驱动P40 esxi amd显卡驱动_ide_10


5,软件与性能



在恩智浦官网,您可以下载到适用于MIMXRT1050-EVKB的SDK软件开发包。



在SDK中,您可以找到一个叫sd_jpeg的工程(boards\evkbimxrt1050\demo_apps\sd_jpeg);在sd_jpeg工程中,您可以看到软件如何根据LCD面板的特性配置eLCDIF,并和硬件协同合作,最终在显示屏上呈现精彩图片。



sd_jpeg代码会按顺序从SD卡中读取JPEG图片,在framebuffer中进行解码计算,然后通过eLCDIF刷新到LCD屏上显示。详细的硬件环境和软件步骤,可以参考sd_jpeg工程的readme.txt。



我们来运用上边学到的知识,基于已有的sd_jpeg工程,通过部分修改代码,实现eLCDIF对1280×800高清LCD屏的驱动。其硬件环境如下图所示。

<ignore_js_op>

ESXI 显卡驱动P40 esxi amd显卡驱动_ide_11


SDK中的sd_jpeg工程对应的是一块480×272 LCD显示屏。现在想要驱动一块1280 × 800 LCD面板,软件需要做以下修改。


sd_jpeg.c:按照1280 × 800 LCD屏要求设置其大小,HSW/HBP/HFP/VSW/VBP/VFP等参数,以及信号线的有效极性。


#define APP_IMG_WIDTH   1280
#define APP_IMG_HEIGHT  800


#define APP_HSW 10
#define APP_HBP 80
#define APP_HFP 70
#define APP_VSW 3
#define APP_VBP 10
#define APP_VFP 10
#define APP_POL_FLAGS \
    (kELCDIF_DataEnableActiveHigh | kELCDIF_VsyncActiveLow | kELCDIF_HsyncActiveLow | kELCDIF_DriveDataOnRisingClkEdge)
sd_jpeg.c:设置正确的复位与背光控制信号。


/* Display. */
#define LCD_DISP_GPIO GPIO1
#define LCD_DISP_GPIO_PIN 2


/* Back light. */
#define LCD_BL_GPIO GPIO2
#define LCD_BL_GPIO_PIN 31
sd_jpeg.c:配置正确的RGB色彩模式,framebuffer地址,接口模式等参数。


const elcdif_rgb_mode_config_t config = {
        .panelWidth = APP_IMG_WIDTH,
        .panelHeight = APP_IMG_HEIGHT,
        .hsw = APP_HSW,
        .hfp = APP_HFP,
        .hbp = APP_HBP,
        .vsw = APP_VSW,
        .vfp = APP_VFP,
        .vbp = APP_VBP,
        .polarityFlags = APP_POL_FLAGS,
        .bufferAddr = (uint32_t)g_frameBuffer[0],
        .pixelFormat = kELCDIF_PixelFormatRGB888,
        .dataBus = APP_LCDIF_DATA_BUS,
};
sd_jpeg.c:根据1280 × 800 LCD屏大小和参数,以及想要达到的刷新率,计算并配置DOTCLK。


void BOARD_InitLcdifPixelClock(void)
{
    /*
     * The desired output frame rate is 60Hz. So the pixel clock frequency is:
     * (1280 + 10 + 80 + 70) * (800 + 3 + 10 + 10) * 60 = 71M.
     * Here set the LCDIF pixel clock to 70.5M.
     */


    /*
     * Initialize the Video PLL.
     * Video PLL output clock is OSC24M * (loopDivider + (denominator / numerator)) / postDivider = 70.5MHz.
     */
    clock_video_pll_config_t config = {
        .loopDivider = 47, .postDivider = 16, .numerator = 0, .denominator = 0,
    };


    CLOCK_InitVideoPll(&config);


    /*
     * 000 derive clock from PLL2
     * 001 derive clock from PLL3 PFD3
     * 010 derive clock from PLL5
     * 011 derive clock from PLL2 PFD0
     * 100 derive clock from PLL2 PFD1
     * 101 derive clock from PLL3 PFD1
     */
    CLOCK_SetMux(kCLOCK_LcdifPreMux, 2);
    CLOCK_SetDiv(kCLOCK_LcdifPreDiv, 0);
    CLOCK_SetDiv(kCLOCK_LcdifDiv, 0);
}


sd_jpeg.c:设置eLCDIF AXI master属性,提高内部总线性能,以满足1280 × 800 LCD高清屏的大数据吞吐量的需求。


# In the bottom of function “void APP_ELCDIF_Init(void)”, add the line:
APP_ELCDIF->CTRL2 = 0x00700000;
修改IAR的工程链接脚本文件MIMXRT1052xxxxx_sdram.icf,增大栈和堆的空间。
相对于480 × 272的LCD屏,1280 × 800 LCD高清屏需要大的多的framebuffer空间,以及更大的栈和堆,来确保代码软解码时工作正常。


--- a/boards/evkbimxrt1050/demo_apps/sd_jpeg/iar/MIMXRT1052xxxxx_sdram.icf
+++ b/boards/evkbimxrt1050/demo_apps/sd_jpeg/iar/MIMXRT1052xxxxx_sdram.icf
@@ -73,13 +73,13 @@ define symbol m_ncache_end             = 0x81FFFFFF;
if (isdefinedsymbol(__stack_size__)) {
   define symbol __size_cstack__        = __stack_size__;
} else {
-  define symbol __size_cstack__        = 0x1000;
+  define symbol __size_cstack__        = 0x8000;
}


if (isdefinedsymbol(__heap_size__)) {
   define symbol __size_heap__          = __heap_size__;
} else {
-  define symbol __size_heap__          = 0x20000;
+  define symbol __size_heap__          = 0x800000;
}

搭建好相应的硬件环境,完成以上代码修改并编译下载可执行文件到开发板上,1280 × 800分辨率的图片应该能够在LCD高清屏上流畅显示。



性能,对于显示系统来说至关重要,同时,对性能的调整又是一个系统级的工作,我们需要根据不同的显示方案,找到最适合的性能配置参数。



更大分辨率的LCD显示屏需要更大的framebuffer,在同样的刷新率下(例如60Hz)需要处理更多的数据,这就需要eLCDIF提供更高的像素时钟(DOTCLK)频率,同时eLCDIF的AXI Master也需要占用更多的内部总线带宽,来提高数据读取速率。



例如,1280 × 800分辨率的显示屏,工作在24位色彩深度,60Hz刷新率下,需要的数据传输性能为:

60 × 1280 × 800 × 3 bytes/pixel = 184.32MBytes/sec


eLCDIF控制器给我们提供了一些调整系统性能的方法:


LCDIF_CTRL2寄存器


LCDIF_CTRL2_OUTSTANDING_REQS域用来调整eLCDIF AXI Master在内部总线上一次性发出读数据请求消息的数量,可以选择1、2、4、8和16。


LCDIF_CTRL2_BURST_LEN_8域可以设置单次内部总线读请求的突发传输大小,可以选择8或者16个64位字。


LCDIF_THRES寄存器


LCDIF_THRES_PANIC域用来给eLCDIF的FIFO设置一个阈值,当FIFO中的数据低于此阈值,系统可以自动提高eLCDIF AXI Master发送给总线的读请求消息的优先级,从而可以分配更多的内部总线带宽给eLCDIF,提高性能。


至此,i.MX RT的LCD控制部分就基本介绍完成,希望可以帮助大家驱动起来自己的LCD屏,也希望大家可以走进多彩的i.MX RT跨界MCU处理器的世界。



作者:刘岗