- 前言
- 一、准备工作
- 1.开发环境
- 2.文件准备
- 二、伪代码分析
- 1.读取图片数据到数组
- 2.打开DRM设备,创建fb
- 3.填充framebuffer
- 三、完整代码
- 1.app.h
- 2.app.c
- 四、编译与运行
- 1.编译
- 2.运行
- 五、总结
前言
本篇记录:如何在linux应用层开发一个APP,在开发板显示一张指定的图片。
一、准备工作 1.开发环境
- 虚拟机环境为Ubuntu16.04,安装了交叉编译器 aarch64-poky-linux-gcc。
- 开发板为迅为i.MX8MM,使用的屏幕是 7.0寸MIPI屏 ,烧录的文件系统是yocto。
- 正常编译uboot,kernel,dtbs,yocto后,下载到开发板,uboot启动并设置MIPI屏工作。
- 此时开发板正常显示yocto系统界面,准备工作OK!
- 以上都是开发板使用手册的教程内容
- 在虚拟机下合适目录下创建目录/test ,并在/test目录下创建app.c和app.h文件。
- 准备一张像素尺寸为1280*800的 testpic.png 的图片,利用工具或网页将其转化为.bmp格式,保存到/test目录下。
思路:将图片读取到数组中,然后打开DRM设备填充/dev/fb0进行显示。
先伪代码形式分析,最后贴出完整代码!
bmp格式的图片文件的头部包含了一些info数据,这些数据并不是真正的显示画面内容,送到framebuffer中是没有用的,会导致整个显示不正确。因此用二进制打开图片后,需要先将头部的info数据进行剔除,留下干净的显示像素信息。
伪代码形式分析
int bmp2fb_buff(uint8_t *r_buff, uint32_t r_size, const char *bmp_path)
{
fp = fopen( bmp_path, "rb" );
fseek(fp, offsets, SEEK_SET);
fread(buf,1,total_length,fp);
cursor_bitmap_format_convert(r_buff, bmp_buf);
return 0;
}
这里有一个很重要概念,bmp图片数据存储方式是从下到上的,是倒着存放的,因此需要使用 cursor_bitmap_format_convert()函数数据倒置装入framebuffer.
2.打开DRM设备,创建fb接下来就是使用libdrm库,操作drm设备创建显示缓冲区fb,详细可参考 最简单的DRM应用程序
int main(int argc, char **argv)
{
fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
res = drmModeGetResources(fd);
conn = drmModeGetConnector(fd, conn_id);
modeset_create_fb(fd, &buf);
drmModeSetCrtc(fd, crtc_id, buf.fb_id,
0, 0, &conn_id, 1, &conn->modes[0]);
}
3.填充framebuffer
memcpy(frame_buf->vaddr, g_screen_buff, frame_buf->size);
三、完整代码
使用完整代码前请阅读【一、准备工作】
1.app.h#ifndef __APP_H
#define __APP_H
struct buffer_object {
uint32_t width;
uint32_t height;
uint32_t pitch;
uint32_t handle; //
uint32_t size;
uint32_t *vaddr;
uint32_t fb_id;
};
//14byte文件头
typedef struct
{
char cfType[2];//文件类型,"BM"(0x4D42)
int cfSize;//文件大小(字节)
int cfReserved;//保留,值为0
int cfoffBits;//数据区相对于文件头的偏移量(字节)
}__attribute__((packed)) BITMAPFILEHEADER; //告诉编译器取消结构在编译过程中的优化对齐
//40byte信息头
typedef struct
{
char ciSize[4];//BITMAPFILEHEADER所占的字节数
int ciWidth;//宽度
int ciHeight;//高度
char ciPlanes[2];//目标设备的位平面数,值为1
int ciBitCount;//每个像素的位数
char ciCompress[4];//压缩说明
char ciSizeImage[4];//用字节表示的图像大小,该数据必须是4的倍数
char ciXPelsPerMeter[4];//目标设备的水平像素数/米
char ciYPelsPerMeter[4];//目标设备的垂直像素数/米
char ciClrUsed[4]; //位图使用调色板的颜色数
char ciClrImportant[4]; //指定重要的颜色数,当该域的值等于颜色数时(或者等于0时),表示所有颜色都一样重要
}__attribute__((packed)) BITMAPINFOHEADER;
typedef struct
{
unsigned char blue;
unsigned char green;
unsigned char red;
unsigned char reserved;
}__attribute__((packed)) PIXEL;//颜色模式RGB
#endif //__APP_H
2.app.c
#define _GNU_SOURCE #include四、编译与运行#include #include #include #include #include #include #include #include #include #include #include #include #include #include "app.h" #define PER_PIXEL_BYTES 4 //一个像素点4个字节 #define SCREEN_BUFF_SIZE (1280*800*4) //u32计算 uint8_t g_screen_buff[SCREEN_BUFF_SIZE]; //屏幕是1280*800大小,每个pixel由4字节组成,实际只有3字节24位,这里用uint32_t存储即可 BITMAPFILEHEADER FileHead; BITMAPINFOHEADER InfoHead; static char *fbp = 0; static int xres = 0; static int yres = 0; static int bits_per_pixel = 0; int width, height; int fbfd = 0; static int cursor_bitmap_format_convert(char *dst,char *src) { int i ,j ,k; char *psrc = src ; char *pdst = dst; char *p = psrc; pdst += (width * height * PER_PIXEL_BYTES); for(i=0;i p = psrc + (i+1) * width * PER_PIXEL_BYTES; for(j=0;j pdst -= PER_PIXEL_BYTES; //Damon modify p -= PER_PIXEL_BYTES; //Damon modify for(k=0;k pdst[k] = p[k]; //Damon modify } } } return 0; } int bmp2fb_buff(uint8_t *r_buff, uint32_t r_size, const char *bmp_path) { int i; FILE *fp; int rc; char *bmp_buf = NULL; char *buf = NULL; int flen = 0; int ret = -1; int total_length = 0; if(bmp_path == NULL) { printf("bmp_path Error,returnn"); return -1; } printf("img path = %sn", bmp_path); fp = fopen( bmp_path, "rb" ); if(fp == NULL){ printf("load cursor file open failedn"); return -1; } fseek(fp,0,SEEK_SET); fseek(fp,0,SEEK_END); flen = ftell(fp); bmp_buf = (char*)calloc(1,flen - 54); if(bmp_buf == NULL){ printf("load > malloc bmp out of memory!n"); return -1; } fseek(fp,0,SEEK_SET); rc = fread(&FileHead, sizeof(BITMAPFILEHEADER),1, fp); if ( rc != 1) { printf("read header error!n"); fclose( fp ); return( -2 ); } //检测是否是bmp图像 if (memcmp(FileHead.cfType, "BM", 2) != 0) { printf("it's not a BMP filen"); fclose( fp ); return( -3 ); } //获取Info rc = fread( (char *)&InfoHead, sizeof(BITMAPINFOHEADER),1, fp ); if ( rc != 1) { printf("read infoheader error!n"); fclose( fp ); return( -4 ); } width = InfoHead.ciWidth; height = InfoHead.ciHeight; total_length = width * height * PER_PIXEL_BYTES; printf("file info:n"); printf("t image size:t%dn", flen); //图片大小 printf("image info:n"); printf("t image size:t%dn",FileHead.cfSize); //文件大小 printf("t image pixel:twidth=%d ,height=%dn", InfoHead.ciWidth, InfoHead.ciHeight); printf("t pixel bits:t%dn", InfoHead.ciBitCount); // printf("t buffer bytes:t%dn", total_length); // //跳转的数据区 fseek(fp, FileHead.cfoffBits, SEEK_SET);//fb 从SEEK_SET跳转cfoffBits字节 //读取bmp数据 buf = bmp_buf; while ((ret = fread(buf,1,total_length,fp)) >= 0) { if (ret == 0) { usleep(100); continue; } printf("t read bytes:t%dn", ret); buf = ((char*) buf) + ret; total_length = total_length - ret; if(total_length == 0) break; } //bmp倒叙转换到数组 cursor_bitmap_format_convert(r_buff, bmp_buf); //内存释放 free(bmp_buf); fclose(fp); return 0; } static int modeset_create_fb(int fd, struct buffer_object *frame_buf) { struct drm_mode_create_dumb dumb_buf = {}; //创建显示缓存 显存 struct drm_mode_map_dumb dum_map = {}; //映射到内存空�? uint8_t depth = 24; //每个通道的每个像素分量的有效比特数 uint8_t bpp = 32; //bit per pixel 指每个像素所占用的有效比特数 uint32_t i; dumb_buf.width = frame_buf->width; dumb_buf.height = frame_buf->height; dumb_buf.bpp = bpp; drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &dumb_buf); frame_buf->pitch = dumb_buf.pitch; frame_buf->size = dumb_buf.size; frame_buf->handle = dumb_buf.handle; drmModeAddFB(fd, frame_buf->width, frame_buf->height, depth, bpp, frame_buf->pitch, frame_buf->handle, &frame_buf->fb_id); dum_map.handle = dumb_buf.handle; drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &dum_map); frame_buf->vaddr = mmap(0, dumb_buf.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, dum_map.offset); memcpy(frame_buf->vaddr, g_screen_buff, frame_buf->size); // for (i = 0; i < (frame_buf->size/4 ); i++) // { // frame_buf->vaddr[i] = pic_buff[i]; // } return 0; } static void modeset_destroy_fb(int fd, struct buffer_object *frame_buf) { struct drm_mode_destroy_dumb destroy = {}; drmModeRmFB(fd, frame_buf->fb_id); munmap(frame_buf->vaddr, frame_buf->size); destroy.handle = frame_buf->handle; drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy); } int main(int argc, char **argv) { struct buffer_object buf; int fd; int ret; drmModeConnector *conn; drmModeRes *res; uint32_t conn_id; uint32_t crtc_id; //读取图片,处理成fb数组 bmp2fb_buff(g_screen_buff,SCREEN_BUFF_SIZE,"./testpic.bmp"); //bmp图片转换为g_screen_buff fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC); if(fd<0) { printf("open card0 failed!n"); } res = drmModeGetResources(fd); crtc_id = res->crtcs[0]; conn_id = res->connectors[0]; printf("crtc id=%dn",crtc_id); printf("crtc id=%dn",conn_id); conn = drmModeGetConnector(fd, conn_id); buf.width = conn->modes[0].hdisplay; buf.height = conn->modes[0].vdisplay; printf("buf width=%dn",buf.width); printf("buf height=%dn",buf.height); modeset_create_fb(fd, &buf); drmModeSetCrtc(fd, crtc_id, buf.fb_id, 0, 0, &conn_id, 1, &conn->modes[0]); getchar(); //Enter 退出 modeset_destroy_fb(fd, &buf); drmModeFreeConnector(conn); drmModeFreeResources(res); close(fd); return 0; }
图片文件准备好以后,代码也写好后,Ubuntu中的目录结构如下
/test --app.c --app.h --testpic.bmp1.编译
在bmp/目录下顺序执行以下命令,使用交叉编译器编译app.c
- export ARCH=arm64
- . /opt/fsl-imx-xwayland/4.14-sumo/environment-setup-aarch64-poky-linux
- aarch64-poky-linux-gcc app.c -I/usr/include/drm/ -ldrm -o app --sysroot=/opt/fsl-imx-xwayland/4.14-sumo/sysroots/aarch64-poky-linux
2.运行交叉编译命令与【迅为开发指南】给出的有一些不一致,原因是因为使用交叉编译器编译使用libdrm库的app.c文件时会报错:找不到include
头文件,于是在编译指令中加入了-I/usr/include/drm/ -ldrm。
- 开发板上使用killall weston命令杀死yocto占用的屏幕进程,建议执行多次。
- 将/test文件夹整个拷贝到开发板合适目录下,比如nfs服务绑定目录/mnt下,运行./app。
五、总结拷贝整个文件夹的目的:如果开发板使用wifi 利用 nfs与 windows 进行文件共享,那么传输testpic.bmp资源需要花费一段时间,可能导致图片显示不出来。
本文参考文章:
- 最简单的DRM应用程序 (single-buffer)
- 提取bmp图片的颜色信息,可直接framebuffer显示(c版本与python版本)
感谢两位高人的代码!



