返回项目列表

思澈科技 · 嵌入式软件开发实习

USB 键盘副屏

从 Windows 显示帧到 LCD 上屏:USB 驱动适配、设备端组帧与内存同步。

SF32LB586RT-ThreadUSB Bulk OUTRX DMAPSRAM

USB 副屏实机演示

Windows 桌面画面通过 USB 传输至设备屏幕,展示副屏的实际显示效果。

USB 键盘副屏实机演示

用 Vibe Coding 设计键盘副屏原型

基于 KeySim 工程,我借助 AI 辅助编程(Vibe Coding)将自己的产品交互想法快速转化为可操作的网页原型,并持续调整界面与操作流程。原型包含实体按键与虚拟按键联动、键位参数、灯光调节和壁纸预览,用于直观呈现键盘与副屏的协同体验。

键盘 + 副屏交互原型 在新窗口体验

项目背景与承担工作

面向客户对键盘屏幕芯片与显示功能的需求,接手键盘屏幕交互仿真设计,并负责 USB 副屏的可行性验证、主机驱动适配及设备端功能实现。通过 USB 将 Windows 桌面画面传到 SF32LB586,使键盘屏幕能够作为电脑副屏使用。

基于已有 Windows IDD 驱动和 SiFli SDK 开发,工作集中在传输协议适配、接收状态管理、DMA 接收、帧缓冲布局和显示同步,完成驱动与固件的联合调试及性能优化。

主机到屏幕的数据链路

Windows IDD 编码 JPEG,经 USB Bulk OUT 传入 PSRAM1 接收帧池,校验后由 JPEGDEC 与后处理器输出 RGB565 到 PSRAM2 双帧缓冲,最终由 LCDC 通过 DPI 显示。
USB 接收与 LCD 扫描使用不同 PSRAM;硬件解码直接输出 RGB565,省去完整 YUV 中间帧。

USB 协议与接收驱动

枚举参数与主机驱动适配

适配 WinUSB 设备枚举与 Bulk OUT 数据通路,通过设备能力字符串约定分辨率、JPEG 质量和目标帧率。主机侧在 IDD 取帧链路中接入固定 Q50 编码,修正图像行跨度处理,增加编码输出和发送缓冲区长度检查,避免超长帧越界拷贝。

跨包组帧与异常恢复

沿用 16 字节帧头+变长图像负载,设备端按声明长度组帧,不将 USB 包边界当作图像帧边界。接收过程中校验帧类型、坐标、尺寸和负载长度;遇到无效头时保留末尾 15 字节,逐字节寻找下一个有效帧头,避免一次错位导致后续画面持续异常。

送入解码器前,再检查 JPEG 起止标记、baseline 格式和内部图像尺寸;异常帧丢弃后继续接收。该链路限定完整 1024×600 JPEG 帧,减少无效数据进入硬件解码器的机会。

512 字节 RX DMA 与接收时序

使用 USB 控制器 Mode 0 单包 RX DMA,将 FIFO 数据搬入对齐的 512 字节接收缓冲区,DMA 完成后交给组帧逻辑。分离接收缓冲注册和显示工作线程启动,使已连接主机的上电场景也能按顺序准备接收池与 LCD 缓冲。

PSRAM 布局、Cache 与双缓冲

区域存放数据实现要点
PSRAM1USB 接收帧池接收与组帧使用固定帧槽,独立于 LCD 扫描访问。
PSRAM2两块 RGB565 framebuffer解码写入与显示交替使用,复用前等待目标缓冲完成。
对齐接收缓冲512 B USB 数据包按 Cache line 对齐,协调 CPU 与 DMA 对同一内存的访问。
  • 缓冲区所有权:解码前等待目标 framebuffer 空闲,补充显示同步接口声明并修正等待超时返回,避免覆盖 LCDC 正在读取的数据。
  • Cache 一致性:结合 SDK 的输入 clean/输出 invalidate 流程接入硬件解码,对 CPU 绘制的 OSD 区域执行必要的 Cache clean,保证 LCDC 读到最新像素。
  • 解码输出对齐:JPEG 后处理器按 16 行块写出,将 600 行向上对齐到 608 行分配空间。单块缓冲为 1,245,184 字节,显示区域仍为 1024×600。
  • LCD 显示链路:复用 SDK 的 LCDC 与 DPI/RGB 屏驱动,接入双 framebuffer 提交和完成通知;硬件解码直接写入待显示缓冲。

通过分段计时定位性能瓶颈

在主机端记录取帧、编码、USB 请求提交与完成耗时,在设备端记录收帧跨度、解码、缓冲等待和 LCD 提交耗时。对比 PIO 与 RX DMA、不同缓冲布局及传输格式,确认原始 RGB565 全帧传输的数据量是主要限制之一。

指标RGB565 整帧JPEG Q50 整帧
分辨率1024×6001024×600
典型每帧传输量约 1.23 MB约 35–74 KB
测试帧率约 10–12 FPS约 30–40 FPS
设备硬件解码耗时无需 JPEG 解码约 10–11 ms

最终采用主机 JPEG 压缩、设备 JPEGDEC+后处理器直接输出 RGB565 的方案,降低 USB 传输负担。开发中也对 ROI 脏刷新、连续多包 DMA 做过对照实验;综合数据完整性与实际收益,保留单包 RX DMA 和全帧 JPEG 作为独立副屏实现。

副屏 APP 开发与集成

1024×600完整画面传输与显示
30–40 FPSJPEG Q50 典型测试帧率
94%–97%典型单帧传输量降低

数据来自独立副屏版本的实机开发记录;帧率随画面内容波动,原 RGB565 全帧方案约为 10–12 FPS。

在网页原型梳理交互流程的基础上,我进一步将副屏能力拆分为传输组件与 LVGL APP,使界面交互与底层收帧、解码逻辑分离,便于独立维护和后续复用。

USB 工作线程负责接收和解码,GUI 线程获取最新完成帧;通过帧槽引用管理协调缓冲区使用,避免并发覆盖。结合 APP 页面生命周期,退出页面时暂停接收与解码,再次进入时恢复,兼顾显示响应、内存使用与运行稳定性。

上方 30–40 FPS 对应独立副屏版本。LVGL APP 属于另一条显示路径,其 GUI 取帧计数与 LCD 实际完成显示的帧数分别统计。

RTOS 线程协作:互斥量与信号量设计

在 RT-Thread 上,我将 USB 收帧与 JPEG 解码交给独立工作线程,界面更新由 LVGL 定时器在 GUI 线程中完成。工作线程的优先级设置为比 GUI 线程低一级,让界面有机会优先消费已完成帧;收帧采用 20 ms 超时等待,兼顾数据处理与退出响应。

  • 互斥量保护共享状态:使用 s_lock 统一保护帧槽状态、待显示帧索引和统计信息,协调工作线程与 GUI 线程对共享数据的访问。预留帧槽后先释放锁,再进行 JPEG 解码,完成后重新加锁发布结果,缩短临界区,减少耗时解码对界面取帧的阻塞。
  • 帧槽状态管理缓冲区所有权:通过 FREE → WRITING → PENDING → VISIBLE 描述帧槽流转。GUI 获取最新帧后保留其使用权,完成界面刷新后再释放上一帧;新帧到来时可回收尚未显示的待处理帧,优先展示最新画面,避免覆盖 GUI 仍在使用的像素数据。
  • 信号量同步线程退出:将 s_worker_done 初始化为 0,由工作线程退出循环后释放。停止副屏时先关闭接收并清除运行标志,再以 2 秒超时等待完成通知,收到通知后才清理接收队列和帧槽;等待超时则返回错误,避免在线程仍运行时直接重置共享资源。
  • 处理重复启停与异常路径:重新启动前以非阻塞方式清空残留完成通知;若上次停止超时,先重试清理再开启接收。初始化或线程启动失败时回收已创建资源,将同步机制与 APP 的进入、暂停和退出流程衔接起来。

使用的技术与调试手段

  • C / C++
  • RT-Thread
  • 互斥量 / 信号量
  • 线程协作与生命周期
  • Windows IDD
  • WinUSB / Bulk OUT
  • USB RX DMA
  • PSRAM / DCache
  • JPEGDEC
  • LCDC / DPI
  • 双 framebuffer
  • 串口日志与分段计时