本篇记录:如何在linux应用层开发一个APP,在开发板显示一张指定的图片。
以上都是开发板使用手册的教程内容
/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);
/* bmp倒叙转换到数组*/
cursor_bitmap_format_convert(r_buff, bmp_buf);
return 0;
}
这里有一个很重要概念,bmp图片数据存储方式是从下到上的,是倒着存放的,因此需要使用 cursor_bitmap_format_convert()
函数数据倒置装入framebuffer
.
接下来就是使用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]);
}
/* 绘制图片将fb区填充图片数组 */
memcpy(frame_buf->vaddr, g_screen_buff, frame_buf->size);
使用完整代码前请阅读【一、准备工作】
#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
#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
在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
。
killall weston
命令杀死yocto
占用的屏幕进程,建议执行多次。/test
文件夹整个拷贝到开发板合适目录下,比如nfs
服务绑定目录/mnt
下,运行./app
。拷贝整个文件夹的目的:如果开发板使用
wifi
利用nfs
与windows
进行文件共享,那么传输testpic.bmp
资源需要花费一段时间,可能导致图片显示不出来。
本文参考文章:
感谢两位高人的代码!
文章浏览阅读2.5w次,点赞6次,收藏50次。官方解释是,docker 容器是机器上的沙盒进程,它与主机上的所有其他进程隔离。所以容器只是操作系统中被隔离开来的一个进程,所谓的容器化,其实也只是对操作系统进行欺骗的一种语法糖。_docker菜鸟教程
文章浏览阅读5.7k次,点赞3次,收藏14次。该如何避免的,今天小编给大家推荐两个下载Windows系统官方软件的资源网站,可以杜绝软件捆绑等行为。该站提供了丰富的Windows官方技术资源,比较重要的有MSDN技术资源文档库、官方工具和资源、应用程序、开发人员工具(Visual Studio 、SQLServer等等)、系统镜像、设计人员工具等。总的来说,这两个都是非常优秀的Windows系统镜像资源站,提供了丰富的Windows系统镜像资源,并且保证了资源的纯净和安全性,有需要的朋友可以去了解一下。这个非常实用的资源网站的创建者是国内的一个网友。_msdn我告诉你
文章浏览阅读1.2k次。vue2封装对话框el-dialog组件_
文章浏览阅读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同一框内输入二行怎么换行
文章浏览阅读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次。济大数电实验报告_数据选择器及其应用
文章浏览阅读236次。1研究内容消费在生产中占据十分重要的地位,是生产的最终目的和动力,是保持省内经济稳定快速发展的核心要素。预测河南省社会消费品零售总额,是进行宏观经济调控和消费体制改变创新的基础,是河南省内人民对美好的全面和谐社会的追求的要求,保持河南省经济稳定和可持续发展具有重要意义。本文建立灰色预测模型,利用MATLAB软件,预测出2019年~2023年河南省社会消费品零售总额预测值分别为21881...._灰色预测模型用什么软件
文章浏览阅读1.2k次。12.4-在Qt中使用Log4Qt输出Log文件,看这一篇就足够了一、为啥要使用第三方Log库,而不用平台自带的Log库二、Log4j系列库的功能介绍与基本概念三、Log4Qt库的基本介绍四、将Log4qt组装成为一个单独模块五、使用配置文件的方式配置Log4Qt六、使用代码的方式配置Log4Qt七、在Qt工程中引入Log4Qt库模块的方法八、获取示例中的源代码一、为啥要使用第三方Log库,而不用平台自带的Log库首先要说明的是,在平时开发和调试中开发平台自带的“打印输出”已经足够了。但_log4qt
文章浏览阅读786次。全局观思维模型,一个教我们由点到线,由线到面,再由面到体,不断的放大格局去思考问题的思维模型。_计算机中对于全局观的
文章浏览阅读330次。一、CountDownLatch介绍CountDownLatch采用减法计算;是一个同步辅助工具类和CyclicBarrier类功能类似,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。二、CountDownLatch俩种应用场景: 场景一:所有线程在等待开始信号(startSignal.await()),主流程发出开始信号通知,既执行startSignal.countDown()方法后;所有线程才开始执行;每个线程执行完发出做完信号,既执行do..._countdownluach于cyclicbarrier的用法
文章浏览阅读508次。Prometheus 算是一个全能型选手,原生支持容器监控,当然监控传统应用也不是吃干饭的,所以就是容器和非容器他都支持,所有的监控系统都具备这个流程,_-自动化监控系统prometheus&grafana实战
文章浏览阅读4.7k次。输入关键字,可以通过键盘的搜索按钮完成搜索功能。_react search