《迅为开发板i.MX8MM 学习记录》——【MIPI篇】Linux 应用程序显示一张图片_ubuntu设置mipi显示-程序员宅基地

技术标签: 学习  Linux  c语言  linux  


前言

本篇记录:如何在linux应用层开发一个APP,在开发板显示一张指定的图片。


一、准备工作

1.开发环境

  • 虚拟机环境为Ubuntu16.04,安装了交叉编译器 aarch64-poky-linux-gcc。
  • 开发板为迅为i.MX8MM,使用的屏幕是 7.0寸MIPI屏 ,烧录的文件系统是yocto。
  • 正常编译uboot,kernel,dtbs,yocto后,下载到开发板,uboot启动并设置MIPI屏工作。
  • 此时开发板正常显示yocto系统界面,准备工作OK!
  • 以上都是开发板使用手册的教程内容

2.文件准备

  • 在虚拟机下合适目录下创建目录/test ,并在/test目录下创建app.capp.h文件。
  • 准备一张像素尺寸为1280*800testpic.png 的图片,利用工具或网页将其转化为.bmp格式,保存到/test目录下。

二、伪代码分析

思路:将图片读取到数组中,然后打开DRM设备填充/dev/fb0进行显示。
先伪代码形式分析,最后贴出完整代码!

1.读取图片数据到数组

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);
 	/* bmp倒叙转换到数组*/
    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)
{
    
	/* 打开drm设备*/
	fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
	/* 获取设配 crtc/encoder/connector id */
	res = drmModeGetResources(fd);
	/*	获得 connector*/
	conn = drmModeGetConnector(fd, conn_id);
	/* 为 CRTC 创建扫描帧缓冲区 --> framebuffer*/
	modeset_create_fb(fd, &buf);
	/* Set a CRTC configuration,开始显示输出显示*/
	drmModeSetCrtc(fd, crtc_id, buf.fb_id,
			0, 0, &conn_id, 1, &conn->modes[0]);
}

3.填充framebuffer

	/* 绘制图片将fb区填充图片数组 */
	memcpy(frame_buf->vaddr, g_screen_buff, frame_buf->size);

三、完整代码

使用完整代码前请阅读【一、准备工作】

1.app.h

#ifndef __APP_H
#define __APP_H

/* 构建一个结构体,用于存放DRM KMS配置的参数*/
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 <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <linux/fb.h>

#include "app.h"

/* 
 	DRM 设备应用程序实现步骤
	1.打开 DRM 设备	     
 	2.查找适配的'crtc +  encoder + connector'
	3.为 CRTC 创建扫描帧缓冲区
	4. KMS 模式配置
	5.绘制图像
*/
#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;

/* bmp数据倒叙转换*/
static int cursor_bitmap_format_convert(char *dst,char *src)
{
    
    int i ,j ,k;
    char *psrc = src ;
    char *pdst = dst;
    char *p = psrc;
 
    /* 由于bmp存储是从后面往前面,所以需要倒序进行转换 */
    pdst += (width * height * PER_PIXEL_BYTES);
    for(i=0;i<height;i++){
    
        p = psrc + (i+1) * width * PER_PIXEL_BYTES;
        for(j=0;j<width;j++){
    
            pdst -= PER_PIXEL_BYTES;    //Damon modify
            p -= PER_PIXEL_BYTES;       //Damon modify
            for(k=0;k<PER_PIXEL_BYTES;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,return\n");
        return -1;
    }
    printf("img path = %s\n", bmp_path);
    fp = fopen( bmp_path, "rb" );
    if(fp == NULL){
    
        printf("load cursor file open failed\n");
        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 file\n");
        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%d\n", flen);               //图片大小
    printf("image info:\n");
    printf("\t image size:\t%d\n",FileHead.cfSize);      //文件大小  
    printf("\t image pixel:\twidth=%d ,height=%d\n", InfoHead.ciWidth, InfoHead.ciHeight);
    printf("\t pixel bits:\t%d\n", InfoHead.ciBitCount); //
    printf("\t buffer bytes:\t%d\n", 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%d\n", 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;
}


/* 创建fb缓存,显示图片*/
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;

	/* create a dumb-buffer, the pixel format is XRGB888 */
	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);

	/* bind the dumb-buffer to an FB object */
	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);

	/* map the dumb-buffer to userspace */
	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);

	/* 绘制图片将fb区填充图片数组 */
	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;
}

/* 销毁fb 缓存*/
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
   
	/* 打开drm设备*/
	fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
	if(fd<0)
	{
    
		printf("open card0 failed!\n");
	}
	
	/* 获取设配 crtc/encoder/connector id */
	res = drmModeGetResources(fd);
	crtc_id = res->crtcs[0];
	conn_id = res->connectors[0];
	printf("crtc id=%d\n",crtc_id);
	printf("crtc id=%d\n",conn_id);


	/*	获得 connector*/
	conn = drmModeGetConnector(fd, conn_id);
	buf.width = conn->modes[0].hdisplay;
	buf.height = conn->modes[0].vdisplay;
	printf("buf width=%d\n",buf.width);
	printf("buf height=%d\n",buf.height);


	/* 为 CRTC 创建扫描帧缓冲区 --> framebuffer*/
	modeset_create_fb(fd, &buf);

	/* Sets a CRTC configuration,这之后就会开始在 crtc0 + connector0 pipeline 上进行以 mode0 输出显示*/
	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.bmp

1.编译

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

交叉编译命令与【迅为开发指南】给出的有一些不一致,原因是因为使用交叉编译器编译使用libdrm库的app.c文件时会报错:找不到include <drm.h>头文件,于是在编译指令中加入了-I/usr/include/drm/ -ldrm

2.运行

  • 开发板上使用killall weston命令杀死yocto占用的屏幕进程,建议执行多次。
  • /test文件夹整个拷贝到开发板合适目录下,比如nfs服务绑定目录/mnt下,运行./app

拷贝整个文件夹的目的:如果开发板使用wifi 利用 nfswindows 进行文件共享,那么传输testpic.bmp资源需要花费一段时间,可能导致图片显示不出来。

五、总结

本文参考文章:

感谢两位高人的代码!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Demonyang1/article/details/125002209

智能推荐

Docker 快速上手学习入门教程_docker菜鸟教程-程序员宅基地

文章浏览阅读2.5w次,点赞6次,收藏50次。官方解释是,docker 容器是机器上的沙盒进程,它与主机上的所有其他进程隔离。所以容器只是操作系统中被隔离开来的一个进程,所谓的容器化,其实也只是对操作系统进行欺骗的一种语法糖。_docker菜鸟教程

电脑技巧:Windows系统原版纯净软件必备的两个网站_msdn我告诉你-程序员宅基地

文章浏览阅读5.7k次,点赞3次,收藏14次。该如何避免的,今天小编给大家推荐两个下载Windows系统官方软件的资源网站,可以杜绝软件捆绑等行为。该站提供了丰富的Windows官方技术资源,比较重要的有MSDN技术资源文档库、官方工具和资源、应用程序、开发人员工具(Visual Studio 、SQLServer等等)、系统镜像、设计人员工具等。总的来说,这两个都是非常优秀的Windows系统镜像资源站,提供了丰富的Windows系统镜像资源,并且保证了资源的纯净和安全性,有需要的朋友可以去了解一下。这个非常实用的资源网站的创建者是国内的一个网友。_msdn我告诉你

vue2封装对话框el-dialog组件_<el-dialog 封装成组件 vue2-程序员宅基地

文章浏览阅读1.2k次。vue2封装对话框el-dialog组件_

MFC 文本框换行_c++ mfc同一框内输入二行怎么换行-程序员宅基地

文章浏览阅读4.7k次,点赞5次,收藏6次。MFC 文本框换行 标签: it mfc 文本框1.将Multiline属性设置为True2.换行是使用"\r\n" (宽字符串为L"\r\n")3.如果需要编辑并且按Enter键换行,还要将 Want Return 设置为 True4.如果需要垂直滚动条的话将Vertical Scroll属性设置为True,需要水平滚动条的话将Horizontal Scroll属性设_c++ mfc同一框内输入二行怎么换行

redis-desktop-manager无法连接redis-server的解决方法_redis-server doesn't support auth command or ismis-程序员宅基地

文章浏览阅读832次。检查Linux是否是否开启所需端口,默认为6379,若未打开,将其开启:以root用户执行iptables -I INPUT -p tcp --dport 6379 -j ACCEPT如果还是未能解决,修改redis.conf,修改主机地址:bind 192.168.85.**;然后使用该配置文件,重新启动Redis服务./redis-server redis.conf..._redis-server doesn't support auth command or ismisconfigured. try

实验四 数据选择器及其应用-程序员宅基地

文章浏览阅读4.9k次。济大数电实验报告_数据选择器及其应用

随便推点

灰色预测模型matlab_MATLAB实战|基于灰色预测河南省社会消费品零售总额预测-程序员宅基地

文章浏览阅读236次。1研究内容消费在生产中占据十分重要的地位,是生产的最终目的和动力,是保持省内经济稳定快速发展的核心要素。预测河南省社会消费品零售总额,是进行宏观经济调控和消费体制改变创新的基础,是河南省内人民对美好的全面和谐社会的追求的要求,保持河南省经济稳定和可持续发展具有重要意义。本文建立灰色预测模型,利用MATLAB软件,预测出2019年~2023年河南省社会消费品零售总额预测值分别为21881...._灰色预测模型用什么软件

log4qt-程序员宅基地

文章浏览阅读1.2k次。12.4-在Qt中使用Log4Qt输出Log文件,看这一篇就足够了一、为啥要使用第三方Log库,而不用平台自带的Log库二、Log4j系列库的功能介绍与基本概念三、Log4Qt库的基本介绍四、将Log4qt组装成为一个单独模块五、使用配置文件的方式配置Log4Qt六、使用代码的方式配置Log4Qt七、在Qt工程中引入Log4Qt库模块的方法八、获取示例中的源代码一、为啥要使用第三方Log库,而不用平台自带的Log库首先要说明的是,在平时开发和调试中开发平台自带的“打印输出”已经足够了。但_log4qt

100种思维模型之全局观思维模型-67_计算机中对于全局观的-程序员宅基地

文章浏览阅读786次。全局观思维模型,一个教我们由点到线,由线到面,再由面到体,不断的放大格局去思考问题的思维模型。_计算机中对于全局观的

线程间控制之CountDownLatch和CyclicBarrier使用介绍_countdownluach于cyclicbarrier的用法-程序员宅基地

文章浏览阅读330次。一、CountDownLatch介绍CountDownLatch采用减法计算;是一个同步辅助工具类和CyclicBarrier类功能类似,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。二、CountDownLatch俩种应用场景: 场景一:所有线程在等待开始信号(startSignal.await()),主流程发出开始信号通知,既执行startSignal.countDown()方法后;所有线程才开始执行;每个线程执行完发出做完信号,既执行do..._countdownluach于cyclicbarrier的用法

自动化监控系统Prometheus&Grafana_-自动化监控系统prometheus&grafana实战-程序员宅基地

文章浏览阅读508次。Prometheus 算是一个全能型选手,原生支持容器监控,当然监控传统应用也不是吃干饭的,所以就是容器和非容器他都支持,所有的监控系统都具备这个流程,_-自动化监控系统prometheus&grafana实战

React 组件封装之 Search 搜索_react search-程序员宅基地

文章浏览阅读4.7k次。输入关键字,可以通过键盘的搜索按钮完成搜索功能。_react search