K510(DongshanPI-Vision)DRM 屏幕显示入门:drm_probe探测屏幕

文章目录

前言

昨天完成了基础的屏幕测试后,今天决定对drm操作屏幕这块,做一点细化的学习和测试

但要做到:每天结束时我清楚知道"我做了什么 + 打通了什么链路"

并且能快速看到结果,于是今天决定完成:

** 屏幕上出现可控画面(纯色/渐变/色块动画)**

于是我决定做三部分:

  • 屏幕信息探测
  • 最小显示封装:mini_drm
  • 验证 demo:drm_color_demo

屏幕探测部分

首先创建一个ui_drm_probe的文件夹,并编写一个探测代码

c 复制代码
// drm_probe.c
// 用途:探测 DRM/KMS 资源,列出 connector/mode,并自动选择最合适的显示输出。
// 编译:见 Makefile
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

#include <xf86drm.h>
#include <xf86drmMode.h>

static const char* conn_type_name(uint32_t type) {
    switch (type) {
        case DRM_MODE_CONNECTOR_Unknown: return "Unknown";
        case DRM_MODE_CONNECTOR_VGA: return "VGA";
        case DRM_MODE_CONNECTOR_DVII: return "DVI-I";
        case DRM_MODE_CONNECTOR_DVID: return "DVI-D";
        case DRM_MODE_CONNECTOR_DVIA: return "DVI-A";
        case DRM_MODE_CONNECTOR_Composite: return "Composite";
        case DRM_MODE_CONNECTOR_SVIDEO: return "SVIDEO";
        case DRM_MODE_CONNECTOR_LVDS: return "LVDS";
        case DRM_MODE_CONNECTOR_Component: return "Component";
        case DRM_MODE_CONNECTOR_9PinDIN: return "DIN";
        case DRM_MODE_CONNECTOR_DisplayPort: return "DP";
        case DRM_MODE_CONNECTOR_HDMIA: return "HDMI-A";
        case DRM_MODE_CONNECTOR_HDMIB: return "HDMI-B";
        case DRM_MODE_CONNECTOR_TV: return "TV";
        case DRM_MODE_CONNECTOR_eDP: return "eDP";
        case DRM_MODE_CONNECTOR_VIRTUAL: return "VIRTUAL";
        case DRM_MODE_CONNECTOR_DSI: return "DSI";
        default: return "Other";
    }
}

static int mode_score(const drmModeModeInfo* m) {
    // 评分策略:
    // 1) 优先 1920x1080
    // 2) 否则按分辨率面积最大优先
    int w = m->hdisplay;
    int h = m->vdisplay;
    int area = w * h;
    if (w == 1920 && h == 1080) return 100000000 + area;
    return area;
}

int main(int argc, char** argv) {
    const char* card = "/dev/dri/card0";
    if (argc >= 2) card = argv[1];

    printf("[DRM探测] 打开设备:%s\n", card);
    int fd = open(card, O_RDWR | O_CLOEXEC);
    if (fd < 0) {
        printf("[DRM探测][错误] open 失败:%s\n", strerror(errno));
        return 1;
    }

    drmModeRes* res = drmModeGetResources(fd);
    if (!res) {
        printf("[DRM探测][错误] drmModeGetResources 失败:%s\n", strerror(errno));
        close(fd);
        return 1;
    }

    printf("[DRM探测] connectors=%d encoders=%d crtcs=%d fbs=%d\n",
           res->count_connectors, res->count_encoders, res->count_crtcs, res->count_fbs);

    uint32_t best_conn_id = 0;
    drmModeModeInfo best_mode;
    memset(&best_mode, 0, sizeof(best_mode));
    int best_score = -1;
    uint32_t best_enc_id = 0;

    for (int i = 0; i < res->count_connectors; i++) {
        uint32_t cid = res->connectors[i];
        drmModeConnector* conn = drmModeGetConnector(fd, cid);
        if (!conn) continue;

        printf("\n[Connector] id=%u 类型=%s(%u) 状态=%s 模式数=%d\n",
               conn->connector_id,
               conn_type_name(conn->connector_type),
               conn->connector_type,
               (conn->connection == DRM_MODE_CONNECTED) ? "已连接" :
               (conn->connection == DRM_MODE_DISCONNECTED) ? "未连接" : "未知",
               conn->count_modes);

        for (int m = 0; m < conn->count_modes; m++) {
            drmModeModeInfo* mode = &conn->modes[m];
            int vrefresh = 0;
            if (mode->htotal && mode->vtotal)
                vrefresh = (int)((mode->clock * 1000LL) / (mode->htotal * mode->vtotal));
            printf("  - mode[%d]: %s %dx%d refresh≈%dHz flags=0x%x type=0x%x\n",
                   m, mode->name, mode->hdisplay, mode->vdisplay, vrefresh, mode->flags, mode->type);
        }

        if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0) {
            // 选择此 connector 下的最优 mode
            int local_best = -1;
            int local_best_score = -1;
            for (int m = 0; m < conn->count_modes; m++) {
                int sc = mode_score(&conn->modes[m]);
                if (sc > local_best_score) {
                    local_best_score = sc;
                    local_best = m;
                }
            }

            // encoder_id 可能为 0,需要通过 encoders 关联再选(这里先打印出来)
            printf("[Connector] 当前 encoder_id=%u\n", conn->encoder_id);

            if (local_best >= 0 && local_best_score > best_score) {
                best_score = local_best_score;
                best_conn_id = conn->connector_id;
                best_mode = conn->modes[local_best];
                best_enc_id = conn->encoder_id;
            }
        }

        drmModeFreeConnector(conn);
    }

    if (!best_conn_id) {
        printf("\n[DRM探测][错误] 没有找到"已连接且有模式"的 connector。\n");
        drmModeFreeResources(res);
        close(fd);
        return 2;
    }

    // 尝试找到与 connector 对应的 encoder/crtc
    drmModeConnector* best_conn = drmModeGetConnector(fd, best_conn_id);
    if (!best_conn) {
        printf("[DRM探测][错误] 读取 best connector 失败\n");
        drmModeFreeResources(res);
        close(fd);
        return 3;
    }

    uint32_t crtc_id = 0;
    uint32_t enc_id = best_conn->encoder_id;

    // 如果 connector->encoder_id 为 0,就尝试从 connector->encoders 列表选一个
    if (enc_id == 0 && best_conn->count_encoders > 0) {
        enc_id = best_conn->encoders[0];
    }

    drmModeEncoder* enc = NULL;
    if (enc_id) enc = drmModeGetEncoder(fd, enc_id);

    if (enc) {
        crtc_id = enc->crtc_id;
    }

    printf("\n[DRM探测][选择结果]\n");
    printf("  - connector_id = %u\n", best_conn_id);
    printf("  - mode         = %s %dx%d\n", best_mode.name, best_mode.hdisplay, best_mode.vdisplay);
    printf("  - encoder_id   = %u\n", enc_id);
    printf("  - crtc_id      = %u\n", crtc_id);
    if (!crtc_id) {
        printf("  - 提示:crtc_id 为 0,可能需要更复杂的 encoder/crtc 匹配逻辑。\n");
    }

    if (enc) drmModeFreeEncoder(enc);
    drmModeFreeConnector(best_conn);
    drmModeFreeResources(res);
    close(fd);

    printf("\n[DRM探测] 完成。\n");
    return 0;
}

运行结果如下:

sh 复制代码
[root@canaan ~ ]$ ./drm_probe
[DRM探测] 打开设备:/dev/dri/card0
[DRM探测] connectors=1 encoders=1 crtcs=1 fbs=0

[Connector] id=49 类型=DSI(16) 状态=已连接 模式数=1
  - mode[0]: 1080x1920 1080x1920 refresh≈30Hz flags=0xa type=0x48
[Connector] 当前 encoder_id=48

[DRM探测][选择结果]
  - connector_id = 49
  - mode         = 1080x1920 1080x1920
  - encoder_id   = 48
  - crtc_id      = 57

[DRM探测] 完成。

这部分代码比较简单,我只说几个关键的函数接口和理解

1) 打开 DRM 设备节点

日志:

sh 复制代码
[DRM探测] 打开设备:/dev/dri/card0

代码:

c 复制代码
open(card, O_RDWR | O_CLOEXEC)

解释:

DRM 显示设备在 Linux 下通常是 /dev/dri/cardX

板子上是 card0

2) 读取 DRM 全局资源表

sh 复制代码
[DRM探测] connectors=1 encoders=1 crtcs=1 fbs=0

代码是这个:

c 复制代码
    drmModeRes* res = drmModeGetResources(fd);
    if (!res) {
        printf("[DRM探测][错误] drmModeGetResources 失败:%s\n", strerror(errno));
        close(fd);
        return 1;
    }

    printf("[DRM探测] connectors=%d encoders=%d crtcs=%d fbs=%d\n",
           res->count_connectors, res->count_encoders, res->count_crtcs, res->count_fbs);

解释一下:

  • res->count_connectors / res->connectors[]

    机器上有哪些"输出口"(DSI/HDMI/eDP...)

  • res->count_encoders / res->encoders[]

    把"像素数据流"编码/绑定到输出口的部件(抽象层)

  • res->count_crtcs / res->crtcs[]

    真正负责"扫描输出"的显示控制器(CRTC 常被理解成一个显示 pipeline)

  • res->count_fbs / res->fbs[]

    当前内核里注册的 framebuffer 对象数量

3) 遍历每个 connector(输出口),打印其状态和模式列表

这部分日志如下

sh 复制代码
[Connector] id=49 类型=DSI(16) 状态=已连接 模式数=1
  - mode[0]: 1080x1920 1080x1920 refresh≈30Hz flags=0xa type=0x48
[Connector] 当前 encoder_id=48

主要代码如下

c 复制代码
    uint32_t best_conn_id = 0;
    drmModeModeInfo best_mode;
    memset(&best_mode, 0, sizeof(best_mode));
    int best_score = -1;
    uint32_t best_enc_id = 0;

    for (int i = 0; i < res->count_connectors; i++) {
        uint32_t cid = res->connectors[i];
        drmModeConnector* conn = drmModeGetConnector(fd, cid);
        if (!conn) continue;

        printf("\n[Connector] id=%u 类型=%s(%u) 状态=%s 模式数=%d\n",
               conn->connector_id,
               conn_type_name(conn->connector_type),
               conn->connector_type,
               (conn->connection == DRM_MODE_CONNECTED) ? "已连接" :
               (conn->connection == DRM_MODE_DISCONNECTED) ? "未连接" : "未知",
               conn->count_modes);

        for (int m = 0; m < conn->count_modes; m++) {
            drmModeModeInfo* mode = &conn->modes[m];
            int vrefresh = 0;
            if (mode->htotal && mode->vtotal)
                vrefresh = (int)((mode->clock * 1000LL) / (mode->htotal * mode->vtotal));
            printf("  - mode[%d]: %s %dx%d refresh≈%dHz flags=0x%x type=0x%x\n",
                   m, mode->name, mode->hdisplay, mode->vdisplay, vrefresh, mode->flags, mode->type);
        }

        if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0) {
            // 选择此 connector 下的最优 mode
            int local_best = -1;
            int local_best_score = -1;
            for (int m = 0; m < conn->count_modes; m++) {
                int sc = mode_score(&conn->modes[m]);
                if (sc > local_best_score) {
                    local_best_score = sc;
                    local_best = m;
                }
            }

            // encoder_id 可能为 0,需要通过 encoders 关联再选(这里先打印出来)
            printf("[Connector] 当前 encoder_id=%u\n", conn->encoder_id);

            if (local_best >= 0 && local_best_score > best_score) {
                best_score = local_best_score;
                best_conn_id = conn->connector_id;
                best_mode = conn->modes[local_best];
                best_enc_id = conn->encoder_id;
            }
        }

        drmModeFreeConnector(conn);
    }
  • cid = res->connectors[i]

  • drmModeConnector* conn = drmModeGetConnector(fd, cid);

  • 打印:

    • conn->connector_id
    • conn->connector_type(用 conn_type_name() 翻译成人话)
    • conn->connection(CONNECTED / DISCONNECTED / UNKNOWN)
    • conn->count_modes
  • 遍历 conn->modes[m](每个是 drmModeModeInfo

drmModeGetConnector() 返回的关键字段含义
  • connector_id:connector 的唯一 ID(这里是 49
  • connector_type:类型(这里是 DSI(16),典型就是 MIPI-DSI 屏)
  • connection:是否连接(这里是 已连接
  • count_modes + modes[]:这个输出口支持的显示模式列表
    这里只有 1 个模式:1080x1920
drmModeModeInfo 里打印了哪些关键字段?
  • mode->name:通常类似 "1080x1920"(驱动给的名字)
  • hdisplay / vdisplay:分辨率(宽/高)
  • clock / htotal / vtotal:用来推算刷新率
  • flags / type:驱动标记(比如 preferred、driver 等)
刷新率怎么算出来的?

代码:

c 复制代码
if (mode->htotal && mode->vtotal)
    vrefresh = (int)((mode->clock * 1000LL) / (mode->htotal * mode->vtotal));

解释:

  • mode->clock 单位通常是 kHz
  • (clock * 1000) / (htotal * vtotal) ≈ Hz
  • 所以这里看到 refresh≈30Hz(大约 30Hz)

4) "自动选择最合适的模式"

代码的策略在 mode_score()

c 复制代码
// 1) 优先 1920x1080
// 2) 否则按分辨率面积最大优先
if (w == 1920 && h == 1080) return 100000000 + area;
return area;

但实际 connector 只有一个模式:1080x1920

所以最终就选它(面积也是最大,因为只有它)。


5) 找到 encoder / crtc(把"输出口"落到"实际显示 pipeline")

日志:

sh 复制代码
[DRM探测][选择结果]
  - connector_id = 49
  - mode         = 1080x1920 1080x1920
  - encoder_id   = 48
  - crtc_id      = 57

代码:

  1. 重新拿一次 connector(为了稳妥)

    • best_conn = drmModeGetConnector(fd, best_conn_id);
  2. 取 encoder id

    • enc_id = best_conn->encoder_id;
    • 如果 encoder_id == 0,就从 best_conn->encoders[0] 猜一个
  3. 读取 encoder

    • enc = drmModeGetEncoder(fd, enc_id);
  4. 从 encoder 里拿到 CRTC

    • crtc_id = enc->crtc_id;
这几个对象之间的关系
  • Connector(输出口):DSI/HDMI... "屏插在哪"
  • Encoder(编码器/绑定器):把 CRTC 输出"接到"某个 connector
  • CRTC(显示管线):负责扫描输出(决定时序、从 framebuffer 取像素输出)
  • Mode(分辨率/时序):告诉 CRTC 要怎么扫(1080x1920@30Hz)

现在确定了:

  • 输出口:DSI connector_id=49
  • 对应 encoder:48
  • 最终走的 CRTC:57
  • 模式:1080x1920@~30Hz

到这一步,屏幕信息探测的部分就完成了

相关推荐
禅口魔心5 小时前
Win10 + WSL2 + Docker:K510(DongshanPI-Vision)开发环境从踩坑到跑通全记录(交叉编译 + 上板运行)
docker·嵌入式开发·wsl2·k510
禅口魔心6 小时前
K510 开发记录:通用 CMake 交叉编译 + DRM 显示测试
嵌入式开发·k510
MounRiver_Studio1 天前
RISC-V IDE MRS2进阶分享(三):MRS语言服务器
ide·mcu·risc-v·嵌入式开发
MounRiver_Studio1 天前
RISC-V IDE MRS2进阶分享(四):CH32H417双核芯片项目开发
ide·mcu·risc-v·嵌入式开发
龙智DevSecOps解决方案4 天前
C语言安全编码指南:MISRA C、CERT C、CWE 与 C Secure 标准对比与Perforce QAC应用详解
嵌入式开发·代码安全·perforce qac·c语言安全·misrac
charlie1145141916 天前
嵌入式C++开发——RAII 在驱动 / 外设管理中的应用
开发语言·c++·笔记·嵌入式开发·工程实践
Tronlong创龙10 天前
DR1 系列评估板 OpenAMP 双核 ARM 通信案例开发手册
开发板·嵌入式开发·硬件开发·工业控制
CodeCraft Studio11 天前
从框架到体验:Qt + Qtitan 构建制造业嵌入式UI整体解决方案
开发语言·qt·ui·gui·嵌入式开发·hmi·制造业嵌入式ui
ElfBoard12 天前
ElfBoard技术贴|如何在ELF-RK3506开发板上构建AI编程环境
c语言·开发语言·单片机·嵌入式硬件·智能路由器·ai编程·嵌入式开发