本项目为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开发实践指南》贡献名单

实物运行: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 排针:

板载 LED:P3_12(板载三色 LED 中的红色一路)。
引脚定义见 drv_ili9341.c 顶部宏。
3 软件架构
3.1 线程划分

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

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 寄存器——直接对比:


关键观察:仅仅把 rt_pin_write(pin, val) 换成 GPIO1->PSOR = bit,整体吞吐量就提升到原来的 10 倍。这说明在 GPIO 翻转密集的场景下,框架函数调用开销才是真正的瓶颈,而不是 GPIO 硬件本身。
5.2 完整基准(启动单线程 vs MSH 多线程)
通过对比启动时(仅 main 线程)和系统跑起来后(main + info + anim + led + tshell + tidle 共 6 个线程并发)下的同一组测试,量化”多线程开销”:


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 次才正:

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:

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开发实践指南》详细列出了各个内容板块及其贡献者。在此,衷心感谢所有小伙伴的支持与贡献!

-
lcd
+关注
关注
36文章
4640浏览量
178593 -
恩智浦
+关注
关注
14文章
6149浏览量
155330 -
SPI
+关注
关注
17文章
1910浏览量
102808 -
RT-Thread
+关注
关注
32文章
1669浏览量
45598
发布评论请先 登录
LCD显示中的SPI瓶颈分析与刷新率优化 | 技术集结
评论