裸机中如何操作LCD
LCD的显示原理:DDR中分出一块内存,将要显示的内容放到显存中,硬件自动将显存数据放到驱动器中,驱动器操控LCD显示内容。裸机操作LCD的方法由LCD的本身的工作原理决定。
OS下操作LCD的难点
内核(驱动)做底层硬件操作相关的那部分(初始化LCD控制器的寄存器、内存,建立显存与LCD之间的映射关系),应用做让LCD显示具体内容的那部分(把显示的内容丢到显存中去,让LCD显示具体的内容)。
应用与内核的显存的虚拟地址不同,应用使用的是应用层的虚拟地址空间,而内核使用的是内核层的虚拟地址空间,但是二者可以对应同一块物理地址,这样就可以提升效率。
内核与应用进行数据交换:
小容量数据:copy_to_user,copy_from_user
大容量数据:mmap(可用于显示的情况下,显示画面时数据量较大)
什么是framebuffer
framebuffer帧缓冲(简称fb)是linux内核中虚拟出的一个设备,使用代码构建出的一个设备,具有设备文件可以去读写,代替LCD显示器这个硬件设施以及LCD显示器所需的软件设施和硬件设施,如显卡驱动和显卡。framebuffer可以进行不同的配置从而支持不同的接口如VGA、HDMI等等。
很多人都会说操纵lcd显示就是操纵framebuffer,表面上来看是这样的。实际上是frambuffer就是linux内核驱动申请的一片内存空间,然后lcd内有一片sram,cpu内部有个lcd控制器,它有个单独的dma用来将frambuffer中的数据拷贝到lcd的sram中去 拷贝到lcd的sram中的数据就会显示在lcd上,LCD驱动和framebuffer驱动没有必然的联系,它只是驱动LCD正常工作的,比如有信号传过来,那么LCD驱动负责把信号转成显示屏上的内容,至于什么内容这就是应用层要处理的。
ls /dev/fb*
一般为fb0,fb为设备名称,数字为序号,有几个显示设备就有几个fbX,X表示从零开始的整数
一般只有一个屏幕但是却有好多个fbX文件,是因为这些表示的是虚拟屏幕
framebuffer向应用层提供一个统一标准接口的显示设备,忽略不同显示设备的差异,屏蔽了不同的硬件的差异
从驱动来看,fb是一个典型的字符设备,而且创建了一个类/sys/class/graphics
一、framebuffer的使用
(1)设备文件 /dev/fb0(我的开发板是这个,不同开发板不同内核驱动可能会有所差异)
(2)获取设备信息(显示设备的大小,分辨率等) #include <linux/fb.h>
(3)mmap做映射(详解:13.mmap内存映射_哔哩哔哩_bilibili)
在LINUX中我们可以使用mmap用来在进程虚拟内存地址空间中分配地址空间,创建和物理内存的映射关系。mmap将一个文件或者其它对象映射进内存。
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
mmap只是在虚拟内存分配了地址空间,只有在第一次访问虚拟内存的时候才分配物理内存。
驱动在内核中申请一块显存,多个进程可同时操控LCD,可进行多对一映射,后来的覆盖前面的。
(4)填充framebuffer(即将显示的内容放到framebuffer中)
二、framebuffer驱动源码分析
1、驱动框架部分
-
drivers/video/fbmem.c主要任务:fbmen_init()函数负责创建graphics类、注册FB的字符设备驱动、register_framebuffer()函数提供接口给具体framebuffer驱动编写着来注册fb设备。本文件相对于fb来说,地位和作用和misc.c文件相对于杂散类设备来说一样的,结构和分析方法也是类似的。
-
drivers/video/fbsys.c这个文件是处理fb在/sys目录下的一些属性文件的。
-
drivers/video/modedb.c这个文件是管理显示模式的(譬如VGA、720P、刷新率等等就是显示模式)。
-
drivers/video/fb_notify.c这个文件是frame buff用来管理相关通知的,进行反向唤醒,管理了一个链表,链表发生变动时告知链表中的各个成员。
2、驱动部分
-
drivers/video/samsung/s3cfb.c驱动主体
-
drivers/video/samsung/s3cfb_fimd6x.c里面有很多LCD硬件操作的函数
-
arch/arm/mach-s5pv210/mach-x210.c负责提供platform_device的
-
arch/arm/plat-s5p/devs.c为platform_device提供一些硬件描述信息的
3、framebuffer驱动框架分析:
1、fbmem_init函数
(1)#ifdef MODULE
(2)fb_proc_fops和fb在proc文件系统中的表现
在驱动框架并不进行真正的硬件操作,而是通过函数指针指向一个真正可用的函数
(3)register_chrdev注册fb设备
(4)class_create创建graphics类
(5)fbmem_exit的对应当。以非模块的方式即以内核一部分集成编译进内核时,开机自动加载执行后是无法像模块卸载的,故而这种非模块方式并不需要fbmem_exit函数
【2】分析一下fbmem_init层的register_chrdev注册函数的参数struct file_operations fb_fops:
1、fb_fops
所有的framebuffer设备共用一个主设备号,用不同的次设备号进行区分,类似于misc类设备。
(1)read/write/mmap/ioctl函数
(2)registered_fb和num_registered_fb
(3)struct fb_info描述一个framebuffer设备
【3】例举fb_read,fb_open(流程图为在SI中查看源码,追进去看到的部分细节)
struct fb_info {
int node;//当前这个framebuffer设备在framebuffer设备数组中的下表
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 */
该结构体部分成员
【4】通过查询SI查询内核中使用了register_framebuffer的文件(即具体实现的有关硬件操作相关的驱动)有很多
【5】总结
4.framebuffer驱动分析
1、s3cfb.c
int register_framebuffer(struct fb_info *fb_info)
{
int i;
struct fb_event event;
struct fb_videomode mode;//显示模式
if (num_registered_fb == FB_MAX)//已注册的设备个数是否等于最大设备数
return -ENXIO;
if (fb_check_foreignness(fb_info))//判断大小端模式
return -ENOSYS;
remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id,
fb_is_primary_device(fb_info));//防止冲突机制
num_registered_fb++;
for (i = 0 ; i < FB_MAX; i++)//找到一个空的可用的
if (!registered_fb[i])
break;
fb_info->node = i;
mutex_init(&fb_info->lock);
mutex_init(&fb_info->mm_lock);
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), NULL, "fb%d", i);//创建设备
if (IS_ERR(fb_info->dev)) {
/* Not fatal */
printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
fb_info->dev = NULL;
} else
fb_init_device(fb_info);//对设备进行初始化
if (fb_info->pixmap.addr == NULL) {
fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
if (fb_info->pixmap.addr) {
fb_info->pixmap.size = FBPIXMAPSIZE;
fb_info->pixmap.buf_align = 1;
fb_info->pixmap.scan_align = 1;
fb_info->pixmap.access_align = 32;
fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
}
}
fb_info->pixmap.offset = 0;
if (!fb_info->pixmap.blit_x)
fb_info->pixmap.blit_x = ~(u32)0;
if (!fb_info->pixmap.blit_y)
fb_info->pixmap.blit_y = ~(u32)0;
if (!fb_info->modelist.prev || !fb_info->modelist.next)
INIT_LIST_HEAD(&fb_info->modelist);
fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
registered_fb[i] = fb_info;
event.info = fb_info;
if (!lock_fb_info(fb_info))
return -ENODEV;
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
unlock_fb_info(fb_info);
return 0;
}
2、s3c_device_fb
3、probe函数分析
(1)struct s3c_platform_fb :这个结构体是fb的platform_data结构体,这个结构体变量就是platform设备的私有数据,这个数据在platform_device.device.platform_data中存储。在mach文件中去准备并填充这些数据,在probe函数中通过传参的platform_device指针取出来。
(2)struct s3cfb_global: 这个结构体主要作用是在驱动部分的2个文件(s3cfb.c和s3cfb_fimd6x.c)的函数中做数据传递用的。
framebuffer应用编程实践
实验步骤:
1、打开设备
2、获取设备信息
#include <linux/fb.h>是内核源码中的一个文件,但制作交叉编译链时也将这个文件加入了进去。分析查看文件该文件,可得知相关的设备信息。
(1)不可变信息FSCREENINFO,使用ioctl的FBIOGET_FSCREENINFO命令获得,操作命令码类似于上篇文章操作buzzer的那些命令码。
从fb.h文件摘录部分命令码
/* ioctls
0x46 is 'F' */
#define FBIOGET_VSCREENINFO 0x4600
#define FBIOPUT_VSCREENINFO 0x4601
#define FBIOGET_FSCREENINFO 0x4602
#define FBIOGETCMAP 0x4604
#define FBIOPUTCMAP 0x4605
#define FBIOPAN_DISPLAY 0x4606
#ifdef __KERNEL__
struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin" */
unsigned long smem_start; /* Start of frame buffer mem */
/* (physical address) */
__u32 smem_len; /* Length of frame buffer mem */
__u32 type; /* see FB_TYPE_* */
__u32 type_aux; /* Interleave for interleaved Planes */
__u32 visual; /* see FB_VISUAL_* */
__u16 xpanstep; /* zero if no hardware panning */
__u16 ypanstep; /* zero if no hardware panning */
__u16 ywrapstep; /* zero if no hardware ywrap */
__u32 line_length; /* length of a line in bytes */
unsigned long mmio_start; /* Start of Memory Mapped I/O */
/* (physical address) */
__u32 mmio_len; /* Length of Memory Mapped I/O */
__u32 accel; /* Indicate to driver which */
/* specific chip/card we have */
__u16 reserved[3]; /* Reserved for future compatibility */
};
计算方法:smem_len = xres*yres*bpp/8
(2)可变信息VSCREENINFO,使用ioctl的FBIOGET_VSCREENINFO命令获得
struct fb_var_screeninfo {
__u32 xres; /* visible resolution 真实分辨率即屏幕分辨率*/
__u32 yres;
__u32 xres_virtual; /* virtual resolution 虚拟分辨率,即实际图片的分辨率 */
__u32 yres_virtual;
__u32 xoffset; /* offset from virtual to visible,偏移量,开始显示的左上角坐标 */
__u32 yoffset; /* resolution */
__u32 bits_per_pixel; /* guess what,bpp,每个像素用多少个字节来表示 */
__u32 grayscale; /* != 0 Graylevels instead of colors,灰度等级 */
struct fb_bitfield red; /* bitfield in fb mem if true color, */
struct fb_bitfield green; /* else only length is significant */
struct fb_bitfield blue; //struct fb_bitfield各个颜色以及透明度的阈值
struct fb_bitfield transp; /* transparency,透明度 */
__u32 nonstd; /* != 0 Non standard pixel format */
__u32 activate; /* see FB_ACTIVATE_* */
__u32 height; /* height of picture in mm,屏幕的物理尺寸大小 */
__u32 width; /* width of picture in mm */
__u32 accel_flags; /* (OBSOLETE) see fb_info.flags */
/* Timing: All values in pixclocks, except pixclock (of course) */
__u32 pixclock; /* pixel clock in ps (pico seconds) ,像素时钟*/
__u32 left_margin; /* time from sync to picture */
__u32 right_margin; /* time from picture to sync */
__u32 upper_margin; /* time from sync to picture */
__u32 lower_margin; //初始化LCD时序的六个参数
__u32 hsync_len; /* length of horizontal sync */
__u32 vsync_len; /* length of vertical sync */
__u32 sync; /* see FB_SYNC_* */
__u32 vmode; /* see FB_VMODE_* */
__u32 rotate; /* angle we rotate counter clockwise */
__u32 reserved[5]; /* Reserved for future compatibility */
虚拟分辨率与真实分辨率最好的理解就是一张分辨率高于屏幕的图片无法在这个屏幕完全显示,需要通过滑动左右栏或者上下栏来查看整张图片。
一个像素点数据由四个字节组成,包括RGB(RGB888颜色编码)以及透明度,各占一个字节。
三星驱动的虚拟分辨率是真是分辨率的两倍,采用了双缓冲结构(有时也称乒乓结构),屏幕首先从(0,0)开始显示,当上半部分显示完后,可以直接切换到(0, 480),显示剩下的内容。
#define FBDEVICE "/dev/fb0"
struct fb_fix_screeninfo finfo = {0};
struct fb_var_screeninfo vinfo = {0};
// 第1步:打开设备
fd = open(FBDEVICE, O_RDWR);
if (fd < 0)
{
perror("open");
return -1;
}
printf("open %s success.\n", FBDEVICE);
// 第2步:获取设备的硬件信息
ret = ioctl(fd, FBIOGET_FSCREENINFO, &finfo);//获取不变信息
if (ret < 0)
{
perror("ioctl");
return -1;
}
printf("smem_start = 0x%x, smem_len = %u.\n", finfo.smem_start, finfo.smem_len);
ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);//获取可变信息
if (ret < 0)
{
perror("ioctl");
return -1;
}
printf("xres = %u, yres = %u.\n", vinfo.xres, vinfo.yres);
printf("xres_virtual = %u, yres_virtual = %u.\n", vinfo.xres_virtual, vinfo.yres_virtual);
printf("bpp = %u.\n", vinfo.bits_per_pixel);
3、可设置一些东东比如分辨率
// 修改驱动中屏幕的分辨率
vinfo.xres = 1024;
vinfo.yres = 600;
vinfo.xres_virtual = 1024;
vinfo.yres_virtual = 1200;
ret = ioctl(fd, FBIOPUT_VSCREENINFO, &vinfo);
if (ret < 0)
{
perror("ioctl");
return -1;
}
// 再次读出来检验一下
ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
if (ret < 0)
{
perror("ioctl");
return -1;
}
printf("修改过之后的参数:\n");
printf("xres = %u, yres = %u.\n", vinfo.xres, vinfo.yres);
printf("xres_virtual = %u, yres_virtual = %u.\n", vinfo.xres_virtual, vinfo.yres_virtual);
printf("bpp = %u.\n", vinfo.bits_per_pixel);
4、mmap做映射
mmap返回值指向一段内存。做完了mmap后fb在当前进程中就已经就绪了,随时可以去读写LCD显示器了。
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
是否指定映射地址 显存大小 权限 是否可共享 偏移量
多线程同时
操作
void *addr:传入NULL,让自动分配地址
示例程序:
// 全局变量
unsigned int *pfb = NULL;
//finfo.smem_len以虚拟分辨率进行计算的
pfb = mmap(NULL, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (NULL == pfb)
{
perror("mmap fail");
return -1;
}
printf("pfb = %p.\n", pfb);
5、做一些任务以及结束程序
#define WHITE 0xffffffff
#define RED 0xffff0000
// 新开发板
#define WIDTH 1024
#define HEIGHT 600
draw_back(WIDTH, HEIGHT, WHITE);
draw_line(RED);//(代码在下面)
close(fd);
return 0;
}
6、写字、画线、图片显示等
void draw_line(unsigned int color)
{
unsigned int x, y;
for (x=50; x<600; x++)
{
*(pfb + 200 * WIDTH + x) = color;
}
}
//画1024*600像素的图片,图像数据存在pData所指向的数组中,图片数据可用专用的软件生成
//在下方我会给出我使用的软件的链接:Image2Lcd.rar
//https://www.aliyundrive.com/s/D4WwdKcfg63
void lcd_draw_picture(const unsigned char *pData)
{
unsigned int x, y, color, p = 0;
for(y = 0; y < 600;y++)
{
for(x = 0;x < 1024; x++)
{
// 在这里将坐标点(x, y)的那个像素填充上相应的颜色值即可
color = ((pData[p+2] << 0)|(pData[p+1] << 8)|(pData[p+0] << 16));
*(pfb + y * WIDTH + x) = color;
p += 3;
}
}
}
上述的这些程序以及实验效果是无法在ubuntu实现的,因为ubuntu做了一些限制,是无法操作framebuffer的,要去开发板执行程序。要去开发板执行程序。要去开发板执行程序。
问题BUG
我写的代码的分辨率和板子上实际的分辨率不匹配,就导致显示不正常,导致刷屏只能刷出一部分,就想通过应用层去设置分辨率能不能行,通过应用层修改完分辨率后,结果显示失败了。
描述:试图在应用层设置分辨率失败了,原因何在?
(1)定位问题:肯定是驱动的事儿。
为什么?bpp是可通过应用层改的,但是x和y的resolution不可以改,但是他们又都在同一个位置,如果是应用层的问题,那肯定是都不可以通过应用层去修改。
(2)进一步驱动中定位:ioctl部分的事儿
因为fb是典型的字符设备驱动,注册自己是去fbmen.c框架那里的