Linux:LCD驱动开发

embedded/2024/10/19 2:22:41/

目录

1.不同接口的LCD硬件操作原理

应用工程师眼中看到的LCD

1.1像素的颜色怎么表示

​编辑 1.2怎么把颜色发给LCD

 驱动工程师眼中看到的LCD

统一的LCD硬件模型

8080接口

TFTRGB接口

什么是MIPI

Framebuffer驱动程序框架

 怎么编写Framebuffer驱动框架

硬件LCD时序分析

8080接口LCD

 TFTRGB接口LCD

IMX6ULL中的LCD控制器

一、LCD接口概述

 二、external Signal

 三、时钟

四、功能描述

单buffer和双biffer

使用双buffer


1.不同接口的LCD硬件操作原理

应用工程师眼中看到的LCD

LCD由一个一个像素组成:每行由xres个像素,有yres行,他的分辨率是:xres*yres.

只要我们能控制任意一个像素的颜色,就可以在LCD上绘制图片,文字

1.1像素的颜色怎么表示

用红绿蓝三种颜色来表示,可以用24位数据来表示红绿蓝,也可以用16位等等格式。比如:

  • bpp : bits per pixel ,每个像素用多少位来表示
  • 24bpp:实际上会用到32位,其中8位未使用,其余24位中分别用8位来表示红(R),绿(G),蓝(B)
  • 16bpp:有rbg565,rbg555
  1. rbg565:用5位表示红,用6位表示绿,用5位表示蓝
  2. rbg555:16位数据中用5位表示红,5位表示绿,5位表示蓝,浪费一位

 1.2怎么把颜色发给LCD

假设每个像素的颜色用16位来表示,那么一个LCD的所有像素点假设有xres*yres个,

需要的内存为:xres * yres *16/8 ,也就是要设置所有像素的颜色,需要这么大的内存

这块内存就被称为framebuffer:

  • Framebuffer中每块数据对应一个像素
  • 每块数据的大小可能是16位,32位,这跟LCD上的像素的颜色格式有关
  • 设置好LCD硬件后,只需要把颜色数据写入到Framebuffer即可

 驱动工程师眼中看到的LCD

驱动工程师对LCD的理解要深入硬件,必须要回答这几个问题:

  • Framebuffer在哪里
  • 谁把Framebuffer中的数据发给LCD

统一的LCD硬件模型

第一种情况:将显存,LCD控制器,LCD屏幕组成一个模组(LCM)

第二种情况:将LCD控制器,显存与LCD屏幕分隔开,LCD控制器是位于ARM芯片内部的,ARM                       芯片外接内存,从内存中分配出一块空间供显存使用

 第一种情况一般用于单片机MCU(F103):这种接口称为8080

第二种情况一般用于能运行Linux的高性能的芯片MPU:这种接口一般被称为TFTRGB接口

这两种接口是不一样的,但整个模块的原理是一样的

8080接口

用这种接口的单片机一般性能比较弱, 所以外接的模块用内存的接口最好,我们在访问内存时,一般用数据线访问(读信号,写信号,地址线,数据线,片选信号),这样的话需要的IO口太多了,如何进行精简呢?数据线不可少,所以只能精简地址线,可以通过将地址信号转换为数据通过数据线发送,但是怎么分辨发送的是数据还是地址呢,通过引出一个引脚来分辨(data/cmd)

这种显存在屏幕上,显存一般是SRAM,它比较贵,所以一般的 8080屏幕分辨率不高

要使用更高分辨率的屏幕,就可以用以下接口的方式,这种方式内存接口比较便宜

TFTRGB接口

LCD控制器在ARM芯片内部,它会自动获取Framebuffer中的数据,那它怎么将数据发送到LCD屏幕上边呢:

1.移动像素:DCLK

2.从最后跳到下一行:HSYNC(每来一个脉冲就执行一次)

3.跳回一帧的开始(从最后一个像素段跳到第一个像素点):VSYNC 

4.数据来源:RGB三组线(24条线)

5.当数据从最后跳到下一行和从会后一个像素点跳到第一个像素点的时候数据是无效的,所以使用:DE(来决定数据是否有效)

什么是MIPI

 实际上上边这两种接口的实质是一样的,这两种接口都可以归入一个标准:MIPI标准

MIPI表示“Mobile Industry Proc8080essor Interface”,即移动产业处理器接口,是MIPI联盟发起的为移动应用处理器制定的开放标准和一个规范。主要是手机内部的接口(摄像头,显示器接口,射频/基带接口)等标准化,从而减少手机内部接口的复杂程度及增加设计的灵活性

对于LCD,MIPI可以分为3类:

MIPI-DBI(Display Bus Interface)

既可以是Bus(总线),就是既能发送命令,常用的8080接口就是属于DBI接口

MIPI-DPI(Display Pixel Interface)

Pixel(像素),强调的是操作单个像素,在MPU上的LCD控制器就是这种接口 

MIPI-DSI(Display Serial Interface)

Serial,相比于DBI,DPI需要使用很多接口线,DSI需要的接口线大为减少 

Framebuffer驱动程序框架

它再怎么复杂,还是一个字符驱动程序

LCD驱动程序分为

1.上层比较通用性的代码(框架):fbmem.c

2.硬件驱动:对于不同的芯片还有不同的硬件相关的文件:s3c2410fb.c   STM32MP157_fb.c

调用关系:

示例1:
app: open("/dev/fb0", ...)  主设备号:29  , 次设备号: 0
---------------------------------------------------------------------------
kernel:fb_openint fbidx = iminor(inode);struct fb_info *info = = registered_fb[0];示例2:
app:    read()
----------------------------------------------------------------------------
kernel:fb_readint fbidx = iminor(inode);struct fb_info *info = registered_fb[fbidx];if(info -> fbops -> fb_read)return info -> fbops -> fb_read(info, buf count ppos);src = (u32 __inomem *) (info -> screen_base + p);dst = buffer;*dst++ == fb_readl(src++);copy_to_user(buf, buffer, c);

 怎么编写Framebuffer驱动框架

核心:


struct fb_info {atomic_t count;int node;int flags;struct mutex lock;		/* Lock for open/release/ioctl funcs */struct mutex mm_lock;		/* Lock for fb_mmap and smem_* fields */struct fb_var_screeninfo var;	/* Current var */struct fb_fix_screeninfo fix;	/* Current fix */struct fb_monspecs monspecs;	/* Current Monitor specs */struct work_struct queue;	/* Framebuffer event queue */struct fb_pixmap pixmap;	/* Image hardware mapper */struct fb_pixmap sprite;	/* Cursor hardware mapper */struct fb_cmap cmap;		/* Current cmap */struct list_head modelist;      /* mode list */struct fb_videomode *mode;	/* current mode */#ifdef CONFIG_FB_BACKLIGHT/* assigned backlight device *//* set before framebuffer registration, remove after unregister */struct backlight_device *bl_dev;/* Backlight level curve */struct mutex bl_curve_mutex;	u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
#ifdef CONFIG_FB_DEFERRED_IOstruct delayed_work deferred_work;struct fb_deferred_io *fbdefio;
#endifstruct fb_ops *fbops;struct device *device;		/* This is the parent */struct device *dev;		/* This is this fb device */int class_flag;                    /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTINGstruct fb_tile_ops *tileops;    /* Tile Blitting */
#endifunion {char __iomem *screen_base;	/* Virtual address */char *screen_buffer;};unsigned long screen_size;	/* Amount of ioremapped VRAM or 0 */ void *pseudo_palette;		/* Fake palette of 16 colors */ 
#define FBINFO_STATE_RUNNING	0
#define FBINFO_STATE_SUSPENDED	1u32 state;			/* Hardware state i.e suspend */void *fbcon_par;                /* fbcon use-only private area *//* From here on everything is device dependent */void *par;/* we need the PCI or similar aperture base/size notsmem_start/size as smem_start may just be an objectallocated inside the aperture so may not actually overlap */struct apertures_struct {unsigned int count;struct aperture {resource_size_t base;resource_size_t size;} ranges[0];} *apertures;bool skip_vt_switch; /* no VT switch on suspend/resume required */
};

1.分配fb_info:framebuffer_alloc

2.设置fb_info:var   ,    fbops       ,硬件相关操作

3.注册fb_info:register_framebuffer

4.进行硬件相关的操作

代码如下:

#include <linux/module.h>#include <linux/compat.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/mman.h>
#include <linux/vt.h>
#include <linux/init.h>
#include <linux/linux_logo.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/console.h>
#include <linux/kmod.h>
#include <linux/err.h>
#include <linux/device.h>
#include <linux/efi.h>
#include <linux/fb.h>#include <asm/fb.h>static strcut fb_info *myfb_info;static struct fb_ops myfb_ops = {.owner = THIS_MODULE,.fb_fillrect = cfb_fillrect,.fb_copyarea = cfb_copyarea,.fb_imageblit = cfb_imageblit,
};/*入口函数*/
static int __init lcd_drv_init(void)
{dma_addr_t phy_addr;/*分配fb_info*/myfb_info = framebuffer_alloc(0,NULL);/*设置fb_info*//*1.var: LCD的分辨率,颜色格式等*/myfb_info -> var.xres = 1024;myfb_info -> var.yres = 600;myfb_info -> var.bits_per_pixel = 16;myfb_info -> var.red.offset = 11;myfb_info -> var.red.length = 5;myfb_info -> var.green.offset = 5;myfb_info -> var.green.length = 6;myfb_info -> var.blue.offset = 0;myfb_info -> var.blue.length = 5;/*2.fix*/myfb_info -> fiX.smem_len = myfb_info -> var.xres * myfb_info -> var.yres * 4;if(myfb_info ->var.bits_per_pixel == 24){myfb_inco -> fix.smem_len = myfb_info -> var.xres * myfb_info -> var.yres * myfb_info -> var.bits_per_pixel / 8}//framebuffer的虚拟地址myfb_info -> screen_base = dma_alloc_wc(NULL,myfb_info -> fix.smem_len, &phy_addr, GFP_KERNEL);myfb_info -> fix.smem_start = phy_addr;  //framebuffer的物理地址myfb_info -> fix.type = FB_TYPE_PACKED_PIXELS;myfb_info -> fix.visual = FB_VISUAL_TRUECOLOR;/*3.fbops*/myfb_info->fbops = &myfb_ops;/*注册fb_info*/register_framebuffer(myfb_info);/*硬件操作*/return 0;
}/*出口函数*/
static void __exit lcd_drv_exit(void)
{/*反过来操作*//*反注册fb_info*/unregister_framebuffer(myfb_info);/*释放fb_info*/framebuffer_release(myfb_info);}module_init(lcd_drv_init);
module_exit(lcd_drv_exit);
MODULE_AUTHOR("zt");
MODULE_DESCRIPTION("Framebuffer driver for the linux");
MODULE_LICENSE("GPL");

对于硬件相关的操作:

这里使用QEMU中简单的四个寄存器

struct lcd_regs{
    volatile unsigned int fb_base_phys;   //这个寄存器存放的是显存的物理地址
    volatile unsigned int fb_xres;             //这个寄存器存放的是X像素
    volatile unsigned int fb_yres;             //这个寄存器存放的是Y像素
    volatile unsigned int fb_bpp;              //这个寄存器存放的是颜色的位数
};

static struct lcd_regs *mylcd_regs;

/*硬件操作*/

    //因为驱动不能直接操作物理地址所以需要将物理地址映射为虚拟地址
    mylcd_regs = ioremap(0x021C8000,sizeof(struct lcd_regs));
    //地址寄存器存放的是之前myfb_info分配的物理地址(也就是显存的物理地址)
    mylcd_regs->fb_base_phys = phy_addr;
    mylcd_regs->fb_xres = 500;
    mylcd_regs->fb_yres = 300;
    mylcd_regs->fb_bpp  = 16;

硬件LCD时序分析

8080接口LCD

8080接口其实就是一般的内存接口

显存,LCD控制器,LCD屏幕放在是一块的(LCM)

1.接口原理图

数据线,读信号,写信号,地址/数据引脚(当这个引脚为高电平时,传输的为数据,为低电平时,传输的是地址),片选引脚(在Linux中内存空间都是分开的,当使用其中一段内存空间的时候,对应的片选引脚会自动有效)

 当位于第一块内存区间时,片选7自动有效,当位于第二块内存区间时,片选6自动有效

其引脚图如下:

 TFTRGB接口LCD

IMX6ULL中的LCD控制器

下面只是简单介绍一下,具体请看这篇文章:LCD控制器

 它是TFTRGB的接口,显存和LCD控制器在IMX6Ull中所以主要就是LCD控制器与LCD屏幕进行互动

一、LCD接口概述

IMX6ULL的控制器名称为eLCDIF(增强型LCD接口)主要特性如下:

1.支持MPU模式:有些显示屏自带显存,只需要把命令、数据发送给显示屏就可以

2.支持DOTCLK模式:RGB接口,本板子就是此模式

3.VSYNC模式:针对高速数据传输(行场信号)

4.支持ITU-R BT.656接口,可以把4:2:2YcbCr格式的数据转换为模拟电视信号

5.8/16/18/24/32 bit的bpp数据都支持,取决于IO的复用设置及寄存器配置

6.MPU模式,VSYNC模式,DOTCLK模式,都可以配置时序参数

 二、external Signal

 三、时钟

 LCD控制器有两个时钟域:外设总线时钟域,LCD像素时钟域。前者是用来让LCD控制器来正常工作,后者是用来控制电子枪移动

四、功能描述

 我们在内存中划出一块内存,称之为显存,软件把数据写入到显存中

设置好LCD控制器之后,它会通过AXI总线协议从显存把RGB数据读入FIFO,再到达LCD接口(LCD Interface)。上图的Read_Data操作,在MPU模式下才用到。

单buffer和双biffer

首先要知道为什么要区分出单Buffer和双buffer

因为在IMX6ULL中,显存和LCD控制器都是在屏幕外的,当使用单buffer时,因为应用层和LCD控制器都是操作的一块显存(framebuffer),如果应用层写入数据时过快,LCD控制器还没有将显存中的数据写到屏幕中,显存有刷新了,此时就会出现问题。

使用双buffer甚至是多buffer可以有效的解决这个问题,因为应用层和LCD控制器操作的不是同一块显存,设有显存1和显存2(这里是在驱动层直接分配了两个显存大小的内存空间{实际上在IMX6ULL中是直接分配了32Mb的内存空间,相当于13个显存,是非常豪横的!!!}),刚开始应用层先写显存1,LCD控制器读显存2的数据,再一次LCD控制器读显存1的数据,应用层向显存2写数据,这样往复操作,就不会出现单buffer时的情况

使用双buffer

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <time.h>static int fd_fb;
static struct fb_fix_screeninfo fix;	/* Current fix */
static struct fb_var_screeninfo var;	/* Current var */
static int screen_size;
static unsigned char *fb_base;
static unsigned int line_width;
static unsigned int pixel_width;/*********************************************************************** 函数名称: lcd_put_pixel* 功能描述: 在LCD指定位置上输出指定颜色(描点),一个像素一个像素的放* 输入参数: x坐标,y坐标,颜色* 输出参数: 无* 返 回 值: 会* 修改日期        版本号     修改人	      修改内容* -----------------------------------------------* 2020/05/12	     V1.0	  zh(angenao)	      创建***********************************************************************/ 
void lcd_put_pixel(void *fb_base, int x, int y, unsigned int color)
{//unsigned char *pen_8 = fb_base+y*line_width+x*pixel_width;unsigned short *pen_16;	unsigned int *pen_32;	unsigned int red, green, blue;	pen_16 = (unsigned short *)pen_8;pen_32 = (unsigned int *)pen_8;switch (var.bits_per_pixel){case 8:{*pen_8 = color;break;}case 16:{/* 565 */red   = (color >> 16) & 0xff;green = (color >> 8) & 0xff;blue  = (color >> 0) & 0xff;color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);*pen_16 = color;break;}case 32:{*pen_32 = color;break;}default:{printf("can't surport %dbpp\n", var.bits_per_pixel);break;}}
}void lcd_draw_screen(void *fb_base, unsigned int color)
{int x, y;for (x = 0; x < var.xres; x++)for (y = 0; y < var.yres; y++)lcd_put_pixel(fb_base, x, y, color);
}/* ./multi_framebuffer_test single* ./multi_framebuffer_test double*/
int main(int argc, char **argv)
{int i;int ret;//这个参数是记录的int nBuffers;int nNextBuffer = 1;char *pNextBuffer;unsigned int colors[] = {0x00FF0000, 0x0000FF00, 0x000000FF, 0, 0x00FFFFFF};  /* 0x00RRGGBB */struct timespec time;time.tv_sec  = 0;time.tv_nsec = 100000000;if (argc != 2){printf("Usage : %s <single|double>\n", argv[0]);return -1;}fd_fb = open("/dev/fb0", O_RDWR);if (fd_fb < 0){printf("can't open /dev/fb0\n");return -1;}if (ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix)){printf("can't get fix\n");return -1;}if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)){printf("can't get var\n");return -1;}line_width  = var.xres * var.bits_per_pixel / 8;pixel_width = var.bits_per_pixel / 8;screen_size = var.xres * var.yres * var.bits_per_pixel / 8;nBuffers = fix.smem_len / screen_size;printf("nBuffers = %d\n", nBuffers);fb_base = (unsigned char *)mmap(NULL , fix.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);if (fb_base == (unsigned char *)-1){printf("can't mmap\n");return -1;}if ((argv[1][0] == 's') || (nBuffers == 1)){while (1){/* use single buffer */for (i = 0; i < sizeof(colors)/sizeof(colors[0]); i++){lcd_draw_screen(fb_base, colors[i]);nanosleep(&time, NULL);}}}else{/* use double buffer *//* a. enable use multi buffers */var.yres_virtual = nBuffers * var.yres;ioctl(fd_fb, FBIOPUT_VSCREENINFO, &var);while (1){for (i = 0; i < sizeof(colors)/sizeof(colors[0]); i++){/* get buffer */pNextBuffer =  fb_base + nNextBuffer * screen_size;/* set buffer */lcd_draw_screen(pNextBuffer, colors[i]);/* switch buffer */var.yoffset = nNextBuffer * var.yres;ioctl(fd_fb, FBIOPAN_DISPLAY, &var);ret = 0;ioctl(fd_fb, FBIO_WAITFORVSYNC, &ret);nNextBuffer = !nNextBuffer;nanosleep(&time, NULL);}}}munmap(fb_base , screen_size);close(fd_fb);return 0;	
}

当输入single时使用单buffer,当输入double时使用双buffer,经过对比会发现明显的差异,使用双buffer的刷屏会更加流畅,并且不会出现颜色覆盖的情况。


http://www.ppmy.cn/embedded/120990.html

相关文章

cat用来查看文件内容、合并文件,或者将文件内容输出到终端

cat 是 Unix 和 Linux 系统中的一个命令&#xff0c;它的名称来源于 “concatenate”&#xff08;连接&#xff09;&#xff0c;主要用来查看文件内容、合并文件&#xff0c;或者将文件内容输出到终端。 常用用法 查看文件内容 cat filename输出 filename 的内容到终端中。 例…

西电25考研 VS 24考研专业课大纲变动汇总

01专业课变动 西安电子科技大学专业课学长看到953网络安全基础综合变为 893网络安全基础综合&#xff0c;这是因为工科要求都必须是8开头的专业课&#xff0c;里面参考课本还是没变的&#xff0c;无非就是变了一个名字 对于其他变动专业课也是同理的 02专业课考纲内容变化 对于…

解决 GitHub 文件大小限制的问题

要解决 GitHub 文件大小限制的问题&#xff0c;可以使用 Git Large File Storage (Git LFS)。以下是设置步骤&#xff1a; 安装 Git LFS&#xff1a; 对于 macOS&#xff1a;brew install git-lfs对于 Windows&#xff1a;从 Git LFS官网 下载并安装。 初始化 Git LFS&#xff…

逻辑回归(下): Sigmoid 函数的发展历史

背景 闲来无事翻了一下之前买的一个机器学习课程及之前记录的网络笔记&#xff0c;发现遇到公式都是截图&#xff0c;甚至是在纸上用笔推导的。重新整理一遍之前逻辑回归函数的学习笔记&#xff0c;主要是为了玩一下 LaTex 语法&#xff0c;写公式挺有意思的。 整理之前三篇笔…

第十章 MySQL主从复制搭建Docker版

目录 1.新建主服务器容器示例3307 2. 进入/mydata/mysql-master/conf目录下创建my.cnf配置 3.修改完配置后重启master实例 4.进入mysql-master容器 5.master容器实例内创建数据同步的用户 6.新建从服务器容器实例3308 7.进入/mydata/mysql-slave/conf目录下新建my.c…

Html--笔记01:使用软件vscode,简介Html5--基础骨架以及标题、段落、图片标签的使用

一.使用VSC--全称&#xff1a;Visual Studio Code vscode用来写html文件&#xff0c;打开文件夹与创建文件夹&#xff1a;①选择文件夹 ②拖拽文件 生成浏览器的html文件的快捷方式&#xff1a; &#xff01;enter 运行代码到网页的方法&#xff1a; 普通方法&#xff1a…

09_OpenCV彩色图片直方图

import cv2 import numpy as np import matplotlib.pyplot as plt %matplotlib inlineimg cv2.imread(computer.jpeg, 1) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) plt.imshow(img) plt.show()plot绘制直方图 plt.hist(img.ravel(), 256) #ravel() 二维降一维 256灰度级…

RedissonClient 这个类有什么作用?

文章目录 连接管理数据结构支持分布式功能异步和反应式编程配置和定制 RedissonClient 是 Redisson 库中的核心接口&#xff0c;Redisson 是一个用于 Java 的 Redis 客户端&#xff0c;提供了对 Redis 数据结构的高级抽象和支持。 主要作用&#xff1a; 连接管理 它负责与 Red…