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

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

3天内不再提示

LCD显示中的SPI瓶颈分析与刷新率优化 | 技术集结

RT-Thread官方账号 2026-06-04 11:51 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

本项目为LCD显示中的SPI瓶颈分析与刷新率优化。目前,恩智浦已有多款产品对RT-Thread完成了适配。近期,MCX A 系列产品的重要成员,FRDM-MCXA366也完成了适配,并在社区开发者的协作下完成了电子书《恩智浦FRDM-MCX A366开发实践指南》https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/tutorial/make-bsp/MCX-A366/%E6%81%A9%E6%99%BA%E6%B5%A6FRDM-MCXA366%E5%AE%9E%E8%B7%B5%E6%8C%87%E5%8D%97

目录


项目概述


硬件连接


软件架构


关键代码


性能测试数据(实测)


踩过的坑


MSH 命令行验证


项目目录


心得与总结


附录:源代码


《恩智浦FRDM-MCX A366开发实践指南》贡献名单

a2fd493c-5fc8-11f1-ab55-92fbcf53809c.png

实物运行:FRDM-MCXA156 + 2.4” ILI9341 LCD。屏幕上同时显示标题栏、运行时间(1h23m)、内存占用、Tick 计数、基准测试结果(Yld:6us / Mem:72us / Fill:460ms),底部 4 个彩色小球反弹动画,板子上红色 LED 心跳闪烁。

1 项目概述

项目名称:基于 RT-Thread 的 FRDM-MCXA156 多线程 LCD 显示与性能测试 Demo

硬件平台:NXP FRDM-MCXA156 开发板(主控 MCXA156,Arm Cortex-M33)

软件平台:RT-Thread 5.3.0 RTOS

显示外设:2.4 寸 ILI9341 SPI LCD(240×320 像素,16 位色,正点原子 ATK-MD0240 V1.2)

开发工具:RT-Thread Studio + scons 构建系统 + pyocd 烧录(CMSIS-DAP / MCU-Link)

本项目在 NXP FRDM-MCXA156 开发板上完成 RT-Thread RTOS 移植,外接 2.4 寸 ILI9341 LCD,通过 GPIO 直接寄存器写实现高速软件 SPI,并使用多线程实现实时系统信息显示、性能测试结果展示、动画演示与 LED 心跳指示。重点验证了 RT-Thread 在 MCXA156 平台上的多线程能力,并通过 GPIO 操作优化实现了10.1× 的 SPI 吞吐量提升(同板实测)。

2 硬件连接

LCD 外接到 FRDM-MCXA156 的 FlexIO/LCD 排针

a329b33c-5fc8-11f1-ab55-92fbcf53809c.png

板载 LED:P3_12(板载三色 LED 中的红色一路)。

引脚定义见 drv_ili9341.c 顶部宏。

3 软件架构

3.1 线程划分

a340b19a-5fc8-11f1-ab55-92fbcf53809c.png

多线程证据:ps 命令可一次性看到 6 个线程并发运行。

a3581ed4-5fc8-11f1-ab55-92fbcf53809c.png

3.2 屏幕布局(240×320 竖屏)

┌────────────────┐ y=0│ RT-Thread v5.3│ ← 蓝底白字 / 黄字标题栏│ FRDM-MCXA156 │├────────────────┤ y=36│ Up00:05 │ ← info 线程实时刷新│ Mem:118KB ││ Tick:512 │├════════════════┤ y=92 白色分隔线│ Benchmark: │ ← 启动时跑一次的基准(静态显示)│ Yld:6us ││ Mem:72us ││ Fill:460ms │├════════════════┤ y=168 白色分隔线│ ││ • • • • │ ← anim 线程动画区│ (4 个小球反弹)││ │└────────────────┘ y=320

4 关键代码

4.1 多线程创建(main.c)

rt_thread_tt1 = rt_thread_create("info", info_thread_entry, RT_NULL,1024,12,10);rt_thread_tt2 = rt_thread_create("anim", anim_thread_entry, RT_NULL,1024,13,10);rt_thread_tt3 = rt_thread_create("led", led_thread_entry, RT_NULL, 512,20,10);if(t1) rt_thread_startup(t1);if(t2) rt_thread_startup(t2);if(t3) rt_thread_startup(t3);

4.2 性能基准(main.c)

/* 1) 线程切换:5000 次 yield */t0 = rt_tick_get();for(inti =0; i < 5000; i++) rt_thread_yield();bench_yield_us = (rt_tick_get() - t0) * 1000 / 5000;/* 2) 内存分配:1000 次 malloc+free */t0 = rt_tick_get();for (int i = 0; i < 1000; i++) {    void *p = rt_malloc(64);    if (p) rt_free(p);}bench_mem_us = (rt_tick_get() - t0) * 1000 / 1000;/* 3) 屏幕全屏填充耗时 */t0 = rt_tick_get();ili9341_fill(COLOR_BLACK);bench_fill_ms = rt_tick_get() - t0;

4.3 GPIO 直接寄存器写(关键性能优化)

LCD 驱动核心是把 SPI 字节通过 GPIO 一位一位”打”出去。最初使用 RT-Thread 标准 API:

/* 慢版:每次翻转引脚都要走一遍 RT-Thread 框架函数 */#defineSCK_H() rt_pin_write(PIN_SCK, PIN_HIGH)#defineSCK_L() rt_pin_write(PIN_SCK, PIN_LOW)

rt_pin_write 内部要做:参数检查 → 查 pin 表 → 调 NXP SDK 的 GPIO_PinWrite → 操作寄存器。一次翻转大约 3 条以上指令。

后来改成直接写 GPIO 寄存器:

/* 快版:直接往 GPIO 寄存器写一位即可 */#define SCK_H() (GPIO1->PSOR = (1U << 9))#define SCK_L()  (GPIO1->PCOR = (1U << 9))#define SDA_H()  (GPIO1->PSOR = (1U << 8))#define SDA_L()  (GPIO1->PCOR = (1U << 8))

PSOR(Port Set Output Register):写入 1 把对应引脚拉高

PCOR(Port Clear Output Register):写入 1 把对应引脚拉低

这一步省去了所有中间层,单个 GPIO 翻转从 ~3 条指令降到 1 条指令。配合 static inline 让编译器把 spi_write_byte 内联到 fill_rect 的循环里,进一步消除函数调用开销。

4.4 ILI9341 驱动主要接口

voidili9341_init(void); /* 初始化 */voidili9341_fill(uint16_tcolor); /* 全屏填色 */voidili9341_fill_rect(x,y, w, h,color); /* 矩形填充 */voidili9341_draw_pixel(x,y,color); /* 画点 */voidili9341_draw_string(x,y, str, fg, bg); /* 12×16 像素 ASCII */voidili9341_backlight(int on); /* 背光控制 */

字体使用 5×7 column-major bitmap,渲染时 2× 放大到 12×16 像素,方便 240 像素宽的屏幕一行能容纳 20 个字符。

5 性能测试数据(实测)

5.1 GPIO 写法的速度对比(同板实测)

通过自定义 MSH 命令 bench_gpio,在同一块板子、同样多线程负载下,先后跑两遍全屏填充——一次用 rt_pin_write,一次用直接 GPIO 寄存器——直接对比:

a36f2d9a-5fc8-11f1-ab55-92fbcf53809c.pnga382f8a2-5fc8-11f1-ab55-92fbcf53809c.png

关键观察:仅仅把 rt_pin_write(pin, val) 换成 GPIO1->PSOR = bit,整体吞吐量就提升到原来的 10 倍。这说明在 GPIO 翻转密集的场景下,框架函数调用开销才是真正的瓶颈,而不是 GPIO 硬件本身。

5.2 完整基准(启动单线程 vs MSH 多线程)

通过对比启动时(仅 main 线程)和系统跑起来后(main + info + anim + led + tshell + tidle 共 6 个线程并发)下的同一组测试,量化”多线程开销”:

a3937a42-5fc8-11f1-ab55-92fbcf53809c.pnga3af920e-5fc8-11f1-ab55-92fbcf53809c.png

5.3 数据分析

线程切换 6 → 8 µs:Cortex-M33 在 RT-Thread 调度下表现优秀,符合实时系统要求。多线程下增加 2 µs 是因为调度器要扫描更多的就绪队列。

malloc+free 72 → 95 µs:Small Heap 算法本身性能稳定,多线程下增加约 30% 是因为内存池有互斥保护,存在锁开销。

全屏刷新 460 → 614 ms:软件 SPI 在 240×320 大屏上仍是瓶颈,多线程下慢约 33% 是因为绘制中途被其他线程抢占。但通过直接寄存器写已将吞吐量提升到 127–167 px/ms。理论硬件 SPI(10 MHz LPSPI1 + DMA)可降至 ~120 ms。

6 踩过的坑

6.1 ILI9341 颜色反相

裸初始化序列后整个屏幕显示反色(黑变白、彩色全部反转)。原因:本面板(ATK-MD0240 V1.2)出厂默认开启色反转。

解决:在 init 序列中加入 0x21(INVON)命令,反向显示就回到正常色彩。

6.2 屏幕方向调试

ILI9341 通过 MADCTL(0x36)寄存器控制扫描方向,4 位组合(MY/MX/MV/BGR)共 16 种。本面板 FPC 排线方向特殊,调了 4 次才正:

a3c34358-5fc8-11f1-ab55-92fbcf53809c.png

6.3 BSP 硬件 SPI 配置坑

最初想用 LPSPI1 硬件 SPI 加速,启用 BSP_USING_SPI1 后编译能过、串口能跑(fill 也确实变成了 292 ms),但屏幕完全没反应、只有微弱背光。

排查过程:

怀疑 SPI 完全无信号 → 但 fill 时间真的在变(说明 LPSPI 寄存器在工作)

翻 BSP 的 pin_mux.c:LPSPI1_SCK = P2_12, LPSPI1_SDO = P2_13

翻 FRDM-MCXA156 Quick Start Guide 引脚图:P2_12 是 Arduino D13(OK),但P2_13 没引到任何排针上

Arduino D11/SDO 在板上其实是P3_15(P2_13 是 FRDM motor control 用途,悬空)

也就是说 NXP 的 BSP 把 LPSPI1 配错了引脚——硬件 SPI 走的是死路。

解决方案:退回软件 SPI,用直接 GPIO 寄存器写优化掉瓶颈,效果已经够用(460 ms)。如果以后真要上硬件 SPI,必须修改 pin_mux.c 把 LPSPI1_SDO 重新映射到 P3_15(同时也需要查 MCXA156 数据手册确认 P3_15 的 alt mux 值)。

6.4 启动时 fill 重复调用

最初版本在 splash → benchmark → draw_static_ui 各做了一次 ili9341_fill(COLOR_BLACK),相当于全屏刷了 3 遍。在还没优化的早期版本下启动等待 ~45 秒。

优化:splash 改成只填一小块文字区域;draw_static_ui 因为 benchmark 已经把屏幕填黑了,直接去掉重复的 fill。startup 砍到只跑一次全屏 fill。

6.5 空闲线程栈溢出

RT-Thread 默认 IDLE_THREAD_STACK_SIZE = 256 字节,在开了 ULOG/调试输出之后会栈溢出。改成 1024 字节后稳定。

7 MSH 命令行验证

通过串口连接(115200, 8N1)使用 FinSH/MSH:

a3d4432e-5fc8-11f1-ab55-92fbcf53809c.png

8 项目目录

frdm-mcxa156/├── applications/│ ├── main.c ← 主程序、多线程、基准测试│ ├── drv_ili9341.c ← ILI9341 软件 SPI 驱动(直接寄存器写)│ ├── drv_ili9341.h ← LCD 接口头文件│ ├── drv_st7735.c/h ← 旧版 ST7735 驱动(保留对照)│ └── SConscript├── board/├── drivers/└── rtconfig.h ← RT-Thread 内核配置

9 心得与总结

RT-Thread 上手友好:scons 构建 + Kconfig 裁剪,配置直观,内核 API 与 Linux 风格相近。多线程编程逻辑清晰,rt_thread_create + rt_thread_startup 一目了然。

MCXA156 + RT-Thread 适配总体良好:NXP 官方 SDK 已集成,GPIO/UART 驱动开箱即用。但 BSP 里 LPSPI1 的引脚映射存在问题(P2_13 没引出),用之前要先核对 BSP 的 pin_mux.c 是不是和实际板子的 Quick Start Guide 引脚图对得上。

抽象层的代价:RT-Thread 的 rt_pin_write API 通用、可移植、可读性好,但在大量 GPIO 翻转的场景下函数调用开销显著。本项目通过直接写 GPIO 寄存器(PSOR/PCOR),像素吞吐量提升 10.1×(同板实测,bench_gpio 命令)。这是嵌入式开发中典型的 “易用性 vs 性能” 权衡——在性能关键路径上为速度让步、其他地方仍用标准 API 保持可移植性。

多线程编程关键点:

线程栈大小要根据实际调用栈预留充足;

共享外设(LCD/SPI 总线)需考虑互斥——本项目通过线程优先级隔离 + 同步绘制函数避免冲突;

不能假设 mdelay(30) 真的等 30 ms 就能跑出 30 ms 一帧的动画——绘制本身耗时也得算进去。

性能瓶颈定位的方法论:通过 bench 命令把”线程切换 / 内存分配 / 屏幕刷新”三个指标量化测出来,问题就显而易见——CPU 没瓶颈、内存没瓶颈,瓶颈完全在 SPI 这条窄通道上。这就为下一步优化(直接寄存器 → 硬件 SPI → DMA)提供了清晰的方向。

遇到的坑汇总:

空闲线程栈默认 256 字节过小 → 改 1024;

ILI9341 颜色反相 → 加 INVON(0x21);

MADCTL 调了 4 次才把方向调正(0xE8 = MY+MX+MV+BGR);

BSP 硬件 SPI 配错引脚(P2_13 未引出)→ 退回软件 SPI 优化;

启动时全屏 fill 调用了 3 次 → 优化到 1 次;

串口助手默认不发换行 → 勾选”发送新行”否则 MSH 不响应。

10 附录:源代码

源代码.zip

main.c — 主程序、多线程、基准测试

drv_ili9341.c — 当前驱动(直接寄存器写软件 SPI)

drv_ili9341.h — LCD 接口

rtconfig.h — RT-Thread 内核配置

旧版-ST7735/ — 旧版 ST7735 驱动(保留对照)

11 《恩智浦FRDM-MCX A366开发实践指南》贡献名单

RT-Thread社区携手恩智浦半导体联合发起FRDM-MCXA366 开发板评测活动《恩智浦FRDM-MCX A366开发实践指南》详细列出了各个内容板块及其贡献者。在此,衷心感谢所有小伙伴的支持与贡献!

a3e7ce62-5fc8-11f1-ab55-92fbcf53809c.png

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

    关注

    36

    文章

    4640

    浏览量

    178593
  • 恩智浦
    +关注

    关注

    14

    文章

    6149

    浏览量

    155330
  • SPI
    SPI
    +关注

    关注

    17

    文章

    1910

    浏览量

    102808
  • RT-Thread
    +关注

    关注

    32

    文章

    1669

    浏览量

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

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    LED显示屏的灰阶和刷新率技术解析

    决定一块LED显示屏图像质量的主要因素有:均匀性、灰阶、刷新率、对比度、色域和色温。
    发表于 08-18 11:02 6672次阅读
    LED<b class='flag-5'>显示</b>屏的灰阶和<b class='flag-5'>刷新率</b><b class='flag-5'>技术</b>解析

    STM32MP135使用HAL库驱动2.4寸刷新率巨低的原因?

    使用STM32MP135 驱动2.4寸 LCD屏幕,刷新率巨低; 测试条件:(HAL库) 1、硬件SPI;2、IO操作使用寄存器配置;3、SPI时钟达到16M;
    发表于 03-07 07:17

    使用STM32MP135驱动2.4寸 LCD屏幕,刷新率巨低为什么?

    使用STM32MP135 驱动2.4寸 LCD屏幕,刷新率巨低; 测试条件:(HAL库) 1、硬件SPI;2、IO操作使用寄存器配置;3、SPI时钟达到16M;
    发表于 05-20 06:51

    什么是波形刷新率? 影响波形刷新率的因素有哪些?

    什么是波形刷新率?影响波形刷新率的因素有哪些?对刷新率有重要影响的死区时间是如何计算出来的呢?
    发表于 04-29 06:03

    示波器刷新率怎么测量

    波形刷新率再高屏幕都是可以显示的过来。图1波形合成器2、准备工具准备工具:被测示波器、测量仪器(示波器、逻辑分析仪)、20Mhz信号源。示波器每采样一帧波形,都会在Trig out输出一个脉冲信号,这个
    发表于 05-12 14:44

    三个简单步骤我就把LCD刷新率逼到了理论极限

    Arm-2D的都知道),Arm-2D有一个小工具就是会给我们计算FPS和LCD Latency的时间,并在屏幕下方用绿色的小字显示出来  其中,FPS就是我们常说的刷新率(单位为Hz),即芯片每秒
    发表于 11-22 15:27

    TouchGfx屏幕刷新率可以更改吗?

    我使用的 TouchGfx 是 4.15.0 版本,屏幕刷新率是 60Hz。现在我把项目移到4.17.0版本的TouchGfx,屏幕刷新率达到了70Hz。用户可以更改此参数吗?
    发表于 12-28 06:54

    LCD的帧率与刷新率有什么区别 ?

    LCD的帧率与刷新率有什么区别
    发表于 10-09 06:53

    屏幕刷新率越高越好吗?小心被刷新率参数骗了

    屏幕刷新率越高越好吗?大家都认为屏幕刷新率越高越养眼了,像普通电视只到60,闪烁感太强,当然对眼睛不好.LCD原理不同于CRT,不要求太高的刷新率。如果你的
    发表于 09-13 09:54 7.7w次阅读

    144Hz屏幕刷新率来了,高刷新率屏幕手机的新体验

    从去年开始,90Hz 屏幕刷新率开始流行,一度引领了行业旗舰机搭载高刷新率屏幕的风潮。
    的头像 发表于 03-05 17:23 7755次阅读

    刷新率屏幕,选择OLED还是LCD

    昨天跟大家普及了一下高刷新率,今天我们来说一下不同材质屏幕的高刷新率有何不同。进入2020年之后,高刷新率已经越来越普及了。因为vivo的iQOO系列不断刷新着高刷屏手机的价格底线,导
    发表于 07-18 10:41 6492次阅读

    显示器上不了高刷新率是怎么回事 显示器上不了高刷新率的解决方案

    电竞显示器早就从高端变成了白菜价,1000元上下就能入手不错的产品,资金富裕点的话,加点钱2K高刷新率,甚至是200Hz+的刷新率也能轻易买到,现在还出了360Hz的刷新率。不过很多小
    的头像 发表于 09-27 11:11 2.6w次阅读

    什么是LED显示屏的刷新率?有几种?LED显示屏的刷新率与什么有关?

    LED显示屏的刷新率又叫“视觉刷新频率”、“刷新频率”,是指每秒钟显示画面被显示屏重复
    发表于 08-24 09:37 9557次阅读

    led显示刷新率高好还是低好

    LED显示屏的刷新率又叫“视觉刷新频率”、“刷新频率”,是指每秒钟显示画面被显示屏重复
    发表于 10-20 09:34 3336次阅读

    led屏亮度和刷新率的关系 led屏刷新率高低有什么影响

    led屏亮度和刷新率的关系 led屏刷新率高低有什么影响  LED显示屏已成为了现代生活不可或缺的一部分,其广泛应用于大型活动、商业广告、电视和计算机等领域。然而,很少有人深入了解L
    的头像 发表于 12-11 17:07 6802次阅读