首页
商城
  • English
  • 简体中文
首页
商城
  • English
  • 简体中文
  • 产品系列

    • FPGA+ARM

      • GM-3568JHF

        • 一、简介

          • GM-3568JHF 简介
        • 二、快速开始

          • 00 前言
          • 01 环境搭建
          • 02 编译说明
          • 03 烧录指南
          • 04 调试工具
          • 05 软件更新
          • 06 查看信息
          • 07 测试命令
          • 08 应用编译
          • 09 源码获取
        • 三、外设与接口

          • 01 USB
          • 02 显示与触摸
          • 03 以太网
          • 04 WIFI
          • 05 蓝牙
          • 06 TF-Card
          • 07 音频
          • 08 串口
          • 09 CAN
          • 10 RTC
        • 四、应用开发

          • 01 UART读写案例
          • 02 按键检测案例
          • 03 LED灯闪烁案例
          • 04 MIPI屏幕检测案例
          • 05 读取 USB 设备信息案例
          • 06 FAN 检测案例
          • 07 FPGA FSPI 通信案例
          • 08 FPGA DMA 读写案例
          • 09 GPS调试案例
          • 10 以太网测试案例
          • 11 RS485读写案例
          • 12 FPGA IIC 读写案例
          • 13 PN532 NFC读卡案例
          • 14 TF卡读写案例
        • 五、QT开发

          • 01 ARM64交叉编译器环境搭建
          • 02 QT 程序加入开机自启服务
        • 六、RKNN_NPU开发

          • 01 RK3568 NPU 概述
          • 02 开发环境搭建
          • 运行官方 YOLOv5 示例
        • 七、FPGA开发

          • ARM与FPGA通讯
          • FPGA开发手册
        • 八、其他

          • 01 根目录文件系统的修改
          • 02 系统自启服务
        • 九、资料下载

          • 资料下载
    • ShimetaPi

      • M4-R1

        • 一、简介

          • M4-R1简介
        • 二、快速上手

          • 01 OpenHarmony概述
          • 02 镜像烧录
          • 03 应用开发快速上手
          • 04 设备开发快速上手
        • 三、应用开发

          • 01 ArkUI

            • 1 ArkTS语言简介
            • 2 UI 组件-Row 容器介绍
            • 3 UI 组件-Column 容器介绍
            • 4 UI 组件-Text 组件
            • 5 UI 组件-Toggle 组件
            • 6 UI 组件-Slider 组件
            • 7 UI 组件-Animation 组件&Transition 组件
          • 02 资料获取

            • 1 OpenHarmony 官方资料
          • 03 开发须知

            • 1 Full-SDK替换教程
            • 2 引入和使用三方库
            • 3 HDC调试
            • 4 命令行恢复出厂模式
            • 5 升级App为system权限
          • 04 构建第一个应用

            • 1 构建第一个ArkTs应用-HelloWorld
          • 05 案例

            • 01 串口调试助手应用案例
            • 02 手写板应用案例
            • 03 数字时钟应用案例
            • 04 WIFI 信息获取应用案例
        • 四、设备开发

          • 1 Ubuntu环境开发

            • 01 环境搭建
            • 02 下载源码
            • 03 编译源码
          • 2 使用DevEco Device Tool 工具

            • 01 工具简介
            • 02 开发环境的搭建
            • 03 导入SDK
            • 04 HUAWEI DevEco Tool 功能介绍
        • 五、内核外设与接口

          • 01 指南
          • 02 设备树介绍
          • 03 NAPI 入门
          • 04 ArkTS入门
          • 05 NAPI开发实战演示
          • 06 GPIO介绍
          • 07 I2C通讯
          • 08 SPI通信
          • 09 PWM 控制
          • 10 串口通讯
          • 11 TF卡
          • 12 屏幕
          • 13 触摸
          • 14 Ethernet(以太网)
          • 15 M.2 硬盘
          • 16 音频
          • 17 WIFI & BT
          • 18 摄像头
        • 六、资料下载

          • 资料下载
      • M4-R2

        • 一、产品简介

          • M4-R2 产品简介
        • 二、快速上手

          • 快速上手
        • 三、Android 系统

          • 1 快速上手

            • Android系统烧录与启动
            • 开发环境搭建
            • ADB调试与日志分析
            • SDK下载与编译环境搭建
            • Android系统基础操作
          • 2 SDK 编译

            • Android_SDK编译
            • Android构建系统与增量编译
          • 3 系统开发

            • Android系统定制
          • 4 应用开发

            • Android应用开发入门
            • Android JNI开发
          • 5 驱动与 HAL

            • Android HAL开发
          • 6 AI 与 NPU

            • Android NPU使用指南
        • 四、硬件参考

          • 核心板参数
          • 底板接口定义
          • 40PIN引脚定义
          • 电源管理
        • 五、常见问题与故障排除

          • 系统启动问题
          • 外设驱动问题
          • 网络连接问题
          • 显示问题
          • 常见编译错误
        • 六、应用方案参考

          • 智能显示终端方案
          • 边缘AI计算方案
          • 工业控制方案
          • NVR安防监控方案
          • 智能家居网关方案
        • 七、资料下载

          • 资料下载
      • M5-R2A

        • 一、产品简介

          • M5-R2A 产品简介
        • 二、快速上手

          • 系统烧录指南
          • 开发环境搭建
          • 快速上手
          • SDK下载与编译环境搭建
        • 四、Debian/Ubuntu 系统

          • 1 快速上手

            • 系统启动与基础操作
            • 网络配置与SSH
            • 软件包管理与更新
            • Debian 与 Ubuntu 镜像差异
          • 2 系统开发

            • Linux SDK编译
            • 内核配置与裁剪
            • 设备树详解与实践
            • 根文件系统构建
          • 3 外设驱动开发

            • GPIO驱动与控制
            • I2C驱动开发
            • SPI驱动开发
            • UART串口驱动
            • PWM驱动
            • ADC驱动
            • CAN/CAN-FD驱动
            • USB主机与设备驱动
            • SDMMC/eMMC驱动
            • 以太网GMAC驱动
            • 音频驱动
            • 摄像头Camera驱动
            • 触摸屏驱动
            • 显示DRM驱动
            • HDMI显示驱动
            • PCIe驱动
            • WiFi/蓝牙驱动
            • 看门狗WDT驱动
            • 热敏传感器THERMAL驱动
          • 4 应用开发

            • C/C++应用开发入门
            • Python应用开发
            • Qt嵌入式开发实战
            • GStreamer多媒体开发
            • 网络编程与应用
            • 文件系统与存储
          • 5 AI 与 NPU

            • NPU架构与性能概览
            • RKNN Toolkit2安装与使用
            • 模型转换与量化
            • 分类模型部署(LeNet/ResNet)
            • 目标检测部署(YOLOv5/v7/v8)
            • 语义分割部署
            • 人脸检测与识别
            • OCR文字识别
            • NPU性能调优与调试
          • 6 多媒体

            • 显示框架
            • 摄像头应用
            • 视频编解码(MPP)
            • 音频开发与ALSA
            • RGA图像处理
          • 7 性能优化与调试

            • 系统启动时间优化
            • CPU/GPU/NPU调优
            • 内存与存储优化
            • 功耗分析与优化
            • 调试工具使用指南
          • 8 高级主题

            • 安全启动与加密
            • OTA远程升级
            • Docker容器部署
            • ROS/ROS2机器人开发
            • RTOS与Linux协同
        • 五、Android 系统

          • 1 快速上手

            • Android系统烧录与启动
            • Android系统基础操作
            • ADB调试与日志分析
          • 2 SDK 编译

            • Android SDK编译
            • Android构建系统与增量编译
          • 3 系统开发

            • Android系统定制
          • 4 应用开发

            • Android应用开发入门
            • Android JNI开发
          • 5 驱动与 HAL

            • Android HAL开发
          • 6 AI 与 NPU

            • Android NPU使用指南
        • 六、硬件参考

          • 核心板参数
          • 底板接口定义
          • 40PIN引脚定义
          • 电源管理
        • 七、资料下载

          • 资料下载
      • Pico-G1

        • 一、产品概述

          • 01 产品介绍
          • 02 SDK版本基本信息
        • 二、快速入门

          • 01 开发环境搭建
          • 02 镜像编译
          • 03 镜像烧录
          • 04 系统登录
          • 05 网络配置
          • 06 文件传输
          • 07 SDK目录结构
          • 08 部署第一个应用程序
          • 09 部署第一个驱动程序
          • 10 SD卡挂载
        • 三、外设与接口

          • 01 GPIO控制
          • 02 UART串口通信
          • 03 I2C 通信
          • 04 SPI 通信
        • 四、MPP媒体开发

          • 01 MPP媒体处理软件
          • 02 图像处理链路
          • 03 视频输入
          • 04 图像编码
        • 五、NPU与AI

          • 01 NPU驱动与运行库架构
          • 02 .xmm 模型加载
          • 03 SVP视频处理
          • 04 AI降噪 (AI_NR)
        • 六、应用程序示例

          • 01 加解密接口应用
          • 02 ADC 采集应用
          • 03 低功耗应用
          • 04 音频处理应用
          • 05 视频编码应用
          • 06 视频输入应用
          • 07 视频图形子系统应用
          • 08 区域叠加应用
          • 09 智能视频引擎应用
          • 10 UVC 网络摄像头应用
          • 11 综合快速演示应用
          • 12 FPN 校正应用
          • 13 区域运动检测应用
          • 14 MTCNN 人脸检测应用
        • 七、扩展板外设功能例程

          • 00 - Pico 扩展板外设功能例程总览
          • 01 - OLED 显示应用
          • 02 - TFT 显示应用
          • 03 - MPU6050 陀螺仪应用
          • 04 - ADC 采集应用
          • 05 - 无源蜂鸣器应用
          • 06 - MQ 气体传感器应用
          • 07 - GPS 定位应用
          • 08 - SHT20 温湿度应用
          • 09 - 超声波测距应用
          • 10 - SPO2 血氧传感器应用
          • 11 - 电机控制应用
          • 12 - 舵机控制应用
    • 开源鸿蒙

      • SC-3568HA

        • 一、简介

          • SC-3568HA简介
        • 二、快速上手

          • OpenHarmony概述
          • 镜像烧录
          • 开发环境准备
          • Hello World应用以及部署
        • 三、应用开发

          • ArkUI

            • 第一章 ArkTS语言简介
            • 第二章 UI组件介绍和实际应用(上)
            • 第三章 UI组件介绍和实际应用(中)
            • 第四章 UI组件介绍和实际应用(下)
          • 拓展

            • 第一章 入门指引
            • 第二章 三方库的引用和使用
            • 第三章 应用编译以及部署
            • 第四章 命令行恢复出厂设置
            • 第五章 系统调试--HDC调试
            • 第六章 APP 稳定性测试
            • 第七章 应用测试
        • 四、设备开发

          • 第一章 环境搭建
          • 第二章 下载源码
          • 第三章 编译源码
        • 五、内核的外设与接口

          • 树莓派接口
          • GPIO 接口
          • I2C 接口
          • SPI通信
          • PWM控制
          • 串口通讯
          • TF Card
          • 屏幕
          • 触摸
          • 音频
          • RTC
          • Ethernet
          • M.2
          • MINI-PCIE
          • Camera
          • WIFI&BT
          • 树莓派拓展板
        • 六、资料下载

          • 资料下载
      • M-K1HSE

        • 一、简介

          • M-K1HSE 简介
        • 二、快速开始

          • 开发环境搭建
          • 源码获取
          • 编译说明
          • 烧录指南
        • 三、应用开发

          • 00 应用开发环境搭建
          • 01 第一个应用-Hello World
        • 四、外设与接口

          • 01 Audio
          • 02 RS485
          • 03 Display
        • 五、系统定制开发

          • 系统移植
          • 系统定制
          • 驱动开发
          • 系统调试
          • OTA升级
        • 六、资料下载

          • 资料下载
    • HVS视觉融合相机

      • 快速开始

        • 01 SDK 介绍
        • 02 资料下载
        • 03 设备固件烧录
        • 04 第一个 C++ 程序
        • 05 第一个 Python 程序
        • 06 Python 数据分析
        • 07 MultiVision Studio
        • 08 版本介绍
      • 二次开发

        • 01 编程指引

          • 01 打开相机
          • 02 读取事件
          • 03 录制与回放
          • 04 事件处理
          • 05 显示与可视化
          • 06 调参
          • 07 采集 APS 图像
          • 08 帧率切换
          • 09 时间戳同步
        • 02 Toolkit SDK

          • 01 Hybrid Vision Toolkit
          • 02 快速上手
          • 03 C++ API
          • 04 Python API
        • 03 Algo SDK

          • 01 Hybrid Vision Algo
          • 02 Algo API
          • 03 Windows Algo SDK
        • 04 USB数据协议
        • 05 示例程序总览
        • 06 应用场景
      • 基础知识

        • 01 事件相机基础原理
        • 02 HVS 融合视觉
        • 03 事件可视化
        • 04 数据格式参考
        • 05 术语表
        • 06 偏置与调参
        • 07 视频教程
      • HVS 相机

        • 01 HVS 相机快速上手
        • 02 网络化能力

          • 01 HVS 相机系统架构
          • 2 EVS Network Server
          • 3 EVS Time Sync
          • 4 Web Window
        • 03 HVS 相机兼容性矩阵
        • 04 常见问题解决指南
        • 05 产品

          • CF-NRS1(灵光一号视觉融合相机)
      • HVS 模块

        • 01 MIPI 模块快速上手
        • 02 载板适配

          • RDK X5 载板适配
          • RDK S100 载板适配
          • 树莓派载板适配
          • shimetapi 板载板适配
        • 03 MIPI 模块兼容性矩阵
        • 04 产品

          • EVS_003 传感器模块
    • AI硬件

      • 1684XB-32T

        • 一、简介

          • AIBOX-1684XB-32简介
        • 二、快速上手

          • 初次使用
          • 网络配置
          • 磁盘使用
          • 内存分配
          • 风扇策略
          • 固件升级
          • 交叉编译
          • 模型量化
        • 三、应用开发

          • 开发简介

            • Sophgo SDK开发
            • SOPHON-DEMO简介
          • 大语言模型

            • 部署Llama3示例
            • Sophon LLM_api_server开发
            • 部署MiniCPM-V-2_6
            • Qwen-2-5-VL图片视频识别DEMO
            • Qwen3-chat-DEMO
            • Qwen3-Qwen Agent-MCP开发
            • Qwen3-langchain-AI Agent
          • 深度学习

            • ResNet(图像分类)
            • LPRNet(车牌识别)
            • SAM(通用图像分割基础模型)
            • YOLOv5(目标检测)
            • OpenPose(人体关键点检测)
            • PP-OCR(光学字符识别)
        • 四、资料下载

          • 资料下载
      • 1684X-416T

        • 简介

          • AIBOX-1684X-416简介
        • Demo简单操作指引

          • shimeta智慧监控demo的简单使用说明
      • RDK-X5

        • 简介

          • RDK-X5 硬件简介
        • 快速开始

          • RDK-X5 快速开始
        • 应用开发

          • AI在线模型开发

            • 实验01-接入火山引擎豆包 AI
            • 实验02-图片分析
            • 实验03-多模态视觉分析定位
            • 实验04-多模态图文比较分析
            • 实验05-多模态文档表格分析
            • 实验06-摄像头运用-AI视觉分析
          • 大语言模型

            • 实验01-语音识别
            • 实验02-语音对话
            • 实验03-多模态图片分析-语音对话
            • 实验04-多模态图片比较-语音对话
            • 实验05-多模态文档分析-语音对话
            • 实验06-多模态视觉运用-语音对话
          • ROS2基础开发

            • 实验01-搭建环境
            • 实验02-工作包的创建及编译
            • 实验03-运行 ROS2 话题通信节点
            • 实验04-ROS2 相机应用
          • 40pin-IO开发

            • 实验01-GPIO 输出(LED闪烁)
            • 实验02-GPIO 输入
            • 实验03-按键控制 LED
            • 实验04-PWM 输出
            • 实验05-串口输出
            • 实验06-IIC 实验
            • 实验07-SPI 实验
          • USB模块开发使用

            • 实验01-USB 语音模块使用
            • 实验02-声源定位模块使用
          • 机器视觉技术实战

            • 实验01-打开 USB 摄像头
            • 实验02-颜色识别检测
            • 实验03-手势识别体验
            • 实验04-YOLOv5物体检测
      • RDK-S100

        • 简介

          • RDK-S100 硬件简介
        • 快速开始

          • RDK-S100 硬件简介
        • 应用开发

          • AI在线模型开发

            • 实验01-接入火山引擎豆包 AI
            • 实验02-图片分析
            • 实验03-多模态视觉分析定位
            • 实验04-多模态图文比较分析
            • 实验05-多模态文档表格分析
            • 实验06-摄像头运用-AI视觉分析
          • 大语言模型

            • 实验01-语音识别
            • 实验02-语音对话
            • 实验03-多模态图片分析-语音对话
            • 实验04-多模态图片比较-语音对话
            • 实验05-多模态文档分析-语音对话
            • 实验06-多模态视觉运用-语音对话
          • ROS2基础开发

            • 实验01-搭建环境
            • 实验02-工作包的创建及编译
            • 实验03-运行 ROS2 话题通信节点
            • 实验04-ROS2 相机应用
          • 40pin-IO开发

            • 实验01-GPIO 输出(LED闪烁)
            • 实验02-GPIO 输入
            • 实验03-按键控制 LED
            • 实验04-PWM 输出
            • 实验05-串口输出
            • 实验06-IIC 实验
            • 实验07-SPI 实验
          • USB模块开发使用

            • 实验01-USB 语音模块使用
            • 实验02-声源定位模块使用
          • 机器视觉技术实战

            • 实验01-打开 USB 摄像头
            • 实验02-图像处理基础
            • 实验03-目标检测
            • 实验04-图像分割
      • AIBOX-M88(RK1828)

        • 一、简介

          • AIBOX-M88(RK1828) - 产品介绍
          • AIBOX-M88(RK1828) 硬件规格
          • AIBOX-M88(RK1828) 使用与安全
        • 二、快速开始

          • AIBOX-M88(RK1828) 镜像烧录
          • RK182X 硬件安装与验证
          • RK182X 开发环境快速搭建
          • RK182X SDK 总览
          • RK182X 环境搭建详解
          • RK182X 快速开始
          • 原厂 SDK 资料抽取记录
        • 三、开发指南

          • ClawChips 架构与原理
          • SKILL 使用手册
          • RK182X系列LLM推理(RK1828型号)
          • RK182X系列CNN推理(RK1828型号)
          • 模型转换
          • RK182X AI Agent 应用开发指南
          • RK182X 工业异常检测应用
        • 四、SDK 参考

          • RKNN3-SDK 概述

            • RKNN3 SDK 概述
          • RKNN3-Toolkit

            • RKNN3 Toolkit 安装与使用
          • RKLLM

            • RKLLM 设备端 LLM 推理
          • RK182X系列 NPU 概述与架构(RK1828型号)
          • RK182X INT8 量化推理部署
          • RK182X MPP 多媒体处理框架
          • MPP 详解

            • RK182X 视频解码
            • RK182X 视频编码
          • NPU 详解

            • RKNN 模型转换
            • RK182X NPU INT8 量化推理
            • RK182X 多模型并行推理
          • RGA 详解

            • RK182X RGA 2D 图形加速
          • VPU 详解

            • RK182X VPU 编解码
        • 五、硬件参考

          • RK182X系列 硬件架构概述(RK1828型号)
          • RK182X 引脚定义与复用配置
          • RK182X 引脚定义
          • RK182X 电源管理
          • RK182X 时钟与 PLL 配置
          • RK182X 时钟与频率配置
        • 六、实战教程

          • Hello World
          • Hello RK1828 - 第一个程序
          • RTSP 推流
          • RTSP 推流 + AI 分析
          • 视美泰AI龙虾一键部署
          • PaddleOCR-VL 文字识别
          • Qwen3-1.7B LLM 文本对话
          • AI 巡检多视角(Qwen3-VL 包装)
          • YOLOv5 目标检测
        • 七、资料下载

          • 资料下载
        • 八、常见问题

          • 常见问题
    • 核心板

      • C-3568BQ

        • 简介

          • C-3568BQ 简介
      • C-3588LQ

        • 简介

          • C-3588LQ 简介
      • GC-3568JBAF

        • 简介

          • GC-3568JBAF 简介
      • C-K1BA

        • 简介

          • C-K1BA 简介
    • 软件平台

      • ShiMetaPi Workbench

        • 一、产品简介

          • 产品概述
          • 核心架构
          • 功能入口
          • 支持的硬件
          • 版本说明
        • 二、快速入门

          • 安装与登录
          • 连接设备
          • 配置环境
          • 接入 AIHub
          • 首次调用模型
        • 三、用户指南

          • 工作区总览
          • 设备管理
          • 模型广场
          • 一键部署
          • 视觉 - SVP
          • 视觉 - 自定义模型
          • shimeta-py IDE
          • 终端
          • Agent 调试助手
          • 设置与资源
        • 四、常见问题

          • 安装与登录
          • 设备连接
          • 模型与部署
          • 视觉与运行时
          • 设置与其他
      • ShimetaPi软件源

        • 简介

          • ShimetaPi 软件源
        • Pico G1 (GK7206)

          • 快速开始

            • 安装与首次推理
            • shimeta_infer — 图片推理
            • shimeta_camera — 摄像头实时推理
            • SVP 场景检测
            • 文件传输与内置模型参考
            • 常见问题
          • HTTP API & Python SDK

            • HTTP API 参考
      • 模型微调训练平台

        • 一、平台介绍

          • 模型训练平台
        • 二、快速上手

          • 注册与登录
          • 创建第一个模型(30 分钟快速体验)
        • 三、训练指南

          • 数据准备与标注
          • 训练参数配置
          • 启动与监控训练
          • 模型评估与测试
        • 四、模型部署

          • 导出模型
          • 部署到边缘设备

Qt 嵌入式开发实战

1. 概述

1.1 功能简介

Qt 是跨平台的 C++ GUI 开发框架,广泛用于嵌入式设备的图形界面开发。在 RK3588 平台上,Qt 5.15.x 支持 OpenGL ES 3.2 GPU 加速渲染、QML 声明式 UI(一种类似标记语言的界面描述方式,界面写起来像搭积木)、串口/I2C/SPI 通信、虚拟键盘输入等完整功能。RK3588 搭载 Mali-G610 MP4 GPU,为 Qt Quick/QML 提供硬件加速的 2D/3D 渲染能力,适合工业控制面板、车载仪表、智能家居中控这类嵌入式场景。

1.2 应用场景

  • 工业 HMI:触摸屏人机交互界面、设备控制面板
  • 车载显示:仪表盘、中控娱乐系统
  • 智能家居:中控屏、门锁面板
  • 医疗设备:监护仪界面、超声显示终端
  • 物联网终端:数据可视化看板、信息展示屏

1.3 工作原理

下面这张图是 RK3588 上 Qt 嵌入式开发的整体架构,从上到下依次是 Qt 应用层、平台插件层、图形接口层、操作系统层和硬件层:

Qt 应用层
  ├── Qt Widgets — 传统 C++ 控件
  ├── Qt Quick / QML — 声明式 UI(GPU 加速)
  ├── QSerialPort — 串口通信
  ├── Qt Virtual Keyboard — 虚拟键盘
  └── QSystemTrayIcon — 系统托盘

Qt 平台插件层
  ├── QLinuxFbIntegration — LinuxFramebuffer
  ├── QEglFSIntegration — EGLFS(推荐,GPU 加速)
  ├── QEvdevKeyboardDriver — 键盘输入
  └── QEvdevMouseDriver — 触摸/鼠标输入

EGL/OpenGL ES 3.2
  ├── Mali-G610 MP4 驱动
  ├── EGL 上下文管理
  └── OpenGL ES 3.2 渲染管线

操作系统层(Linux 6.1)
  ├── DRM/KMS 显示框架
  ├── /dev/dri/card0 — GPU 设备节点
  ├── /dev/tty* — 串口设备
  └── /dev/i2c-* /dev/spidev* — 总线设备

硬件层(RK3588)
  ├── Mali-G610 MP4 GPU(4 核)
  ├── VOP2 显示控制器
  ├── HDMI 输出
  └── UART / I2C / SPI 控制器

1.4 运行环境

本节数据来自实机(linaro-alip / 6.1.141 / Debian 12)。

工具 / 库情况
gcc / g++12.2.0(系统装好)
cmake3.25.1
Mesa / EGL / GL22.3.6(含 libEGL / libGL / GLES)
Qt 5 运行时库5.15.8 已预装 libqt5gui5 / libqt5widgets5 / libqt5network5 / libqt5dbus5 / libqt5opengl5
Qt 6 运行时库未预装(需 apt install qt6-base-dev)
qmake / qml CLI未预装(需 apt install qt5-qmake qtdeclarative5-dev 或交叉编译)
DRM / GPU 节点/dev/dri/card0 card1 renderD128 renderD129(Mali-G610)
HDMI 输出card0-HDMI-A-1 connected
Framebuffer无 /dev/fb*(仅 DRM 模式)

实际 Qt 应用应使用 EGLFS 平台插件(Qt 在嵌入式 Linux 上的一种渲染后端,基于 DRM / GBM 直接绘图,走 GPU 加速),比 LinuxFB 性能更好。

下面是实际跑过的验证命令:

# 实际跑过的验证
$ apt list --installed 2>/dev/null | grep -iE "^libqt5" | awk '{print $1, $2}' | head
libqt5core5a 5.15.8+dfsg-11+deb12u3
libqt5dbus5  5.15.8+dfsg-11+deb12u3
libqt5gui5   5.15.8+dfsg-11+deb12u3
libqt5network5 5.15.8+dfsg-11+deb12u3
libqt5opengl5 5.15.8+dfsg-11+deb12u3
libqt5test5  5.15.8+dfsg-11+deb12u3
libqt5widgets5 5.15.8+dfsg-11+deb12u3

$ which qmake qmake6
# 都找不到(apt 包 qt5-qmake 没装)

$ ls /dev/dri/
by-path  card0  card1  renderD128  renderD129

$ for f in /sys/class/drm/card0-*/status; do echo -n "$f: "; cat $f; done
/sys/class/drm/card0-HDMI-A-1/status: connected
/sys/class/drm/card0-Writeback-1/status: unknown

由此可以得出三条实用结论:

  1. 想在板上直接编译 Qt 应用:sudo apt install qt5-qmake qtbase5-dev qtdeclarative5-dev qt5-default。
  2. 想跑现成 Qt 二进制:库已够,缺 qmake 不会影响运行。
  3. EGLFS 走 DRM,无 framebuffer 也能工作。

在板上装 qmake + 编 QWidgets 例子

qmake 是 Qt 自带的构建工具,读一个 .pro 工程文件就能生成 Makefile(作用类似 CMake,但语法更简单)。下面是在板上从装工具到跑通程序的完整过程:

# 1) 装 qmake + qtbase5-dev
$ sudo apt-get install -y qt5-qmake qtbase5-dev qtchooser
... (略过 apt 过程日志)
正在设置 qt5-qmake:arm64 (5.15.8+dfsg-11+deb12u3) ...
正在设置 qtbase5-dev-tools (5.15.8+dfsg-11+deb12u3) ...
正在设置 libqt5concurrent5:arm64 (5.15.8+dfsg-11+deb12u3) ...

$ qmake -v
QMake version 3.1
Using Qt version 5.15.8 in /usr/lib/aarch64-linux-gnu

# 2) 写一个最小 QWidgets 程序 qt_demo.pro + main.cpp(4 个文件)
$ cat qt_demo.pro
QT += core gui widgets
CONFIG += c++17 console
SOURCES += main.cpp
TARGET = qt_demo

# 3) qmake 生成 Makefile
$ qmake qt_demo.pro
Info: creating stash file /var/tmp/qt_demo/.qmake.stash
$ ls
Makefile  main.cpp  qt_demo.pro  .qmake.stash

# 4) make
$ make -j4
g++ -c -pipe -O2 -std=gnu++1z -Wall -Wextra -D_REENTRANT -fPIC \
    -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB \
    -I. -I/usr/include/aarch64-linux-gnu/qt5 ... -o main.o main.cpp
g++ -Wl,-O1 -o qt_demo main.o \
    /usr/lib/aarch64-linux-gnu/libQt5Widgets.so \
    /usr/lib/aarch64-linux-gnu/libQt5Gui.so \
    /usr/lib/aarch64-linux-gnu/libQt5Core.so -lGL -lpthread
$ file qt_demo
qt_demo: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV),
  dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, ...
$ ls -la qt_demo
-rwxr-xr-x 1 linaro linaro 72984 10月15日 09:15 qt_demo

# 5) 跑(headless 环境用 offscreen 平台插件)
$ QT_QPA_PLATFORM=offscreen KERNEL=$(uname -r) timeout 5 ./qt_demo
$ echo $?
0

整个过程验证下来的结论:

  1. 板上 apt install qt5-qmake qtbase5-dev(约 50 MB)即可获得完整 Qt5 编译能力,不需要 PC 交叉编译。
  2. QWidgets + offscreen 平台插件(离屏渲染,不接 HDMI 显示器也能跑)启动 0 ms 级、退出码 0。
  3. 链接了 -lGL -lpthread,库 libQt5Widgets.so / Qt5Gui.so / Qt5Core.so 全部走系统 /lib/aarch64-linux-gnu/,无未解析符号。
  4. 想看 GUI 画面:去掉 QT_QPA_PLATFORM=offscreen,改用 eglfs 即可走 DRM/HDMI 输出。

2. 硬件准备

2.1 硬件清单

组件型号/规格数量备注
主控板C-3588LQ V1.0 核心板 + SHIMETAPI-M5-R2A V1.0 底板1
HDMI 显示器1080p 或以上1用于 Qt GUI 输出
调试串口线USB 转 TTL(3.3V)1波特率 1500000
网线标准 RJ451SSH 远程部署
USB 键盘(可选)标准 USB 键盘1Qt 输入测试
USB 鼠标(可选)标准 USB 鼠标1Qt 指针输入
电源适配器12V/2A DC1

2.2 硬件连接

  1. 显示连接:用 HDMI 线把底板的 HDMI 接口连到显示器
  2. 调试串口:USB 转 TTL 模块接底板 UART 调试口(TX/RX/GND)
  3. 网络:网线把底板 RJ45 接到局域网交换机
  4. 电源:接入 12V DC 电源

2.3 硬件确认

通过调试串口或 SSH 登录后,确认显示和 GPU 就绪。四项检查都通过即可继续:/dev/dri 下有 card 和 render 节点,dmesg 里能看到 Mali-G610 驱动,HDMI 状态是 connected,串口设备存在:

# 通过调试串口或 SSH 登录后,确认 GPU 设备节点
ls -la /dev/dri/
# 预期输出:
# card0
# renderD128

# 确认 Mali GPU 驱动加载
dmesg | grep -i mali
# 预期输出包含:Mali-G610

# 确认显示控制器
cat /sys/class/drm/card*/status
# HDMI 接口应显示 "connected"

# 确认串口设备
ls /dev/ttyS*

3. 软件准备

3.1 内核配置

RK3588 Linux 6.1 内核需要启用以下配置才能支持 Qt EGLFS 渲染。make menuconfig 会打开一个文本界面的配置菜单,在里面搜索并勾选对应选项即可:

# 进入内核源码目录
cd $SDK_DIR/kernel

# 打开内核配置
make menuconfig

关键内核配置项:

# DRM 显示框架
CONFIG_DRM=y
CONFIG_DRM_ROCKCHIP=y
CONFIG_ROCKCHIP_VOP2=y

# Mali-G610 GPU 驱动
CONFIG_MALI_MIDGARD=y
CONFIG_MALI_PLATFORM_NAME="rk"
CONFIG_MALI_GROWABLE_PAGE_TABLE=y
CONFIG_MALI_BIFROST=y

# EGLFS 所需的 framebuffer 支持
CONFIG_FB=y
CONFIG_FB_VIRTUAL=y

# 输入设备(Evdev)
CONFIG_INPUT_EVDEV=y

# I2C 支持
CONFIG_I2C_CHARDEV=y

# SPI 支持
CONFIG_SPI=y
CONFIG_SPI_SPIDEV=y

# 串口
CONFIG_SERIAL_8250=y
# 编译并安装内核
make -j$(nproc)
# 将 Image 和 .dtb 拷贝到目标板对应位置

3.2 设备树配置

设备树(DTS)是向内核描述板子硬件配置的文件,编译成 .dtb 后随内核加载。确认设备树中启用以下节点(status 为 "okay"):

/* GPU 节点 */
gpu: gpu@fde00000 {
    compatible = "arm,mali-bifrost", "arm,mali-timer";
    reg = <0x0 0xfde00000 0x0 0x40000>;
    interrupts = <GIC_SPI 90 IRQ_TYPE_LEVEL_HIGH>,
                 <GIC_SPI 91 IRQ_TYPE_LEVEL_HIGH>,
                 <GIC_SPI 92 IRQ_TYPE_LEVEL_HIGH>;
    interrupt-names = "job", "mmu", "gpu";
    clocks = <&cru ACLK_GPU>, <&cru PCLK_GPU>;
    clock-names = "core", "bus";
    operating-points-v2 = <&gpu_opp_table>;
    power-domains = <&power RK3588_PD_GPU>;
    status = "okay";
};

/* VOP2 显示控制器 */
vop2: vop2@fe040000 {
    compatible = "rockchip,rk3588-vop2";
    reg = <0x0 0xfe040000 0x0 0x2000>,
          <0x0 0xfe044000 0x0 0x1000>,
          <0x0 0xfe045000 0x0 0x1000>,
          <0x0 0xfe046000 0x0 0x1000>,
          <0x0 0xfe048000 0x0 0x1000>,
          <0x0 0xfe04a000 0x0 0x1000>;
    interrupts = <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&cru ACLK_VOP2>, <&cru HCLK_VOP2>,
             <&cru DCLK_VP0>, <&cru DCLK_VP1>,
             <&cru DCLK_VP2>, <&cru DCLK_VP3>;
    power-domains = <&power RK3588_PD_VO0>;
    status = "okay";
};

/* HDMI 接口 */
hdmi: hdmi@fe0a0000 {
    compatible = "rockchip,rk3588-dw-hdmi-qp";
    reg = <0x0 0xfe0a0000 0x0 0x20000>;
    interrupts = <GIC_SPI 169 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&cru PCLK_HDMI>,
             <&cru HCLK_VO1>,
             <&cru DCLK_VOP1>;
    status = "okay";
};

4. 操作步骤

4.1 交叉编译 Qt 5.15.x

4.1.1 准备交叉编译环境

先把工具链路径加进 PATH,并验证交叉编译器能跑:

# 设置工具链路径
export SDK_DIR=/path/to/rk3588sdk
export TOOLCHAIN=$SDK_DIR/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu
export PATH=$TOOLCHAIN/bin:$PATH

# 验证编译器
aarch64-linux-gnu-g++ --version
# 输出:aarch64-linux-gnu-g++ (GCC) 10.3.1 20210621

# 创建 Qt 构建目录
mkdir -p $HOME/qt5-build && cd $HOME/qt5-build

4.1.2 配置 Qt 源码

编译 Qt 前要给目标设备写一份 mkspecs 配置:mkspecs 目录是 qmake 存放平台描述的地方,告诉 qmake 用什么编译器、头文件和库。下面的 qmake.conf 描述的是 RK3588 + Mali-G610 走 EGLFS 的配置:

# 解压 Qt 源码(以 5.15.13 为例)
tar xf qt-everywhere-src-5.15.13.tar.xz
cd qt-everywhere-src-5.15.13

# 创建 mkspecs 交叉编译配置
mkdir -p qtbase/mkspecs/devices/linux-rk3588-g++

cat > qtbase/mkspecs/devices/linux-rk3588-g++/qmake.conf << 'EOF'
#
# qmake configuration for RK3588 with Mali-G610 MP4 (EGLFS)
#

MAKEFILE_GENERATOR      = UNIX
CONFIG                 += incremental
QMAKE_INCREMENTAL_STYLE = subdirs

QT_HOST_PATH            = /usr
QT_TARGET_ARCH          = aarch64

include(../common/linux_device_pre.conf)

# 交叉编译工具链
CROSS_COMPILE           = aarch64-linux-gnu-
LIBC                    = g++

QMAKE_CC                = $${CROSS_COMPILE}gcc
QMAKE_CXX               = $${CROSS_COMPILE}g++
QMAKE_LINK              = $${CROSS_COMPILE}g++
QMAKE_LINK_SHLIB        = $${CROSS_COMPILE}g++

# sysroot 路径(如有)
# QMAKE_SYSROOT          = /path/to/sysroot

# Mali-G610 MP4 EGLFS 配置
QMAKE_INCDIR_EGL        = /usr/include
QMAKE_LIBDIR_EGL        = /usr/lib
QMAKE_LIBS_EGL          = -lEGL
QMAKE_LIBS_OPENGL_ES2   = -lGLESv2 -lEGL -lmali

# DRM/KMS 支持(EGLFS 依赖)
QMAKE_LIBS_DRM          = -ldrm

# 输入设备
QMAKE_LIBS_EVDEV        = -levdev

# 硬件优化
QMAKE_CFLAGS            += -march=armv8-a -mtune=cortex-a76.cortex-a55
QMAKE_CXXFLAGS          += -march=armv8-a -mtune=cortex-a76.cortex-a55

include(../common/linux_device_post.conf)
EOF

4.1.3 编译 Qt

configure 脚本用来生成构建配置:-xplatform 选刚写的 mkspec,-sysroot 指向目标板的 sysroot(目标系统头文件和库所在的目录),-eglfs -kms -gbm -opengl es2 打开 GPU 渲染这条路:

./configure \
    -prefix /opt/qt5-rk3588 \
    -opensource -confirm-license \
    -xplatform devices/linux-rk3588-g++ \
    -device-option CROSS_COMPILE=aarch64-linux-gnu- \
    -sysroot $TOOLCHAIN/aarch64-none-linux-gnu/ \
    -no-rpath \
    -skip qtwebengine \
    -nomake examples \
    -nomake tests \
    -opensource -confirm-license \
    -qt-zlib \
    -qt-libjpeg \
    -qt-libpng \
    -qt-freetype \
    -qt-harfbuzz \
    -qt-pcre \
    -qt-xcb \
    -eglfs \
    -kms \
    -gbm \
    -linuxfb \
    -no-opengl \
    -opengl es2 \
    -egl -gbm

# 使用 EGLFS + KMS/GBM 后端
# Mali-G610 MP4 通过 GBM 分配缓冲区,DRM/KMS 进行显示
# 编译(使用全部 8 核)
make -j$(nproc)

# 安装到本地目录
make install
# 默认安装到 /opt/qt5-rk3588

4.2 Qt Creator 远程部署

Qt Creator 是 Qt 官方 IDE,支持交叉编译和远程部署。

4.2.1 配置交叉编译 Kit

Kit 是 Qt Creator 里"编译器 + Qt 版本 + 调试器 + 设备"一套组合的叫法。在 Qt Creator 中配置 RK3588 交叉编译 Kit:

  1. 打开 Tools > Options > Kits
  2. 点击 Add 新增 Kit
  3. 配置如下:
    • Name: RK3588
    • Compiler: aarch64-linux-gnu-g++
    • Qt Version: 选择 4.1.1 中安装的 Qt 5.15.x
    • Debugger: aarch64-linux-gnu-gdb
    • Device Type: Generic Linux Device

4.2.2 配置远程设备

  1. 打开 Tools > Options > Devices
  2. 点击 Add > Generic Linux Device
  3. 填写:
    • Name: RK3588 Board
    • Host name: 目标板 IP(如 192.168.49.100)
    • Port: 22
    • User name: root
  4. 选择 Authentication type: Password 或 SSH Key
  5. 点击 Test 验证连接,能连通就说明配置正确

4.2.3 部署与运行

Kit 和设备配好后,在 Qt Creator 里点 Run(绿色三角按钮),它会自动通过 SSH 把可执行文件部署到板子并运行;也可以手动部署:

# 在 Qt Creator 中创建项目后:
# 1. 选择 RK3588 Kit
# 2. 选择远程设备
# 3. 点击 Run(绿色三角按钮)
# Qt Creator 自动通过 SSH 部署可执行文件并运行

# 手动部署方式
scp myapp root@192.168.49.100:/root/
ssh root@192.168.49.100 "export LD_LIBRARY_PATH=/opt/qt5-rk3588/lib:\$LD_LIBRARY_PATH; /root/myapp"

4.3 EGLFS 后端配置

RK3588 Mali-G610 MP4 使用 EGLFS 后端实现 GPU 加速渲染。

4.3.1 环境变量配置

# 在目标板上设置 EGLFS 环境变量
export QT_QPA_PLATFORM=eglfs
export QT_QPA_EGLFS_INTEGRATION=eglfs_kms
export QT_QPA_EGLFS_KMS_CONFIG=/etc/eglfs_kms.json

# 可选:指定 DRM 设备
export QT_QPA_EGLFS_KMS_DEVICE=/dev/dri/card0

# 可选:设置屏幕分辨率
export QT_QPA_EGLFS_KMS_CONFIG_WIDTH=1920
export QT_QPA_EGLFS_KMS_CONFIG_HEIGHT=1080

# 运行 Qt 应用
./myapp

4.3.2 EGLFS KMS 配置文件

// /etc/eglfs_kms.json
{
  "device": "/dev/dri/card0",
  "outputs": [
    {
      "name": "HDMI-A-1",
      "mode": "1920x1080",
      "refresh": 60
    }
  ],
  "hwcursor": true,
  "pbuffers": true
}

4.3.3 验证 EGLFS 渲染

# 测试 OpenGL ES 3.2 渲染
export QT_QPA_PLATFORM=eglfs
export QT_QPA_EGLFS_INTEGRATION=eglfs_kms
export QT_QPA_EGLFS_KMS_CONFIG=/etc/eglfs_kms.json

# 运行 Qt 自带的几何图形示例
/usr/local/qt5/examples/opengl/cube/cube

# 验证 GPU 信息
cat /sys/class/misc/mali0/device/gpuinfo

4.4 串口通信

Qt 使用 QSerialPort 模块进行串口通信。

4.4.1 启用 QSerialPort

# myapp.pro
QT += serialport

4.4.2 串口通信代码

下面两个文件把 QSerialPort 封装成一个 C++ 和 QML 都能用的类,负责打开/关闭串口、读写数据和错误上报:

// serial_comm.h
#ifndef SERIAL_COMM_H
#define SERIAL_COMM_H

#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QByteArray>

class SerialComm : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString portName READ portName WRITE setPortName NOTIFY portNameChanged)
    Q_PROPERTY(int baudRate READ baudRate WRITE setBaudRate NOTIFY baudRateChanged)
    Q_PROPERTY(bool isOpen READ isOpen NOTIFY isOpenChanged)

public:
    explicit SerialComm(QObject *parent = nullptr);
    ~SerialComm();

    QString portName() const { return m_portName; }
    void setPortName(const QString &name);

    int baudRate() const { return m_baudRate; }
    void setBaudRate(int rate);

    bool isOpen() const { return m_serial.isOpen(); }

    // 操作接口
    Q_INVOKABLE bool openPort();
    Q_INVOKABLE void closePort();
    Q_INVOKABLE bool writeData(const QByteArray &data);
    Q_INVOKABLE QStringList availablePorts();

signals:
    void portNameChanged();
    void baudRateChanged();
    void isOpenChanged();
    void dataReceived(const QByteArray &data);
    void errorOccurred(const QString &errorString);

private slots:
    void onReadyRead();
    void onSerialError(QSerialPort::SerialPortError error);

private:
    QSerialPort m_serial;
    QString m_portName;
    int m_baudRate = QSerialPort::Baud115200;
};

#endif // SERIAL_COMM_H
// serial_comm.cpp
#include "serial_comm.h"

SerialComm::SerialComm(QObject *parent)
    : QObject(parent)
{
    connect(&m_serial, &QSerialPort::readyRead, this, &SerialComm::onReadyRead);
    connect(&m_serial, &QSerialPort::errorOccurred, this, &SerialComm::onSerialError);
}

SerialComm::~SerialComm()
{
    closePort();
}

void SerialComm::setPortName(const QString &name)
{
    if (m_portName != name) {
        m_portName = name;
        m_serial.setPortName(name);
        emit portNameChanged();
    }
}

void SerialComm::setBaudRate(int rate)
{
    if (m_baudRate != rate) {
        m_baudRate = rate;
        m_serial.setBaudRate(static_cast<QSerialPort::BaudRate>(rate));
        emit baudRateChanged();
    }
}

bool SerialComm::openPort()
{
    if (m_serial.isOpen()) {
        m_serial.close();
    }

    m_serial.setPortName(m_portName);
    m_serial.setBaudRate(static_cast<QSerialPort::BaudRate>(m_baudRate));
    m_serial.setDataBits(QSerialPort::Data8);
    m_serial.setParity(QSerialPort::NoParity);
    m_serial.setStopBits(QSerialPort::OneStop);
    m_serial.setFlowControl(QSerialPort::NoFlowControl);

    if (m_serial.open(QIODevice::ReadWrite)) {
        emit isOpenChanged();
        return true;
    }

    return false;
}

void SerialComm::closePort()
{
    if (m_serial.isOpen()) {
        m_serial.close();
        emit isOpenChanged();
    }
}

bool SerialComm::writeData(const QByteArray &data)
{
    if (!m_serial.isOpen())
        return false;
    return m_serial.write(data) == data.size();
}

QStringList SerialComm::availablePorts()
{
    QStringList ports;
    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
        ports << info.portName();
    }
    return ports;
}

void SerialComm::onReadyRead()
{
    QByteArray data = m_serial.readAll();
    emit dataReceived(data);
}

void SerialComm::onSerialError(QSerialPort::SerialPortError error)
{
    if (error == QSerialPort::NoError)
        return;
    emit errorOccurred(m_serial.errorString());
}

4.4.3 QML 中使用串口

在 QML 里直接实例化上面注册的 SerialComm 类型,选好串口和波特率就能收发:

// SerialPortPage.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import SerialComm 1.0

Page {
    title: "串口通信"

    SerialComm {
        id: serial
        portName: "/dev/ttyS2"
        baudRate: 115200
        onDataReceived: {
            receivedText.append(data)
        }
        onErrorOccurred: {
            statusLabel.text = "错误: " + errorString
        }
    }

    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 20

        RowLayout {
            Label { text: "串口:" }
            ComboBox {
                id: portCombo
                model: serial.availablePorts()
            }
            Label { text: "波特率:" }
            ComboBox {
                id: baudCombo
                model: [9600, 19200, 38400, 57600, 115200, 230400, 460800]
                currentIndex: 4
            }
            Button {
                text: serial.isOpen ? "断开" : "连接"
                onClicked: {
                    if (serial.isOpen) {
                        serial.closePort()
                    } else {
                        serial.portName = portCombo.currentText
                        serial.baudRate = parseInt(baudCombo.currentText)
                        serial.openPort()
                    }
                }
            }
        }

        TextArea {
            id: sendArea
            Layout.fillWidth: true
            Layout.preferredHeight: 100
            placeholderText: "输入要发送的数据..."
        }

        Button {
            text: "发送"
            enabled: serial.isOpen
            onClicked: serial.writeData(sendArea.text)
        }

        ScrollView {
            Layout.fillWidth: true
            Layout.fillHeight: true
            TextArea {
                id: receivedText
                readOnly: true
                placeholderText: "接收到的数据..."
            }
        }

        Label {
            id: statusLabel
            Layout.fillWidth: true
            text: serial.isOpen ? "已连接: " + portCombo.currentText : "未连接"
        }
    }
}

4.5 I2C 通信

Qt 没有现成的 I2C 模块,要走 Linux 文件 I/O:打开 /dev/i2c-N,用 I2C_SLAVE 这个 ioctl 选定从机地址,然后 read/write:

// i2c_comm.h
#ifndef I2C_COMM_H
#define I2C_COMM_H

#include <QObject>
#include <QString>
#include <QByteArray>
#include <cstdint>

class I2CComm : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int bus READ bus WRITE setBus NOTIFY busChanged)
    Q_PROPERTY(int address READ address WRITE setAddress NOTIFY addressChanged)

public:
    explicit I2CComm(QObject *parent = nullptr);
    ~I2CComm();

    int bus() const { return m_bus; }
    void setBus(int bus);

    int address() const { return m_address; }
    void setAddress(int addr);

    Q_INVOKABLE bool openBus();
    Q_INVOKABLE void closeBus();
    Q_INVOKABLE quint8 readByte(quint8 reg);
    Q_INVOKABLE QByteArray readBytes(quint8 reg, int length);
    Q_INVOKABLE bool writeByte(quint8 reg, quint8 value);
    Q_INVOKABLE bool writeBytes(quint8 reg, const QByteArray &data);

signals:
    void busChanged();
    void addressChanged();

private:
    int m_fd = -1;
    int m_bus = 1;
    int m_address = 0x50;
};

#endif // I2C_COMM_H
// i2c_comm.cpp
#include "i2c_comm.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>

I2CComm::I2CComm(QObject *parent) : QObject(parent) {}

I2CComm::~I2CComm() { closeBus(); }

void I2CComm::setBus(int bus)
{
    if (m_bus != bus) { m_bus = bus; emit busChanged(); }
}

void I2CComm::setAddress(int addr)
{
    if (m_address != addr) { m_address = addr; emit addressChanged(); }
}

bool I2CComm::openBus()
{
    QString devPath = QString("/dev/i2c-%1").arg(m_bus);
    m_fd = ::open(devPath.toStdString().c_str(), O_RDWR);
    if (m_fd < 0)
        return false;

    if (ioctl(m_fd, I2C_SLAVE, m_address) < 0) {
        close(m_fd);
        m_fd = -1;
        return false;
    }
    return true;
}

void I2CComm::closeBus()
{
    if (m_fd >= 0) { close(m_fd); m_fd = -1; }
}

quint8 I2CComm::readByte(quint8 reg)
{
    if (m_fd < 0) return 0;
    quint8 buf[1] = { reg };
    if (write(m_fd, buf, 1) != 1) return 0;
    if (read(m_fd, buf, 1) != 1) return 0;
    return buf[0];
}

QByteArray I2CComm::readBytes(quint8 reg, int length)
{
    QByteArray result;
    if (m_fd < 0) return result;
    quint8 buf[1] = { reg };
    if (write(m_fd, buf, 1) != 1) return result;
    result.resize(length);
    if (read(m_fd, result.data(), length) != length) result.clear();
    return result;
}

bool I2CComm::writeByte(quint8 reg, quint8 value)
{
    if (m_fd < 0) return false;
    quint8 buf[2] = { reg, value };
    return write(m_fd, buf, 2) == 2;
}

bool I2CComm::writeBytes(quint8 reg, const QByteArray &data)
{
    if (m_fd < 0) return false;
    QByteArray buf = QByteArray(1, static_cast<char>(reg)) + data;
    return write(m_fd, buf.data(), buf.size()) == buf.size();
}

4.6 SPI 通信

SPI 同样没有 Qt 原生模块,通过 /dev/spidev 文件接口实现,用 SPI_IOC_MESSAGE 这个 ioctl 做全双工传输:

// spi_comm.h
#ifndef SPI_COMM_H
#define SPI_COMM_H

#include <QObject>
#include <QByteArray>

class SPIComm : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int bus READ bus WRITE setBus NOTIFY busChanged)
    Q_PROPERTY(int chipSelect READ chipSelect WRITE setChipSelect NOTIFY chipSelectChanged)

public:
    explicit SPIComm(QObject *parent = nullptr);
    ~SPIComm();

    int bus() const { return m_bus; }
    void setBus(int b);
    int chipSelect() const { return m_chipSelect; }
    void setChipSelect(int cs);

    Q_INVOKABLE bool openDevice(int mode = 0, int speed = 1000000, int bitsPerWord = 8);
    Q_INVOKABLE void closeDevice();
    Q_INVOKABLE QByteArray transfer(const QByteArray &txData);

signals:
    void busChanged();
    void chipSelectChanged();

private:
    int m_fd = -1;
    int m_bus = 0;
    int m_chipSelect = 0;
};

#endif // SPI_COMM_H
// spi_comm.cpp
#include "spi_comm.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>

SPIComm::SPIComm(QObject *parent) : QObject(parent) {}
SPIComm::~SPIComm() { closeDevice(); }

void SPIComm::setBus(int b) { if (m_bus != b) { m_bus = b; emit busChanged(); } }
void SPIComm::setChipSelect(int cs) { if (m_chipSelect != cs) { m_chipSelect = cs; emit chipSelectChanged(); } }

bool SPIComm::openDevice(int mode, int speed, int bitsPerWord)
{
    QString devPath = QString("/dev/spidev%1.%2").arg(m_bus).arg(m_chipSelect);
    m_fd = ::open(devPath.toStdString().c_str(), O_RDWR);
    if (m_fd < 0) return false;

    ioctl(m_fd, SPI_IOC_WR_MODE, &mode);
    ioctl(m_fd, SPI_IOC_WR_BITS_PER_WORD, &bitsPerWord);
    ioctl(m_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
    return true;
}

void SPIComm::closeDevice()
{
    if (m_fd >= 0) { close(m_fd); m_fd = -1; }
}

QByteArray SPIComm::transfer(const QByteArray &txData)
{
    QByteArray rxData(txData.size(), 0);
    struct spi_ioc_transfer xfer = {};
    xfer.tx_buf = reinterpret_cast<unsigned long>(txData.constData());
    xfer.rx_buf = reinterpret_cast<unsigned long>(rxData.data());
    xfer.len = txData.size();

    if (ioctl(m_fd, SPI_IOC_MESSAGE(1), &xfer) < 0)
        rxData.clear();
    return rxData;
}

4.7 虚拟键盘

RK3588 触摸屏设备使用 Qt Virtual Keyboard 实现软键盘输入。

4.7.1 启用 Virtual Keyboard

# myapp.pro
QT += virtualkeyboard

4.7.2 配置虚拟键盘

# 设置虚拟键盘环境变量
export QT_IM_MODULE=qtvirtualkeyboard
export QT_VIRTUALKEYBOARD_LANGUAGE=zh_CN

4.7.3 QML 集成

在 QML 页面底部放一个 InputPanel,把它的可见性绑定到输入法状态,文本框获得焦点时键盘会自动弹出:

// MainWindow.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.VirtualKeyboard 2.15

ApplicationWindow {
    visible: true
    width: 1920
    height: 1080
    title: "RK3588 Qt Demo"

    ColumnLayout {
        anchors.fill: parent

        TextField {
            id: searchField
            Layout.fillWidth: true
            placeholderText: "点击输入搜索内容"
            inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
        }

        ListView {
            Layout.fillWidth: true
            Layout.fillHeight: true
            model: ListModel {
                ListElement { name: "项目 A"; status: "进行中" }
                ListElement { name: "项目 B"; status: "已完成" }
                ListElement { name: "项目 C"; status: "待启动" }
            }
            delegate: ItemDelegate {
                width: parent.width
                height: 60
                contentItem: RowLayout {
                    Label { text: model.name; font.bold: true }
                    Label { text: model.status }
                }
            }
        }

        // 虚拟键盘
        InputPanel {
            id: inputPanel
            Layout.fillWidth: true
            Layout.preferredHeight: 300
            visible: Qt.inputMethod && Qt.inputMethod.visible
            z: 10
        }
    }
}

4.8 系统托盘

QSystemTrayIcon 把应用图标放进系统托盘,配一个右键菜单:

// main.cpp
#include <QApplication>
#include <QSystemTrayIcon>
#include <QMenu>
#include <QAction>
#include <QMessageBox>
#include <QIcon>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    // 系统托盘
    QSystemTrayIcon trayIcon(QIcon(":/icons/app.png"), &app);

    QMenu trayMenu;
    QAction showAction("显示主窗口", &tray);
    QAction settingsAction("设置", &tray);
    QAction quitAction("退出", &tray);

    QObject::connect(&showAction, &QAction::triggered, [&]() {
        // 显示主窗口
    });
    QObject::connect(&quitAction, &QAction::triggered, &app, &QApplication::quit);

    trayMenu.addAction(&showAction);
    trayMenu.addAction(&settingsAction);
    trayMenu.addSeparator();
    trayMenu.addAction(&quitAction);

    trayIcon.setContextMenu(&trayMenu);
    trayIcon.setToolTip("RK3588 Qt Application");
    trayIcon.show();

    return app.exec();
}

4.9 QML 性能优化

4.9.1 启用 QML Profiler

# 运行时启用 QML 性能分析
export QML_TRACE=1
export QML_COMPILER_TRACE=1

# 启动应用
./myapp

# 另一终端查看性能数据
# 使用 Qt Creator 的 QML Profiler 工具连接

4.9.2 Scene Graph 调试

Scene Graph 是 Qt Quick 背后的场景图渲染框架。设置下面两个环境变量,启动时会打印渲染信息:

# 启用 Scene Graph 渲染调试
export QSG_INFO=1
export QT_LOGGING_RULES="qt.qpa.*=true"

# 查看渲染信息
# 输出应包含:
# - 使用的渲染后端(OpenGL ES 3.2)
# - GPU 信息(Mali-G610 MP4)
# - Scene Graph 线程模式

4.9.3 性能优化要点

// 使用 Repeater 替代动态创建
Repeater {
    model: 1000
    Rectangle {
        width: 100
        height: 100
        color: index % 2 ? "red" : "blue"
    }
}

// 避免在 QML 中使用 JavaScript 做密集计算
// 推荐:将数据处理放在 C++ 层

// 使用 Loader 按需加载
Loader {
    active: isPageVisible
    sourceComponent: heavyComponent
}

// 启用 ShaderEffect 的 OpenVG 后端(如支持)

4.9.4 GPU 加速验证

# 在 Mali-G610 MP4 上验证 OpenGL ES 3.2 渲染
# 运行 Qt 提供的 OpenGL 示例
cd /opt/qt5-rk3588/examples/opengl/cube
export QT_QPA_PLATFORM=eglfs
export QT_QPA_EGLFS_INTEGRATION=eglfs_kms
export QT_QPA_EGLFS_KMS_CONFIG=/etc/eglfs_kms.json
./cube

# 使用 glxinfo 查看 OpenGL ES 版本
apt-get install mesa-utils
# 目标板上
glxinfo | grep "OpenGL ES"
# 输出应包含:OpenGL ES 3.2 v1.r32p1-...

5. 代码详解

5.1 Qt 交叉编译架构

下面这张图是交叉编译的分工:构建工具和 host tools 留在 x86 主机上,编出来的 Qt 运行库和插件则部署到板子:

x86_64 主机(开发端)
  ├── Qt Creator(IDE)
  │   ├── qmake / CMake 构建
  │   ├── 交叉编译器 (aarch64-linux-gnu-g++)
  │   └── 远程部署(SSH + scp)
  └── 交叉编译 Qt 5.15.x
      ├── qmake 配置(mkspecs/devices/linux-rk3588-g++)
      ├── host tools(uic, moc, rcc — x86 版本)
      └── target libs(aarch64 版本)

RK3588 目标板
  ├── Qt 运行时库(libQt5Core, libQt5Gui, libQt5Widgets...)
  ├── Qt 平台插件
  │   ├── libqeglfs.so — EGLFS 平台插件
  │   ├── libqlinuxfb.so — LinuxFramebuffer 插件
  │   ├── libqevdevkeyboardplugin.so — 键盘驱动
  │   └── libqevdevmouseplugin.so — 鼠标驱动
  ├── EGL + Mali-G610 MP4 驱动
  │   ├── libEGL.so
  │   └── libGLESv2.so(含 libmali.so)
  └── DRM/KMS 显示框架

5.2 EGLFS 渲染管线

一帧 QML 界面从绘制到上屏经过的路径:

QML/Qt Quick 界面
  ↓
Scene Graph(场景图渲染)
  ↓
OpenGL ES 3.2 调用
  ↓
Mali-G610 MP4 GPU 驱动(libmali.so)
  ↓
GBM 缓冲区分配
  ↓
DRM/KMS 显示提交
  ↓
VOP2 显示控制器
  ↓
HDMI / MIPI DSI 输出

5.3 输入事件处理

各类硬件输入都经 evdev 设备节点进入,再转换成对应的 Qt 事件:

硬件输入事件
  ├── 键盘 → /dev/input/eventX → evdev → Qt Key Events
  ├── 触摸屏 → /dev/input/eventX → evdev → Qt Touch Events
  └── 鼠标 → /dev/input/eventX → evdev → Qt Mouse Events

Qt 输入处理
  ├── QKeyEvent — 键盘事件
  ├── QTouchEvent — 触摸事件
  ├── QMouseEvent — 鼠标事件
  └── QInputMethodEvent — 输入法事件

6. 常见问题

6.1 EGLFS 无法初始化

现象: 运行 Qt 应用报 Could not initialize EGL display 或 EGLFS: eglGetDisplay() failed

解决方案: 按顺序排查 GPU 节点、驱动、EGL 库,最后用 glxinfo 手动测 EGL:

# 1. 确认 GPU 设备节点存在
ls -la /dev/dri/

# 2. 确认 Mali 驱动加载
dmesg | grep -i mali
cat /sys/class/misc/mali0/device/gpuinfo 2>/dev/null

# 3. 检查 EGL 库路径
ls /usr/lib/libEGL.so*
ls /usr/lib/libmali.so*

# 4. 手动测试 EGL
# 安装 mesa-utils
apt-get install mesa-utils
glxinfo | grep "OpenGL ES"

6.2 屏幕无输出

现象: Qt 应用运行正常但显示器无画面

解决方案:

# 1. 检查 HDMI 连接状态
cat /sys/class/drm/card0-HDMI-A-1/status
# 应显示 "connected"

# 2. 检查显示分辨率
cat /sys/class/drm/card0-HDMI-A-1/modes

# 3. 设置正确的输出分辨率
export QT_QPA_EGLFS_KMS_CONFIG=/etc/eglfs_kms.json
# 确保 JSON 中的分辨率与显示器支持的分辨率匹配

# 4. 尝试 LinuxFramebuffer 模式
export QT_QPA_PLATFORM=linuxfb
export QT_QPA_LINUXFB_FB=/dev/fb0
./myapp

6.3 触摸屏无响应

现象: Qt 应用能显示但无法响应触摸操作

解决方案:

# 1. 检查触摸设备
cat /proc/bus/input/devices | grep -A4 -i touch

# 2. 检查输入设备节点
ls -la /dev/input/event*
evtest /dev/input/eventX  # 手动测试触摸事件

# 3. 配置触摸设备名
export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS=/dev/input/event3:rotate=0

# 4. 启用 evdev 触摸驱动
export QT_QPA_EGLFS_INTEGRATION=eglfs_kms_egldevice

6.4 虚拟键盘不弹出

现象: 点击 TextField 但虚拟键盘不显示

解决方案:

# 1. 确认已设置输入法模块
export QT_IM_MODULE=qtvirtualkeyboard

# 2. 确认语言设置
export QT_VIRTUALKEYBOARD_LANGUAGE=zh_CN

# 3. 在 QML 中确认 InputPanel 可见
# 确保 InputPanel 的 visible 属性绑定了 Qt.inputMethod.visible

6.5 串口权限问题

现象: Permission denied: /dev/ttyS2

解决方案:

# 1. 添加用户到 dialout 组
sudo usermod -a -G dialout root

# 2. 或直接修改设备权限
sudo chmod 666 /dev/ttyS2

# 3. 验证串口设备
ls -la /dev/ttyS*
dmesg | grep ttyS

6.6 应用启动报 "could not find or load the Qt platform plugin"

现象: qt.qpa.plugin: Could not load the Qt platform plugin "eglfs"

解决方案:

# 1. 设置插件路径
export QT_PLUGIN_PATH=/opt/qt5-rk3588/plugins

# 2. 或指定平台插件
export QT_QPA_PLATFORM=eglfs

# 3. 调试插件加载
export QT_DEBUG_PLUGINS=1
./myapp
# 输出将显示插件搜索路径和加载失败原因

相关文档

  • 01_C/C++应用开发入门:C/C++ 基础开发
  • ch02 Python 应用开发:Python 应用开发
  • ch04 GStreamer 多媒体开发:多媒体处理
  • 03_设备树详解与实践:设备树配置
  • 01_Mali GPU 驱动开发:GPU 驱动
在 GitHub 上编辑此页
Prev
Python应用开发
Next
GStreamer多媒体开发