《嵌入式硬件(二十):基于IMX6ULL的LCD操作》
一、LCD相关概念
        LCD 全称是 Liquid Crystal Display,也就是液晶显示器,是现在最常用到的显示器,手机、
电脑、各种人机交互设备等基本都用到了 LCD,最常见就是手机和电脑显示器了。这里需要注意的是,我们所提及的LCD不包括触摸功能,触摸功能是通过另外的设备实现的,LCD在这里只是输出设备。
百度百科对于 LCD 的原理解释如下:LCD  的构造是在两片平行的玻璃基板当中放置液晶盒,下基板玻璃上设置 TFT(薄膜晶体管),上基板玻璃上设置彩色滤光片,通过 TFT 上的信号与电压改变来控制液晶分子的转动方向,从而达到控制每个像素点偏振光出射与否而达到显示目的。我们现在要在 I.MX6U-ALPHA 开发板上使用 LCD,所以不需要去研究 LCD 的具体实现原理,我们只需要从使用的角度去关注 LCD 的几个重要点:
分辨率:提起 LCD 显示器,我们都会听到 720P、1080P、2K 或 4K 这样的字眼,这个就是 
LCD 显示器分辨率。LCD 显示器都是由一个一个的像素点组成,像素点就类似一个灯(在 OLED 显示器中,像素点就是一个小灯),这个小灯是 RGB 灯,也就是由 R(红色)、G(绿色)和 B(蓝色)这三种颜色组成的,而 RGB 就是光的三原色。1080P 的意思就是一个 LCD 屏幕上的像素数量是1920*1080 个,也就是这个屏幕一列 1080 个像素点,一共 1920 列
像素格式:一个像素点就相当于一个 RGB 小灯,通过控制 R、G、B 这三种颜色的亮度就可以显示出各种各样的色彩。那该如何控制 R、G、B 这三种颜色的显示亮度呢?一般一个 R、G、B 这三部分分别使用 8bit 的数据,那么一个像素点就是 8bit*3=24bit,也就是说一个像素点3 个字节,这种像素格式称为 RGB888。如果再加入 8bit 的 Alpha(透明)通道的话一个像素点就是 32bit,也就是 4 个字节,这种像素格式称为 ARGB8888。如果学习过 STM32 的话应该还听过 RGB565 这种像素格式,在本次实验中我们使用 ARGB8888 这种像素格式,一个像素占用 4个字节的内存。

        上图中,一个像素点是 4 个字节,其中 bit31~bit24 是 Alpha 通道,bit23~bit16 是RED 通
道,bit15~bit14 是 GREEN 通道,bit7~bit0 是 BLUE 通道。所以红色对应的值就0X00FF0000,
蓝色对应的值就是 0X000000FF,绿色对应的值为 0X0000FF00。通过调节 R、G、B 的比例可以产生其它的颜色,比如 0X00FFFF00 就是黄色,0X00000000 就是黑色,0X00FFFFFF就是白色。
二、LCD原理图及时序
1.RGBLCD原理图

3~28:数据信号线(ARGB四字节的像素)
30LCD PCLK:像素时钟信号线(一个)
31LCD HSYNC:行同步信号(一行)
32LCD VSYNC:帧/垂直同步信号(一帧)
33LCD DE:数据有效信号线
2.RGBLCD时序
| 1 | 10 | ||||||
| 11 | |||||||
| 70 | 80 | 
假设这是屏幕,电子枪从1到10后,要移动到11,为了防止乱码需要先关闭电子枪,这段时间称为行同步信号前肩,到11后,打开电子枪所需的时间称为行同步信号后肩。从10到11挪动的这段时间被称为行同步信号脉冲宽度。他们的单位都是clk。
同理,从80到1,需要先关闭电子枪从80挪到70,这是帧同步信号前肩,从70挪到1,被称为帧同步信号脉冲宽度,到了1之后,打开的时间被称为帧同步信号后肩。他们的单位是 1 行的时间。
2.1行显示时序

        ① HSYNC:行同步信号,当此信号有效的话就表示开始显示新的一行数据,查阅所使用的LCD 数据手册可以知道此信号是低电平有效还是高电平有效,假设此时是低电平有效;
② HSPW:有些地方也叫做 thp,是 HSYNC 信号宽度,也就是 HSYNC 信号持续时间。HSYNC信号不是一个脉冲,而是需要持续一段时间才是有效的,单位为 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.2帧显示时序

        ① VSYNC:帧同步信号,当此信号有效的话就表示开始显示新的一帧数据,查阅所使用的LCD 数据手册可以知道此信号是低电平有效还是高电平有效,假设此时是低电平有效;
② VSPW:有些地方也叫做 tvp,是 VSYNC 信号宽度,也就是 VSYNC 信号持续时间,单位为 1 行的时间;
③ VBP:有些地方叫做 tvb,前面已经讲过了,术语叫做帧同步信号后肩,单位为 1 行的时间;
④ LINE:有些地方叫做 tvd,显示一帧有效数据所需的时间,假如屏幕分辨率为 1024*600,那么 LINE 就是 600 行的时间;
⑤ VFP:有些地方叫做 tvf,前面已经讲过了,术语叫做帧同步信号前肩,单位为 1 行的时间。  
2.3结论
显示一帧所需要的时间就是:VSPW+VBP+LINE+VFP 个行时间,最终的计算公式: 为T = (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP) 因此我们在配置一款 RGBLCD 的时候需要知道这几个参数:HOZVAL(屏幕有效宽度)、LINE(屏幕有效高度)、HBP、HSPW、HFP、VSPW、VBP 和 VFP。
3.像素时钟
我们使用的alpha开发板搭载了一块由正点原子生产的RGB LCD,型号为ATK4384,分辨率为800*480。其时间参数如下:

按照之前的推导,显示一帧图像所需要的时钟数是:(VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)= (3 + 32 + 480 + 13) * (48 + 88 + 800 + 40) =515328。显示一帧图像需515328个时钟数,那么显示60帧就是:515328 * 60 = 30,919,680≈31M,所以像素时钟就是 31MHz。
这里的31MHz其实指的就是IMX这片Soc的LCD控制器的工作频率。我们必须按照这个数值来配置好LCD控制器的时钟。I.MX6UL的LCD控制器全称为Enhanced LCD Interface (eLCDIF)(增强型LED接口),在手册第34章第2131页。如果需要配置eLCDIF的工作频率为51.2MHz的话,可以参考时钟树:

图示1的位置是一个选择器,相当于是选择eLCDIF的时钟源。时钟源有6种选择:PLL2 、PLL3_PFD3、PLL5、PLL2_PFD0 、PLL2_PFD1和 PLL3_PFD1。PLL5又名VIDEO PLL,很明显就是专门给视频外设作为时钟源的,自然是选择PLL5了。之前我们并没有配置过PLL5,因此肯定是需要配置一下。此后的2、3位置是两个分频器,再经由一个选择器最终到达eLCDIF外设,eLCDIF外设的实际时钟名为LCDIF1_CLK_ROOT。

3.1步骤
                        1) 切走显示域时钟根(把 LCDIF 时钟源临时改到非 PLL_VIDEO)。
2) 在 CCM_ANALOG_PLL_VIDEO 中:
置 BYPASS = 1
置 POWERDOWN = 1
3) 写分数环路寄存器:
PLL_VIDEO_NUM = 0x0
PLL_VIDEO_DENOM = 0x1
4) 设定倍频和后分频:
DIV_SELECT = 42
POST_DIV_SELECT = 0(即 /1)
5) 上电并使能:
清 POWERDOWN = 0
置 ENABLE = 1
6) 轮询 LOCK 直到为 1(通常几十微秒到数百微秒)。
7) 清旁路:
BYPASS = 0
8) 将显示域时钟根切回 PLL_VIDEO,并按需设置前后分频器得到像素时钟。
三、IMX6U寄存器
1.数据信号线LCD DATE0~16
复用

电气属性


2.原理图上其他的引脚设置
像素时钟信号线,行同步信号,帧/垂直同步信号,数据有效信号线
背光PWM拉高(复用为GPIO)
复位(复用为GPIO)
3.总时钟(见二、3)

把NUM/DENOM写成0


不能设置为0
DIV_SELECT



POST默认为四分频,手册是错的。


4.时钟分频(见二、3)



5.通用控制寄存器(CTRL)



6.通用控制寄存器1(CTRL1)



7.水平垂直总计寄存器(TRANSFER_COUNT)

8.当前缓冲地址寄存器(CUR_BUF)

9.下一个缓冲地址寄存器(NEXT_BUF)

10.垂直同步模式和点时钟模式寄存器(VDCTRL0)

11.垂直同步模式和点时钟模式寄存器1(VDCTRL1)

12.垂直同步模式和点时钟模式寄存器2(VDCTRL2)

13.垂直同步模式和点时钟模式寄存器3(VDCTRL3)
14.垂直同步模式和点时钟模式寄存器4(VDCTRL4)

四、代码
1.lcd.h
#ifndef _LCD_H_
#define _LCD_H_extern void init_lcd(void);
extern void lcd_clear(unsigned int color);
extern void lcd_drawpoint(int x, int y, unsigned int color);struct tftlcd_t
{unsigned short height;unsigned short width;unsigned short pix_size;unsigned short vspw;unsigned short vbp;unsigned short vfp;unsigned short hspw;unsigned short hbp;unsigned short hfp;unsigned int frame_buffer;unsigned int fore_color;unsigned int back_color;
};extern struct tftlcd_t lcd_dev;#define LCD_FRAMEBUFFER_ADDR        (0x89000000)#define COLOR(r, g, b) ((r << 16) | (g << 8) | (b << 0))#endif
2.lcd.c
#include "lcd.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "gpio.h"
#include "delay.h"
#include "stdio.h"void init_lcd_io(void)
{//1、引脚初始化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_CLK_LCDIF_CLK, 0);IOMUXC_SetPinMux(IOMUXC_LCD_HSYNC_LCDIF_HSYNC, 0);IOMUXC_SetPinMux(IOMUXC_LCD_VSYNC_LCDIF_VSYNC, 0);IOMUXC_SetPinMux(IOMUXC_LCD_ENABLE_LCDIF_ENABLE, 0);IOMUXC_SetPinMux(IOMUXC_GPIO1_IO08_GPIO1_IO08, 0);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_CLK_LCDIF_CLK, 0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_HSYNC_LCDIF_HSYNC, 0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_VSYNC_LCDIF_VSYNC, 0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_ENABLE_LCDIF_ENABLE, 0xB9);IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO08_GPIO1_IO08, 0xB9);struct GPIO_Type_t t = {.direction = gpio_output,.defalut_value = 1};init_gpio(GPIO1, 8, &t);write_gpio(GPIO1, 8, 1);
}void init_lcd_clk(void)
{CCM_ANALOG->PLL_VIDEO |= (1 << 16);CCM_ANALOG->PLL_VIDEO |= (1 << 12);CCM_ANALOG->PLL_VIDEO_NUM = 0;CCM_ANALOG->PLL_VIDEO_DENOM = 1;CCM_ANALOG->PLL_VIDEO &= ~(0x7F << 0);CCM_ANALOG->PLL_VIDEO |= (42 << 0);CCM_ANALOG->PLL_VIDEO &= ~(3 << 19);CCM_ANALOG->PLL_VIDEO |= (2 << 19);CCM_ANALOG->PLL_VIDEO &= ~(1 << 12);CCM_ANALOG->PLL_VIDEO |= (1 << 13);while((CCM_ANALOG->PLL_VIDEO & (1 << 31)) == 0);CCM_ANALOG->PLL_VIDEO &= ~(1 << 16);unsigned int t;t = CCM->CSCDR2;t &= ~(7 << 15);t |= (2 << 15);t &= ~(7 << 12);t |= (3 << 12);t &= ~(7 << 9);CCM->CSCDR2 = t;CCM->CBCMR |= (7 << 23);
}void reset_lcd(void)
{LCDIF->CTRL |= (1 << 31);delayms(20);LCDIF->CTRL &= ~(3 << 30);
}struct tftlcd_t lcd_dev;void lcd_clear(unsigned int color)
{unsigned int (*p)[800] = (unsigned int (*)[800])lcd_dev.frame_buffer;int i, j;for(i = 0;i < lcd_dev.height;++i){for(j = 0;j < lcd_dev.width;++j){p[i][j] =color;}}
}void init_lcd(void)
{init_lcd_io();reset_lcd();    printf("before Init lcd \n");init_lcd_clk();printf("after Init lcd \n");lcd_dev.width = 800;lcd_dev.height = 480;lcd_dev.hspw = 48;lcd_dev.hbp = 88;lcd_dev.hfp = 40;lcd_dev.vspw = 3;lcd_dev.vbp = 32;lcd_dev.vfp = 13;lcd_dev.pix_size = 4;lcd_dev.frame_buffer = LCD_FRAMEBUFFER_ADDR;lcd_dev.fore_color = 0x0000FF00;lcd_dev.back_color = 0x00000000;LCDIF->CTRL |= (1 << 19) | (1 << 17) | (3 << 10) | (3 << 8) | (1 << 5);LCDIF->CTRL1 = (7 << 16);LCDIF->TRANSFER_COUNT = (lcd_dev.height << 16) | (lcd_dev.width);LCDIF->CUR_BUF = lcd_dev.frame_buffer;LCDIF->NEXT_BUF = lcd_dev.frame_buffer;LCDIF->VDCTRL0 = (1 << 28) | (1 << 24) | (1 << 21) | (1 << 20) | (lcd_dev.vspw << 0);LCDIF->VDCTRL1 = lcd_dev.height + lcd_dev.vspw + lcd_dev.vfp + lcd_dev.vbp;LCDIF->VDCTRL2 = (lcd_dev.hspw << 18) | (lcd_dev.hspw + lcd_dev.hfp + lcd_dev.hbp + lcd_dev.width);LCDIF->VDCTRL3 = ((lcd_dev.hspw + lcd_dev.hbp) << 16) | (lcd_dev.vspw + lcd_dev.vbp);LCDIF->VDCTRL4 = (1 << 18) | (lcd_dev.width);LCDIF->CTRL |= (1 << 0);delayms(20);
}void lcd_drawpoint(int x, int y, unsigned int color)
{unsigned int *p = (unsigned int *)lcd_dev.frame_buffer;if(x >= lcd_dev.height || y >= lcd_dev.width){return;}*(p + lcd_dev.width * y + x) = color;
}3.main.c
#include "string.h"
#include "led.h"
#include "beep.h"
#include "MCIMX6Y2.h"
#include "key.h"
#include "interrupt.h"
#include "clock.h"
#include "epit.h"
#include "gpt.h"
#include "delay.h"
#include "uart.h"
#include "stdio.h"
#include "i2c.h"
#include "lm75.h"
#include "adc.h"
#include "spi.h"
#include "adxl345.h"
#include "lcd.h"
#include "framebuffer.h"int main(void)
{init_clock();system_interrupt_init();init_led();init_beep();// init_key();
//    init_epit1();init_gpt1();init_uart1(); init_i2c1();init_adc1_channle1();init_spi3();
//    init_adxl345();init_lcd();lcd_clear(0x00FFFFFF);const char *s = "Hello World!";lcd_show_string(100, 100, 16 * strlen(s), 32, 32, s);while(1){}return 0;
}
