RK3506 drm显示编程

  1. 概述

     RK3506虽然是Cortex-A7架构的处理器,但是其并显示未使用linuxfb,而是使用了drm(Direct Rendering Manager)即使。
    
     Linux DRM(Direct Rendering Manager)是 Linux 内核中的一个子系统,专门用于图形硬件的管理,提供统一的接口和驱动框架来支持显示硬件的操作。它的目标是允许用户空间直接控制图形硬件,并实现高效的图形渲染。通过 DRM,用户空间的图形库(如 Xorg、Wayland)和应用程序能够访问图形硬件以进行渲染和显示。
    
  2. Linux DRM 驱动原理
    2.1 DRM 的基本架构
    DRM 内核模块
    DRM(Direct Rendering Manager)内核模块是Linux内核的一部分,负责直接控制和管理硬件资源,例如显卡和显示设备。它实现了硬件抽象层(HAL),提供了对不同硬件的统一接口。主要功能包括:

内存管理:管理显存(Video RAM)的分配和释放。
缓冲区管理:创建、销毁和管理帧缓冲区。
模式设置:设置显示模式(分辨率、刷新率等)和配置连接器(如HDMI、DisplayPort)。
中断处理:处理与图形硬件相关的中断请求(IRQ)。
用户空间(libdrm)
用户空间通过libdrm库与DRM内核模块交互。libdrm是一个用户空间库,提供了一组API,使应用程序能够方便地与DRM内核模块进行通信。主要功能包括:

缓冲区管理:创建和管理帧缓冲区。
显示控制:配置显示参数,例如分辨率、刷新率等。
图形渲染:管理图形渲染任务。
图形硬件(GPU)
图形硬件(GPU)是负责图形渲染和图像输出的设备。它包括渲染引擎、显示引擎和视频处理引擎等。GPU的主要功能是:

图形渲染:生成和处理图形图像。
视频解码:解码视频流以供显示。
显示输出:通过连接器(如MIPI、DisplayPort)输出图像。
显示设备(Display Device)
显示设备(如LCD、HDMI显示器等)是用于显示图像的设备。通过连接器(如HDMI、DisplayPort)和显示控制器(CRTC)进行连接和控制。主要功能是:

图像显示:将GPU输出的图像显示在屏幕上。
显示模式设置:配置分辨率、刷新率等显示参数。
2.2 内核与用户空间的交互
在Linux系统中,内核通过DRM驱动框架提供图形硬件的统一管理和控制接口。用户空间应用程序通过libdrm库与内核进行交互,主要通过IOCTL(输入/输出控制)系统调用实现。具体过程如下:

用户空间应用程序:应用程序通过调用libdrm库中的函数向内核发送请求。例如,应用程序可能需要获取硬件资源、设置显示参数、创建和操作帧缓冲区等。
libdrm库:libdrm库将应用程序的请求转化为DRM内核模块能够理解的IOCTL调用。IOCTL(Input/Output Control)是Linux系统中用于设备控制的系统调用。
DRM内核模块:DRM内核模块接收并处理这些IOCTL请求,配置图形硬件、分配内存、设置显示模式、渲染图像等。
图形硬件:根据DRM内核模块的配置和控制,执行相应的图形渲染和显示任务
3. Libdrm库的安装
在开发嵌入式系统或其他需要交叉编译的应用程序时,开发者通常在主机系统上进行编译,而目标程序将运行在不同的架构上。为了使用libdrm库进行交叉编译,以下是安装步骤

3.1 下载libdrm 压缩包
地址:Index of /libdrm
在这里插入图片描述
本示例使用2.4.115版本,一般而言,高版本兼容低版本,所以尽量下载较新的版本

3.2 编译libdrm 库
将下载好的lib压缩包放入安装好的ubuntu linux 环境当中

tar -xvf libdrm-2.4.115.tar.xz   #解压命令
 
#进入libdrm 目录
cd libdrm-2.4.115/
 
##安装相关工具
sudo apt update
sudo apt install meson
sudo apt install ninja-build
 
##创建build 和 install 目录
mkdir build
mkdir install
 
##创建编译配置文件cross_file.txt
vim cross_file.txt
 
##文件内容
[binaries]
c = '/opt/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/bin/arm-none-linux-gnueabihf-gcc'
cpp = '/opt/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/bin/arm-none-linux-gnueabihf-g++'
ar = '/opt/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/bin/arm-none-linux-gnueabihf-ar'
strip = '/opt/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/bin/arm-none-linux-gnueabihf-strip'
 
[host_machine]
system = 'linux'
cpu_family = 'arm'
cpu = 'armv7'
endian = 'little'
 
[build_machine]
system = 'linux'
cpu_family = 'x86_64'
cpu = 'x86_64'
endian = 'little'
 
##构建编译,注意,将编译的相关工具链换位自己的实际工具链的绝对目录
 
meson  --prefix=$(pwd)/../install   --cross-file=../cross_file.txt 
 
##编译
ninja
 
##安装
ninja install

按照上述步骤编译安装即可
4. DRM 驱动架构
4.1 内核空间架构
DRM 驱动在内核中主要包括以下几个部分:

**DRM 驱动模块:**为每种图形硬件提供驱动支持,内核中每个显卡都对应一个 DRM 驱动(例如 i915 代表 Intel 显卡,amdgpu 代表 AMD 显卡)。
**设备模型:**每个显卡设备都会在内核中注册成一个设备节点,通常位于 /dev/dri/ 目录下,如 /dev/dri/card0 和 /dev/dri/renderD128。
/dev/dri/card0:这是一个图形显示控制设备节点,用于处理与显示相关的操作。它代表显卡或 GPU 的主要控制接口,主要用于配置显示模式(如分辨率、刷新率等)、管理显示输出、设置帧缓冲区等任务。所有显示设备(如显示器)通过这个接口来连接和控制。
/dev/dri/renderD128:这是一个渲染设备节点,专门用于 3D 渲染和计算任务。与 /dev/dri/card0 不同,renderD128 设备节点不处理显示输出,而是用于处理 GPU 的计算和渲染任务。它允许用户空间应用程序直接访问 GPU 的渲染能力,以实现高效的图形处理和计算任务。
资源管理:管理图形硬件的资源,包括显示模式、帧缓冲区、内存等。通过 DRM IOCTL 与用户空间交互。
硬件抽象:在内核中,硬件控制被封装为一组统一的 API。无论是 Intel 显卡还是 AMD 显卡,用户空间应用通过相同的接口进行访问。
4.2 图形硬件的工作原理
图形硬件在显示和渲染方面的工作原理主要包括以下几个方面:

显存管理:DRM 管理显卡的内存,通过分配显卡内存缓冲区来存储图像数据。内核提供相应的 API,用户空间通过这些 API 来创建、映射和操作这些内存区域。
显示控制:内核通过 CRTC(显示控制器)来控制显示输出。每个 CRTC 负责一个显示设备的输出,管理显示内容、分辨率、刷新率等参数。
内存映射:通过 mmap 系统调用,用户空间能够将帧缓冲区直接映射到进程的地址空间,这样可以直接操作显示内存。
4.3 常见的内核空间 IOCTL 调用
以下是几个常见的用于控制图形硬件的 IOCTL 调用:

DRM_IOCTL_MODE_CREATE_DUMB:创建一个简单的帧缓冲区对象,返回一个内存句柄和尺寸等信息。
DRM_IOCTL_MODE_MAP_DUMB:将创建的帧缓冲区对象映射到用户空间,使用户可以直接操作它。
DRM_IOCTL_MODE_SET_CRTC:将渲染的图像输出到指定的显示设备上。
5. 用户空间程序设计
5.1 程序步骤详解
以下是实现一个简单显示程序的步骤,以及每个步骤中使用的函数、变量原型和参数的详细解释:

打开设备节点 程序通过 open() 函数打开 /dev/dri/card0 或 /dev/dri/renderD128 设备文件。

int fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);

int fd:文件描述符,表示打开的设备节点。
O_RDWR:表示以读写方式打开文件。
O_CLOEXEC:表示在执行新程序时关闭文件描述符。
获取设备资源 使用 drmModeGetResources() 函数获取设备的资源信息(如连接器、编码器、CRTC 等)。

drmModeRes *resources = drmModeGetResources(fd);

drmModeRes *resources:指向 drmModeRes 结构体的指针,该结构体包含所有显示资源的信息。
drmModeRes 结构体包含以下主要成员:
count_fbs:帧缓冲区的数量。
fbs:帧缓冲区的数组。
count_crtcs:CRTC 的数量。
crtcs:CRTC 的数组。
count_connectors:连接器的数量。
connectors:连接器的数组。
其他成员:count_encoders, encoders, 等。
查找已连接的显示设备 遍历所有连接器,查找状态为已连接(DRM_MODE_CONNECTED)的设备。

drmModeConnector *connector = drmModeGetConnector(fd, connector_id);

drmModeConnector *connector:指向 drmModeConnector 结构体的指针,该结构体包含连接器的详细信息。
drmModeConnector 结构体包含以下主要成员:
connector_id:连接器的唯一标识符。
encoder_id:当前连接到该连接器的编码器的标识符。
connection:连接状态(如 DRM_MODE_CONNECTED 表示已连接)。
count_modes:支持的显示模式数量。
modes:显示模式的数组。
其他成员:mmWidth, mmHeight, subpixel, 等。
创建帧缓冲区 使用 DRM_IOCTL_MODE_CREATE_DUMB 创建一个简单的帧缓冲区(即显存中的图像区域)。

struct drm_mode_create_dumb create_req = {0};
create_req.width = width;
create_req.height = height;
create_req.bpp = 32;
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req);

struct drm_mode_create_dumb create_req:drm_mode_create_dumb 结构体的实例,用于定义帧缓冲区的参数。
width:帧缓冲区的宽度(像素)。
height:帧缓冲区的高度(像素)。
bpp(Bits Per Pixel):每个像素的位数(例如,32 表示每个像素占用 32 位)。
drmIoctl():用于发送 IOCTL 命令到 DRM 驱动。
映射帧缓冲区 通过 mmap() 函数将帧缓冲区映射到用户空间,从而直接操作内存进行图形渲染。

void *map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, map_req.offset);

void *map:指向映射到用户空间的内存区域的指针。
size:映射内存区域的大小(字节)。
PROT_READ:映射区域可读。
PROT_WRITE:映射区域可写。
MAP_SHARED:映射区域在进程间共享。
fd:文件描述符。
map_req.offset:帧缓冲区的偏移量。
渲染图像并设置输出 根据用户输入的选项,绘制不同的图形(如填充颜色、矩形、圆形等),并通过 drmModeSetCrtc() 函数将渲染结果输出到显示设备。

drmModeSetCrtc(fd, crtc->crtc_id, fb_id, 0, 0, &connector->connector_id, 1, &connector->modes[0]);

drmModeSetCrtc():设置 CRTC 的参数,将帧缓冲区的内容显示到屏幕上。
crtc->crtc_id:CRTC 的标识符。
fb_id:帧缓冲区的标识符。
connector->connector_id:连接器的标识符。
connector->modes[0]:显示模式的结构体,包含分辨率、刷新率等参数。
6.示例程序以及编译
6.1 Makeifle
drm程序编程时,需要使用libdrm库的头文件以及lib库。因此,进行了标题2的libdrm 库的交叉编译,编译后的include 和 lib 在创建的install 路径下。且要将相关的路径加入到Makefile当中。以下是一个Makefile的示例。

CFLAGS = -Iinclude \
		 -I/work/prj/rk3506/libdrm-2.4.115/install/include \
		 -I/work/prj/rk3506/libdrm-2.4.115/install/include/libdrm
LDFLAGS = -L/work/prj/rk3506/libdrm-2.4.115/install/lib
export CC=/opt/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/bin/arm-none-linux-gnueabihf-gcc
 
drm_test: drm_test.c
	$(CC) $(CFLAGS) -o drm_test drm_test.c $(LDFLAGS) -ldrm
 
clean:
	rm -f *.o drm_test

Makefile 的路径需换成自身实际的绝对路径
6.2 示例程序
这个示例程序展示了如何使用Linux DRM API来打开一个显示设备、分配帧缓冲区、映射内存并在屏幕上绘制一些简单的形状(矩形和圆形) 。

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <drm/drm.h>
#include <drm/drm_mode.h>
#include <sys/mman.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
 
#define DEVICE_PATH "/dev/dri/card0"
 
// 绘制矩形函数
void draw_rectangle(uint32_t *fb, int fb_width, int fb_height, int x, int y, int rect_width, int rect_height, uint32_t color) {
	int i, j;
	for (j = y; j < y + rect_height && j < fb_height; j++) {
		for (i = x; i < x + rect_width && i < fb_width; i++) {
			fb[j * fb_width + i] = color;
		}
	}
}
 
// 绘制圆形函数
void draw_circle(uint32_t *fb, int fb_width, int fb_height, int cx, int cy, int radius, uint32_t color) {
	int x, y;
	for (y = -radius; y <= radius; y++) {
		for (x = -radius; x <= radius; x++) {
			if (x * x + y * y <= radius * radius) { // 圆方程: x² + y² ≤ r²
				int px = cx + x;
				int py = cy + y;
				if (px >= 0 && px < fb_width && py >= 0 && py < fb_height) {
					fb[py * fb_width + px] = color;
				}
			}
		}
	}
}
 
int main() {
	int fd, i;
	drmModeRes *resources;
	drmModeConnector *connector = NULL;
	drmModeEncoder *encoder = NULL;
	drmModeCrtc *crtc = NULL;
 
	uint32_t fb_id;
	void *map = NULL;
	int width, height;
	uint64_t size;
 
	// 1. 打开DRM设备
	fd = open(DEVICE_PATH, O_RDWR | O_CLOEXEC);
	if (fd < 0) {
		perror("Failed to open DRM device");
		return -1;
	}
 
	// 2. 获取资源信息
	resources = drmModeGetResources(fd);
	if (!resources) {
		perror("Failed to get DRM resources");
		close(fd);
		return -1;
	}
 
	// 3. 查找连接器(输出接口)
	for (i = 0; i < resources->count_connectors; i++) {
		connector = drmModeGetConnector(fd, resources->connectors[i]);
		if (connector->connection == DRM_MODE_CONNECTED) {
			break;
		}
		drmModeFreeConnector(connector);
	}
	if (!connector || connector->connection != DRM_MODE_CONNECTED) {
		printf("No connected connector found.\n");
		drmModeFreeResources(resources);
		close(fd);
		return -1;
	}
 
	// 4. 查找编码器
	encoder = drmModeGetEncoder(fd, connector->encoder_id);
	crtc = drmModeGetCrtc(fd, encoder->crtc_id);
 
	width = connector->modes[0].hdisplay;
	height = connector->modes[0].vdisplay;
	printf("Display resolution: %dx%d\n", width, height);
 
	// 5. 创建FrameBuffer
	struct drm_mode_create_dumb create_req = {0};
	create_req.width = width;
	create_req.height = height;
	create_req.bpp = 32; // 32-bit color depth
 
	if (drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req) < 0) {
		perror("Failed to create dumb buffer");
		goto cleanup;
	}
 
	uint32_t handle = create_req.handle;
	uint32_t pitch = create_req.pitch;
	size = create_req.size;
 
	if (drmModeAddFB(fd, width, height, 24, 32, pitch, handle, &fb_id)) {
		perror("Failed to add framebuffer");
		goto cleanup;
	}
 
	// 6. 映射FrameBuffer内存
	struct drm_mode_map_dumb map_req = {0};
	map_req.handle = handle;
 
	if (drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req)) {
		perror("Failed to map dumb buffer");
		goto cleanup;
	}
 
	map = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, map_req.offset);
	if (map == MAP_FAILED) {
		perror("Failed to mmap framebuffer");
		goto cleanup;
	}
 
	// 7. 设置CRTC输出到新FrameBuffer
	if (drmModeSetCrtc(fd, crtc->crtc_id, fb_id, 0, 0, &connector->connector_id, 1, &connector->modes[0])) {
		perror("Failed to set CRTC");
		goto cleanup;
	}
 
	uint32_t *pixel = (uint32_t *)map;
	int option;
 
 
	int current_option = -1; // 添加一个变量来跟踪当前的显示选项
 
	while (1) {
		printf("\n选择要显示的内容:\n");
		printf("1 - 显示红色\n");
		printf("2 - 显示蓝色\n");
		printf("3 - 显示绿色\n");
		printf("4 - 显示矩形\n");
		printf("5 - 显示圆形\n");
		printf("0 - 退出显示\n");
		printf("输入选项 (Ctrl+C退出): ");
		scanf("%d", &option);
 
		if (option == 0) break; // 如果输入0,则退出显示循环
 
		if (option != current_option) { // 只有在选项改变时才清屏和重绘
			current_option = option;
			memset(map, 0, size); // 清屏
 
			switch (option) {
				case 1: // 红色
					for (i = 0; i < (width * height); i++) pixel[i] = 0x00FF0000; // ARGB 红色
					break;
				case 2: // 蓝色
					for (i = 0; i < (width * height); i++) pixel[i] = 0x000000FF; // ARGB 蓝色
					break;
				case 3: // 绿色
					for (i = 0; i < (width * height); i++) pixel[i] = 0x0000FF00; // ARGB 绿色
					break;
				case 4: // 矩形
					draw_rectangle(pixel, width, height, 100, 100, 300, 200, 0x00FFFFFF); // 白色矩形
					break;
				case 5: // 圆形
					draw_circle(pixel, width, height, width / 2, height / 2, 150, 0x00FFFF00); // 黄色圆形
					break;
				default:
					printf("无效选项,请重试!\n");
					continue;
			}
		}
		usleep(100000); // 短暂延时,避免CPU占用过高
	}
cleanup:
	if (map) munmap(map, size);
	if (fb_id) drmModeRmFB(fd, fb_id);
	drmModeFreeCrtc(crtc);
	drmModeFreeEncoder(encoder);
	drmModeFreeConnector(connector);
	drmModeFreeResources(resources);
	close(fd);
	return 0;
}

运行程序后,按照提示输入选项编号来选择要显示的内容:

  • 输入 1 显示红色。
  • 输入 2 显示蓝色。
  • 输入 3 显示绿色。
  • 输入 4 显示一个矩形。
  • 输入 5 显示一个圆形。
  • 输入 0 退出显示。
  • 程序退出

输入 0 或按 Ctrl+C 退出程序。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值