STM32H743上RT-Thread+LVGL+QSPI驱动ST77903全流程

简介:这份资源是矽创ST77903小体积LCD驱动IC的完整使用DEMO,面向嵌入式显示开发工程师与穿戴设备方案开发者,运行环境为STM32H743 + RT-Thread + LVGL,通过QSPI接口驱动屏幕。由于ST77903内部不带显存,QSPI必须连续传输,HOST端需使用专门线程完成刷屏,DEMO中已给出这一关键机制的实现范例,并内置3个手表表盘界面,可直接作为穿戴类UI项目的参考起点。压缩包共2591个文件,约13.66MB,以1090个h头文件与1016个c源文件为主体,配合135个s汇编、69个SConscript构建脚本、48个icf与16个ld链接配置,另有Kconfig、Makefile、CMake等工程文件及少量png、gif素材与pdf文档,完整呈现RT-Thread与LVGL的目录组织方式。目前已有2588人学习下载,适合希望快速跑通ST77903显示链路、理解QSPI连续刷屏线程设计的开发者参考。

1. 矽创 ST77903 驱动 DEMO:STM32H743 上跑通 RT-Thread + LVGL + QSPI 的完整路径

如果你手头正好有一块 ST77903 的屏,又不想从零去啃矽创那份寄存器手册,这个 DEMO 值得先跑一遍。它把 STM32H743、RT-Thread、LVGL、QSPI 和 ST77903 这几层串成了一条能直接编译下载的链路,省掉了自己搭工程、对时序、调初始化的时间。ST77903 是矽创面向中小尺寸 TFT 的驱动 IC,常见于 320×480 到 480×800 这类分辨率,接口支持 SPI/QSPI,很多便携设备、工控面板都在用。这个 DEMO 的价值不在于功能多花哨,而在于它把 QSPI 四线高速刷屏和 LVGL 的对接做通了,适合已经熟悉 STM32 和 RT-Thread、想快速验证 ST77903 显示效果的嵌入式开发者。

2. 工程结构与三层驱动模型:从 QSPI 到 LVGL 的数据流

拿到一个 DEMO,先别急着编译,把目录结构和数据流理清楚,后面改起来才不慌。这个工程的分层思路很典型:底层是 STM32H743 的 QSPI 外设配置,中间是 ST77903 的寄存器操作和显存刷新,上层是 RT-Thread 的设备框架和 LVGL 的显示驱动接口。三层各管各的,耦合点集中在两个地方——QSPI 的读写函数和 LVGL 的 flush_cb 回调。

2.1 目录布局与关键文件职责

工程大致会分成这么几块:bsp下放 STM32H743 的时钟、GPIO、QSPI 初始化;drivers下是 ST77903 的驱动文件,通常叫drv_st77903.c和对应的头文件;lvgl是移植好的 LVGL 源码和lv_port_disp.c;applications里是 main 线程和界面 demo。RT-Thread 的rtconfig.h决定了开哪些组件,QSPI 设备驱动是否使能、LVGL 的 tick 来源都在这里配。

关键文件职责可以对照下面这张表:

文件职责改动频率
bsp_qspi.cQSPI 时钟、引脚、DMA 配置换板子才改
drv_st77903.c初始化序列、读写寄存器、刷屏换屏或调时序时改
lv_port_disp.cLVGL 显示注册、flush 回调分辨率或颜色格式变时改
rtconfig.h组件开关、QSPI 设备使能按需调整

我一般会先看drv_st77903.c里的初始化序列,因为 ST77903 上电后需要一串寄存器写入才能正常显示,顺序错了就是白屏或者花屏。

2.2 QSPI 四线模式与 ST77903 的对接逻辑

STM32H743 的 QSPI 支持单线、双线、四线模式,ST77903 在 QSPI 下通常用四线发数据。这里有个容易忽略的点:命令和地址阶段可能还是单线,只有数据阶段切四线,具体要看 ST77903 的时序要求。DEMO 里一般会配成QSPI_ACCESS_INDIRECT_WRITE模式,把要发的数据塞进 FIFO 再触发传输。

下面是一段典型的 QSPI 写数据函数,基于 HAL 库风格:

/* 向 ST77903 写入数据,使用 QSPI 间接写模式 */ void st77903_qspi_write(uint8_t *buf, uint32_t len) { QSPI_CommandTypeDef cmd = {0}; cmd.Instruction = 0x02; /* QSPI 写命令,具体值看 ST77903 手册 */ cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE; cmd.AddressMode = QSPI_ADDRESS_NONE; cmd.DataMode = QSPI_DATA_4_LINES; /* 数据阶段走四线 */ cmd.NbData = len; cmd.DummyCycles = 0; HAL_QSPI_Command(&hqspi, &cmd, HAL_QSPI_TIMEOUT_DEFAULT_VALUE); HAL_QSPI_Transmit(&hqspi, buf, HAL_QSPI_TIMEOUT_DEFAULT_VALUE); }

这段代码的逻辑是:先配置命令结构体,指定命令阶段单线、数据阶段四线,然后发命令再发数据。参数里Instruction的值必须和 ST77903 手册对上,不同批次或不同模式可能不一样。DataMode改成QSPI_DATA_1_LINE就退回单线,刷屏速度会明显下降,调试时可以先用单线确认能显示,再切四线提速。

提示:QSPI 时钟频率不要一上来就拉满,STM32H743 的 QSPI 在高频下对 PCB 走线敏感,先降到 20MHz 左右确认稳定,再逐步往上加。

2.3 RT-Thread 设备框架下的驱动注册

RT-Thread 的好处是设备管理统一,QSPI 可以注册成qspi10这样的设备名,ST77903 驱动通过rt_device_find拿到句柄再操作。DEMO 里通常会在drv_st77903.c的初始化函数里完成注册,然后在 main 线程里打开设备。

/* ST77903 驱动初始化,注册为 RT-Thread 设备 */ static int st77903_init(void) { rt_device_t qspi_dev = rt_device_find("qspi10"); if (qspi_dev == RT_NULL) { rt_kprintf("qspi10 not found!\n"); return -RT_ERROR; } rt_device_open(qspi_dev, RT_DEVICE_FLAG_RDWR); /* 执行 ST77903 上电初始化序列 */ st77903_hw_init(); /* 注册显示设备,供 LVGL 使用 */ st77903_disp_register(); return RT_EOK; } INIT_DEVICE_EXPORT(st77903_init);

这里用INIT_DEVICE_EXPORT把初始化挂到 RT-Thread 的自动初始化流程里,系统启动时会自己调用。st77903_hw_init里就是那一长串寄存器写入,st77903_disp_register负责把显示设备挂到 RT-Thread 的设备链表上。如果启动后找不到设备,先查rt_device_find的返回值,再看rtconfig.h里 QSPI 驱动有没有使能。

3. LVGL 移植与刷屏优化:flush_cb 怎么写才不撕裂

LVGL 本身不直接操作硬件,它通过一个flush_cb回调把渲染好的区域推给屏幕。这个回调写得好不好,直接决定刷屏流不流畅、有没有撕裂感。DEMO 里这部分是重点,也是新手最容易翻车的地方。

3.1 LVGL 显示驱动注册与缓冲区配置

LVGL 需要至少一个绘制缓冲区,大小可以是屏幕的 1/10 到全屏。缓冲区越大,刷新越流畅,但吃 RAM。STM32H743 有 1MB RAM,跑 480×800 的屏,开 1/4 屏缓冲比较稳妥。

/* LVGL 显示缓冲区配置,放在 SDRAM 或内部 RAM */ #define DISP_BUF_SIZE (480 * 800 / 4) static lv_color_t buf1[DISP_BUF_SIZE]; void lv_port_disp_init(void) { static lv_disp_draw_buf_t draw_buf; lv_disp_draw_buf_init(&draw_buf, buf1, NULL, DISP_BUF_SIZE); static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.hor_res = 480; disp_drv.ver_res = 800; disp_drv.flush_cb = st77903_flush_cb; disp_drv.draw_buf = &draw_buf; lv_disp_drv_register(&disp_drv); }

lv_disp_draw_buf_init的第三个参数是缓冲区能存的像素数,这里取了屏幕的四分之一。hor_res和ver_res必须和 ST77903 实际分辨率一致,填错了画面会错位。flush_cb指向自己实现的刷屏函数,LVGL 每次渲染完一块区域就会调它。

3.2 flush_cb 的实现与 DMA 传输

flush_cb收到的是lv_disp_drv_t和lv_area_t,里面标了要刷新的区域坐标和颜色数据指针。ST77903 需要先设窗口再写数据,所以回调里分两步:设窗口、发像素。

/* LVGL 刷屏回调,把渲染好的区域推给 ST77903 */ static void st77903_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_p) { /* 设置 ST77903 显示窗口 */ st77903_set_window(area->x1, area->y1, area->x2, area->y2); /* 计算像素字节数,RGB565 每像素 2 字节 */ uint32_t px_cnt = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1); st77903_qspi_write((uint8_t *)color_p, px_cnt * 2); /* 通知 LVGL 刷新完成 */ lv_disp_flush_ready(drv); }

st77903_set_window里发的是列地址和行地址设置命令,把要写的区域框出来。px_cnt * 2是因为 RGB565 每个像素占两个字节。最后必须调lv_disp_flush_ready,否则 LVGL 会一直等,界面就卡住了。如果刷屏有撕裂,常见做法是开双缓冲或者用 DMA 传输加垂直同步,但 ST77903 一般没有 TE 引脚引出,所以 DEMO 里多半是单缓冲直接刷,靠提高 QSPI 速率来压撕裂。

注意:color_p指向的缓冲区在lv_disp_flush_ready调用前不能被改写,如果用了 DMA,要等 DMA 传输完成再调 ready,否则会花屏。

3.3 刷屏速率实测与参数调整

刷屏速率主要受三个因素影响:QSPI 时钟、数据线数、以及每次刷新的区域大小。我一般会用一个简单的计数法测:在flush_cb里翻转一个 GPIO,用示波器看频率,或者用 RT-Thread 的rt_tick_get算每秒刷新次数。

假设 QSPI 时钟 50MHz,四线模式下理论带宽是 50M × 4 / 8 = 25MB/s,刷一屏 480×800×2 = 768KB,理论最快约 30 帧。实际因为命令开销和 LVGL 渲染耗时,能到 15 到 20 帧就算不错。如果帧率明显偏低,先查 QSPI 时钟是不是被分频拉低了,再看flush_cb里有没有多余的延时。

4. 避坑与排查:ST77903 调试中最容易翻车的五个点

这个 DEMO 跑通不难,但中间有几个坑几乎每个人都会踩一遍。下面这五条是我自己调试时记下来的,按现象、原因、解决来写。

现象一:上电后屏幕全白,背光亮但没图像。原因通常是初始化序列没发完或者发错了。ST77903 上电后需要等一段时间再发初始化命令,有些 DEMO 里用rt_thread_mdelay延时,如果延时不够,芯片还没准备好,后续命令全丢。解决方法是把上电延时加到 120ms 以上,再逐条确认初始化命令的寄存器地址和参数值是否和手册一致。

现象二:画面能显示但颜色不对,红色变蓝色。这是 RGB 顺序配错了。ST77903 支持 RGB 和 BGR 两种像素顺序,初始化序列里有一个寄存器控制这个。LVGL 默认按 RGB565 发数据,如果屏幕端设成了 BGR,红蓝就对调。解决办法是找到初始化序列里控制颜色顺序的寄存器,改成和 LVGL 一致,或者在flush_cb里手动交换 R 和 B 分量。

现象三:刷屏时画面撕裂,上半屏和下半屏不同步。单缓冲直接刷屏时,如果 QSPI 传输还没完成,LVGL 已经开始渲染下一帧并改写缓冲区,就会撕裂。常见做法是开双缓冲,让 LVGL 渲染和 QSPI 传输交替进行。如果 RAM 不够开双缓冲,可以缩小刷新区域,让每次传输的数据量小一点,撕裂会减轻但不会完全消失。

现象四:QSPI 四线模式一开就花屏,单线正常。四线模式对时序和走线要求高,如果 PCB 上 QSPI 的四根数据线长度差异大,或者没有做等长,高频下就会采样错误。先降 QSPI 时钟到 20MHz 试试,如果单线正常四线花屏,基本就是信号完整性问题。解决方法是降低时钟、检查走线,或者在 QSPI 配置里增加 dummy cycles 给信号留出建立时间。

现象五:LVGL 界面卡死,点按钮没反应。先查lv_disp_flush_ready有没有被调用。如果flush_cb里因为 QSPI 传输失败提前返回,没调 ready,LVGL 就会一直等,整个界面线程卡住。另外 RT-Thread 里 LVGL 的 tick 来源要确认,通常用rt_tick_get或者一个硬件定时器,tick 不走 LVGL 的任务就不处理。解决方法是加错误处理,QSPI 传输失败也要调 ready,同时确认 tick 中断正常。

5. 进阶技巧:用 RT-Thread 的 SFUD 和 DMA 把刷屏做到极致

DEMO 跑通之后,如果想把刷屏速率再往上推一截,有两个方向可以挖:一是把 QSPI 传输改成 DMA 模式,二是用 RT-Thread 的 SFUD 组件管理 QSPI 设备。DMA 模式能让 CPU 在刷屏时去干别的,LVGL 渲染和 QSPI 传输可以并行,帧率提升明显。

先看 DMA 配置。STM32H743 的 QSPI 支持 MDMA 和 DMA,用 HAL 库的话,把HAL_QSPI_Transmit换成HAL_QSPI_Transmit_DMA,然后在 DMA 传输完成回调里调lv_disp_flush_ready。

/* QSPI DMA 传输完成回调,通知 LVGL 刷新完成 */ void HAL_QSPI_TxCpltCallback(QSPI_HandleTypeDef *hqspi) { if (hqspi->Instance == QUADSPI) { /* 传输完成,通知 LVGL 可以渲染下一帧 */ lv_disp_flush_ready(&disp_drv); } } /* flush_cb 里改用 DMA 传输 */ static void st77903_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_p) { st77903_set_window(area->x1, area->y1, area->x2, area->y2); uint32_t px_cnt = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1); /* 用 DMA 发数据,完成后在回调里调 ready */ st77903_qspi_write_dma((uint8_t *)color_p, px_cnt * 2); }

这里的关键是lv_disp_flush_ready不能在flush_cb里直接调了,要挪到 DMA 完成回调里。否则 DMA 还没传完,LVGL 就以为刷完了,会改写缓冲区导致花屏。参数上,DMA 的优先级要设得比 LVGL 任务高,不然传输会被打断。

另一个方向是用 SFUD。RT-Thread 的 SFUD 组件本来是用来管理 SPI Flash 的,但它的设备框架可以借鉴,把 ST77903 的 QSPI 操作封装成标准设备接口,这样换屏或者换 QSPI 控制器时上层代码不用动。我一般会在drv_st77903.c里实现一套rt_qspi_device的操作函数,注册到 RT-Thread 的 QSPI 设备框架下,LVGL 那边只认显示设备,不关心底层是 QSPI 还是 SPI。

最后说一个验证方法:在flush_cb里加一个计数器,每秒打印一次刷新次数,同时用rt_tick_get算实际帧率。如果帧率和理论值差距大,先查 QSPI 时钟,再查 DMA 有没有真正启用,最后看 LVGL 的渲染耗时是不是成了瓶颈。我习惯在调刷屏时把 LVGL 的LV_USE_PERF_MONITOR打开,它能直接显示 CPU 占用和帧率,比手动算省事。

从那以后我每次拿到新的屏驱动 DEMO,都强制先跑一遍单线模式确认初始化序列没问题,再切四线、加 DMA、调时钟,一步一步来,不跳步。希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

STM32驱动INA226电流监测模块:寄存器配置与串口输出实战

STM32驱动INA226电流监测模块:寄存器配置与串口输出实战

简介:这是一套面向嵌入式开发者的INA226电流/功率传感器驱动完整工程,基于STM32微控制器,采用C语言编写,配套Keil MDK开发环境,适合电源管理、电池管理、系统监控及工业自动化等场景下的电流电压采集需求。压缩包共151…

2026/10/20 11:46:43 阅读更多 →
AI时代论文查重与生成痕迹检测:原理、工具与实操指南

AI时代论文查重与生成痕迹检测:原理、工具与实操指南

上个月帮一个朋友看论文,他发过来的时候特别自信:“学长,我在某系统里查过了,重复率只有 8%。”我扫了一眼第一章,心里就咯噔一下——文字确实流畅,但每句话都像教科书在说话,句式整齐到有点不正…

2026/10/20 11:45:42 阅读更多 →
Label Studio预标注实操:从Prediction格式到标注效率提升

Label Studio预标注实操:从Prediction格式到标注效率提升

预标注这事,听起来像是"标注工具的锦上添花",但实际上它是整个标注环节里最值得投入的一步。我自己接手过一个图像分割项目,前两周全部时间都耗在拉框和调整多边形顶点上,眼睛都快花了,直到后来把预标注流程…

2026/10/20 11:45:42 阅读更多 →

最新新闻

Spring Boot社区在线问诊系统实战:从需求到部署的完整技术拆解

Spring Boot社区在线问诊系统实战:从需求到部署的完整技术拆解

最近后台经常有人问社区家庭医生在线问诊系统怎么做,正好我前阵子完整跟过一个类似项目,从需求梳理到部署上线走了不少弯路。我干脆把这套系统的技术拆解和踩坑记录整理成一篇长文,一方面给自己做个复盘,另一方面给正在做毕业设计…

2026/10/20 13:09:01 阅读更多 →
Git克隆Demo代码仓库实战:从地址到跑通全流程

Git克隆Demo代码仓库实战:从地址到跑通全流程

1. 拿到仓库地址后,先别急着敲命令很多同学第一次接触代码仓库时,最容易犯的毛病就是:复制了地址,打开终端,直接输入git clone,回车,然后开始疯狂刷新浏览器看着进度条发呆。如果仓库小&#xf…

2026/10/20 13:09:01 阅读更多 →
OpenHarmony设备上Flutter淡出过渡动画移植实战与性能优化

OpenHarmony设备上Flutter淡出过渡动画移植实战与性能优化

上个月我把一个原本跑在标准Flutter环境里的页面过渡动画方案移植到OpenHarmony设备上,遇到的第一个问题倒不是动画本身,而是animations 这个官方维护的三方动画库直接编译不过。项目里本来打算用它实现页面切换时的淡出过渡效果,想着省一笔自…

2026/10/20 13:09:01 阅读更多 →
轻量级技能管理方案:从零搭建可量化可组合的技能表

轻量级技能管理方案:从零搭建可量化可组合的技能表

1. 从“skills”这个词说起:它到底指什么“skills”这个词看起来简单,但放在不同语境里,指向完全不同。做招聘的人看到它,想到的是岗位能力模型;做在线教育的人看到它,想到的是课程标签体系;做开…

2026/10/20 13:09:01 阅读更多 →
企业级RAG问答系统实战:知识库、检索链路与大模型应用的完整落地指南

企业级RAG问答系统实战:知识库、检索链路与大模型应用的完整落地指南

做了将近半年的基于大模型的智能问答系统,上线之后我最大的感受是:**决定这套系统好不好用的,往往不是大模型本身,而是知识库和检索链路。**模型答得再聪明,检索不到对的资料,它也只能一本正经地编。我们做…

2026/10/20 13:09:01 阅读更多 →
GraphRAG工程实践:知识图谱如何补足向量检索的推理短板

GraphRAG工程实践:知识图谱如何补足向量检索的推理短板

向量检索失效的那一刻,我才决定认真搞GraphRAG年初做过一个企业内部制度问答系统,刚开始用的是经典的向量库RAG方案:文档切块、embedding、top-k召回、拼prompt喂给大模型。原型阶段跑得很顺,演示的时候领导问了一个问题——"…

2026/10/20 13:08:00 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →