一、LCD 概述
LCD 全称是 Liquid Crystal Display,也就是液晶显示器,是现在最常用到的显示器,手机、电脑、各种人机交互设备等基本都用到了 LCD,最常见就是手机和电脑显示器了。这里需要注意的是,我们所提及的 LCD 不包括触摸功能,触摸功能是通过另外的设备实现的,LCD 在这里只是输出设备。
LCD 的构造是在两片平行的玻璃基板当中放置液晶盒,下基板玻璃上设置 TFT(薄膜晶体管),上基板玻璃上设置彩色滤光片,通过 TFT 上的信号与电压改变来控制液晶分子的转动方向,从而达到控制每个像素点偏振光出射与否而达到显示目的。
1. 像素点与屏幕分辨率
屏幕分辨率是指横向像素点个数乘以纵向像素点个数,例如 800 乘以 480、1920 乘以 1080(1080P)、2560 乘以 1440(2K)。
屏幕尺寸以英寸(inch)为单位,指左上角到右下角之间的距离,1 英寸等于 2.54 厘米,常见尺寸如 6.22 英寸、4.3 英寸。
分辨率相同的情况下,屏幕尺寸越小,图像显示效果越好;尺寸相同的情况下,分辨率越高,图像显示效果越好。
2. 像素点颜色格式
像素点的颜色由 RGB 三通道组成,常见格式如下:
- RGB888:每个像素点占 3 个字节,对应 8bit 面板,可显示 1670 万色(2 的 24 次方),每个通道取值范围 0-255,对应一字节 8bit,可用 unsigned char 表示。
- RGB565:每个像素点占 2 个字节,R 占 5 位(0-31)、G 占 6 位(0-63)、B 占 5 位(0-31)。
- ARGB:每个像素点占 4 个字节,A 为透明度通道(0-255)。
- RGB101010:10bit 面板,可显示 10 亿色(2 的 30 次方),成本较高,常用于视频剪辑,也有 8 抖 10bit 方案。
3. 显存
显存是专门存放图像数据的内存空间。以 800 乘以 480 分辨率、ARGB 格式为例,每个像素占 4 字节,所需显存为 800 乘以 480 乘以 4,约 1.5MB。例如 DDR3 有 512MB 的空间(0x80000000 到 0xA0000000),显存空间可开到 0x89000000。
显存可看作二维数组,例如 unsigned int a480800,其中 a00 对应屏幕左上角第一个像素,每个像素的 R、G、B 通道分别占 8 位(R0-R7、G0-G7、B0-B7)。
4. LCD 时序信号
- PCLK:Pixel Clock,像素时钟信号线,同步每个像素点发送的时间间隔,边沿采样(具体查看 LCD 手册)。
- HSYNC:Horizontal Sync,行(水平)同步时钟信号线,同步每一行发送的时间间隔。
- VSYNC:Vertical Sync,帧(垂直)同步时钟信号线,同步每一帧图像的时间间隔。
- DE:Data Enable,数据使能信号线。
- PWM:根据占空比调节背光亮度。
刷新率指每秒能够刷新的帧数,板子上常见为 60Hz。
IMX6ULL 驱动的RGB‑TFT LCD,信号线分为 4 大类:
(1)数据数据线(RGB 像素数据) LCD_DATA0 ~ LCD_DATA23,一共 24 根。
- 24 位:R8 位 + G8 位 + B8 位;
- 16 位屏只用其中低 16 根数据线。
(2)时序控制信号线(4 根)
PCLK:像素时钟HSYNC:行同步信号VSYNC:场同步信号DE:数据使能信号
(3)背光控制信号线 BLT_PWM:PWM 背光亮度控制引脚,GPIO 输出,可以 PWM 调节 LCD 背光亮度。
(4)电源与地线(硬件板子上) VCC、GND,给 LCD 屏幕供电。
软件操作的就是上面 24 根数据 + 4 根时序 + 背光,就是代码里面 lcd_pin_init 初始化的所有引脚。
PCLK、HSYNC、VSYNC、DE 信号线作用
① PCLK 像素时钟(Pixel Clock)
像素时钟,每来 1 个 PCLK 脉冲,传输1 个像素点的 RGB 数据。
- LCD 在 PCLK 边沿采样 RGB 数据线;
- PCLK 频率决定屏幕刷新速度,800*480 屏幕 PCLK 一般 30M 左右;
- 时钟由 LCDIF 外设输出,就是代码里面
lcd_clock_init配置的时钟。
② HSYNC 行同步信号(Horizontal Sync)
行同步,一行图像的开始标记
- 每扫描完一行,HSYNC 发出一个脉冲;
- 脉冲宽度叫
hspw(行同步脉宽); - 脉冲之后是行后沿
hbp,然后才输出一行真正的像素数据;行数据结束之后是行前沿hfp。
对应代码里面:
lcd_dev.hspw、hbp、hfp。
③ VSYNC 场同步信号(Vertical Sync)
- 完整显示完一帧画面,VSYNC 发出脉冲;
- 脉冲宽度
vspw场同步脉宽; - 之后场后沿
vbp,然后输出一帧图像各行像素;图像结束后场前沿vfp。
④ DE 数据使能(Data Enable)
高电平有效:代表当前 RGB 数据线上的数据是有效像素数据
- DE = 高:RGB 数据线输出有效画面像素;
- DE = 低:RGB 数据无效(属于 hbp/hfp/vbp/vfp 这些消隐区间)。
两种 LCD 驱动模式:
- 同步模式(HSYNC+VSYNC):依靠 HSYNC、VSYNC 脉冲判断行、帧,DE 不用;
- DE 模式:依靠 DE 信号判断像素有效,现在开发板 LCD 大多用DE 模式,代码中 LCDIF 打开 DE 模式。
| 信号 | 中文 | 功能 |
|---|---|---|
| PCLK | 像素时钟 | 每个时钟传送 1 个像素 |
| HSYNC | 行同步 | 标志一行画面的起始 |
| VSYNC | 场同步 | 标志一帧画面的起始 |
| DE | 数据使能 | 高电平时 RGB 数据才有效 |
二、LCD 初始化配置步骤
- 配置引脚复用功能以及引脚电气属性。
- 配置 LCD 时钟,例如 31MHz。
- 配置 LCD 控制器相关寄存器。
// 设置lcd引脚复用功能
IOMUXC_SetPinMux(IOMUXC_LCD_DATA00_LCDIF_DATA00, 0);
......
// 设置lcd引脚电气属性
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA00_LCDIF_DATA00, 0xB9);
......
// lcd参数配置
lcd_dev.width = 800;
lcd_dev.height = 480;
lcd_dev.hspw = 48;
lcd_dev.hfp = 40;
lcd_dev.hbp = 88;
lcd_dev.vspw = 3;
lcd_dev.vfp = 13;
lcd_dev.vbp = 32;
lcd_dev.pix_size = 4;
lcd_dev.frame_buffer = LCD_FRAMEBUF_ADDR;
lcd_dev.fore_color = 0x00FF0000;
lcd_dev.back_color = 0x00FFFFFF;
// LCD寄存器
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 << 0);
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.vfp + lcd_dev.vbp + lcd_dev.vspw);
LCDIF->VDCTRL2 = (lcd_dev.hspw << 18) | (lcd_dev.width + lcd_dev.hfp + lcd_dev.hbp + lcd_dev.hspw);
LCDIF->VDCTRL3 = ((lcd_dev.hspw + lcd_dev.hbp) << 16) | ((lcd_dev.vspw + lcd_dev.vbp) << 0);
LCDIF->VDCTRL4 |= (1 << 18) | (lcd_dev.width << 0);
LCDIF->CTRL |= (1 << 0);
三、显存地址与像素写入
显存基地址为 0x89000000,可通过指针操作写入像素颜色。
例如定义 unsigned int (*p)800,p 指向显存首地址,则 p 加 800 行、480 列对应屏幕坐标。也可使用一维指针 unsigned int *p = 0x89000000,通过公式 p 加(800 乘以 y 加 x)定位到第 y 行第 x 列像素,写入颜色值 col,即 *(p + 800 * y + x) = col,其中 x 为列坐标、y 为行坐标。

四、PWM 背光调节
PWM(Pulse Width Modulation,脉冲宽度调制)通过调节占空比来控制 LCD 背光亮度。背光引脚为 BLT_PWM,由 GPIO 输出 PWM 波形。
1. PWM 核心概念
- 计数器:以 1MHz 计数,即 1 个计数周期为 1us。
- PWM 周期:由周期寄存器决定。例如 PWM 频率为 1KHz,则 PWM 周期为 1ms,对应计数器计数 1000 次。
- PWM 占空比:由采样寄存器决定,同时受引脚极性影响。
- 引脚极性 :
- 引脚默认高电平:采样寄存器中的值越大,占空比越大,背光越亮。
- 引脚默认低电平:采样寄存器中的值越大,占空比越小,背光越暗。
- FIFO:用于缓存待输出的 PWM 计数值。
- 回滚点 / 比较点:回滚点决定周期,比较点决定占空比。
2. PWM 参数示例
以 1MHz 计数器、1KHz PWM 频率、50% 占空比为例:
- PWM 周期 = 1ms,计数器计数 1000 次(周期寄存器 = 1000);
- 占空比 50%,即高电平持续 500 个计数周期(采样寄存器 = 500);
- 若引脚默认高电平,采样值越大,占空比越大,背光越亮。
五、触控屏驱动开发
LCD 仅负责显示输出,触摸功能由独立的触控屏芯片实现。触控屏通过 I2C 接口与主控通信,常见类型如下:
- 电容触控屏:通过感应电压变化检测触摸位置,无需校准。
- 电阻屏:通过压力感应检测触摸位置,需要校准。
1. 触控屏芯片
常见触控屏芯片及其 I2C 设备地址如下:
| 屏幕型号 | 触控芯片 | I2C 地址(7 位) |
|---|---|---|
| ALIENTEK 4.3 寸 | GT1158 | 0x14 |
| 4.3 寸 | GT9147 | 0x5D |
本笔记使用的触控屏芯片为 GT9147 ,I2C 7 位地址为 0x14,支持多点触控(最多 5 个点)。
2. GT9147 关键寄存器
- 设备地址:0x14(7 位地址);
- 芯片 ID:寄存器 0x8140,读取值应为 0x9147;
- INT 中断触发方式:寄存器 0x804D / 0x8056,配置为下降沿触发(01);
- 按下点数与坐标:寄存器 0x8150 - 0x815B,用于读取当前按下的点数和各触点坐标。
3. GT9147 驱动初始化流程

- 配置 RST 和 INT 引脚的复用功能及电气属性;
- 将触控屏芯片复位;
- 初始化 I2C2 接口;
- 通过 gt9147_read / gt9147_write 读取芯片 ID(0x8140),校验是否为 0x9147;
- 读取 INT 引脚中断触发方式寄存器(0x804D / 0x8056),配置为下降沿触发;
- 使能 INT 引脚中断;
- 在 GIC 中注册 gt9147 中断函数,设置优先级并使能中断;
- 在中断函数中判断当前按下几个点,并读取按下的坐标点(0x8158 - 0x815B)。
4. 触摸坐标读取
当 INT 引脚触发中断后,通过 I2C 读取 0x8150 - 0x8170 寄存器,即可获得当前按下的触点坐标。GT9147 支持多点触控,最多可同时上报 5 个触摸点,每个触点包含 X、Y 坐标信息。
// gt9147结构体变量
struct GT9147_Device gt9147_dev;
// gt9147读
void gt9147_read(unsigned short reg_addr, unsigned char *data, int len)
{
I2C_Msg msg =
{
.direction = I2C_Read,
.dev_address = 0x14,
.reg_address = reg_addr,
.reg_len = 2,
.data = data,
.len = len
};
master_xfer(I2C2, &msg);
}
// gt9147写
void gt9147_write(unsigned short reg_addr, unsigned char *data, int len)
{
I2C_Msg msg =
{
.direction = I2C_Write,
.dev_address = 0x14,
.reg_address = reg_addr,
.reg_len = 2,
.data = data,
.len = len
};
master_xfer(I2C2, &msg);
}
// 存放多点x y坐标寄存器地址
const unsigned short gt9147_reg5 = {0x8150, 0x8158, 0x8160, 0x8168, 0x8170};
// 读取触摸点坐标
void read_points(void)
{
// 触摸点数量
unsigned char buffer16 = {0};
gt9147_read(0x814E, buffer, 1);
gt9147_dev.num = buffer0 & 0x0F;
// 读取触摸点x y坐标
int i = 0;
for (i = 0; i < gt9147_dev.num; i++)
{
gt9147_read(gt9147_regi, buffer, 4); //buffer0:low buffer1:high
gt9147_dev.xi = buffer1 << 8 | buffer0;
gt9147_dev.yi = buffer3 << 8 | buffer2;
}
// 判断按下是否有效
if (gt9147_dev.num == 0)
{
gt9147_dev.available = 0;
}
else
{
gt9147_dev.available = 1;
}
buffer0 = 0;
gt9147_write(0x814E, buffer, 1);
}