LCD 基本概念与结构
核心定义
LCD(Liquid Crystal Display)即液晶显示器,核心是通过液晶分子的电光效应控制光线透过,结合光学组件实现图像显示。其基本构造是在两片平行玻璃基板间夹着液晶盒,关键组件分工如下:
-
下基板玻璃:集成TFT(薄膜晶体管),作用是控制每个像素点的液晶分子排列(相当于像素的 “开关”)。
-
上基板玻璃:覆盖彩色滤光片(CF),由红(R)、绿(G)、蓝(B)三种滤光单元规律排列而成,负责过滤白光得到三原色。
-
偏光片:上下基板外侧各有一层,仅允许特定方向的光线通过,配合液晶分子的旋转实现 “透光 / 遮光” 控制。
全彩色显示原理
-
背光模块发出白光,经过下偏光片后变成单一方向的光线。
-
TFT 控制液晶分子旋转角度,调节透过液晶盒的光强。
-
光线到达上基板的彩色滤光片,被过滤成 R、G、B 单色光。
-
三种单色光以不同强弱比例混合(如 R=255、G=0、B=0 时显示纯红),最终在屏幕上呈现全彩色图像。
LCD 显示核心参数
像素(Pixel)
-
定义:屏幕显示颜色的最小独立单位,也是位图(如 JPG、BMP)的基本构成单位。
-
原理:显示位图时,系统会将图片的每个像素点 “复制” 到屏幕对应的像素点上,实现图像还原。
分辨率(Resolution)
-
定义:屏幕宽度 × 高度方向上的像素点总数,例如:
-
FULL HD(1080P):1920×1080,约 207 万像素;
-
4K(UHD):3840×2160,约 829 万像素。
-
-
影响:
- 清晰度:分辨率越高,像素密度越大,图像细节越精细;
- 存储空间:分辨率越高,存储一帧图像所需的内存越大(如 32 位色深下,1080P 单帧内存 = 1920×1080×4 字节≈8.2MB)。
色深(Color Depth)
-
定义:每个像素点占用的二进制位数(bit),决定像素能表达的颜色数量。
-
常见规格与颜色数量:
-
8 位:2⁸=256 种颜色(仅支持基础色彩,如早期黑白屏);
-
16 位:2¹⁶=65536 种颜色(高彩色,满足一般显示需求);
-
24 位:2²⁴≈1678 万种颜色(真彩色,多数普通显示器采用);
-
32 位:2³²≈42 亿种颜色(ARGB 格式,含透明度通道)。
-
-
32 位色深细节(ARGB):
-
格式:每个像素占 4 字节(32bit),分配为
A(8bit)+ R(8bit)+ G(8bit)+ B(8bit)
; -
A(Alpha):透明度,取值 0(全透明)~255(不透明),多数 LCD 不支持透明度,实际用
0x00
填充; -
R/G/B:三原色, each 取值 0(无此色)~255(纯色最大值),例如纯红为
0x00FF0000
(A=00,R=FF,G=00,B=00)。
-
Linux 下 LCD 驱动架构(Framebuffer)
核心概念
Framebuffer(帧缓冲)是 Linux 为显示设备提供的驱动子系统,作用是 “桥梁”—— 让应用程序无需直接操作硬件,只需读写内存即可控制屏幕显示。
工作原理
-
驱动初始化:Framebuffer 子系统在内存中申请一块连续空间(称为 “帧缓冲”),用于存储一帧图像的颜色数据;
-
地址映射:将帧缓冲的内存地址映射到应用程序的虚拟地址空间;
-
应用操作:应用程序直接读写映射后的内存(如修改某地址的值 = 修改对应像素的颜色);
-
屏幕刷新:驱动自动将帧缓冲中的数据同步到 LCD 硬件,实现屏幕显示更新。
-
简单理解:Framebuffer 把屏幕 “变成” 了一块可直接读写的内存,操作内存 = 操作屏幕。
Linux 下 LCD 设备文件
Linux 硬件设备分类
设备类型 | 特点 | 举例 |
---|---|---|
字符设备 | 按字节流顺序读写 | LCD、触摸屏、键盘 |
块设备 | 按固定大小 “块” 读写 | 硬盘、U 盘、SD 卡 |
LCD 属于字符设备,需通过对应的驱动程序(.ko
文件)控制。
设备文件生成与路径
- 驱动安装后,Linux 会自动在
/dev
目录下生成 LCD 的设备文件,命名格式为/dev/fb{n}
(n 为 0~31,代表第 n 块 LCD,默认第一块为/dev/fb0
); - 应用程序通过操作
/dev/fb0
文件,间接控制 LCD 硬件(如打开文件、读写数据、关闭文件)。
对比 Windows:类似 “设备管理器” 中显示的 “监视器” 设备,是用户操作硬件的入口。
LCD 硬件参数(fb.h 头文件)
Linux 系统中,LCD 的所有硬件参数定义在/usr/include/linux/fb.h
头文件中,核心是 3 个结构体,用于获取 / 设置 LCD 的硬件信息。
固定参数结构体(struct fb_fix_screeninfo)
- 作用:存储 LCD 的不可修改参数(由硬件决定,应用层只能读取,不能修改);
- 获取方式:通过
ioctl
函数 + 请求码FBIOGET_FSCREENINFO
获取; - 关键字段及解释:
字段名 | 数据类型 | 含义 |
---|---|---|
id | char[16] | 设备驱动名称(如 “fb0”) |
smem_start | __u32 | 帧缓冲的物理内存起始地址(应用层一般用不到,内核态使用) |
smem_len | __u32 | 帧缓冲的总大小(字节)= 分辨率宽度 × 分辨率高度 × 色深字节数 |
type | __u32 | 显卡类型,LCD 默认FB_TYPE_PACKED_PIXELS (像素紧密排列) |
visual | __u32 | 色彩模式,32 位色深默认FB_VISUAL_TRUECOLOR (真彩色) |
line_length | __u32 | 屏幕每行像素占用的字节数(= 分辨率宽度 × 色深字节数) |
accel | __u32 | 硬件加速支持,默认FB_ACCEL_NONE (无加速) |
可变参数结构体(struct fb_var_screeninfo)
- 作用:存储 LCD 的可修改参数(如分辨率、色深,应用层可读取也可修改);
- 获取 / 修改方式:
- 读取:
ioctl
+FBIOGET_VSCREENINFO
; - 修改:
ioctl
+FBIOPUT_VSCREENINFO
;
- 读取:
- 关键字段及解释:
字段名 | 数据类型 | 含义 |
---|---|---|
xres | __u32 | 可见屏幕宽度(像素数,即横向分辨率) |
yres | __u32 | 可见屏幕高度(像素数,即纵向分辨率) |
xres_virtual | __u32 | 虚拟屏幕宽度(显存中图像宽度,一般与 xres 相等) |
yres_virtual | __u32 | 虚拟屏幕高度(显存中图像高度,一般与 yres 相等) |
bits_per_pixel | __u32 | 色深(每个像素的 bit 数) |
red/green/blue | struct fb_bitfield | 红 / 绿 / 蓝三原色的位域信息(见下文 3) |
height | __u32 | 屏幕物理高度(毫米) |
width | __u32 | 屏幕物理宽度(毫米) |
pixclock | __u32 | 像素时钟(显示 1 个像素所需时间,单位:皮秒 ps) |
颜色位域结构体(struct fb_bitfield)
- 作用:定义单一颜色分量(如红色)在像素 32bit 数据中的位置和长度;
- 关键字段及解释:
字段名 | 数据类型 | 含义 | 示例(32 位 ARGB) |
---|---|---|---|
offset | __u32 | 该颜色分量在 32bit 中的起始位(从 0 开始计数) | R:16,G:8,B:0 |
length | __u32 | 该颜色分量占用的bit 数 | 8(R/G/B 各 8bit) |
msb_right | __u32 | 是否右对齐(0 = 左对齐,1 = 右对齐),默认 0 | 0 |
示例:32 位 ARGB 中,红色(R)的offset=16
、length=8
,表示 R 的 8bit 数据位于像素 32bit 中的第 16~23 位(从 0 开始)。
LCD 设备控制(ioctl 函数)
函数作用
ioctl(Input/Output Control)是 Linux 系统中专用于设备控制的系统调用,可实现 “获取硬件参数”“修改硬件配置” 等操作(read/write 只能读写数据,无法控制硬件参数)。
函数原型与参数
#include <sys/ioctl.h>
/*** @brief LCD设备控制函数(通用设备控制接口)* @param fd 设备文件描述符(通过open("/dev/fb0", O_RDWR)获取)* @param request 控制请求码(由fb.h定义,指定要执行的操作)* @param ... 可变参数(根据request不同,传入结构体指针/数值,用于存储或传递参数)* @return 成功返回0;失败返回-1,同时设置errno(如EBADF=fd无效,EINVAL=request无效)*/
int ioctl(int fd, unsigned long request, ...);
LCD 常用请求码(fb.h 定义)
请求码 | 作用 | 可变参数类型 |
---|---|---|
FBIOGET_FSCREENINFO | 获取固定参数(fb_fix_screeninfo) | struct fb_fix_screeninfo * |
FBIOGET_VSCREENINFO | 获取可变参数(fb_var_screeninfo) | struct fb_var_screeninfo * |
FBIOPUT_VSCREENINFO | 修改可变参数(fb_var_screeninfo) | struct fb_var_screeninfo * |
FBIOBLANK | 屏幕黑屏控制(0 = 亮屏,其他 = 黑屏) | int *(传入 0 或非 0 值) |
示例:获取 LCD 宽、高、色深
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <linux/fb.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{// 打开LCD设备文件/*** open函数:打开文件/设备* @param "/dev/fb0" 设备文件路径(LCD默认第一块设备)* @param O_RDWR 打开模式:读写模式* @return 成功返回文件描述符(非负整数);失败返回-1*/int lcd_fd = open("/dev/fb0", O_RDWR);if (lcd_fd == -1){perror("open /dev/fb0 failed"); // 打印错误信息exit(1); // 退出程序,状态码1表示异常}// 定义可变参数结构体,用于存储获取到的LCD参数struct fb_var_screeninfo lcd_var;// 调用ioctl获取LCD可变参数/*** ioctl函数:获取LCD可变参数* @param lcd_fd 已打开的LCD设备文件描述符* @param FBIOGET_VSCREENINFO 请求码:获取可变参数* @param &lcd_var 结构体指针:存储获取到的参数(传出参数)* @return 成功返回0;失败返回-1*/int ret = ioctl(lcd_fd, FBIOGET_VSCREENINFO, &lcd_var);if (ret == -1){perror("ioctl FBIOGET_VSCREENINFO failed");close(lcd_fd); // 失败时关闭设备,避免资源泄漏exit(1);}// 输出LCD关键参数printf("LCD宽度(xres):%d 像素\n", lcd_var.xres); // 如800printf("LCD高度(yres):%d 像素\n", lcd_var.yres); // 如480printf("LCD色深(bits_per_pixel):%d bit\n", lcd_var.bits_per_pixel); // 如32// 关闭设备文件,释放资源close(lcd_fd);return 0;
}
运行结果(以 800×480、32 位色深为例):
LCD宽度(xres):800 像素
LCD高度(yres):480 像素
LCD色深(bits_per_pixel):32 bit
提高 LCD 显示效率(内存映射 mmap)
传统 write 函数的问题
用write
函数向/dev/fb0
写入数据时,存在两次数据拷贝:
-
应用层缓冲区 → 内核缓冲区;
-
内核缓冲区 → LCD 硬件。
拷贝过程耗时,可能导致屏幕出现 “黑线”(数据未及时同步),且应用层需额外申请缓冲区,浪费内存。
mmap 函数原理
mmap(Memory Map,内存映射)是 Linux 提供的内存映射接口,可将 “设备文件” 或 “普通文件” 直接映射到应用层的虚拟地址空间,
实现:
-
应用层直接读写映射后的内存,无需
read/write
; -
数据仅需一次拷贝(应用层内存 → LCD 硬件),效率大幅提升;
-
无需申请应用层缓冲区,节约内存。
函数原型与参数
#include <sys/mman.h>
/*** @brief 内存映射函数,将设备/文件映射到应用层虚拟地址* @param addr 指定映射后的内存地址(NULL=让系统自动分配,推荐)* @param length 映射的内存长度(字节)= 帧缓冲大小= xres*yres*(bits_per_pixel/8)* @param prot 映射内存的保护权限(PROT_READ=读,PROT_WRITE=写,需同时设置)* @param flags 映射标志(MAP_SHARED=共享映射,修改内存会同步到设备;必须设置)* @param fd 设备文件描述符(已打开的/dev/fb0)* @param offset 映射偏移量(设备文件内的偏移,LCD一般设为0)* @return 成功返回映射后的内存起始地址(void*);失败返回MAP_FAILED((void*)-1)*/
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
注意:映射完成后,需调用munmap
函数释放映射内存,避免内存泄漏:
/*** @brief 释放内存映射* @param addr mmap返回的映射内存起始地址* @param length 映射的内存长度(与mmap的length一致)* @return 成功返回0;失败返回-1*/
int munmap(void *addr, size_t length);
示例:用 mmap 实现 LCD 显示德国国旗
德国国旗由上到下为 “黑、红、金” 三色,比例 1:1:1。假设 LCD 分辨率为 800×480,则每色高度 = 480/3=160 像素。
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <linux/fb.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
int main(int argc, char const *argv[])
{// 打开LCD设备文件int lcd_fd = open("/dev/fb0", O_RDWR);if (lcd_fd == -1){perror("open /dev/fb0 failed");exit(1);}// 获取LCD可变参数(分辨率、色深)struct fb_var_screeninfo lcd_var;if (ioctl(lcd_fd, FBIOGET_VSCREENINFO, &lcd_var) == -1){perror("ioctl FBIOGET_VSCREENINFO failed");close(lcd_fd);exit(1);}// 计算映射内存长度(帧缓冲大小)int lcd_width = lcd_var.xres; // LCD宽度(如800)int lcd_height = lcd_var.yres; // LCD高度(如480)int pixel_bytes = lcd_var.bits_per_pixel / 8; // 每个像素的字节数(32bit=4字节)size_t map_len = lcd_width * lcd_height * pixel_bytes; // 映射总长度// 内存映射:将/dev/fb0映射到应用层内存void *fb_mem = mmap(NULL, map_len, PROT_READ | PROT_WRITE, MAP_SHARED, lcd_fd, 0);if (fb_mem == MAP_FAILED){perror("mmap failed");close(lcd_fd);exit(1);}// 定义国旗颜色(32位ARGB,A=00不透明)unsigned int color_black = 0x00000000; // 黑色unsigned int color_red = 0x00FF0000; // 红色unsigned int color_gold = 0x00FFFF00; // 金色(黄)// 填充国旗颜色(按行填充,每色160行)int y, x;unsigned int *pixels = (unsigned int *)fb_mem; // 像素指针(强转为32位整型,方便赋值)// 上半部分:黑色(0~159行)for (y = 0; y < lcd_height / 3; y++){for (x = 0; x < lcd_width; x++){pixels[y * lcd_width + x] = color_black; // 计算当前像素地址并赋值}}// 中间部分:红色(160~319行)for (; y < 2 * lcd_height / 3; y++){for (x = 0; x < lcd_width; x++){pixels[y * lcd_width + x] = color_red;}}// 下半部分:金色(320~479行)for (; y < lcd_height; y++){for (x = 0; x < lcd_width; x++){pixels[y * lcd_width + x] = color_gold;}}// 释放资源:先解除映射,再关闭设备munmap(fb_mem, map_len);close(lcd_fd);return 0;
}
常见问题与注意事项
- 屏幕出现 “黑线”:
- 原因:
write
函数两次拷贝导致数据同步延迟; - 解决:改用
mmap
内存映射,减少拷贝次数。
- 原因:
- 更换 LCD 型号后代码无法运行:
- 原因:不同 LCD 的分辨率、色深、位域不同,硬编码参数不匹配;
- 解决:通过
ioctl
动态获取 LCD 参数(如lcd_var.xres
、lcd_var.bits_per_pixel
),避免硬编码。
- mmap 返回 MAP_FAILED:
- 可能原因:
fd
未正确打开(如路径错误、无读写权限);length
计算错误(超出设备内存大小);- 权限不足(未用 root 用户运行,无法读写
/dev/fb0
)。
- 可能原因:
- 像素颜色与预期不符:
- 原因:色深格式错误(如把 24 位色深按 32 位处理)或位域顺序错误(如 RGB 顺序变成 BGR);
- 解决:通过
lcd_var.red.offset
确认三原色的位域位置,调整颜色值的字节顺序。