0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

RT-Thread 睿擎派 LVGL 屏幕旋转的软件实现

RT-Thread官方账号 2026-05-27 18:10 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

效果预览

49829c5a-59b4-11f1-ab55-92fbcf53809c.jpg49913314-59b4-11f1-ab55-92fbcf53809c.jpg


上图展示了RT-Thread 睿擎平台(Rockchip RK3506)上接入一块 4.3 寸 MIPI DSI LCD(物理分辨率为 480×800 竖屏),通过软件层面的旋转处理,以 800×480 横屏方向完成 UI 内容呈现。


一、LVGL 概述

LVGL,是一款专为嵌入式系统设计的轻量级开源 GUI 库。该库在资源极度受限的 MCU 及 MPU 平台上同样能够实现流畅的图形界面渲染,被广泛应用于智能家电、车载仪表、工业 HMI 等嵌入式人机交互场景。


二、LVGL 渲染流水线

在讨论旋转实现之前,需要介绍 LVGL 完整渲染过程——从应用层发起初始化,到像素数据最终输出至物理显示设备,数据经过了哪些组件、完成了哪些阶段的处理。下图以框图形式展示了这一流水线的整体架构。


49a6439e-59b4-11f1-ab55-92fbcf53809c.png


上图展示了 LVGL 渲染流水线的七个核心阶段,自上而下依次为:应用层入口 → LVGL 线程创建与初始化 → 刷新定时器周期性触发渲染 → 绘制缓冲区承载一帧完整画面 → call_flush_cb 完成坐标修正与回调分发 → 用户 flush_cb 执行旋转处理及平台加速传输 → LCD 硬件显示。


2.1 应用层入口与线程模型

对应于框图中 ①→② 阶段,LVGL 在 RT-Thread 睿擎平台上运行于一个独立线程之中。main() 函数仅负责调用 lvgl_thread_init() 创建并启动该线程:

intmain(void){ rt_kprintf("Hello, RT-Thread app\n"); // 创建并启动 LVGL 线程 lvgl_thread_init(); return0;}

lvgl_thread_init 通过 rt_thread_init 创建一个名为 “LVGL” 的线程,其入口函数为 lvgl_thread_entry,随后通过 rt_thread_startup 启动。进入入口函数后,即可观察到 LVGL 的完整初始化序列与主循环结构:

staticvoidlvgl_thread_entry(void*parameter){ // LVGL 内核初始化 lv_init(); // 注册 RT-Thread tick 作为心跳时钟 lv_tick_set_cb(rt_tick_get_millisecond); // 显示驱动初始化 lv_port_disp_init(); // 输入设备初始化 lv_port_indev_init(); // 用户 UI 入口 lv_user_gui_init(); while(1) { lv_task_handler(); rt_thread_mdelay(1); }}

上述五个初始化步骤与末尾的死循环,构成了 LVGL 运行的全部生命周期。


2.2 显示驱动初始化与渲染模式配置

对应于框图中 ② 阶段内部 lv_port_disp_init 的执行,该函数是整个旋转机制的起点:

voidlv_port_disp_init(void){ // ... 变量声明 ...
// 步骤1: 查找 LCD 设备 device =rt_device_find("lcd");
// ... 错误检查 ...
rt_device_init(device); // 步骤2: 打开设备 rt_device_open(device, RT_DEVICE_FLAG_RDWR); // 步骤3: 获取物理分辨率 (480×800) rt_device_control(device, RTGRAPHIC_CTRL_GET_INFO, &info); // 步骤4: 分配绘制缓冲,大小 = 480×800×2 字节(RGB565) framebuffer =rt_malloc( info.width * info.height *sizeof(lv_color_t)); // ... 错误检查 ...
// 步骤5: 创建显示对象,传入物理分辨率 display =lv_display_create(info.width, info.height); // ... 错误检查 ...
// 步骤6: 单缓冲 + FULL 渲染模式 lv_display_set_buffers(display, framebuffer,NULL, info.width * info.height *sizeof(lv_color_t), LV_DISPLAY_RENDER_MODE_FULL); // 步骤7: 注册 flush_cb 与旋转角度 lv_display_set_flush_cb(display, lvgl_flush_cb); lv_display_set_rotation(display, LV_DISPLAY_ROTATION_270); return; // ... __fail 清理逻辑 ...}


该函数涵盖七个关键步骤:查找 LCD 设备 → 打开设备 → 获取物理分辨率(480×800) → 分配绘制缓冲区 → 创建 LVGL 显示对象 → 配置单缓冲 FULL 渲染模式(每帧刷新完整画面,便于旋转处理) → 注册 flush_cb 回调并声明 270° 旋转角度。其中 lv_display_set_rotation 的实现机制将在第四章详述。


2.3 帧刷新机制与回调分发

对应于框图中 ③→④→⑤ 阶段。LVGL 内部维护一个刷新定时器 _lv_display_refr_timer,周期性触发:合并脏区域 → 逐层渲染像素至缓冲区 → 调用 draw_buf_flush。call_flush_cb 作为内核与用户回调的分界点,将刷新区域坐标叠加 display 偏移量后调用用户注册的 flush_cb:

staticvoidcall_flush_cb(lv_display_t* disp,constlv_area_t* area,uint8_t* px_map){ lv_area_toffset_area = { .x1 = area->x1 + disp->offset_x, .y1 = area->y1 + disp->offset_y, .x2 = area->x2 + disp->offset_x, .y2 = area->y2 + disp->offset_y }; disp->flush_cb(disp, &offset_area, px_map);}

关键点:LVGL 使用的坐标空间是逻辑分辨率。物理 LCD 为 480×800,声明 270° 旋转后 LVGL 识别 800×480,所有 UI 均按此逻辑空间布局。flush_cb 接收到的 area 与 px_map 也处于同一逻辑空间,其核心职责即为完成逻辑空间到物理硬件的映射转换。

完整处理链路:LVGL 800×480 逻辑渲染 →call_flush_cb→ 用户flush_cb旋转变换 (480×800) → LCD。底层 MIPI DSI 驱动始终以物理 480×800 工作,对旋转完全无感知。


2.4 旋转模块在流水线中的定位

观察上述框图可以发现,旋转处理的所有逻辑全部集中在第 ⑥ 阶段——用户 flush_cb 内部。LVGL 内核(① 至 ⑤)完全不涉及旋转相关的逻辑,LCD 硬件驱动(⑦)也不感知旋转的存在。这种架构设计的优势在于:旋转处理与渲染引擎和硬件驱动均实现了解耦,修改旋转角度仅需调整 flush_cb 中的处理逻辑,不影响其他任何模块。


三、屏幕旋转实现原理

实现屏幕旋转,本质上需要完成三个关键操作:“声明旋转角度 → 坐标映射 → 像素重排”

1.声明旋转角度— lv_display_set_rotation 让 LVGL 将水平分辨率识别为 800、垂直 480,UI 代码如同在原生 800×480 横屏上开发。

2.刷新区域坐标映射— lv_display_rotate_area 将 flush_cb 收到的 800×480 逻辑坐标映射为 480×800 物理坐标,仅改坐标不改像素。

3.像素数据重排— lv_draw_sw_rotate 将像素从 800×480 逻辑排布重组为 480×800 物理排布。

数据流向:LVGL 逻辑层 (800×480) → flush_cb → [rotate_area + sw_rotate + RGA] → MIPI LCD (480×800)


四、旋转机制核心函数源码分析

4.1 lv_display_set_rotation:旋转角度声明

voidlv_display_set_rotation(lv_display_t* disp,lv_display_rotation_trotation){ // ... NULL 检查 ...
// 仅将角度值写入字段 disp->rotation = rotation;
// 触发 screen/layer 尺寸重算 update_resolution(disp); }


该函数不修改disp->hor_res 与 disp->ver_res(始终为物理分辨率 480 和 800)。逻辑分辨率的切换实现在 getter 函数中:

int32_tlv_display_get_horizontal_resolution(constlv_display_t* disp){ // ... NULL 检查 ... switch(disp->rotation) { caseLV_DISPLAY_ROTATION_90: caseLV_DISPLAY_ROTATION_270: returndisp->ver_res; default: returndisp->hor_res; }}

90°/270° 时水平分辨率返回 ver_res(800),垂直分辨率同理反向映射。上层代码统一通过 getter 获取尺寸,所有布局自动适配。update_resolution 负责向对象树广播变更——更新所有 screen/layer 尺寸、清空无效区域强制重绘、触发 LV_EVENT_RESOLUTION_CHANGED 事件。

49f7a4aa-59b4-11f1-ab55-92fbcf53809c.png

核心思想:存储与访问分离。


4.2 lv_display_rotate_area:刷新区域坐标映射

将 flush_cb 中逻辑空间的 area 坐标映射为物理空间坐标:

voidlv_display_rotate_area(lv_display_t* disp,lv_area_t* area){ lv_display_rotation_trotation =lv_display_get_rotation(disp); if(rotation == LV_DISPLAY_ROTATION_0)return; int32_tw =lv_area_get_width(area); int32_th =lv_area_get_height(area); switch(rotation) { caseLV_DISPLAY_ROTATION_90: area->y2 = disp->ver_res - area->x1 -1; area->x1 = area->y1; area->x2 = area->x1 + h -1; area->y1 = area->y2 - w +1; break; caseLV_DISPLAY_ROTATION_180: // ... px = hor_res - lx - 1, py = ver_res - ly - 1 ... break; caseLV_DISPLAY_ROTATION_270: area->x1 = disp->hor_res - area->y2 -1; area->y2 = area->x2; area->x2 = area->x1 + h -1; area->y1 = area->y2 - w +1; break; }}

注意函数直接访问字段 disp->hor_res(480)和 disp->ver_res(800),不经 getter,始终为物理分辨率。以 270° 为例,逻辑区域 {100,50,300,150} 变换后为 {329,100,429,300}。


4a02a206-59b4-11f1-ab55-92fbcf53809c.png


4.3 lv_draw_sw_rotate:像素数据重排

分发函数,按颜色格式和角度派发至对应的旋转实现:

voidlv_draw_sw_rotate(...,lv_display_rotation_trotation,lv_color_format_tcolor_format){ uint32_tpx_bpp =lv_color_format_get_bpp(color_format); if(rotation == LV_DISPLAY_ROTATION_90) { if(px_bpp ==16)rotate90_rgb565(...); // ... } // ... 180°、270° 同理 ...}

本项目为 RGB565(BPP=16)+ 270°,最终执行 rotate270_rgb565:

staticvoidrotate270_rgb565(constuint16_t* src,uint16_t* dst, int32_tsrcW,int32_tsrcH,int32_tsrcStride,int32_tdstStride){// 硬件加速钩子 if(LV_RESULT_OK ==LV_DRAW_SW_ROTATE270_RGB565(...))return; srcStride /=sizeof(uint16_t); dstStride /=sizeof(uint16_t); for(int32_tx =0; x < srcW; ++x) {    // 源第 x 列 → 目标倒数第 x 列        int32_t dstIndex = (srcW - x - 1);           int32_t srcIndex = x;        for(int32_t y = 0; y < srcH; ++y) {            dst[dstIndex * dstStride + y] = src[srcIndex];            srcIndex += srcStride;        }    }}


核心算法:外层按列遍历,dstIndex = srcW - x - 1 将源第 0 列映射至目标最右列;内层沿列方向逐像素搬运。源 srcW×srcH 矩阵旋转变为目标 srcH×srcW 矩阵。


4.4 flush_cb 完整旋转流水线

staticvoidlvgl_flush_cb(lv_display_t*display,constlv_area_t*area,uint8_t*px_map){ // ... 取出 device、framebuffer ... staticuint8_trotated_buf[480*800*8]; lv_display_rotation_trotation =lv_display_get_rotation(display); if(rotation != LV_DISPLAY_ROTATION_0) { lv_area_trotated_area = *area; // A. 坐标映射 lv_display_rotate_area(display, &rotated_area); uint32_tsrc_stride =lv_draw_buf_width_to_stride(lv_area_get_width(area), cf); uint32_tdest_stride =lv_draw_buf_width_to_stride(lv_area_get_width(&rotated_area), cf); // C. 像素重排 lv_draw_sw_rotate(px_map, rotated_buf, lv_area_get_width(area),lv_area_get_height(area), src_stride, dest_stride, rotation, cf); // 重定向至物理空间 area = &rotated_area; px_map = rotated_buf; } // ... RGA 封装 → imcopy(RGB565→RGB888) → RTGRAPHIC_CTRL_RECT_UPDATE ... lv_display_flush_ready(display);}

整体流程:旋转判断 → 坐标映射 → 像素重排 → RGA 硬件传输 → 完成通知。旋转逻辑封闭于 if 块内,平台加速独立于旋转处理。



五、工程实践要点

四种旋转角度的区别。物理竖屏 LCD 需要以横屏方向显示时,可选用 90° 或 270°。两种角度的逻辑分辨率相同(均为 800×480),区别在于逻辑坐标原点分别对应物理屏幕的不同角落——90° 时逻辑 (0,0) 对应物理 (0,799),270° 时逻辑 (0,0) 对应物理 (479,0)。180° 适用于物理横屏的倒置显示场景,此时逻辑分辨率与物理分辨率保持一致。


触摸坐标的同步。显示方向发生旋转后,触摸坐标也必须执行同步旋转变换。若通过 lv_indev_set_display 将输入设备绑定至显示对象,LVGL v9 可自动完成触摸坐标的旋转变换。若采用手动方式处理触摸回调,则需在回调中自行实现坐标变换逻辑。


双缓冲模式的注意事项。本文所述项目采用单缓冲 + FULL 渲染模式。若切换为双缓冲 + DIRECT 模式以利用"一个缓冲区渲染、另一个缓冲区传输"的并行优势,旋转逻辑本身无需修改——rotated_buf 为静态分配,每次 flush_cb 调用时直接覆写即可。但若启用多线程架构(例如单独线程负责渲染、另一线程负责 flush),rotated_buf 可能面临并发访问风险,此时需引入互斥保护机制。


配套资料包

想在自己的项目里实现 LVGL 屏幕旋转?我们整理了完整资料包:

完整示例工程源码(RuiChing Studio 可直接导入,含 270° 旋转 + RGA 加速)

旋转配置代码与注释(坐标映射、像素重排关键函数解析)

本篇文章涉及的全部代码片段


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 屏幕
    +关注

    关注

    7

    文章

    1248

    浏览量

    57253
  • RT-Thread
    +关注

    关注

    32

    文章

    1661

    浏览量

    45489
  • LVGL
    +关注

    关注

    3

    文章

    128

    浏览量

    4704
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    RT-Thread NUC97x 移植 LVGL

    不涉及 rt-thread 驱动,但是它是 LVGLrt-thread 的接口。LVGLrt-thread 上运行的基石。
    发表于 07-08 09:37 2261次阅读

    RT-Thread的C语言编码规范

    这是一份 RT-Thread 开发人员的开发指引。RT-Thread 做为一份开源软件,它需要由不同的人采用合作的方式完成,这份文档是开发人员的一个指引。RT-Thread 的开发人员
    的头像 发表于 02-21 16:50 3671次阅读

    在基于PC的RT-Thread模拟器上搭建LVGL图形库

    前言RT-Thread 当前的版本:4.1.0,通过简单的配置就可以支持最新的LVGL图形库,LVGL图形库以软件包的方式加入工程LVGL
    发表于 06-13 11:03

    基于RT-thread柿饼实现一个网络音频流播放器的设计

    1、基于RT-thread柿饼实现一个网络音频流播放器的设计  基于柿饼实现一个网络音频流播放器,目前
    发表于 09-21 16:37

    基于树莓pico移植LVGL软件包的设计如何去实现

    个 UART、2 个 SPI 控制器和 2 个 I2C 控制器16 个 PWM 通道更多详见:树莓中文站树莓拓展板:屏幕:ST7789 SPI 接口添加 LVGL
    发表于 09-22 11:26

    树莓PICO:使用rt-thread micropython软件包联网获取天气

    1、在树莓PICO上实现http请求demo  项目描述  本项目使用树莓PICO开发板和ESP8266作为硬件,使用RT-Thread MicroPython - Visual
    发表于 11-14 16:19

    RT-Thread编程指南

    RT-Thread编程指南——RT-Thread开发组(2015-03-31)。RT-Thread做为国内有较大影响力的开源实时操作系统,本文是RT-Thread实时操作系统的编程指南
    发表于 11-26 16:06 0次下载

    RT-Thread软件包定义和使用

    RT-Thread软件包是运行于RT-Thread物联网操作系统平台上,面向不同应用领域的通用软件组件 。RT-Thread 同时提供了开放
    的头像 发表于 05-21 11:29 1.2w次阅读
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>软件</b>包定义和使用

    RT-Thread全球技术大会:RT-Thread开源重塑软件发展新生态

    RT-Thread全球技术大会:RT-Thread开源重塑软件发展新生态,市场空间巨大,以开放的心态不断提升技术水平,大胆创新,迎接挑战!       审核编辑:彭静
    的头像 发表于 05-27 10:47 2126次阅读
    <b class='flag-5'>RT-Thread</b>全球技术大会:<b class='flag-5'>RT-Thread</b>开源重塑<b class='flag-5'>软件</b>发展新生态

    RT-Thread全球技术大会:使用RT-Thread开发大规模嵌入式软件带来的挑战与好处

    RT-Thread全球技术大会:使用RT-Thread开发大规模嵌入式软件带来的挑战与好处       审核编辑:彭静
    的头像 发表于 05-27 11:45 2266次阅读
    <b class='flag-5'>RT-Thread</b>全球技术大会:使用<b class='flag-5'>RT-Thread</b>开发大规模嵌入式<b class='flag-5'>软件</b>带来的挑战与好处

    RT-Thread全球技术大会:RT-Thread对POSIX的实现情况介绍

    RT-Thread全球技术大会:RT-Thread对POSIX的实现情况介绍             审核编辑:彭静
    的头像 发表于 05-27 16:52 3078次阅读
    <b class='flag-5'>RT-Thread</b>全球技术大会:<b class='flag-5'>RT-Thread</b>对POSIX的<b class='flag-5'>实现</b>情况介绍

    RT-Thread文档_RT-Thread 简介

    RT-Thread文档_RT-Thread 简介
    发表于 02-22 18:22 5次下载
    <b class='flag-5'>RT-Thread</b>文档_<b class='flag-5'>RT-Thread</b> 简介

    RT-Thread文档_RT-Thread SMP 介绍与移植

    RT-Thread文档_RT-Thread SMP 介绍与移植
    发表于 02-22 18:31 9次下载
    <b class='flag-5'>RT-Thread</b>文档_<b class='flag-5'>RT-Thread</b> SMP 介绍与移植

    中新社:RT-Thread携“平台”亮相工博会 | 媒体视角

    4月23日,成都国际工业博览会拉开帷幕。中国新闻网专访RT-Thread赛德,以“国产操作系统再亮剑,平台开启工业新时代”为题,报道了“
    的头像 发表于 04-24 11:16 1879次阅读
    中新社:<b class='flag-5'>RT-Thread</b>携“<b class='flag-5'>睿</b><b class='flag-5'>擎</b>平台”亮相工博会 | 媒体视角

    像STM32一样轻松玩转 MPU!RT-Thread 平台 Workshop 上海站开启硬核实战!下一城?你定!

    为什么参加1零门槛体验:我们致力于让MPU开发如MCU般简单易上手。2一站式掌握:从概念演示到动手实践再到项目构思,全流程深度体验。3业界领先技术:了解RT-Thread平台如何革新MPU
    的头像 发表于 08-18 17:05 1717次阅读
    像STM32一样轻松玩转 MPU!<b class='flag-5'>RT-Thread</b> <b class='flag-5'>睿</b><b class='flag-5'>擎</b>平台 Workshop 上海站开启硬核实战!下一城?你定!