思澈科技 · 嵌入式软件开发实习
USB 键盘副屏
从 Windows 显示帧到 LCD 上屏:USB 驱动适配、设备端组帧与内存同步。
USB 副屏实机演示
Windows 桌面画面通过 USB 传输至设备屏幕,展示副屏的实际显示效果。
用 Vibe Coding 设计键盘副屏原型
基于 KeySim 工程,我借助 AI 辅助编程(Vibe Coding)将自己的产品交互想法快速转化为可操作的网页原型,并持续调整界面与操作流程。原型包含实体按键与虚拟按键联动、键位参数、灯光调节和壁纸预览,用于直观呈现键盘与副屏的协同体验。
项目背景与承担工作
面向客户对键盘屏幕芯片与显示功能的需求,接手键盘屏幕交互仿真设计,并负责 USB 副屏的可行性验证、主机驱动适配及设备端功能实现。通过 USB 将 Windows 桌面画面传到 SF32LB586,使键盘屏幕能够作为电脑副屏使用。
基于已有 Windows IDD 驱动和 SiFli SDK 开发,工作集中在传输协议适配、接收状态管理、DMA 接收、帧缓冲布局和显示同步,完成驱动与固件的联合调试及性能优化。
主机到屏幕的数据链路
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 与双缓冲
| 区域 | 存放数据 | 实现要点 |
|---|---|---|
| PSRAM1 | USB 接收帧池 | 接收与组帧使用固定帧槽,独立于 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×600 | 1024×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 开发与集成
数据来自独立副屏版本的实机开发记录;帧率随画面内容波动,原 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
- 串口日志与分段计时