一. LCD 屏幕及其重要参数
1.1 LCD 屏幕简介
LCD 全称是Liquid Crystal Display,也就是液晶显示器,是现在最常用到的显示器,手机、 电脑、各种人机交互设备等基本都用到了 LCD,最常见就是手机和电脑显示器。
LCD 的构造是在两片平行的玻璃基板当中放置液晶盒,下基板玻璃上设置 TFT(薄膜晶体 管),上基板玻璃上设置彩色滤光片,通过 TFT 上的信号与电压改变来控制液晶分子的转动方 向,从而达到控制每个像素点偏振光出射与否而达到显示目的
1.2 主要参数介绍
1.2.1 分辨率
LCD 显示器会有 720P、1080P、2K 或 4K 这样的字眼,这个就是 LCD 显示器分辨率。LCD 显示器都是由一个一个的像素点组成,像素点就类似一个灯(在 OLED 显示 器中,像素点就是一个小灯),这个小灯是 RGB 灯,也就是由 **R(红色)、G(绿色)和 B(蓝色)**这三 种颜色组成的,而 RGB 就是光的三原色。
1080P 的意思就是一个 LCD 屏幕上的像素数量是 1920*1080 个,也就是这个屏幕一列 1080 个像素点,一共 1920 列,如图所示:

上图就是 1080P 显示器的像素示意图,X 轴就是 LCD 显示器的横轴,Y 轴就是显示器的竖轴。图中的小方块就是像素点,一共有 1920*1080=2073600 个像素点 。左上角的 A 点 是第一个像素点,右下角的 C 点就是最后一个像素点。2K 就是 2560*1440 个像素点 ,4K 是 3840*2160 个像素点。很明显,在 LCD 尺寸不变的情况下,分辨率越高越清晰。同样的,分辨率不变的情况下,LCD 尺寸越小越清晰。比如常用的 24 寸显示器基本都是 1080P 的,而现在使用的 5 寸的手机基本也是 1080P 的
显示屏的尺寸: 对角线长度
1英寸=2.54cm
尺寸不变,分辨率越高,显示效果越精细
1.2.2 像素格式
一个像素点就相当于一个 RGB 小灯,通过控制 R、G、B 这三种颜色的亮度就可以显示出各种各样的色彩。那该如何控制 R、G、B 这三种颜色的显示亮度呢?一般一个 R、 G、B 这三部分分别使用 8bit 的数据,那么一个像素点就是 8bit*3=24bit ,也就是说一个像素点 3 个字节 ,这种像素格式称为 RGB888。如果再加入 8bit 的 Alpha(透明)通道的话一个像素点就是 32bit,也就是 4 个字节,这种像素格式称为 ARGB8888,一个像素占用 4 个字节的内存,这四个字节每个位的分配如图所示:

在图中,一个像素点是 4 个字节,其中 bit31~bit24 是 Alpha 通道 ,bit23~bit16 是 RED 通道 ,bit15~bit14 是 GREEN 通道 ,bit7~bit0 是 BLUE 通道。所以红色对应的值就是 0X0 0FF0000,蓝色对应的值就是 0X000000FF,绿色对应的值为 0X0000FF00。通过调节 R、G、B 的比例可以产生其它的颜色,比如 0X00FFFF00 就是黄色,0X00000000 就是黑色,0X00FFFF FF 就是白色
1.2.3 LCD 屏幕接口
LCD 屏幕或者说显示器有很多种接口,比如在显示器上常见的 VGA、HDMI、DP 等等
在 IMX6ULL 开发板上的 LCD 接口如下图所示:

上图就是 RGBLCD 的信号线,R7:0、G7:0和 B7:0这 24 根是数据线,DE、VSYNC、HSYNC 和 PCLK 这四根是控制信号线。RGB LCD 一般有两种驱动模式:DE 模式和 HV 模式,这两个模式的区别是 DE 模式需要用到 DE 信号线,而 HV 模式不需要用到 DE 信号线, 在 DE 模式下是可以不需要 HSYNC 信号线的,即使不接 HSYNC 信号线 LCD 也可以正常工作
1.2.4 LCD 的时间参数
如果将 LCD 显示一帧图像的过程想象成绘画,那么在显示的过程中就是用一根"笔"在不同的像素点画上不同的颜色。这根笔按照从左至右、从上到下的顺序扫描每个像素点,并且在像素画上对应的颜色,当画到最后一个像素点的时候一幅图像就绘制好了。假如一个 LCD 的分辨率为 1024*600,那么其扫描如图所示:

结合上图来看一下 LCD 是怎么扫描显示一帧图像的。一帧图像也是由一行一行组成的。 HSYNC 是水平同步信号 ,也叫做行同步信号,当产生此信号的话就表示开始显示新的 一行了,所以此信号都是在图的最左边。
VSYNC 信号是垂直同步信号 ,也叫做帧同步信号,当产生此信号的话就表示开始显示新的一帧图像了,所以此信号在图的左上角。
在图中可以看到有一圈"黑边",真正有效的显示区域是中间的白色部分。那这一圈 "黑边"是什么东西呢?这就要从显示器的"祖先"CRT 显示器开始说起了,CRT 显示器就是以前很常见的那种大屁股显示器,CRT 显示器屁股后面是个电子枪,电子枪打出的电子撞击到屏幕上的荧光物质使其发光。只要控制电子枪从左到右扫完一行(也就是扫描一行),然后从上到下扫描完所有行,这样一帧图像就显示出来了。也就是说,显示一帧图像电子枪是按照 'Z'形在运动,当扫描速度很快的时候看起来就是一幅完成的画面了。
当显示完一行以后会发出 HSYNC 信号 ,此时电子枪就会关闭,然后迅速移动到屏幕的左边 ,当 HSYNC 信号结束以后就可以显示新的一行数据了,电子枪就会重新打开。在 HSYNC 信号结束到电子枪重新打开之间会插入一段延时,这段延时就图中的 HBP 。当显示完一行以后就会关闭电子枪等待 HSYNC 信号产生,关闭电子枪到 HSYNC 信号产生之间会插入一段延时,这段延时就是图中的 HFP 信号。
同理,当显示完一帧图像以后电子枪也会关闭,然后等到 VSYNC 信号 产生,期间也会加入一段延时,这段延时就是图中的 VFP 。VSYNC 信号 产生,电子枪移动到左上角,当 VSYNC 信号结束以后电子枪重新打开,中间也会加入一段延时,这段延时就是图中的 VBP。
HBP、HFP、VBP 和 VFP 就是导致图中黑边的原因,但是这是 CRT 显示器存在黑边的原因,现在是 LCD 显示器,不需要电子枪了,那么为何还会有黑边呢?这是因为 RGB L CD 屏幕内部是有一个 IC 的,发送一行或者一帧数据给 IC,IC 是需要反应时间的。通过这段反应时间可以让 IC 识别到一行数据扫描完了,要换行了,或者一帧图像扫描完了,要开始下一 帧图像显示了。因此,在 LCD 屏幕中继续存在 HBP、HFP、VPB 和 VFP 这四个参数的主要目的是为了锁定有效的像素数据。这四个时间是 LCD 重要的时间参数,后面编写 LCD 驱动的时候要用到的,至于这四个时间参数具体值是多少,那要需要去查看所使用的 LCD 数据手册
1.2.5 LCD 屏幕时序
1、行显示时序

上图就是 RGB LCD 的行显示时序,其中重要的几个参数:
HSYNC:行同步信号,当此信号有效的话就表示开始显示新的一行数据,查阅所使用的 L CD 数据手册可以知道此信号是低电平有效还是高电平有效。
HSPW :有些地方也叫做 thp,是 HSYNC 信号宽度,也就是 HSYNC 信号持续时间。HSY NC 信号不是一个脉冲,而是需要持续一段时间才是有效的,单位为 CLK。
HBP: 有些地方叫做 thb,术语叫做行同步信号后肩,单位是 CLK。
HOZVAL: 有些地方叫做 thd,显示一行数据所需的时间,假如屏幕分辨率为 1024*600, 那么 HOZVAL 就是 1024,单位为 CLK。
HFP: 有些地方叫做 thf,前面已经讲过了,术语叫做行同步信号前肩,单位是 CLK。
当 HSYNC 信号发出以后,需要等待 HSPW+HBP 个 CLK 时间才会接收到真正有效的像素 数据。当显示完一行数据以后需要等待 HFP 个 CLK 时间才能发出下一个 HSYNC 信号,所以显示一行所需要的时间就是:HSPW + HBP + HOZVAL + HFP
2、帧显示时序

VSYNC:帧同步信号,当此信号有效的话就表示开始显示新的一帧数据,查阅所使用的 L CD 数据手册可以知道此信号是低电平有效还是高电平有效。
VSPW: 有些地方也叫做 tvp,是 VSYNC 信号宽度 ,也就是 VSYNC 信号持续时间,单位为 1 行的时间,单位是 th。
VBP: 有些地方叫做 tvb,术语叫做帧同步信号后肩 ,单位为 1 行的时间,单位是 th
**LINE:**有些地方叫做 tvd,显示一帧有效数据所需的时间,假如屏幕分辨率为 1024*600, 那么 LINE 就是 600 行的时间。
VFP: 有些地方叫做 tvf,前面已经讲过了,术语叫做帧同步信号前肩 ,单位为 1 行的时间,单位是 th
显示一帧所需要的时间就是:VSPW+VBP+LINE+VFP 个行时间,最终的计算公式:
T = (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)
因此配置一款 RGB LCD 的时候需要知道这几个参数:HOZVAL(屏幕有效宽度)、LINE(屏幕有效高度)、HBP、HSPW、HFP、VSPW、VBP 和 VFP
IMX6ULL的LCD是 ATK4384 屏幕型号,各参数如下图所示:

1.2.6 像素时钟
像素时钟就是 RGB LCD 的时钟信号,以 ATK7016 这款屏幕为例,显示一帧图像所需要的 时钟数就是:

显示一帧图像需要 853440 个时钟数,那么显示 60 帧就是:853440 * 60 = 51206400≈51. 2M,所以像素时钟就是 51.2MHz(一秒一般显示60帧)
同理可以算出 ATK4384 这款屏幕的像素时钟:31 MHz,所以后面在配置时钟时,需要将时钟配置为 31 MHz
1.3 显存的概念
显存: 显示存储空间,采用 ARGB8888 = 32bit = 4Byte
4字节的数据表示一个像素点的信息,这些信息必须存储在内存中,
1024x600x4 = 2.5MB,因此需要留出2.5MB的内存给 LCD 使用,直接定义一个32位的数组即可:
u32 lcdframe1024x600;
**另外注意:**显存的数组是一个一维的,但是 LCD 屏幕是一个二维的,想要在指定的一个二维坐标上进行显示,就必须得转换,这个在后面编写程序时会使用
二 .IMX6ULL的LCD模块介绍
2.1 原理图部分

2.2 IMX6ULL 的 LCD 时钟配置
IMX6ULL 的 LCD 时钟部分如图所示:
在IMX6ULL中,LCD的屏幕一般选择的是 PLL5 ,也就是 PLL_VIDEO


PLL5 ,也就是 PLL_VIDEO,这个时钟的配置公式如下:

可以通过寄存器:CCM_ANALOG_PLL_VIDEOn,CCM_ANALOG_PLL_VIDEO_NUM,CCM_ANALOG_PLL_VIDEO_DENOM对PLL5的时钟大小进行,然后通过 LCDIF1 前面的两个选择器与两个分频器的配置得到最终满足要求的时钟:31MHz
示例代码:
这里设计成传参模式的配置,可适配多种屏幕
**注意:**参数一的范围:27 ~ 54

参数二和参数三就是 LCDIF 前面的两个分频器的分频因子,范围是1 - 8
cpp
//LCD_CLK = 24 * div_select / lcdif1_pred / lcdif1_podf
//初始化LCD时钟
void lcd_clk_init(unsigned char div_select, unsigned char lcdif1_pred, unsigned char lcdif1_podf)
{
//不使用小数分频器
CCM_ANALOG->PLL_VIDEO_NUM = 0;
CCM_ANALOG->PLL_SYS_DENOM = 0;
CCM_ANALOG->PLL_VIDEO = (2 << 19) | (1 << 13) | (div_select << 0);
CCM_ANALOG->MISC2 &= ~(3 << 30);
//选择PLL5
CCM->CSCDR2 &= ~(7 << 15);
CCM->CSCDR2 |= (2 << 15);
//设置前置分频
CCM->CSCDR2 &= ~(7 << 12);
CCM->CSCDR2 |= ((lcdif1_pred - 1) << 12);
//设置后置分频
CCM->CBCMR &= ~(7 << 23);
CCM->CBCMR |= ((lcdif1_podf - 1) << 23);
//选择 LCDIF1_CLK_ROOT 的时钟源
CCM->CSCDR2 &= ~(7 << 9);
}
三 .IMX6ULL 与 LCD 相关的寄存器介绍
3.1 LCDIF_CTRL 寄存器
|----------------------------------|-----------------------------------------------------------------------------------------------------------------------|
| SFTRST(bit31) | eLCDIF 软复位控制位,当此位为 1 的话就会强制复位 LCD |
| CLKGATE(bit30) | 正常运行模式下,此位必须为 0!如果此位为 1 的话时钟就不会进入到 LCDIF。 |
| BYPASS_COUNT(bit19) | 如果要工作在 DOTCLK 模式的话就此位必须为 1。 |
| VSYNC_MODE(bit18) | 此位为 1 的话 LCDIF 工作在 VSYNC 接口模式。 |
| DOTCLK_MODE(bit17) | 此位为 1 的话 LCDIF 工作在 DOTCLK 接口模式。 |
| INPUT_DATA_SWIZZLE(bit15:14) | 输入数据字节交换设置,此位为 0 的话不交换字节 也就是小端模式;为 1 的话交换所有字节,也就是大端模式;为 2 的话半字交换;为 3 的话在 每个半字内进行字节交换。 |
| CSC_DATA_SWIZZLE(bit13:12) | CSC 数据字节交换设置,交换方式和 INPUT_DATA_S WIZZLE 一样,设置为 0,不使用字节交换。 |
| LCD_DATABUS_WIDTH(bit11:10) | LCD 数据总线宽度,为 0 的话总线宽度为 16 位;为 1 的话总线宽度为 8 位;为 2 的话总线宽度为 18 位;为 3 的话总线宽度为 24 位。使用 24 位总线宽度。 |
| WORD_LENGTH(bit9:8) | 输入的数据格式,也就是像素数据宽度,为 0 的话每个像素 1 6 位;为 1 的话每个像素 8 位;为 2 的话每个像素 18 位;为 3 的话每个像素 24 位。 |
| MASTER(bit5) | 为 1 的话设置 eLCDIF 工作在主模式。 |
| DATA_FORMAT_16_BIT(bit3) | 当此位为 1 并且 WORD_LENGTH 为 0 的时候像素格式 为 ARGB555,当此位为 0 并且 WORD_LENGTH 为 0 的时候像素格式为 RGB565。 |
| DATA_FORMAT_18_BIT(bit2) | 只有当 WORD_LENGTH 为 2 的时候此位才有效,此位 为 0 的话低 18 位有效,像素格式为 RGB666,高 14 位数据无效。当此位为 1 的话高 18 位有 效,像素格式依旧是 RGB666,但是低 14 位数据无效 |
| DATA_FORMAT_24_BIT(bit1) | 只有当 WORD_LENGTH 为 3 的时候此位才有效,为 0 的时候表示全部的 24 位数据都有效。为 1 的话实际输入的数据有效位只有 18 位,虽然输入的 是 24 位数据,但是每个颜色通道的高 2 位数据会被丢弃掉 |
| RUN(bit0) | eLCDIF 接口运行控制位,当此位为 1 的话 eLCDIF 接口就开始传输数据,也就是 eLCDIF 的使能位。 |

3.2 寄存器 LCDIF_TRANSFER_COUNT
寄存器 LCDIF_TRANSFER_COUNT 分为两部分,高 16 位和低 16 位
高 16 位是 V_COU NT,是 LCD 的垂直分辨率。
低 16 位是 H_COUNT,是 LCD 的水平分辨率。
如果 LCD 分辨率 为 1024*600 的话,那么 V_COUNT 就是 600,H_COUNT 就是 1024

3.3 寄存器 LCDIF_VDCTRL0
|-----------------------------------|------------------------------------------------------------------------------------|
| VSYNC_OEB(bit29) | VSYNC 信号方向控制位,为 0 的话 VSYNC 是输出,为 1 的话 VS YNC 是输入。 |
| ENABLE_PRESENT(bit28) | EBABLE 数据线使能位,也就是 DE 数据线。为 1 的话使能 ENABLE 数据线,为 0 的话关闭 ENABLE 数据线。 |
| VSYNC_POL(bit27) | VSYNC 数据线极性设置位,为 0 的话 VSYNC 低电平有效,为 1 的 话 VSYNC 高电平有效,要根据所使用的 LCD 数据手册来设置。 |
| HSYNC_POL(bit26) | HSYNC 数据线极性设置位,为 0 的话 HSYNC 低电平有效,为 1 的 话 HSYNC 高电平有效,要根据所使用的 LCD 数据手册来设置。 |
| DOTCLK_POL(bit25) | DOTCLK 数据线(像素时钟线 CLK) 极性设置位,为 0 的话下降沿 锁存数据,上升沿捕获数据,为 1 的话相反,要根据所使用的 LCD 数据手册来设置。 |
| ENABLE_POL(bit24) | EANBLE 数据线极性设置位,为 0 的话低电平有效,为 1 的话高 电平有效。 |
| VSYNC_PERIOD_UNIT(bit21) | VSYNC 信号周期单位,为 0 的话 VSYNC 周期单位为像 素时钟。为 1 的话 VSYNC 周期单位是水平行,如果使用 DOTCLK 模式话就要设置为 1。 |
| VSYNC_PULSE_WIDTH_UNIT(bit20) | VSYNC 信号脉冲宽度单位,和 VSYNC_PERIO D_UNUT 一样,如果使用 DOTCLK 模式的话要设置为 1。 |
| VSYNC_PULSE_WIDTH(bit17:0) | VSPW 参数设置位。 |

3.4 寄存器 LCDIF_VDCTRL3
|-----------------------------------|-----------------------------------------------------------|
| HORIZONTAL_WAIT_CNT(bit27:16) | 此位用于 DOTCLK 模式,用于设置 HSYNC 信号 产生到有效数据产生之间的时间,也就是 HSPW+HBP。 |
| VERTICAL_WAIR_CNT(bit15:0) | 和 HORIZONTAL_WAIT_CNT 一样,只是此位用于 V SYNC 信号,也就是 VSPW+VBP。 |

3.5 寄存器 LCDIF_VDCTRL4
|----------------------------------|----------------------------------------------|
| SYNC_SIGNALS_ON(bit18) | 同步信号使能位,设置为 1 的话使能 VSYNC、HSYNC、D OTCLK 这些信号。 |
| DOTCLK_H_VALID_DATA_CNT(bit15:0) | 设置 LCD 的宽度,也就是水平像素数量。 |

3.6 寄存器 LCDIF_CUR_BUF 和 LCDIF_NEXT_BUF
最后在看一下寄存器 LCDIF_CUR_BUF 和 LCDIF_NEXT_BUF,这两个寄存器分别为当前 帧和下一帧缓冲区,也就是 LCD 显存。一般这两个寄存器保存同一个地址,也就是划分给 LC D 的显存首地址。

四. IMX6ULL 的 LCD 驱动示例代码
4.1 初始化 LCD 的GPIO
cpp
/*
* 读取屏幕ID,
* 描述:LCD_DATA23=R7(M0);LCD_DATA15=G7(M1);LCD_DATA07=B7(M2);
* M2:M1:M0
* 0 :0 :0 //4.3寸480*272 RGB屏,ID=0X4342
* 0 :0 :1 //7寸800*480 RGB屏,ID=0X7084
* 0 :1 :0 //7寸1024*600 RGB屏,ID=0X7016
* 1 :0 :1 //10.1寸1280*800,RGB屏,ID=0X1018
* 1 :0 :0 //4.3寸800*480 RGB屏,ID=0X4384
* @param : 无
* @return : 屏幕ID
*/
//读取 LCD 屏幕ID
unsigned short read_lcd_id(void)
{
unsigned char id = 0;
//配置LCD屏幕的ID信号线
IOMUXC_SetPinMux(IOMUXC_LCD_VSYNC_GPIO3_IO03, 0);
IOMUXC_SetPinConfig(IOMUXC_LCD_VSYNC_GPIO3_IO03, 0x10B0);
GPIO3->GDIR |= (1 << 3);
//打开模拟开关,高电平打开
GPIO3->DR |= (1 << 3);
//设置 B7 G7 R7 这三个引脚的复用 + 电气 + 输入
//B7(12) = M2 G7(20) = M1 R7(28) = M0
IOMUXC_SetPinMux(IOMUXC_LCD_DATA23_GPIO3_IO28, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA15_GPIO3_IO20, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA07_GPIO3_IO12, 0);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA23_GPIO3_IO28, 0xF0B0);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA15_GPIO3_IO20, 0xF0B0);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA07_GPIO3_IO12, 0xF0B0);
//将这三个引脚配置为输入模式
GPIO3->GDIR &= ~(1 << 28);
GPIO3->GDIR &= ~(1 << 20);
GPIO3->GDIR &= ~(1 << 12);
//读取 ID 并组合
id |= ((GPIO3->DR >> 28) & 1); //读M0
id |= (((GPIO3->DR >> 20) & 1) << 1); //读M1
id |= (((GPIO3->DR >> 12) & 1) << 2); //读M2
if(id == 0)
{
return 0x4342;
}
else if(id == 1)
{
return 0X7084;
}
else if(id == 2)
{
return 0X7016;
}
else if(id == 4)
{
return 0X4384;
}
else if(id == 5)
{
return 0X1018;
}
else
{
return 0;
}
}
//初始化LCD的GPIO
void lcd_gpio_init(void)
{
//设置LCD引脚复用
IOMUXC_SetPinMux(IOMUXC_LCD_DATA00_LCDIF_DATA00, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA01_LCDIF_DATA01, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA02_LCDIF_DATA02, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA03_LCDIF_DATA03, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA04_LCDIF_DATA04, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA05_LCDIF_DATA05, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA06_LCDIF_DATA06, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA07_LCDIF_DATA07, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA08_LCDIF_DATA08, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA09_LCDIF_DATA09, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA10_LCDIF_DATA10, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA11_LCDIF_DATA11, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA12_LCDIF_DATA12, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA13_LCDIF_DATA13, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA14_LCDIF_DATA14, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA15_LCDIF_DATA15, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA16_LCDIF_DATA16, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA17_LCDIF_DATA17, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA18_LCDIF_DATA18, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA19_LCDIF_DATA19, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA20_LCDIF_DATA20, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA21_LCDIF_DATA21, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA22_LCDIF_DATA22, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA23_LCDIF_DATA23, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_ENABLE_LCDIF_ENABLE, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_CLK_LCDIF_CLK, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_VSYNC_LCDIF_VSYNC, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_HSYNC_LCDIF_HSYNC, 0);
//设置LCD引脚电气属性
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA00_LCDIF_DATA00, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA01_LCDIF_DATA01, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA02_LCDIF_DATA02, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA03_LCDIF_DATA03, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA04_LCDIF_DATA04, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA05_LCDIF_DATA05, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA06_LCDIF_DATA06, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA07_LCDIF_DATA07, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA08_LCDIF_DATA08, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA09_LCDIF_DATA09, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA10_LCDIF_DATA10, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA11_LCDIF_DATA11, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA12_LCDIF_DATA12, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA13_LCDIF_DATA13, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA14_LCDIF_DATA14, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA15_LCDIF_DATA15, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA16_LCDIF_DATA16, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA17_LCDIF_DATA17, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA18_LCDIF_DATA18, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA19_LCDIF_DATA19, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA20_LCDIF_DATA20, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA21_LCDIF_DATA21, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA22_LCDIF_DATA22, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA23_LCDIF_DATA23, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_ENABLE_LCDIF_ENABLE, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_CLK_LCDIF_CLK, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_VSYNC_LCDIF_VSYNC, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_HSYNC_LCDIF_HSYNC, 0xB9);
//设置背光
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO08_GPIO1_IO08, 0);
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO08_GPIO1_IO08, 0x10B0);
GPIO1->GDIR |= (1 << 18);
//输出高电平点亮屏幕
GPIO1->DR |= (1 << 18);
}
4.2 时钟部分
cpp
//LCD_CLK = 24 * div_select / lcdif1_pred / lcdif1_podf
//初始化LCD时钟
void lcd_clk_init(unsigned char div_select, unsigned char lcdif1_pred, unsigned char lcdif1_podf)
{
//不使用小数分频器
CCM_ANALOG->PLL_VIDEO_NUM = 0;
CCM_ANALOG->PLL_SYS_DENOM = 0;
CCM_ANALOG->PLL_VIDEO = (2 << 19) | (1 << 13) | (div_select << 0);
CCM_ANALOG->MISC2 &= ~(3 << 30);
//选择PLL5
CCM->CSCDR2 &= ~(7 << 15);
CCM->CSCDR2 |= (2 << 15);
//设置前置分频
CCM->CSCDR2 &= ~(7 << 12);
CCM->CSCDR2 |= ((lcdif1_pred - 1) << 12);
//设置后置分频
CCM->CBCMR &= ~(7 << 23);
CCM->CBCMR |= ((lcdif1_podf - 1) << 23);
//选择 LCDIF1_CLK_ROOT 的时钟源
CCM->CSCDR2 &= ~(7 << 9);
}
4.3 初始化 LCD
cpp
//LCD 初始化函数
void lcd_init(void)
{
//读取屏幕ID
unsigned short lcd_id = 0;
lcd_id = read_lcd_id();
printf("lcd id is %#x\n", lcd_id);
//初始化LCD的GPIO
lcd_gpio_init();
printf("lcd_gpio_init finish!\n");
//LCD 复位
LCDIF->CTRL |= (1 << 31);
delay_ms(20);
//LCD 停止复位
LCDIF->CTRL &= ~(1 << 31);
//根据 lcd_id 初始化屏幕信息结构体
if(lcd_id == ATK4342)
{
lcd_dev.height = 272;
lcd_dev.width = 480;
lcd_dev.vspw = 1;
lcd_dev.vbpd = 8;
lcd_dev.vfpd = 8;
lcd_dev.hspw = 1;
lcd_dev.hbpd = 40;
lcd_dev.hfpd = 5;
lcd_clk_init(27, 8, 8);
}
else if(lcd_id == ATK1018)
{
lcd_dev.height = 800;
lcd_dev.width = 1280;
lcd_dev.vspw = 3;
lcd_dev.vbpd = 10;
lcd_dev.vfpd = 10;
lcd_dev.hspw = 10;
lcd_dev.hbpd = 80;
lcd_dev.hfpd = 70;
lcd_clk_init(35, 3, 5);
}
else if(lcd_id == ATK4384)
{
lcd_dev.height = 480;
lcd_dev.width = 800;
lcd_dev.vspw = 3;
lcd_dev.vbpd = 32;
lcd_dev.vfpd = 13;
lcd_dev.hspw = 48;
lcd_dev.hbpd = 88;
lcd_dev.hfpd = 40;
lcd_clk_init(42, 4, 8);
}
else if(lcd_id == ATK7016)
{
lcd_dev.height = 600;
lcd_dev.width = 1024;
lcd_dev.vspw = 3;
lcd_dev.vbpd = 20;
lcd_dev.vfpd = 12;
lcd_dev.hspw = 20;
lcd_dev.hbpd = 140;
lcd_dev.hfpd = 160;
lcd_clk_init(32, 3, 5);
}
else if(lcd_id == ATK7084)
{
lcd_dev.height = 480;
lcd_dev.width = 800;
lcd_dev.vspw = 1;
lcd_dev.vbpd = 23;
lcd_dev.vfpd = 22;
lcd_dev.hspw = 1;
lcd_dev.hbpd = 46;
lcd_dev.hfpd = 210;
lcd_clk_init(30, 3, 7);
}
lcd_dev.pixsize = 4;
lcd_dev.framebuffer = LCD_FRAMEBUFFER_ADDR;
lcd_dev.forecolor = LCD_WHITE;
lcd_dev.backcolor = LCD_BLACK;
//配置LCD控制器接口
LCDIF->CTRL = 0;
LCDIF->CTRL |= (1 << 5);
LCDIF->CTRL |= (3 << 8);
LCDIF->CTRL |= (3 << 10);
LCDIF->CTRL |= (1 << 17);
LCDIF->CTRL |= (1 << 19);
//LCDIF->CTRL1 &= ~(0xF << 16);
LCDIF->CTRL1 = 0;
LCDIF->CTRL1 |= (7 << 16);
LCDIF->TRANSFER_COUNT = 0;
//总共的行数
LCDIF->TRANSFER_COUNT |= (lcd_dev.height << 16);
//一行的像素
LCDIF->TRANSFER_COUNT |= (lcd_dev.width);
LCDIF->VDCTRL0 = 0;
LCDIF->VDCTRL0 |= (lcd_dev.vspw << 0);
LCDIF->VDCTRL0 |= (1 << 20);
LCDIF->VDCTRL0 |= (1 << 21);
LCDIF->VDCTRL0 |= (1 << 24);
LCDIF->VDCTRL0 &= ~(1 << 25);
LCDIF->VDCTRL0 &= ~(1 << 26);
LCDIF->VDCTRL0 &= ~(1 << 27);
LCDIF->VDCTRL0 |= (1 << 28);
LCDIF->VDCTRL0 &= ~(1 << 29);
LCDIF->VDCTRL1 = lcd_dev.height + lcd_dev.vspw + lcd_dev.vbpd + lcd_dev.vfpd;
LCDIF->VDCTRL2 = ((lcd_dev.hspw << 18) | (lcd_dev.width + lcd_dev.hspw + lcd_dev.hbpd + lcd_dev.hfpd));
LCDIF->VDCTRL3 = (((lcd_dev.hspw + lcd_dev.hbpd) << 16) | (lcd_dev.vspw + lcd_dev.vbpd));
LCDIF->VDCTRL4 = ((1 << 18) | (lcd_dev.width));
LCDIF->CUR_BUF = lcd_dev.framebuffer;
LCDIF->NEXT_BUF = lcd_dev.framebuffer;
//使能LCD
LCDIF->CTRL |= (1 << 0);
delay_ms(20);
//lcd_clear(LCD_RED);
lcd_clear(LCD_WHITE);
}
4.4 与显示相关函数
cpp
//画点函数
void lcd_draw_point(unsigned short x, unsigned short y, unsigned int color)
{
*( (unsigned int *)(lcd_dev.framebuffer + lcd_dev.pixsize * (lcd_dev.width * y + x)) ) = color;
}
//读点函数
unsigned int lcd_read_point(unsigned short x, unsigned short y)
{
return *( (unsigned int *)(lcd_dev.framebuffer + lcd_dev.pixsize * (lcd_dev.width * y + x)) );
}
//清屏函数
void lcd_clear(unsigned int color)
{
unsigned int i = 0;
unsigned int *start = (unsigned int *)lcd_dev.framebuffer;
unsigned int num = lcd_dev.width * lcd_dev.height;
for(i = 0; i < num; i++)
{
*(start + i) = color;
}
}
//画横线
void lcd_draw_h_line(unsigned int x, unsigned int y, unsigned int len, unsigned int color)
{
int i = 0;
for(i = x; i <= x + len; i++)
{
lcd_draw_point(i, y, color);
lcd_draw_point(i, y + 1, color);
}
}
//画竖线
void lcd_draw_s_line(unsigned int x, unsigned int y, unsigned int len, unsigned int color)
{
int j = 0;
for(j = y; j <= y + len; j++)
{
lcd_draw_point(x, j, color);
}
}
//绘制矩形
void lcd_draw_rectangle(unsigned int x0, unsigned int y0, unsigned int x1, unsigned int y1, unsigned int color)
{
lcd_draw_h_line(x0, y0, x1 - x0, color);
lcd_draw_h_line(x0, y1, x1 - x0, color);
lcd_draw_s_line(x0, y0, y1 - y0, color);
lcd_draw_s_line(x1, y0, y1 - y0, color);
}
//填充指定区域
void lcd_fill(unsigned int x0, unsigned int y0, unsigned int x1, unsigned int y1, unsigned int color)
{
unsigned int i = 0, j = 0;
for(j = y0; j <= y1; j++)
{
for(i = x0; i <= x1; i++)
{
lcd_draw_point(i, j, color);
}
}
}