一、FrameBuffer 帧缓冲设备的原理
1.1 概念
在linux系统中LCD这类设备称为帧缓冲设备,英文frameBuffer设备。
frameBuffer 是出现在2.2.xx 内核当中的一种驱动程序接口。
帧缓冲(framebuffer)是Linux 系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。用户不必关心物理显示缓冲区的具体位置及存放方式,这些都由帧缓冲设备驱动本身来完成。
framebuffer机制模仿显卡的功能,将显卡硬件结构抽象为一系列的数据结构,可以通过framebuffer的读写直接对显存进行操作。用户可以将framebuffer看成是显存的一个映像,将其映射到进程空间后,就可以直接进行读写操作,写操作会直接反映在屏幕上。
1.2 帧缓冲的理解
用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。
这种操作是抽象的、统一的。用户不必关心物理显存的位置、换页机制等等具体细节。
这些都是由Framebuffer设备驱动来完成的。
1.3 了解帧缓冲设备文件
framebuffer的设备文件一般是 /dev/fb0、/dev/fb1等,最多支持32个设备。
framebuffer是个字符设备,主设备号为29,对应于/dev/fb%d设备文件。
通常,使用如下方式(前面的数字表示次设备号):
(次设备号)0 = /dev/fb0 第一个fb设备
(次设备号)1 = /dev/fb1 第二个fb 设备。
fb 也是一种普通的内存设备,可以读写其内容。
例如,屏幕抓屏:cp /dev/fb0 myfilefb;
注意,这个命令保存下来的仅是rgb数据,并不是图片格式,Window下是打不开的。
(前提是framebuffer驱动里实现了read函数)
1.4 如何去操作这个设备文件
对程序员和Linux系统来说,framebuffer设备与其他的文件没有区别;可以通过配置对framebuffer设备文件完成对硬件的参数设置。
framebuffer映射操作:
通过read()和write()进行数据的读取; 或者通过mmap()函数将内部数据映射到应用程序空间; 3)用mmap()函数把内存中的图像数据直接映射到framebuffer并显示出来,耗时短、效率高。 4)通过ioctl()可以获得显示设备的一些信息: 1-固定信息(比如显示内存大小) 2-显示模式相关的可变信息(比如分辨率、象素结构、每扫描线的字节宽度),以及伪彩色模式下的调色板信息等等; |
二、LCD知识介绍
2.1 了解LCD显示的色深
色深(BPP):一个像素使用多少个二进制位表示它的颜色。
1bpp :一个像素使用 1 个二进制位表示它的颜色,可以有两种色,这种屏单色屏。
2bpp :一个像素使用 2 个二进制位表示它的颜色,可以有4种色,恢阶。
4BPP :
8BPP : 一个像素使用 8 个二进制位表示它的颜色,可以有256种色。
16BPP: 一个像素使用 16 个二进制位表示它的颜色,可以有65536种色,伪彩色。
stm32 这种等级 CPU一般使用这个级别的。
RGB565--常用 RGB5551
24BPP: 一个像素使用 24 个二进制位表示它的颜色,可以有16M种色,真彩色。
RGB888
32BPP: 一个像素使用 32 个二进制位表示它的颜色。 增加了透明度控制。
ARGB888
所有颜色都由RGB三原色组成。通过调节三者比例程现不同颜色。
2.2 LCD屏的时序
要驱动一个TFT屏,首先来认识一下LCD工作时序图。在(芯片数据手册)Exynos 4412 SCP_Users Manual_Ver.0.10.00_Preliminary0.pdf的1816页,第41.3.11.2章节LCD RGB Interface Timing。
LCD的工作时序图如下:
可以把LCD看成一个二维数据。从左到右,从上到下,一个点一点描绘(逐行扫描)。当最后一个点描绘完成,循环扫描。所有显示器显示图像的原理都是从上到下,从左到右的。
那么这幅图在LCD上的显示原理就是:
序号 | 名称 | 具体描述 |
1 | 像素 | 一副图像可以看做是一个矩形,由很多排列整齐的点一行一行组成。这些点称之为像素。 |
2 | 像素时钟信号 | 显示指针从矩形左上角第一行第一个点开始,一个点一个点的在LCD上显示;在上面的时序图上用时间线表示就为VCLK,我们称之为像素时钟信号。 |
3 | 1 line | 当显示指针一直显示到矩形的右边就结束这一行,那么这一行的动作在上面的时序图中就称之为1 Line。 |
4 | 行切换 | 接下来显示指针又回到矩形的左边从第二行开始显示; 注意,显示指针在从第一行的右边回到第二行的左边是需要一定的时间的。 我们称之为行切换 |
5 | HSYNC | 如此类推,显示指针就这样一行一行的显示至矩形的右下角才把一副图显示完成。因此,这一行一行的显示在时间线上看,就是时序图上的HSYNC。 |
6 | 帧 | 然而,LCD的显示并不是对一副图像快速的显示一下,为了持续和稳定的在LCD上显示,就需要切换到另一幅图上(另一幅图可以和上一副图一样或者不一样,目的只是为了将图像持续的显示在LCD上)。那么这一副一副的图像就称之为帧; 在时序图上就表示为1 Frame,因此从时序图上可以看出1 Line只是1 Frame中的一行; |
7 | 帧切换 | 同样的,在帧与帧切换之间也是需要一定的时间的,我们称之为帧切换 |
8 | VSYNC | 那么LCD整个显示的过程在时间线上看,就可表示为时序图上的VSYNC。 |
LCD提供的外部接口信号说明:
(Synchronization:同步,vertical:垂直,horizontal:水平)
名称 | 具体描述 |
VSYNC | 垂直同步信号(TFT)/帧同步信号(STN),标志新的一屏数据开始扫描。 |
HSYNC | 水平同步信号(TFT)/行同步脉冲信号(STN) |
VDEN | 数据使能信号(TFT)/LCD驱动交流偏置信号(STN) |
VD[23:0] | LCD像素数据输出端口(TFT/STN) |
VCLK | 象素时钟信号(TFT/STN) |
LEND | 行结束信号(TFT)/SEC TFT信号 |
上面时序图上各时钟延时参数的含义如下:
(这些参数的值,LCD产生厂商会提供相应的数据手册)。
名称 | 对应驱动中变量 | 具体描述 |
VBPD | upper_margin | 表示在一帧图像开始时,垂直同步信号以后的无效的行数。 |
VFBD | lower_margin | 表示在一帧图像结束后,垂直同步信号以前的无效的行数。 |
VSPW | vsync_len | 表示垂直同步脉冲的宽度,用行数计算。 |
HBPD | left_margin | 表示从水平同步信号开始到一行的有效数据开始之间的VCLK的个数。 |
HFPD | right_margin | 表示一行的有效数据结束到下一个水平同步信号开始之间的VCLK的个数。 |
HSPW | hsync_len | 表示水平同步信号的宽度,用VCLK计算 |
它们的英文全称:
VBPD(vertical back porch) :垂直后肩
VFBD(vertical front porch) :垂直前肩
VSPW(vertical sync pulse width):垂直同步脉冲宽度
HBPD(horizontal back porch):水平前肩
HFPD(horizontal front porth):水平后肩
HSPW(horizontal sync pulse width):水平同步脉冲宽度
LINEVAL: 屏实际高度-1。
HOZVAL: 屏实际宽度-1。
像素时钟信号:一个像素时钟(时间)扫描一个点。
帧 :由行组成
行 :由像素组成
扫描过程:
发起帧同步信号-->行同步信号-->像素时钟信号 (三种信号是同步出现)。
2.3 LCD时序参数的值
以上参数都是由LCD屏厂家提供的。参考光盘资料:
Disk-A\Datasheet\LCD\S700-AT070TN92.pdf |
在以上文档的14页,我们一般用的是经典值。
除了以上的时序参数外,还要注意LCD控制器的默认时序极性和LCD屏的时序极性是否相同,不同则配置为相同。(VSYNC,HSYNC,DEN,像素采样边沿)。
三、应用层FrameBuffer 帧缓冲设备编程(LCD屏编程)
在Linux 系统中LCD的应用程序编程是有特定编写模板的。
下面我们就一步一步的来编写linux下的lcd应用程序。
3.1 编程步骤
(1) 打开/dev/fbX
如:fp = open ("/dev/fb0",O_RDWR); |
Open的第一个参数:/dev/fbx,打开lcd设备文件,lcd设备文件在dev目录下都是以fb*存在的。
得到一个可代表这个设备文件的文件描述符。
(2) 获取可变参数、固定参数
如:要获取可变参数
那么就定义一个变量来存放这个可变参数:
struct fb_var_screeninfo vinfo; |
然后用ioctl接口实现数据的传递:
ioctl(fp,FBIOGET_VSCREENINFO,&vinfo)//可变参数 |
既然我们要获取参数,那么ioctl方向肯定是从内核到应用空间;
根据FBIOGET_VSCREENINFO命令,从fbX设备文件中获取对应的数据,存放在vinfo指向的结构中。
具体是什么结构的数据就看从内核传递上来是什么数据了。
FBIOGET_VSCREENINFO命令命名方式可以分解如下:
FB IO GET_V SCREEN INFO |
这样就可以直观的看出来,是获取屏幕的可变信息。
如:要获取固定参数
同样也定义一个变量来存放这个固定参数:
struct fb_fix_screeninfo finfo; //固定参数 |
通过lcd驱动提供的iotcl接口获取:
ioctl(fp,FBIOGET_FSCREENINFO,&finfo)//固定参数 |
(3) 计算屏幕大小(字节为单位)
计算屏幕大小是为了后面的mmap函数使用,这个屏幕大小是根据可变参数来计算的,公式如下:
屏幕内存大小 = 分辨率*(bpp/8) =(x方向分辨率)* (y方向分辨率)*(bpp/8) |
屏幕的一个点就是x方向的32位数据和y方向的32数据构成。
其中Bpp/8意义是每一个像素占用了多少个字节。即32/8=4byte。
我们可以看出来,屏幕内存大小是以字节为单位的。
如:
screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel/8; |
这个screensize有何用处呢,就是给mmap函数去映射空间时使用的。
(4) 内存映射(mmap函数)
Linux下一切都是文件,我们在对Lcd设备文件操作就是对lcd屏进行了操作。
我们一般想到对屏的操作方法:
我们一般想到对屏的操作方法:
1- LCD操作可以像其他字符设备一样可以调用read,write,对lcd屏进行操作;
但是出于效率考虑,一般不这样操作。
例如: 我要让屏幕实现黑白切换。 (驱动层需要实现read和write函数)
While(1){
Memset(buf,0,screensize);
Write(fp,buf,screensize);
Msleep(1);
Memset(buf,0,screensize);
Write(fp,buf,screensize);
Msleep(1);
}
分析这里为什么会出现效率低的问题:
我要把数据给内核,write就会调用copy_from_user传递数据;
如果屏容量1024*768*4 约有3M左右的数据,我在屏上刷一幅图片要传递3M左右 的数据,1S要刷50次呢,就要在1s内传递150M左右的数据;
memset函数也传递了一次数据,一个2次数据传递,150*2=300M。
那么主频低cpu在这样短时间里处理就会卡顿等。
不用read,write原因:
我们在调用memset的时候,传递了一次数据给缓冲区;然后再使用write的copy_fr/
om_user又传递了一次数据,一共就传递数据了2次。
那么是mmap又是怎么样子的呢?
只要值映射的时候传递了一次数据,在效率上提高了一倍。
2- 内核一般对LCD屏操作是通过内存映射(mmap)方式来实现。
这是一种常见的文件操作方法,系统编程课程会有提到。
我们需要知道lcd编程的概念:
把lcd看成是一块内存,使用mmap函数把它的缓冲区映射到进程空间中,然后通过映射后的地址直接操作驱动中的显示缓冲区,往这块缓冲写数据,lcd就会按数值转换成相应颜色显示在LCD屏上。
那么就需要把lcd设备文件映射到进程的地址空间中。
首先来介绍mmap函数:(man 2 mmap可以查看该函数的用法)
void *mmap(void *addr,
size_t length,
int prot,
int flags,
int fd,
off_t offset);
功能:把文件的内存映射到进程的地址空间中。
参数:
addr: 设置一个可用的地址,一般情况你不知道哪个地址是可用的。
所以,给0表示由系统自动分配。
length:映射的长度。单位是字节
prot:属性。如PROT_READ(可读),PROT_WRITE(可写)等。
PROT_EXEC 映射后的地址空间可以执行代码.
PROT_READ 映射后可以读.
PROT_WRITE 映射后可以写
PROT_NONE 映射后不能访问
flags:保护标志。常用的是共享方式 MAP_SHARED。
fd :open返回的文件描述符。
offset:是从文件的哪个地方开始映射。
返回值:映射后的首地址指针。
如下:
fbp =(unsigned char *) mmap (0, //表示系统自动分别可以映射地址
screensize, //映射大小(字节)
PROT_READ | PROT_WRITE, //映射得到的空间属性
MAP_SHARED, // 保护标志
fp, //要映射的文件描述符
0); //从文件的那个地方开始映射
把lcd设备文件映射到进程的地址空间,从映射开始0开始、大小为 screensize 的内容,得到一个指向这块空间首地址的指针fbp。
(5) 使用映射后的地址对屏进行操作
使用上面得到的 fbp 指针,我们就可以来操作lcd的显示缓冲区了。
示例1:要在(100,200) 处画一个点。
1- 计算偏移(固定的)地址:
off = (800*200+100) * 4;
屏幕上的点是呈(x,y)坐标形式,而进程地址空间呈一块连续的地址内存。所以要计算由屏上的点对于进程空间的地址。
写成通用的表示:
Off = (xres*y + x) * bpp/8;
2- 写入颜色数据(是一个32位的数据):
*(unsigned int *)(fbp+off) = 0x00ff0000;//低地址存低字节(小端格式)
或:
* (fbp+off) = 0x00; //B(蓝色)
* (fbp+off+1) = 0x00; //G(绿色)
* (fbp+off+2) = 0xff; //R(红色)
* (fbp+off+3) = 0x00; //透明色
*(fbp+off)就是我们要写入颜色值的地方。
Fbp就是屏的起始点地址指针,fbp+off是指定点的地址指针。
这里的颜色表示是32bpp色深形式,rgb+透明色形式。
从低地址到高地址为B(蓝色)、G(绿色)、透明色。
我们可以把以上两个步骤封装成一个函数:
void show_pixle(int x,int y,unsigned int c)
{
location = x * (vinfo.bits_per_pixel / 8) + y * finfo.line_length;
*(unsigned int*)(fbp + location) = c;
}
参数:
X:要画的点(x,y)的x坐标
Y:要画的点(x,y)的y坐标
C:要写入的颜色值
(6) 释放映射
知道映射的首地址指针,映射的大小就可以把之前映射的进程地址空间释放,还给系统作其他用途。
munmap (fbp, screensize);
(7) 关闭文件
然后就是把你打的设备文件关闭了。
close (fp);
OK,到此,你对这个文件的操作就结束了。
3.2 示例代码: 获取屏幕信息
#include <stdio.h> #include <linux/fb.h> int main(int argc,char**argv) { struct fb_var_screeninfo var;//可变参数 struct fb_fix_screeninfo fix;//固定参数
int fb; fb=open("/dev/fb0",2); if(fb<0) { printf("fb0打开失败!\n"); return -1; }
/*1. 获取可变参数*/ ioctl(fb,FBIOGET_VSCREENINFO,&var); printf("x=%d\n",var.xres); printf("y=%d\n",var.yres); printf("bit=%d\n",var.bits_per_pixel);
/*2. 获取固定参数*/ ioctl(fb,FBIOGET_FSCREENINFO,&fix); printf("line_byte=%d\n",fix.line_length); printf("smem_len=%d\n",fix.smem_len); return 0; } |
输出的结果:
3.3 示例代码: 显示一个中文字符
在LCD屏指定位置显示一个中文字符。
#include <linux/fb.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <stdio.h> #include <sys/mman.h> #include <string.h>
unsigned char font[]={/*-- 文字: 国 --*/ /*-- 宋体42; 此字体下对应的点阵为:宽x高=56x56 --*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x00,0x00,0x00,0x03,0x00,0x01,0xE0,0x00,0x00, 0x00,0x07,0x80,0x01,0xFF,0xFF,0xFF,0xFF,0xFF,0xE0,0x01,0xF0,0x00,0x00,0x00,0x07, 0xC0,0x01,0xF0,0x00,0x00,0x00,0x07,0x80,0x01,0xF0,0x00,0x00,0x00,0x07,0x80,0x01, 0xF0,0x00,0x00,0x01,0x87,0x80,0x01,0xF0,0x00,0x00,0x03,0xC7,0x80,0x01,0xF6,0x00, 0x00,0x07,0xE7,0x80,0x01,0xF7,0xFF,0xFF,0xFF,0xF7,0x80,0x01,0xF1,0x80,0x7C,0x00, 0x07,0x80,0x01,0xF0,0x00,0x7C,0x00,0x07,0x80,0x01,0xF0,0x00,0x7C,0x00,0x07,0x80, 0x01,0xF0,0x00,0x7C,0x00,0x07,0x80,0x01,0xF0,0x00,0x7C,0x00,0x07,0x80,0x01,0xF0, 0x00,0x7C,0x00,0x07,0x80,0x01,0xF0,0x00,0x7C,0x00,0x07,0x80,0x01,0xF0,0x00,0x7C, 0x00,0x07,0x80,0x01,0xF0,0x00,0x7C,0x06,0x07,0x80,0x01,0xF0,0x00,0x7C,0x0F,0x07, 0x80,0x01,0xF1,0x80,0x7C,0x1F,0x87,0x80,0x01,0xF1,0xFF,0xFF,0xFF,0xC7,0x80,0x01, 0xF0,0xE0,0x7C,0x00,0x07,0x80,0x01,0xF0,0x00,0x7C,0x00,0x07,0x80,0x01,0xF0,0x00, 0x7F,0xC0,0x07,0x80,0x01,0xF0,0x00,0x7D,0xF0,0x07,0x80,0x01,0xF0,0x00,0x7C,0xFC, 0x07,0x80,0x01,0xF0,0x00,0x7C,0x7E,0x07,0x80,0x01,0xF0,0x00,0x7C,0x3F,0x07,0x80, 0x01,0xF0,0x00,0x7C,0x3F,0x07,0x80,0x01,0xF0,0x00,0x7C,0x1F,0x07,0x80,0x01,0xF0, 0x00,0x7C,0x0F,0x07,0x80,0x01,0xF0,0x00,0x7C,0x0E,0x07,0x80,0x01,0xF0,0x00,0x7C, 0x06,0x07,0x80,0x01,0xF0,0x00,0x7C,0x03,0x87,0x80,0x01,0xF0,0x00,0x7C,0x07,0xC7, 0x80,0x01,0xFC,0x00,0x7C,0x0F,0xE7,0x80,0x01,0xFF,0xFF,0xFF,0xFF,0xFF,0x80,0x01, 0xF7,0x00,0x00,0x00,0x07,0x80,0x01,0xF0,0x00,0x00,0x00,0x07,0x80,0x01,0xF0,0x00, 0x00,0x00,0x07,0x80,0x01,0xF0,0x00,0x00,0x00,0x07,0x80,0x01,0xF0,0x00,0x00,0x00, 0x07,0x80,0x01,0xF0,0x00,0x00,0x00,0x07,0x80,0x01,0xFF,0xFF,0xFF,0xFF,0xFF,0x80, 0x01,0xF0,0x00,0x00,0x00,0x07,0x80,0x01,0xF0,0x00,0x00,0x00,0x07,0x80,0x01,0xF0, 0x00,0x00,0x00,0x07,0x80,0x01,0xF0,0x00,0x00,0x00,0x06,0x00,0x01,0xC0,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,};
unsigned char *fbmem=NULL; struct fb_var_screeninfo var; // 可变参数-- struct fb_fix_screeninfo fix; // 固定参数--
/*画点*/ void show_pixel(int x,int y,int color) { unsigned long *show32 = NULL; /* 定位到LCD屏上的位置*/ show32 =(unsigned long *)(fbmem + y*var.xres*var.bits_per_pixel/8 + x*var.bits_per_pixel/8); *show32 =color; /*向指向的LCD地址赋数据*/ }
/* 函数功能:在LCD屏上显示字体 */ void LCD_ShowFont(unsigned int x,unsigned int y,unsigned int w,unsigned int h,unsigned char *p) { unsigned int i,j; unsigned char data; unsigned int x0=x; //保存X坐标 for(i=0;i<w/8*h;i++) { data=p[i]; for(j=0;j<8;j++) { if(data&0x80)show_pixel(x,y,0xF800); //字体颜色画红色 else show_pixel(x,y,0x0000); //背景颜色画白色 x++; //画完一个点,x坐标要向后移动 data<<=1; //依次判断 } if(x-x0==w) //判断是否需要换新行 { x=x0; //横坐标归位 y++; //换新的一行 } } }
int main(int argc,char **argv) { int fd; /* 1、打开/dev/fb0 */ fd = open("/dev/fb0",2); if(fd <= 0) { printf("open is error!!\n"); return -1; }
/* 2、获取可变参数,固定参数 */ /* 2.2、FBIOGET_VSCREENINFO获取可变参数:x,y,bpp */ ioctl(fd,FBIOGET_VSCREENINFO,&var); printf("横坐标=%d\n",var.xres); printf("纵坐标=%d\n",var.yres); printf("一个像素点的位数=%dbit\n",var.bits_per_pixel); printf("横坐标*纵坐标*一个像素点的位数/8=Framebuffer的大小=%d字节\n",var.xres*var.yres*var.bits_per_pixel/8);
/* 2.2、FBIOGET_FSCREENINFO获取固定参数:显存大小 */ ioctl(fd,FBIOGET_FSCREENINFO,&fix); printf("屏幕显示缓冲区大小=%d字节\n",fix.smem_len); //FD缓冲区长度 printf("虚拟横坐标=%d字节\n",var.xres_virtual); printf("虚拟纵坐标=%d字节\n",var.yres_virtual);
/* 3、获取显存虚拟起始地址 */ /* * start:虚拟起始地址 null 自动分配 * length: 映射的大小 * prot :权限 PROT_READ | PROT_WRITE * flags : MAP_SHARED * fd:文件描述符 */ fbmem =(unsigned char *)mmap(NULL,fix.smem_len,PROT_READ|PROT_WRITE,MAP_SHARED,fd, 0); printf("LCD 映射地址:%p\n",fbmem); if(fbmem == (unsigned char *)-1) { printf("mmap error!!!\n"); munmap(fbmem,fix.smem_len); //取消映射 return -1; }
/* 4、清屏函数----把fbmem空间覆盖成0x0 ,清屏成黑色*/ memset(fbmem,0x00,fix.smem_len); LCD_ShowFont(50,150,56,56,font); //显示汉字 close(fd); return 0; } |
四、MMAP驱动实现
4.1 mmap简介
mmap函数用于将一个文件或者其它对象映射进内存,通过对这段内存的读取和修改,来实现对文件的读取和修改,而不需要再调用read,write等操作。
头文件:<sys/mman.h>
函数原型:
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset); int munmap(void* start,size_t length); |
4.2 mmap系统调用接口参数说明
映射函数
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset); |
addr: 指定映射的起始地址,通常设为NULL,由系统指定。
length:映射到内存的文件长度。
prot: 映射的保护方式,可以是:
PROT_EXEC:映射区可被执行
PROT_READ:映射区可被读取
PROT_WRITE:映射区可被写入
PROT_NONE:映射区不能存取
Flags: 映射区的特性,可以是:
MAP_SHARED:写入映射区的数据会复制回文件,且允许其他映射该文件的进程共享。
MAP_PRIVATE:对映射区的写入操作会产生一个映射区的复制(copy_on_write),对此区域所做的修改不会写回原文件。
fd:由open返回的文件描述符,代表要映射的文件。
offset:以文件开始处的偏移量,必须是分页大小的整数倍,通常为0,表示从文件头开始映射。
解除映射
int munmap(void *start, size_t length); |
功能:取消参数start所指向的映射内存,参数length表示欲取消的内存大小。
返回值:解除成功返回0,否则返回-1
4.3 Linux内核的mmap接口
Linux内核中使用结构体vm_area_struct来描述虚拟内存的区域,其中几个主要成员如下:
unsigned long vm_start 虚拟内存区域起始地址
unsigned long vm_end 虚拟内存区域结束地址
unsigned long vm_flags 该区域的标志
该结构体定义在<linux/mm_types.h>头文件中。
该结构体的vm_flags成员赋值的标志为:VM_IO和VM_RESERVED。
其中:VM_IO表示对设备IO空间的映射,M_RESERVED表示该内存区不能被换出,在设备驱动中虚拟页和物理页的关系应该是长期的,应该保留起来,不能随便被别的虚拟页换出(取消)。
mmap操作接口
在字符设备的文件操作集合(struct file_operations)中有mmap函数的接口。原型如下:
int (*mmap) (struct file *, struct vm_area_struct *); |
其中第二个参数struct vm_area_struct *相当于内核找到的,可以拿来用的虚拟内存区间。mmap内部可以完成页表的建立。
4.5 实现mmap映射
映射一个设备是指把用户空间的一段地址关联到设备内存上,当程序读写这段用户空间的地址时,它实际上是在访问设备。这里需要做的两个操作:
1. 找到可以用来关联的虚拟地址区间。
2. 实现关联操作。
mmap设备操作实例如下:
static int tiny4412_mmap(struct file *myfile, struct vm_area_struct *vma) { vma->vm_flags |= VM_IO;//表示对设备IO空间的映射 vma->vm_flags |= VM_RESERVED;//标志该内存区不能被换出(取消),在设备驱动中虚拟页和物理页的关系应该是长期的,应该保留起来,不能随便被别的虚拟页换出 if(remap_pfn_range(vma,//虚拟内存区域,即设备地址将要映射到这里 vma->vm_start,//虚拟空间的起始地址 virt_to_phys(buf)>>PAGE_SHIFT,//与物理内存对应的页帧号,物理地址右移12位 vma->vm_end - vma->vm_start,//映射区域大小,一般是页大小的整数倍 vma->vm_page_prot))//保护属性, { return -EAGAIN; }
printk("tiny4412_mmap\n"); return 0; } |
其中的buf就是在内核中申请的一段空间。使用kmalloc函数实现。
代码如下:
buf = (char *)kmalloc(MM_SIZE, GFP_KERNEL); //内核申请内存只能按页申请,申请该内存以便后面把它当作虚拟设备 |
4.6 remap_pfn_range函数
remap_pfn_range函数用于一次建立所有页表。函数原型如下:
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot); |
其中vma是内核为我们找到的虚拟地址空间,addr要关联的是虚拟地址,pfn是要关联的物理地址,size是关联的长度是多少。
ioremap与phys_to_virt、virt_to_phys的区别:
ioremap是用来为IO内存建立映射的, 它为IO内存分配了虚拟地址,这样驱动程序才可以访问这块内存。
phys_to_virt只是计算出某个已知物理地址所对应的虚拟地址。
virt_to_phys :物理地址
4.7 示例代码
(1) 驱动代码示例
#include <linux/init.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/slab.h> //定义kmalloc接口 #include <asm/io.h> //定义virt_to_phys接口 #include <linux/mm.h> //remap_pfn_range
#define MM_SIZE 4096 static char *buf= NULL; static int tiny4412_open(struct inode *my_indoe, struct file *my_file) { buf=(char *)kmalloc(MM_SIZE, GFP_KERNEL);//内核申请内存只能按页申请,申请该内存以便后面把它当作虚拟设备 printk("open ok\n"); return 0; }
static int tiny4412_release(struct inode *my_indoe, struct file *my_file) { printk("驱动层打印=%s\n",buf); kfree(buf); /*释放空间*/ printk("open release\n"); return 0; }
static int tiny4412_mmap(struct file *myfile, struct vm_area_struct *vma) { vma->vm_flags |= VM_IO;//表示对设备IO空间的映射 vma->vm_flags |= VM_RESERVED;//标志该内存区不能被换出,在设备驱动中虚拟页和物理页的关系应该是长期的,应该保留起来,不能随便被别的虚拟页换出 if(remap_pfn_range(vma,//虚拟内存区域,即设备地址将要映射到这里 vma->vm_start,//虚拟空间的起始地址 virt_to_phys(buf)>>PAGE_SHIFT,//与物理内存对应的页帧号,物理地址右移12位 vma->vm_end - vma->vm_start,//映射区域大小,一般是页大小的整数倍 vma->vm_page_prot))//保护属性, { return -EAGAIN; }
printk("tiny4412_mmap ok\n"); return 0; }
static struct file_operations tiny4412_fops= { .open=tiny4412_open, .release=tiny4412_release, .mmap=tiny4412_mmap };
static struct miscdevice misc={ .minor=255, .name="tiny4412_mmap", // /dev/下的名称 .fops=&tiny4412_fops, };
static int __init hello_init(void) { /*1. 注册杂项字符设备*/ misc_register(&misc); printk("hello_init 驱动安装成功!\n"); return 0; }
static void __exit hello_exit(void) { /*2. 注销*/ misc_deregister(&misc); printk("hello_exit驱动卸载成功!\n"); }
module_init(hello_init); module_exit(hello_exit);
MODULE_AUTHOR("www.wanbangee.com"); //声明驱动的作者 MODULE_DESCRIPTION("hello 模块测试"); //描述当前驱动功能 MODULE_LICENSE("GPL"); //驱动许可证。支持的协议GPL。 |
(2) 应用层代码示例
#include <stdio.h> #include <sys/mman.h>
unsigned char *fbmem=NULL; unsigned char buff[10]; int main(int argc,char**argv) { int fd; fd=open("/dev/tiny4412_mmap",2); if(fd<0) { printf("驱动打开失败!\n"); return -1; } fbmem =(unsigned char *)mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if(fbmem==NULL) { printf("映射错误!\n"); } memcpy(fbmem,"123456789",10); //向映射空间拷贝数据 memcpy(buff,fbmem,10); //将映射空间的数据拷贝出来 printf("应用层打印=%s\n",buff); //打印出123456789 close(fd); return 0; } |
五、FrameBuffer帧缓冲驱动框架
帧缓冲设备相关的数据结构存放在: fb.h头文件里
使用帧缓冲相关函数与数据结构需要添加头文件: #include <linux/fb.h>
5.1 帧缓冲注册与注销函数
1. FrameBuffer注册函数
int register_framebuffer(struct fb_info *fb_info) |
函数功能介绍: 注册帧缓冲设备
函数参数介绍:
@ fb_info :帧缓冲(LCD)设备信息。
说明: 注册成功之后,会在/dev目录下生成fb开头的设备文件。
2. FrameBuffer注销函数
int unregister_framebuffer(struct fb_info *fb_info) |
函数功能介绍: 注销帧缓冲设备
函数参数介绍:
@ fb_info :注册时填入的帧缓冲(LCD)设备信息。
5.2 struct fb_info结构介绍
以下列出了struct fb_info常用的几个成员:
struct fb_info { struct fb_var_screeninfo var; /* 可变参数*/ struct fb_fix_screeninfo fix; /* 固定参数*/ struct fb_ops *fbops; /*LCD屏专用的文件操作接口*/ } |
LCD屏专属的文件操作接口,在fb.h定义,以下列出了常用的几个函数接口:
struct fb_ops { int (*fb_open)(struct fb_info *info, int user); int (*fb_release)(struct fb_info *info, int user); ssize_t (*fb_read)(struct fb_info *info, char __user *buf,size_t count, loff_t *ppos); ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,size_t count, loff_t *ppos); int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma); } |
可变形参常用的结构成员:
struct fb_var_screeninfo { __u32 xres; /*可见分辨率 */ __u32 yres; __u32 bits_per_pixel; /* 颜色深度 */ } |
固定参数常用的结构体成员:
struct fb_fix_screeninfo { __u32 smem_len; /* 帧缓冲存储器的长度 */ } |
填充示例:
static struct fb_info fb_info= { .var= //可变形参 { .xres=800, .yres=480, .bits_per_pixel=24 }, .fix= //固定形参 { .smem_len=4096, }, .fbops=&lcd_fops, }; |
5.3 分配显存地址
static inline void *dma_alloc_writecombine(struct device *dev, size_t size,dma_addr_t *dma_handle, gfp_t flag) |
功能介绍:分配一块物理地址连续的内存, 供后续 DMA 传输使用。
函数参数:
struct device *dev : 传入设备指针, 没有填 NULL。
size_t size : 分配的空间大小。
dma_addr_t *dma_handle :存放分配之后的物理地址。
gfp_t flag :分配的属性。 常用: GFP_KERNEL
返回值: 物理地址对应的虚拟地址, 内核代码本身只能操作虚拟地址。
示例:
oled_info.screen_base=dma_alloc_writecombine( NULL, oled_info.fix.smem_len, (dma_addr_t *)&oled_info.fix.smem_start, GFP_KERNEL ); |
5.4 帧缓冲框架注册示例代码
以下演示了帧缓冲的驱动框架,剩下事情可以直接加入硬件代码。
1. 帧缓冲驱动代码
#include <linux/kernel.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/io.h> #include <linux/fb.h> #include <linux/dma-mapping.h> /* struct fb_var_screeninfo var; ///可变参数 struct fb_fix_screeninfo fix; ///固定参数 */ static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf) { chan &= 0xffff; chan >>= 16 - bf->length; return chan << bf->offset; }
static int tiny4412_lcdfb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, unsigned int blue, unsigned int transp, struct fb_info *info) { unsigned int val; if (regno > 16) return 1;
/* 用red,green,blue三原色构造出val */ val = chan_to_field(red, &info->var.red); val |= chan_to_field(green, &info->var.green); val |= chan_to_field(blue, &info->var.blue);
((u32 *)(info->pseudo_palette))[regno] = val; return 0;
}
static struct fb_ops tiny4412_fbops= { .owner = THIS_MODULE, //.fb_setcolreg = tiny4412_lcdfb_setcolreg, //.fb_fillrect = cfb_fillrect, //.fb_copyarea = cfb_copyarea, //.fb_imageblit = cfb_imageblit, };
/*保存使LCD屏硬件信息*/ static struct fb_info tiny4412_fb_info= { .var= { .xres=800, .yres=480, .bits_per_pixel=24 }, .fix= { .smem_len=800*480*3, .line_length=800*3 }, .fbops=&tiny4412_fbops };
static int __init tiny4412_frame_dev_init(void) { /* 分配显存(framebuffer),物理地址连续的一段空间*/ /*在应用层打开该设备节点时,可以通过mmap函数将该地址映射到进程空间*/ tiny4412_fb_info.screen_base = dma_alloc_writecombine(NULL, tiny4412_fb_info.fix.smem_len, &tiny4412_fb_info.fix.smem_start, GFP_KERNEL);
/*注册帧缓冲设备*/ register_framebuffer(&tiny4412_fb_info); printk("LCD:驱动安装成功!\n"); /*提示语句*/ return 0; }
static void __exit tiny4412_frame_dev_exit(void) { /*注销帧缓冲设备*/ unregister_framebuffer(&tiny4412_fb_info);
//打印出应用层写入的数据 printk("数据测试=%s\n",tiny4412_fb_info.screen_base);
/*释放显存*/ dma_free_writecombine(NULL, tiny4412_fb_info.fix.smem_len, tiny4412_fb_info.screen_base, tiny4412_fb_info.fix.smem_start); printk("LCD:驱动卸载成功!\n"); }
module_init(tiny4412_frame_dev_init); module_exit(tiny4412_frame_dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("tiny4412 wbyq"); |
2. 应用层代码
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/fb.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <string.h>
struct fb_var_screeninfo var; ///可变参数 struct fb_fix_screeninfo fix; ///固定参数 unsigned char *fb_mem=NULL; /*LCD屏的首地址*/
char buff[]="1234567890";
int main(int argc,char **argv) { if(argc!=2) { printf("./app </dev/fbX>\n"); return 0; } int fd=open(argv[1],O_RDWR); if(fd<0) { perror("设备文件打开失败"); return 0; }
/*1. 获取LCD屏的可变形参*/ ioctl(fd,FBIOGET_VSCREENINFO,&var); printf("分辨率:%d*%d\n",var.xres,var.yres); printf("像素点位数:%d\n",var.bits_per_pixel);
/*2. 获取LCD屏的固定形参*/ ioctl(fd,FBIOGET_FSCREENINFO,&fix); printf("映射的长度:%d\n",fix.smem_len); printf("一行的字节数:%d\n",fix.line_length);
/*3. 映射LCD缓冲区地址到进程空间*/ fb_mem=mmap(NULL,fix.smem_len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if(fb_mem==NULL) { perror("MMAP映射错误!"); return 0; } printf("mmap映射的空间:0x%X\n",fb_mem);
//向屏幕缓冲区写数据: 测试使用,在驱动卸载的时候打印了出来 memcpy(fb_mem,buff,sizeof(buff));
/*取消映射*/ munmap(fb_mem,fix.smem_len); close(fd); return 0; } |
运行结果:
[root@tiny4412 code]#ls app tiny4412_framebuffer.ko [root@tiny4412 code]#insmod tiny4412_framebuffer.ko [ 28.110000] LCD:驱动安装成功! [root@tiny4412 code]# [root@tiny4412 code]#./app /dev/fb5 分辨率:800*480 像素点位数:24 映射的长度:1152000 一行的字节数:2400 mmap映射的空间:0xB6CBC000 [root@tiny4412 code]#rmmod tiny4412_framebuffer.ko [ 49.730000] 数据测试=1234567890 [ 49.730000] LCD:驱动卸载成功! |
六、OLED显示屏驱动+帧缓冲驱动模板
6.1 OLED简介
OLED,即有机发光二极管( Organic Light Emitting Diode)。 OLED 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。
LCD 都需要背光,而 OLED 不需要,因为它是自发光的。这样同样的显示 OLED 效果要来得好一些。以目前的技术,OLED 的尺寸还难以大型化,但是分辨率确可以做到很高。在此我们使用的是中景园电子的 0.96 寸 OLED 显示屏,该屏有以下特点:
1)0.96 寸 OLED 有黄蓝,白,蓝三种颜色可选;其中黄蓝是屏上 1/4 部分为黄光,下 3/4 为蓝;而且是固定区域显示固定颜色,颜色和显示区域均不能修改;白光则为纯白,也就是黑底白字;
蓝色则为纯蓝,也就是黑底蓝字。
2)分辨率为 128*64
3)多种接口方式;OLED 裸屏总共种接口包括:6800、8080 两种并行接口方式、3 线或 4 线的串行 SPI 接口方式、 IIC 接口方式(只需要 2 根线就可以控制 OLED 了!),这五种接口是通过屏上的 BS0~BS2 来配置的。
4)OLED屏开发了两种接口的 Demo 板,接口分别为七针的 SPI/IIC 兼容模块,四针的IIC 模块。
0.96 寸 OLED屏外观
6.2 OLED驱动代码示例
#include <linux/kernel.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/io.h> #include <linux/fb.h> #include <linux/dma-mapping.h> #include <linux/workqueue.h> #include <linux/wait.h> #include <linux/slab.h> #include <linux/io.h> #include <linux/mm.h> #include <linux/gpio.h> #include <linux/delay.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h>
/* 定义OLED需要使用的寄存器 */ static volatile unsigned int *GPB_CON=NULL; static volatile unsigned int *GPB_DAT=NULL;
//OLED屏幕底层接口 #define OLED_SCK(x) if(x){*GPB_DAT|=1<<0;}else{*GPB_DAT&=~(1<<0);} #define OLED_MOSI(x) if(x){*GPB_DAT|=1<<3;}else{*GPB_DAT&=~(1<<3);} #define OLED_RES(x) if(x){*GPB_DAT|=1<<4;}else{*GPB_DAT&=~(1<<4);} #define OLED_DC(x) if(x){*GPB_DAT|=1<<5;}else{*GPB_DAT&=~(1<<5);} #define OLED_CS(x) if(x){*GPB_DAT|=1<<1;}else{*GPB_DAT&=~(1<<1);}
//命令与数据区分 #define OLED_CMD 0 #define OLED_DAT 1
//函数声明区域 static void OLED_WriteOneByte(u8 data,u8 cmd); static u8 OLED_GPIO_Init(void); static void OLED_Init(void); static void OLED_Clear(u8 data); static void OLED_DrawPoint(u8 x,u8 y,u8 c); static void OLED_RefreshGRAM(void);
/* 函数功能: OLED对应的GPIO口初始化 硬件连接: OLED模块---Tiny4412开发板 GND--------GND VCC--------VCC(5V) D0---------SCL--------------GPB_0 D1---------MOSI-------------GPB_3 RES--------复位-------------GPB_4 //第5个排针 DC---------数据/命令--------GPB_5 //第6个排针 CS---------CS片选-----------GPB_1 */ static u8 OLED_GPIO_Init(void) { /*1. 将物理地址转换为虚拟地址*/ GPB_CON=ioremap(0x11400040,4); GPB_DAT=ioremap(0x11400044,4); if(GPB_CON==NULL||GPB_DAT==NULL) { printk("物理地址转换为虚拟地址出现问题!\n"); return -1; } /*2. 配置GPIO口模式*/ *GPB_CON&=0xFF000F00; *GPB_CON|=0x00111011;
/*3. 上拉GPIO口*/ OLED_CS(1); OLED_DC(1); OLED_MOSI(1); OLED_RES(1); OLED_SCK(1); }
/* 函数功能: OLED屏幕初始化 */ static void OLED_Init(void) { /*1. 初始化配置GPIO口*/ OLED_GPIO_Init();
/*2. 执行OLED屏幕的初始化配置*/ OLED_RES(1); udelay(2000); OLED_RES(0); udelay(2000); OLED_RES(1); udelay(2000);
OLED_WriteOneByte(0xAE,OLED_CMD); //0xAE表示关显示,0xAF表示开显示
OLED_WriteOneByte(0x00,OLED_CMD); OLED_WriteOneByte(0x10,OLED_CMD);
OLED_WriteOneByte(0x40,OLED_CMD);
OLED_WriteOneByte(0xB0,OLED_CMD);
OLED_WriteOneByte(0x81,OLED_CMD); OLED_WriteOneByte(0xCF,OLED_CMD);
OLED_WriteOneByte(0xA1,OLED_CMD);
OLED_WriteOneByte(0xA6,OLED_CMD);
OLED_WriteOneByte(0xA8,OLED_CMD); OLED_WriteOneByte(0x3F,OLED_CMD);
OLED_WriteOneByte(0xC8,OLED_CMD);
OLED_WriteOneByte(0xD3,OLED_CMD); OLED_WriteOneByte(0x00,OLED_CMD);
OLED_WriteOneByte(0xD5,OLED_CMD); OLED_WriteOneByte(0x80,OLED_CMD);
OLED_WriteOneByte(0xD9,OLED_CMD); OLED_WriteOneByte(0xF1,OLED_CMD);
OLED_WriteOneByte(0xDA,OLED_CMD); OLED_WriteOneByte(0x12,OLED_CMD);
OLED_WriteOneByte(0xDB,OLED_CMD); OLED_WriteOneByte(0x30,OLED_CMD);
OLED_WriteOneByte(0x8D,OLED_CMD); OLED_WriteOneByte(0x14,OLED_CMD);
OLED_WriteOneByte(0xAF,OLED_CMD); //正常模式 }
/* 函数功能: 写一个字节 函数参数: cmd=0表示命令,cmd=1表示数据 */ static void OLED_WriteOneByte(u8 data,u8 cmd) { u8 i; /*1. 区分发送数据是命令还是屏幕数据*/ if(cmd){OLED_DC(1);} else {OLED_DC(0);} udelay(2);
/*2. 发送实际的数据*/ OLED_CS(0); //选中OLED
for(i=0;i<8;i++) { udelay(2); OLED_SCK(0); //告诉从机,主机将要发送数据 if(data&0x80){OLED_MOSI(1);} //发送数据 else {OLED_MOSI(0);} udelay(2); OLED_SCK(1); //告诉从机,主机数据发送完毕 data<<=1; //继续发送下一位数据 }
OLED_CS(1); //取消选中OLED OLED_SCK(1); //上拉时钟线,恢复空闲电平 }
/* 函数功能: 清屏 (开全部灯、关全部灯) */ static void OLED_Clear(u8 data) { u8 i,j; for(i=0;i<8;i++) { OLED_WriteOneByte(0xB0+i,OLED_CMD); //设置页地址 OLED_WriteOneByte(0x10,OLED_CMD); //设置列高起始地址(半字节) OLED_WriteOneByte(0x00,OLED_CMD); //设置列低起始地址(半字节) for(j=0;j<128;j++) { OLED_WriteOneByte(data,OLED_DAT); //写数据 } } }
/* 定义显存数组: 8行,每行128列,与OLED屏幕对应 */ static u8 OLED_GRAM[8][128];
/* 函数功能: 画点函数 x: 横向坐标0~128 y: 纵坐标0~64 c: 1表示亮、0表示灭 */ static void OLED_DrawPoint(u8 x,u8 y,u8 c) { u8 page; page=y/8; //得到当前点的页数0/8=0 1/8=0 y=y%8; //得到一列中点的位置。(0~7) //0%8=0 1%8=1 .....7%8=7 8%8=0 9%8=1 ...... if(c) OLED_GRAM[page][x]|=1<<y; else OLED_GRAM[page][x]&=~(1<<y); }
/* 函数功能: 刷新数据到OLED显示屏 */ static void OLED_RefreshGRAM(void) { u8 i,j; for(i=0;i<8;i++) { OLED_WriteOneByte(0xB0+i,OLED_CMD); //设置页地址 OLED_WriteOneByte(0x10,OLED_CMD); //设置列高起始地址(半字节) OLED_WriteOneByte(0x00,OLED_CMD); //设置列低起始地址(半字节) for(j=0;j<128;j++) { OLED_WriteOneByte(OLED_GRAM[i][j],OLED_DAT); //写数据 } } }
#define _OLED_RefreshGRAM 0x12345 /*将应用层的数据刷新到OLED屏幕上*/ #define _OLED_ClearGRAM 0x45678 /*将应用层的数据刷新到OLED屏幕上*/ static int lcd_ioctl(struct fb_info *info, unsigned int cmd,unsigned long arg) { unsigned char *p=(unsigned char *)(info->screen_base); u32 x,y; switch(cmd) { case _OLED_RefreshGRAM: //将数据拷贝到显示屏上 for(y=0;y<info->var.yres;y++) { for(x=0;x<info->var.xres;x++) { if(*p) //亮 { OLED_DrawPoint(x,y,1); } else //灭 { OLED_DrawPoint(x,y,0); } p++; } } OLED_RefreshGRAM(); /*刷新*/ break; case _OLED_ClearGRAM: /*清屏*/ OLED_Clear(0); break; } return 0; }
/* struct fb_var_screeninfo var; ///可变参数 struct fb_fix_screeninfo fix; ///固定参数 */ static struct fb_ops tiny4412_fbops= { .owner= THIS_MODULE, .fb_ioctl=lcd_ioctl };
/*保存使LCD屏硬件信息*/ static struct fb_info tiny4412_fb_info= { .var= { .xres=128, .yres=64, .bits_per_pixel=8 }, .fix= { .smem_len=64*128, .line_length=128, }, .fbops=&tiny4412_fbops };
static int __init tiny4412_frame_dev_init(void) { /*1. 初始化OLED屏幕*/ OLED_Init(); OLED_Clear(0);//清屏为黑色
/* 分配显存(framebuffer),物理地址连续的一段空间*/ /*在应用层打开该设备节点时,可以通过mmap函数将该地址映射到进程空间*/ tiny4412_fb_info.screen_base = dma_alloc_writecombine(NULL,tiny4412_fb_info.fix.smem_len,(dma_addr_t*)&tiny4412_fb_info.fix.smem_start, GFP_KERNEL);
/*注册帧缓冲设备*/ register_framebuffer(&tiny4412_fb_info);
printk("LCD:驱动安装成功!\n"); /*提示语句*/ return 0; }
static void __exit tiny4412_frame_dev_exit(void) { /*注销帧缓冲设备*/ unregister_framebuffer(&tiny4412_fb_info);
/*释放显存*/ dma_free_writecombine(NULL, tiny4412_fb_info.fix.smem_len, tiny4412_fb_info.screen_base, tiny4412_fb_info.fix.smem_start);
/*解除虚拟地址映射关系*/ iounmap(GPB_CON); iounmap(GPB_DAT); printk("LCD:驱动卸载成功!\n"); }
module_init(tiny4412_frame_dev_init); module_exit(tiny4412_frame_dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("tiny4412 wbyq"); |
6.3 应用层测试代码示例
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/fb.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <string.h> #define OLED_RefreshGRAM 0x12345 /*将应用层的数据刷新到OLED屏幕上*/ #define OLED_ClearGRAM 0x45678 /*将应用层的数据刷新到OLED屏幕上*/ struct fb_var_screeninfo var; //可变参数 struct fb_fix_screeninfo fix; //固定参数 unsigned char *fb_mem=NULL; //LCD屏的首地址 extern unsigned char font1[]; extern unsigned char font2[]; /* 函数功能: 画点 */ void Show_Pixel(int x,int y,unsigned char color) { unsigned char *lcd=(unsigned char *)(fb_mem+y*var.xres*var.bits_per_pixel/8+x*var.bits_per_pixel/8); *lcd=color; //颜色赋值 }
/* 函数功能: 显示中文 说明: 取模的字体是按照横向取模进行取点阵码。 取模的字体宽度是8的倍数。 */ void ShowFont(int x,int y,int size,unsigned char *data) { int i,j,x0=x; unsigned char tmp; for(i=0;i<size/8*size;i++) { tmp=data[i]; for(j=0;j<8;j++) { if(tmp&0x80)Show_Pixel(x0,y,0xF); //else 画背景色 x0++; tmp<<=1; } if(x0-x==size) { y++; x0=x; } } }
int main(int argc,char **argv) { if(argc!=2) { printf("./app /dev/fbX\n"); return 0; } int fd=open(argv[1],O_RDWR); if(fd<0) { perror("设备文件打开失败"); return 0; }
/*1. 获取LCD屏的可变形参*/ ioctl(fd,FBIOGET_VSCREENINFO,&var); printf("分辨率:%d*%d\n",var.xres,var.yres); printf("像素点位数:%d\n",var.bits_per_pixel);
/*2. 获取LCD屏的固定形参*/ ioctl(fd,FBIOGET_FSCREENINFO,&fix); printf("映射的长度:%d\n",fix.smem_len); printf("一行的字节数:%d\n",fix.line_length);
/*3. 映射LCD缓冲区地址到进程空间*/ fb_mem=mmap(NULL,fix.smem_len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if(fb_mem==NULL) { perror("空间映射失败!\n"); return 0; }
/*4. 控制显示屏*/ //OLED清屏 ioctl(fd,OLED_ClearGRAM); memset(fb_mem,0,fix.smem_len);
/*显示中文*/ ShowFont(0,0,56,font1); ShowFont(56,0,56,font2); ioctl(fd,OLED_RefreshGRAM);
munmap(fb_mem,fix.smem_len); close(fd); return 0; }
unsigned char font2[]= { /*-- 文字: 入 --*/ /*-- 幼圆42; 此字体下对应的点阵为:宽x高=56x56 --*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xC0,0x00,0x00, 0x00,0x00,0x00,0x0F,0xE0,0x00,0x00,0x00,0x00,0x00,0x07,0xF0,0x00,0x00,0x00,0x00, 0x00,0x01,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0xFC,0x00,0x00,0x00,0x00,0x00,0x00, 0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x00, 0x00,0x00,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00, 0x00,0x00,0x00,0x3F,0x80,0x00,0x00,0x00,0x00,0x00,0x3F,0x80,0x00,0x00,0x00,0x00, 0x00,0x7F,0xC0,0x00,0x00,0x00,0x00,0x00,0x7F,0xC0,0x00,0x00,0x00,0x00,0x00,0x7B, 0xC0,0x00,0x00,0x00,0x00,0x00,0xFB,0xE0,0x00,0x00,0x00,0x00,0x00,0xF1,0xE0,0x00, 0x00,0x00,0x00,0x00,0xF1,0xE0,0x00,0x00,0x00,0x00,0x01,0xF1,0xF0,0x00,0x00,0x00, 0x00,0x01,0xE0,0xF0,0x00,0x00,0x00,0x00,0x03,0xE0,0xF8,0x00,0x00,0x00,0x00,0x03, 0xC0,0xF8,0x00,0x00,0x00,0x00,0x07,0xC0,0x7C,0x00,0x00,0x00,0x00,0x07,0x80,0x7C, 0x00,0x00,0x00,0x00,0x0F,0x80,0x3C,0x00,0x00,0x00,0x00,0x0F,0x00,0x3E,0x00,0x00, 0x00,0x00,0x1F,0x00,0x1E,0x00,0x00,0x00,0x00,0x1E,0x00,0x1F,0x00,0x00,0x00,0x00, 0x3E,0x00,0x0F,0x00,0x00,0x00,0x00,0x7C,0x00,0x0F,0x80,0x00,0x00,0x00,0x7C,0x00, 0x07,0xC0,0x00,0x00,0x00,0xF8,0x00,0x07,0xC0,0x00,0x00,0x01,0xF0,0x00,0x03,0xE0, 0x00,0x00,0x01,0xE0,0x00,0x01,0xE0,0x00,0x00,0x03,0xE0,0x00,0x01,0xF0,0x00,0x00, 0x07,0xC0,0x00,0x00,0xF8,0x00,0x00,0x0F,0x80,0x00,0x00,0xF8,0x00,0x00,0x1F,0x00, 0x00,0x00,0x7C,0x00,0x00,0x1F,0x00,0x00,0x00,0x3E,0x00,0x00,0x3E,0x00,0x00,0x00, 0x3E,0x00,0x00,0x7C,0x00,0x00,0x00,0x1F,0x00,0x00,0xF8,0x00,0x00,0x00,0x0F,0x80, 0x01,0xF0,0x00,0x00,0x00,0x07,0xC0,0x03,0xE0,0x00,0x00,0x00,0x03,0xE0,0x07,0xC0, 0x00,0x00,0x00,0x03,0xF0,0x0F,0x80,0x00,0x00,0x00,0x01,0xF8,0x1F,0x00,0x00,0x00, 0x00,0x00,0xFC,0x1E,0x00,0x00,0x00,0x00,0x00,0x7C,0x1C,0x00,0x00,0x00,0x00,0x00, 0x3C,0x00,0x00,0x00,0x00,0x00,0x00,0x00, };
unsigned char font1[]= { /*-- 文字: 嵌 --*/ /*-- 幼圆42; 此字体下对应的点阵为:宽x高=56x56 --*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38, 0x00,0x00,0x00,0x03,0x80,0x00,0x7C,0x00,0x03,0x80,0x07,0xC0,0x00,0x7C,0x00,0x03, 0xC0,0x07,0xC0,0x00,0x7C,0x00,0x03,0xC0,0x07,0xC0,0x00,0x7C,0x00,0x03,0xC0,0x07, 0xC0,0x00,0x7C,0x00,0x03,0xC0,0x07,0xC0,0x00,0x7C,0x00,0x03,0xC0,0x07,0xC0,0x00, 0x7C,0x00,0x03,0xC0,0x07,0xFF,0xFF,0xFF,0xFF,0xFF,0xC0,0x03,0xFF,0xFF,0xFF,0xFF, 0xFF,0x80,0x01,0xFF,0xFF,0xFF,0xFF,0xFF,0x80,0x00,0xFF,0xFF,0xFF,0xFF,0xFC,0x00, 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0xE0,0x07,0x00,0xF0,0x00,0x00,0x01,0xE0, 0x0F,0x01,0xF0,0x00,0x00,0x01,0xE0,0x0F,0x01,0xF0,0x00,0x00,0x01,0xE0,0x0F,0x01, 0xE0,0x00,0x00,0x01,0xE0,0x0F,0x01,0xE0,0x00,0x00,0x1F,0xFF,0xFF,0xF3,0xFF,0xFF, 0xF0,0x3F,0xFF,0xFF,0xFB,0xFF,0xFF,0xF8,0x3F,0xFF,0xFF,0xFB,0xFF,0xFF,0xF8,0x1F, 0xFF,0xFF,0xE7,0xC3,0x80,0x7C,0x01,0xE0,0x0F,0x07,0x87,0x80,0x7C,0x01,0xE0,0x0F, 0x0F,0x87,0x80,0x7C,0x01,0xE0,0x0F,0x0F,0x87,0x80,0x78,0x01,0xE0,0x0F,0x1F,0x07, 0x80,0x78,0x01,0xE0,0x0F,0x1F,0x07,0x80,0xF8,0x01,0xE0,0x0F,0x3E,0x07,0x80,0xF8, 0x01,0xE0,0x0F,0x3E,0x07,0x80,0xF0,0x01,0xE0,0x0F,0x3C,0x07,0x81,0xF0,0x01,0xE0, 0x0F,0x3C,0x07,0x81,0xE0,0x01,0xE0,0x0F,0x00,0x07,0x83,0xE0,0x01,0xFF,0xFF,0x00, 0x07,0xC3,0xC0,0x01,0xFF,0xFF,0x00,0x07,0xC0,0x00,0x01,0xFF,0xFF,0x00,0x0F,0xE0, 0x00,0x01,0xFF,0xFF,0x00,0x0F,0xE0,0x00,0x01,0xE0,0x0F,0x00,0x0F,0xF0,0x00,0x01, 0xE0,0x0F,0x00,0x1E,0xF0,0x00,0x01,0xE0,0x0F,0x00,0x1E,0xF8,0x00,0x01,0xE0,0x0F, 0x00,0x3E,0x78,0x00,0x01,0xE0,0x0F,0x00,0x7C,0x7C,0x00,0x01,0xE0,0x0F,0x00,0x78, 0x3E,0x00,0x01,0xE0,0x0F,0x00,0xF8,0x1E,0x00,0x01,0xE0,0x0F,0x01,0xF0,0x1F,0x00, 0x01,0xE0,0x0F,0x03,0xE0,0x0F,0x80,0x01,0xE0,0x0F,0x07,0xE0,0x07,0xC0,0x01,0xE0, 0x0F,0x0F,0xC0,0x03,0xE0,0x01,0xE0,0x0F,0x1F,0x80,0x01,0xF0,0x01,0xFF,0xFF,0x3F, 0x00,0x01,0xF8,0x00,0xFF,0xFF,0x7E,0x00,0x00,0xFC,0x00,0x7F,0xFE,0x78,0x00,0x00, 0x3C,0x00,0x00,0x00,0x70,0x00,0x00,0x18, }; |
6.4 OLED显示效果图
七、LCD驱动编写
7.1 编写S70屏幕驱动
如果自己编写了LCD驱动(S720屏幕),测试LCD驱动之前,先去除内核自带的LCD驱动,编译烧写内核:
Device Drivers ---> Graphics support ---> <*> Samsung S3C framebuffer support |
群创S70驱动代码:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/errno.h> #include <linux/string.h> #include <linux/mm.h> #include <linux/slab.h> #include <linux/delay.h> #include <linux/fb.h> #include <linux/init.h> #include <linux/dma-mapping.h> #include <linux/interrupt.h> #include <linux/platform_device.h> #include <linux/clk.h> #include <linux/cpufreq.h>
#include <asm/io.h> #include <asm/div64.h>
#include <asm/mach/map.h> #include <mach/regs-gpio.h>
#define MHZ (1000*1000) #define PRINT_MHZ(m) ((m) / MHZ), ((m / 1000) % 1000) /* LCD参数 */ #define VSPW 0 #define VBPD 22 #define LINEVAL 479 #define VFPD 21
#define HSPW 0 #define HBPD 45 #define HOZVAL 799 #define HFPD 209
#define LeftTopX 0 #define LeftTopY 0 #define RightBotX 799 #define RightBotY 479
/* LCD控制寄存器 */ static unsigned long *vidcon0; /* Configures video output format and displays enable/disable. */ static unsigned long *vidcon1; /* Specifies RGB I/F control signal */ static unsigned long *vidcon2; /* Specifies output data format control. */
static unsigned long *vidtcon0; /* video time control 0 */ static unsigned long *vidtcon1; /* video time control 1 */ static unsigned long *vidtcon2; /* video time control 2 */
static unsigned long *wincon0; /* window control 0 */
static unsigned long *vidosd0a; /* video window 0 position control */ static unsigned long *vidosd0b; /* video window 0 position control1 */ static unsigned long *vidosd0c; /* video window 0 position control */
static unsigned long *vidw00add0b0; /* window 0 buffer start address, buffer 0 */ static unsigned long *vidw00add1b0; /* window 0 buffer end address, buffer 0 */ static unsigned long *vidw00add2; /* window 0 buffer size */
static unsigned long *wpalcon; static unsigned long *shadowcon; static unsigned long *winchmap2;
/* 用于LCD的GPIO */ static unsigned long *gpf0con = NULL; static unsigned long *gpf1con = NULL; static unsigned long *gpf2con = NULL; static unsigned long *gpf3con = NULL; static unsigned long *gpf0pud = NULL; static unsigned long *gpf1pud = NULL; static unsigned long *gpf2pud = NULL; static unsigned long *gpf3pud = NULL; static unsigned long *gpf0drv = NULL; static unsigned long *gpf1drv = NULL; static unsigned long *gpf2drv = NULL; static unsigned long *gpf3drv = NULL; /* 用于背光 */ static unsigned long *gpd0con = NULL; static unsigned long *gpd0dat = NULL; static unsigned long *lcdblk_cfg = NULL; static unsigned long *lcdblk_cfg2 = NULL;
static struct fb_info *mylcd; static u32 pseudo_pal[16];
static struct device dev = { .init_name = "exynos4-fb.0", };
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf) { chan &= 0xffff; chan >>= 16 - bf->length; return chan << bf->offset; }
static int mylcdfb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, unsigned int blue, unsigned int transp, struct fb_info *info) { unsigned int val;
if (regno > 16) return 1;
/* 用red,green,blue三原色构造出val */ val = chan_to_field(red, &info->var.red); val |= chan_to_field(green, &info->var.green); val |= chan_to_field(blue, &info->var.blue);
//((u32 *)(info->pseudo_palette))[regno] = val; pseudo_pal[regno] = val; return 0; }
static struct fb_ops mylcdfb_ops = { .owner = THIS_MODULE, .fb_setcolreg = mylcdfb_setcolreg, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, .fb_imageblit = cfb_imageblit, };
static void init_fimd(void) { gpf0con = ioremap(0x11400180,16); gpf0pud = gpf0con + 2; gpf0drv = gpf0con + 3; gpf1con = ioremap(0x114001A0,16); gpf1pud = gpf1con + 2; gpf1drv = gpf1con + 3; gpf2con = ioremap(0x114001C0,16); gpf2pud = gpf2con + 2; gpf2drv = gpf2con + 3; gpf3con = ioremap(0x114001E0,16); gpf3pud = gpf3con + 2; gpf3drv = gpf3con + 3; /* 设置管脚为LCD接口功能 */ *gpf0con = 0x22222222; *gpf1con = 0x22222222; *gpf2con = 0x22222222; *gpf3con = 0x22222222; /* 设置为上拉 */ *gpf0pud = 0x0000FFFF; *gpf1pud = 0x0000FFFF; *gpf2pud = 0x0000FFFF; *gpf3pud = 0x00000FFF; /* 设置为最高驱动能力 */ *gpf0drv = 0x0000FFFF; *gpf1drv = 0x0000FFFF; *gpf2drv = 0x0000FFFF; *gpf3drv = 0x00000FFF; }
static void init_blanklight(void) { gpd0con = ioremap(0x114000a0,4); gpd0dat = ioremap(0x114000a4,4); *gpd0con |= 1<<4; *gpd0dat |= 1<<1; } static void init_lcd_regs(void) { lcdblk_cfg = ioremap(0x10010210,4); lcdblk_cfg2 = lcdblk_cfg + 1; vidcon0 = ioremap(0x11C00000,4); vidcon1 = ioremap(0x11C00004,4); wincon0 = ioremap(0x11C00020,4); vidosd0a = ioremap(0x11C00040,4); vidosd0b = ioremap(0x11C00044,4); vidosd0c = ioremap(0x11C00048,4); vidw00add0b0 = ioremap(0x11C000A0,4); vidw00add1b0 = ioremap(0x11C000D0,4); vidw00add2 = ioremap(0x11C00100,4); vidtcon0 = ioremap(0x11C00010,4); vidtcon1 = ioremap(0x11C00014,4); vidtcon2 = ioremap(0x11C00018,4); wpalcon = ioremap(0x11C001A0,4); shadowcon = ioremap(0x11C00034,4); winchmap2 = ioremap(0x11C0003c,4); } static void rm_all_regs(void) { iounmap(gpf0con); iounmap(gpf1con); iounmap(gpf2con); iounmap(gpf3con); iounmap(gpd0con); iounmap(gpd0dat); iounmap(lcdblk_cfg); iounmap(vidcon0); iounmap(vidcon1); iounmap(vidtcon2); iounmap(wincon0); iounmap(vidosd0a); iounmap(vidosd0b); iounmap(vidosd0c); iounmap(vidw00add0b0); iounmap(vidw00add1b0); iounmap(vidw00add2); iounmap(vidtcon0); iounmap(vidtcon1); iounmap(shadowcon); iounmap(winchmap2); }
/* 入口函数 */ static int mylcd_init(void) { struct clk *bus; struct clk *lcd_clk; /* 1. 分配一个fb_info */ mylcd = framebuffer_alloc(0, NULL);
/* 2. 设置 */ /* 2.1 设置固定的参数 */ strcpy(mylcd->fix.id, "mylcd"); //mylcd->fix.smem_start = ; //显存的物理起始地址 mylcd->fix.smem_len = 800*480*4; mylcd->fix.type = FB_TYPE_PACKED_PIXELS; mylcd->fix.visual = FB_VISUAL_TRUECOLOR; mylcd->fix.line_length = 800*4;
/* 2.2 设置可变的参数 */ mylcd->var.xres = 800; mylcd->var.yres = 480; mylcd->var.xres_virtual = 800; mylcd->var.yres_virtual = 480; mylcd->var.bits_per_pixel = 32; /*RGB = 8:8:8*/ mylcd->var.red.offset = 16; mylcd->var.red.length = 8; mylcd->var.green.offset = 8; mylcd->var.green.length = 8; mylcd->var.blue.offset = 0; mylcd->var.blue.length = 8; mylcd->var.activate = FB_ACTIVATE_NOW;
/* 2.3 设置操作函数 */ mylcd->fbops = &mylcdfb_ops; /* 2.4 其他的设置 */ //mylcd->screen_base = ; /*显存的虚拟起始地址*/ mylcd->screen_size = 800*480*4; mylcd->pseudo_palette = pseudo_pal;
/* 3. 硬件相关的操作 */ /* 3.1 配置GPIO用于LCD */ init_fimd();
/* 3.2 使能时钟 */ bus = clk_get(&dev, "lcd"); if (IS_ERR(bus)) { printk(KERN_INFO "failed to get lcd clock source\n");
} clk_enable(bus); printk("bus clock = %lu\n",clk_get_rate(bus)); lcd_clk = clk_get(&dev, "sclk_fimd"); if (IS_ERR(lcd_clk)) { printk(KERN_INFO "failed to get lcd clock source\n"); } clk_enable(lcd_clk); printk("lcd clock = %lu\n",clk_get_rate(lcd_clk)); /* LCD控制器初始化 */ init_lcd_regs(); /* * LCDBLK_CFG * [11:10] :Video Type Selection 00 = RGB Interface */ *lcdblk_cfg &= ~(0x3<<10); *lcdblk_cfg |= 1 << 1; *lcdblk_cfg2 |= 1;
/* *VIDCON0: * [13:6]: CLKVAL_F //设置LCD时钟分频系数 * * VCLK == 33.3Mhz * VCLK = FIMD * SCLK/(CLKVAL+1) * VCLK = 800000000 / (CLKVAL + 1) * 33300000 = 800000000 /(CLKVAL + 1) * CLKVAL + 1 = 24.02 * CLKVAL = 23 * */ *vidcon0 &= ~((0xff<<6)|(0x7<<26)|(1<<18)); *vidcon0 |= 23<<6; /* 设置极性(要修改) *VIDTCON1: * [5]:IVSYNC ===> 1 : Inverted(反转) * [6]:IHSYNC ===> 1 : Inverted(反转) * [7]:IVCLK ===> 1 : Fetches video data at VCLK rising edge (下降沿触发) * [10:9]:FIXVCLK ====> 01 : VCLK running * */ *vidcon1 = (1 << 9) | (1 << 7) | (1 << 5) | (1 << 6);
/* 设置时序(需要修改) */ *vidtcon0 = (VBPD << 16) | (VFPD << 8) | (VSPW << 0); *vidtcon1 = (HBPD << 16) | (HFPD << 8) | (HSPW << 0);
/* 设置屏幕的大小 * LINEVAL[21:11]:多少行 = 480 * HOZVAL [10:0] :水平大小 = 800 */ *vidtcon2 = (LINEVAL << 11) | (HOZVAL << 0);
/* * *WINCON0: * [5:2]: 选择窗口的像素 1011 ===> 24BPP * [1]: 使能或者禁止窗口输出 1 = Enables * [15] : 大小端 :1=Enable ; * */ *wincon0 &= ~(0xf << 2); *wincon0 |= (0xB<<2)|(1<<15);
/* 窗口0,左上角的位置(0,0) */ /* 窗口0,右下角的位置(0,0) */ *vidosd0a = (LeftTopX<<11) | (LeftTopY << 0); *vidosd0b = (RightBotX<<11) | (RightBotY << 0); /* 大小 */ *vidosd0c = (LINEVAL + 1) * (HOZVAL + 1);
/* 分配显存 */ mylcd->screen_base = dma_alloc_writecombine(NULL, mylcd->fix.smem_len, &mylcd->fix.smem_start, GFP_KERNEL); *vidw00add0b0 = mylcd->fix.smem_start; //显存的物理起始地址 *vidw00add1b0 = mylcd->fix.smem_start + mylcd->fix.smem_len; //显存的物理结束地址
/* C0_EN_F 0 Enables Channel 0. * 0 = Disables 1 = Enables */ *shadowcon |= 0x1;
/* LCD控制器开启 */ *vidcon0 |= 0x3; /* 开启总控制器 */ *wincon0 |= 1; /* 开启窗口0 */
/*4.注册*/ register_framebuffer(mylcd); return 0; } static void mylcd_exit(void) { unregister_framebuffer(mylcd); dma_free_writecombine(NULL, mylcd->fix.smem_len, &(mylcd->fix.smem_start), GFP_KERNEL); rm_all_regs(); framebuffer_release(mylcd); } module_init(mylcd_init); module_exit(mylcd_exit); MODULE_LICENSE("GPL"); |
7.2 增加自己的LCD驱动
下面步骤演示,在内核自带的LCD驱动框架上增加自己的LCD信息。
- 打开tiny4412-lcds.c文件增加LCD信息列表,增加之后再编译内核烧写
[root@wbyq linux-3.5]# vim arch/arm/mach-exynos/tiny4412-lcds.c +392 |
LCD_wbyq结构体信息如下(拷贝S70屏幕的信息):
/* 增加自己的LCD屏幕信息 */ static struct s3cfb_lcd LCD_wbyq = { .width = 800, .height = 480, .p_width = 155, .p_height = 93, .bpp = 24, .freq = 61,
.timing = { .h_fp = 80, .h_bp = 78, .h_sw = 10, .v_fp = 22, .v_fpe = 1, .v_bp = 24, .v_bpe = 1, .v_sw = 8, }, .polarity = { .rise_vclk = 1, .inv_hsync = 1, .inv_vsync = 1, .inv_vden = 0, }, }; |
修改UBOOT启动代码传入的LCD参数,将参数改为自己的LCD名字
bootargs=root=/dev/nfs nfsroot=192.168.10.11:/work/rootfs ip=192.168.10.123:192.168.10.11:192.168.10.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0 lcd=LCD_wbyq |