ARM---IMX6ULL的LCD模块简介及LCD驱动程序的编写

一. 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 信号 产生,期间也会加入一段延时,这段延时就是图中的 VFPVSYNC 信号 产生,电子枪移动到左上角,当 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);
        }
    }

}
相关推荐
深信达沙箱2 天前
漏洞修不完、合规过不了?先御OS如何让嵌入式设备‘出厂即合规
arm开发·操作系统·嵌入式操作系统
Hi202402172 天前
Cortex-A53 + GIC700 + Boot BRAM Vivado 全流程复现
arm·fpga·vivado
振南的单片机世界2 天前
3.5T静默分帧,CRC16校验:Modbus-RTU帧结构解析
arm开发·stm32·单片机·嵌入式硬件
m0_547486662 天前
《Arm Cortex-M4嵌入式系统-STM32G4系列微控制器的原理架构与开发》全套PPT课件
arm开发·stm32·嵌入式硬件
工业设备方案笔记2 天前
RK3588 AI部署实战:如何在ARM设备上运行AI模型?——从模型转换到边缘推理全过程解析
arm开发·人工智能·边缘计算
2601_954706492 天前
基于 ARM 虚拟化的云手机架构选型与 Python 自动化实战——2026 年 8 月为何我推荐傲晨云手机
arm开发·智能手机·架构
执迷不悟8233 天前
ARM--ADC/SPI
arm开发
weixin_424381003 天前
IAR EW for Arm - IAR命令行
arm开发
咯哦哦哦哦3 天前
【deban13】系统 安装arm qt6.8.2
arm开发