fbtft使用的是framebuffer框架,这种框架将显示设备抽象为帧缓冲区,对framebuffer设备(/dev/fbx(0、1、2..))进行相关操作可以反应到LCD上。

现在尝试下在用户空间用 C 来操作 LCD 设备。

获取参数

要对 framebuffer 进行操作,首先要知道所操作设备的相关参数,因为要驱动一个屏,首先需要知道该屏尺寸、单色还是彩色等,Linux在用户空间提供了两个跟 framebuffer 参数相关的结构体(在文件 fb.h 中):

  • fb_fix_screeninfo
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 capabilities; /* see FB_CAP_* */
__u16 reserved[2]; /* Reserved for future compatibility */
};
  • fb_var_screeninfo
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 */
__u32 grayscale; /* 0 = color, 1 = grayscale, */
/* >1 = FOURCC */
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 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;
__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 colorspace; /* colorspace for FOURCC-based modes */
__u32 reserved[4]; /* Reserved for future compatibility */
};

这两个参数都可以通过ioctl获得,首先定义3个变量,一个是打开的fb设备的句柄,剩下2个分别是fb_var_screeninfo、fb_fix_screeninfo:

int fbfd = 0;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;

打开fb设备:

    fbfd = open("/dev/fb0", O_RDWR);
if (!fbfd)
{
printf("Error: cannot open framebuffer device.\n");
exit(1);
}

然后分别获取两个结构体,并在main函数中调用:

    if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo))
{
printf("Error reading fixed information.\n");
exit(2);
} if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo))
{
printf("Error: reading variable information.\n");
exit(3);
}

创建两个函数分别输出这两个结构体元素:

void show_fb_fix_info(struct fb_fix_screeninfo info)
{
printf("fb's fix msg:\n");
printf("\tid is:%s\n",info.id);
printf("\tsmem_start is:%d\n",info.smem_start);
printf("\tsmem_len is:%d\n",info.smem_len);
printf("\ttype_aux is:%d\n",info.type_aux);
printf("\tvisual is:%d\n",info.visual);
printf("\txpanstep is:%d\n",info.xpanstep);
printf("\typanstep is:%d\n",info.ypanstep);
printf("\tywrapstep is:%d\n",info.ywrapstep);
printf("\tline_length is:%d\n",info.line_length);
printf("\tmmio_start is:%d\n",info.mmio_start);
} void show_fb_var_info(struct fb_var_screeninfo info)
{
printf("fb's var msg:\n");
printf("\txres is:%d\n",info.xres);
printf("\tyres is:%d\n",info.yres);
printf("\txres_virtual is:%d\n",info.xres_virtual);
printf("\tyres_virtual is:%d\n",info.yres_virtual);
printf("\txoffset is:%d\n",info.xoffset);
printf("\tyoffset is:%d\n",info.yoffset);
printf("\tbits_per_pixel is:%d\n",info.bits_per_pixel);
printf("\tgrayscale is:%d\n",info.grayscale);
printf("\tnonstd is:%d\n",info.nonstd);
printf("\tactivate is:%d\n",info.activate);
printf("\theight is:%d\n",info.height);
printf("\twidth is:%d\n",info.width);
printf("\taccel_flags is:%d\n",info.accel_flags);
printf("\tpixclock is:%d\n",info.pixclock);
printf("\tleft_margin is:%d\n",info.left_margin);
printf("\tright_margin is:%d\n",info.right_margin);
printf("\tupper_margin is:%d\n",info.upper_margin);
printf("\tlower_margin is:%d\n",info.lower_margin);
printf("\thsync_len is:%d\n",info.hsync_len);
printf("\tvsync_len is:%d\n",info.vsync_len);
printf("\tsync is:%d\n",info.sync);
printf("\tvmode is:%d\n",info.vmode);
printf("\trotate is:%d\n",info.rotate);
printf("\tcolorspace is:%d\n",info.colorspace);
}

编译、拷贝到目标板上,运行结果如下:

从上面运行结果看,成功的获取了屏的一系列参数。

详细代码framebuffer/show_fb_msg.c

填充颜色

​ 现在试下给整个屏填充颜色,要填充屏的话,需要几个参数,一个是framebuffer所需内存的大小,一个是屏的像素的个数,还有就是颜色深度。

​ 要对framebuffer进行操作首先需要做的是通过mmap进行地址映射,这里就要用到framebuffer所需内存的大小,framebuffer所需内存的大小可以从fb_fix_screeninfo获得,如下:

static char *fbp = 0;

fbp = (char *)mmap(0, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
if ((int)fbp == -1)
{
printf("Error: failed to map framebuffer device to memory.\n");
exit(4);
}

​ 这里申请一段finfo.smem_len大小的连续内存,对这段内存进行操作就会反应到屏上了。

​ 对整个屏进行填充就是操作屏上的所有像素(也可以说遍历所有像素),这里就需要知道屏幕的像素的个数,这个参数可以从之前获取到的fb_var_screeninfo中的参数中的xres,yres。从fb.h中的注释翻译过来,xres,yres是可见分辨率,我理解为可见区域,应该就是对应屏幕显示区域的大小了。

int screensize = 0;

screensize = xres * yres;

​ 还有一个很关键的参数颜色深度(bpp:bits per pixel),也就是表示一个像素颜色所需的位数(bit),一般来说有这么几种:1位,8位,16位,24位,32位。,比如1位的屏,也就是单色屏,用1bit来表示颜色,如果是白色单色屏的话,0表示黑色,1表示白色。24bit屏,就是用24bit(3字节)表示颜色,也就是RGB888,R、G、B分别占8bit。

​ 从上面获取到的参数知道,该屏是16bit的,也就是RGB565,用两个字节表示。应为之前用mmap申请的内存是char指针,用该指针来操作像素的话不方便,索性就把char型转成short型,如下:

short *fb_s;
fb_s = (short *)fbp;

定义4个宏,分别表示红色、绿色、蓝色、黑色,

#define RED 0xf800
#define GREEN 0x07e0
#define BLUE 0x001f
#define BLACK 0x0000

创建一个写framebuffer内存的函数,如下:

void fill_screen(short *fb_men,short color,int pix_size)
{
for(int i=0;i<pix_size;i++)
{
*fb_men = color;
fb_men ++;
}
}

第一个参数是所要操作的framebuffer对应的内存,第二个是所要填充的颜色,第三个是屏幕的尺寸,然后就可以对屏幕进行填充了,如下:

	printf("Fill red\n");
fill_screen(fb_s,RED,screensize/2);
sleep(1);
printf("Fill green\n");
// fb_s = (short *)fbp;
fill_screen(fb_s,GREEN,screensize/2);
sleep(1);
printf("Fill blue\n");
// fb_s = (short *)fbp;
fill_screen(fb_s,BLUE,screensize/2);
sleep(1);
fill_screen(fb_s,BLACK,screensize/2);

上面的代码是先填充红色,等待1秒,然后是绿色、蓝色,最后填充黑色(清屏)。效果如下:

详细代码framebuffer/fb_fill_color.c

显示图片

现有的图片格式非常多,JPEG、TIFF、PNG、BMP,SVG等等,这些又分有压缩、无压缩。这里是为了学习如何操作framebuffer,就选择一种无压缩格式的格式的图片来操作。可以直接以二进制的方式读取图片数据,然后显示到屏上。比较常用的无压缩的图片是BMP,这里就选择BMP格式的图片。

​ BMP格式图片数据有以下4部分组成:

  • 位图头文件数据结构,它包含BMP图像文件的类型、显示内容等信息;
  • 位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息;
  • 调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板;
  • 位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。

由以上信息可以知道24位的bmp图片,数据部分就是图片像素的数据,也就是说读取24位bmp图片的数据部分不用进行任何操作,就可以直接拿来用,所以这里选择使用24bit的BMP图片,以下是我用来测试的24bit图片中的一张图片的信息:

BMP文件的位图头跟位图信息部分占BMP图片数据的前54字节,24位BMP图片的图片数据部分就是剩下的所有数据了(第55字节开始)。要对BMP图片的位图数据进行读,还需要几个关键参数,所读的图片的宽、高。还有颜色深度(bpc),因为选用了24位的图片,颜色深度算是已近知道了。

获取BMP图片信息

首先获取BMP图片的信息,定义个结构体:

typedef struct
{
uint16_t bfType;
uint32_t bfSize;
uint32_t biWidth;
uint32_t biHeight;
uint16_t biBitCount;
}BMP_HEADER;

这个结构体里面的元素分别有图片类型,图片数据大小,图片的尺寸跟颜色深度。

创建个获取该结构图的函数:

BMP_HEADER TFTBmpGetHeadInfo(uint8_t *buf)
{
BMP_HEADER bmpHead; bmpHead.bfType = (buf[0] << 8) + buf[1];
bmpHead.bfSize = (buf[5]<<24) + (buf[4]<<16) + (buf[3]<<8) + buf[2];
bmpHead.biWidth = (buf[21]<<24) + (buf[20]<<16) + (buf[19]<<8) + buf[18];
bmpHead.biHeight = (buf[25]<<24) + (buf[24]<<16) + (buf[23]<<8) + buf[22];
bmpHead.biBitCount = (buf[29] << 8) + buf[28]; return bmpHead;
}

打开图片并读取前54字节,传给函数TFTBmpGetHeadInfo,然后判断图片格式是否为BMP,颜色深度是否为24bit,不是的话,程序不再继续运行:

	bmp_fd  = open(argv[1], O_RDWR);
if(bmp_fd <0)
{
printf("open file faile\n");
} if(read(bmp_fd,buffer,54) <0)
{
printf("read file \"%s\" faile\n",argv[1]);
return -1;
} bmp_header = TFTBmpGetHeadInfo(buffer);
printf("%s's msg:\n",argv[1]);
printf("\ttype:%2x\n",bmp_header.bfType);
printf("\tsize:%d\n",bmp_header.bfSize);
printf("\tWidth:%d\n",bmp_header.biWidth);
printf("\tHeight:%d\n",bmp_header.biHeight);
printf("\tBitCount:%d\n",bmp_header.biBitCount); if(bmp_header.bfType != 0x424d)
{
printf("The picture is not bmp file\n");
return -1;
} if(bmp_header.biBitCount != 24)
{
printf("Just support 24bit bmp file\n");
return -1;
}

把图片显示到屏上

​ 我这里实现的方法是,每次读取BMP图片的一行就写到Framebuffer对应的缓存中,虽然要求read读取指定个字节,可是程序运行的时候并不是每次都能够读取所要求的字节数。所以还需要加个判断,判断每次读取的字节数是否为达到要求,没达到要求就继续读,知道达到要求为止。具体代码如下:

	int perline_size = bmp_header.biWidth * bmp_header.biBitCount /8;

	char * readed_data = (char *)malloc(perline_size);
int i=0;
int line_count=0;
int read_size = perline_size;
int read_count=0;
int read_per_line=0;
int buf_offset=0;
while((ret = read(bmp_fd,readed_data+buf_offset,read_size)) !=0)
{ if(ret == -1)
{
if(errno == EINTR)
continue;
perror("read");
break;
}else if(ret !=perline_size )
{
read_per_line += ret;
read_size = perline_size - read_per_line;
buf_offset = read_per_line;
}else
read_per_line = ret; if(read_per_line == perline_size)
{
read_size = perline_size;
buf_offset =0; read_per_line = 0;
if(bits_per_pixel ==16)
{
short pixl = 0;
if(screan_width > bmp_header.biWidth)
{
for(i=0;i<bmp_header.biWidth;i++)
{
short blue = (readed_data[i*3]>>3) & 0x001f;
short green = (readed_data[i*3+1]>>2) & 0x003f;
short red = (readed_data[i*3+2]>>3) & 0x001f; pixl = (red << 11 ) + (green << 5) + blue;
*fb_s = pixl;
fb_s ++;
}
fb_s += screan_width -bmp_header.biWidth ;
}else if(screan_width <= bmp_header.biWidth)
{
for(i=0;i<screan_width;i++)
{
short blue = (readed_data[i*3]>>3) & 0x001f;
short green = (readed_data[i*3+1]>>2) & 0x003f;
short red = (readed_data[i*3+2]>>3) & 0x001f; pixl = (red << 11 ) + (green << 5) + blue;
*fb_s = pixl;
fb_s ++;
} }
}
}
line_count++;
if(line_count >= screan_height)
break;
}

这里还涉及到一个问题,由于使用的图片是24bit的,显示屏使用的却是16位的,那就需要把24bit转成16bit了,在做之前就有想过,转换的时候是取高位还是低位呢?一开始,我是取低位的,代码为:

short blue = (readed_data[i*3]) & 0x001f;
short green = (readed_data[i*3+1]) & 0x003f;
short red = (readed_data[i*3+2]) & 0x001f; pixl = (red << 11 ) + (green << 5) + blue;
*fb_s = pixl;
fb_s ++;

显示一张有红、绿、蓝色块的图片:

明显是不正常的,红色显示有问题,修改为获取高位:

short blue = (readed_data[i*3]>>3) & 0x001f;
short green = (readed_data[i*3+1]>>2) & 0x003f;
short red = (readed_data[i*3+2]>>3) & 0x001f; pixl = (red << 11 ) + (green << 5) + blue;
*fb_s = pixl;
fb_s ++;

编译运行,结果如下图,显示看起来是正常了,从实验结果来看,转换的时候是取高位。

还有显示一些其他图片的:

显示可爱的祢豆子:

最后附上一张显示图片时输出的信息:

详细代码:show_bmp.c

参考:PNG、JPEG、BMP等几种图片格式详解(三)—— BMP

玩转STM32MP157- 在应用层中使用 fbtft的更多相关文章

  1. 玩转Openstack之Nova中的协同并发(二)

    玩转Openstack之Nova中的协同并发(二) 昨天介绍了Python中的并发处理,主要介绍了Eventlet,今天就接着谈谈Openstack中Nova对其的应用. eventlet 在nova ...

  2. 玩转Openstack之Nova中的协同并发(一)

    玩转Openstack之Nova中的协同并发(一) 前不久参加了个Opnstack的Meetup,其中有一个来自EasyStack的大大就Nova中的协同并发做了一番讲解,有所感触,本想当天就总结一下 ...

  3. [Linux] PHP程序员玩转Linux系列-Nginx中的HTTPS

    1.PHP程序员玩转Linux系列-怎么安装使用CentOS 2.PHP程序员玩转Linux系列-lnmp环境的搭建 3.PHP程序员玩转Linux系列-搭建FTP代码开发环境 4.PHP程序员玩转L ...

  4. 玩转DWZ (一)---项目中怎么使用dwz

    最近一直在找一个完全开源的web客户端框架,看到了dwz,虽然不知道到底怎么样,但还是支持国产,先学习一下.这篇文章先说一下怎么在项目里使用dwz框架. 首先先下载dwz:https://code.c ...

  5. 玩转PHP(一)---php中处理汉字字符串长度:strlen和mb_strlen

    注:本文为小编原创,如若转载,请注明出处:http://blog.csdn.net/u012116457/article/details/42536039 今天正式开始学习PHP了,不过小编一不小心就 ...

  6. 玩转ASP.NET Core中的日志组件

    简介 日志组件,作为程序员使用频率最高的组件,给程序员开发调试程序提供了必要的信息.ASP.NET Core中内置了一个通用日志接口ILogger,并实现了多种内置的日志提供器,例如 Console ...

  7. 没想到 Hash 冲突还能这么玩,你的服务中招了吗?

    背景 其实这个问题我之前也看到过,刚好在前几天,洪教授在某个群里分享的一个<一些有意思的攻击手段.pdf>,我觉得这个话题还是有不少人不清楚的,今天我就准备来“实战”一把,还请各位看官轻拍 ...

  8. 【第1期】腾讯云的1001种玩法征集,Ipad mini和Kindle 等你拿!(文章评审中)

    版权声明:本文由阁主的小跟班原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/695994001482226944 来源:腾云 ...

  9. 玩转STM32MP157- 使用fbtft驱动 lcd st7735r

    什么是fbtft fbtft 在 github 中的介绍是" Linux Framebuffer drivers for small TFT LCD display modules,翻译过来 ...

随机推荐

  1. FreeSWITCH的安装与使用

    FreeSWITCH

  2. OO_Unit2_多线程电梯

    CSDN博客链接 一.第一次作业 1.需求分析 单部多线程傻瓜调度(FAFS)电梯 2.实现方案 输入接口解析 类似于Scanner,我们使用ElevatorInput进行阻塞式读取(第一次作业较简单 ...

  3. COS 数据湖最佳实践:基于 Serverless 架构的入湖方案

    01 前言 数据湖(Data Lake)概念自2011年被推出后,其概念定位.架构设计和相关技术都得到了飞速发展和众多实践,数据湖也从单一数据存储池概念演进为包括 ETL 分析.数据转换及数据处理的下 ...

  4. [转载]libvirt(virsh命令总结)

    libvirt(virsh命令总结) virsh回车进入交互式界面: version pwd hostname 显示本节点主机名 nodeinfo  显示节点信息 list --all 显示所有云主机 ...

  5. 070.Python聚焦爬虫数据解析

    一 聚焦爬虫数据解析 1.1 基本介绍 聚焦爬虫的编码流程 指定url 基于requests模块发起请求 获取响应对象中的数据 数据解析 进行持久化存储 如何实现数据解析 三种数据解析方式 正则表达式 ...

  6. otter源码解读(一)

    概览 lib存放的是项目依赖包,由于项目用到的包比较杂,可能有的包已经不在maven仓库中提供了,所以提供了一个lib包,执行里面的install命令,就可以把包安装到本地maven仓库. manag ...

  7. IEEE 网址

    https://ieeexplore.ieee.org/document/506397

  8. SPI总线 通俗易懂讲解——(转载)

    SPI总线 MOTOROLA公司的SPI总线的基本信号线为3根传输线,即SI.SO.SCK.传输的速率由时钟信号SCK决定,SI为数据输入.SO为数据输出.采用SPI总线的系统如图8-27所示,它包含 ...

  9. 2.1PyCharm 的初始设置

    PyCharm 的初始设置(知道) 目标 恢复 PyCharm 的初始设置 第一次启动 PyCharm 新建一个 Python 项目 设置 PyCharm 的字体显示 PyCharm 的升级以及其他 ...

  10. STM32电路设计注意

    以后画STM32的电路板 VDDA一定要接 张JF说  VDDA是给内部的时钟电路供电的 还有一定要留 串口或者  下载调试口(串口) 或者显示器接口 输入输出设备最好都留着 这样才能方便调试