1. 项目概述:当显示器“插”在USB口上
如果你和我一样,桌面空间寸土寸金,或者经常需要在多台设备间切换一个辅助显示区域,那么一块2.1英寸的USB便携显示器绝对是个“宝藏”小工具。它不像传统显示器那样需要独立的电源和视频线缆,仅凭一根USB线就能从你的电脑、笔记本甚至某些支持USB视频输出的开发板上“偷”出一块屏幕来。
这个项目的核心,就是围绕一块2.1英寸的LCD屏幕,构建一个完整的USB视频显示解决方案。它不仅仅是把屏幕点亮那么简单,背后涉及到USB协议的选择、视频数据的压缩与传输、驱动程序的适配,以及最终在Windows系统上稳定呈现图像的完整链路。市面上虽然有成品,但自己动手从零搭建,不仅能彻底吃透从“像素”到“画面”的每一个环节,更能根据需求进行深度定制,比如将其改造成一个系统状态监控屏、一个便携的代码调试终端,或者一个超迷你的相框。
2. 核心方案选型与设计思路
2.1 显示核心:屏幕驱动与接口抉择
2.1英寸的LCD屏,分辨率常见的有320x240或480x272。对于USB带宽而言,即便是480x272@60Hz的RGB888(24位色)原始数据,理论带宽也达到了48027260*3 ≈ 22.3 Mbps,这还没算上行场同步等控制信号。直接通过USB传输原始RGB数据对USB2.0的全速(12Mbps)模式压力巨大,对高速(480Mbps)模式虽可行,但会独占大量带宽且方案复杂。
因此,成熟的方案普遍采用“桥接芯片”策略。核心思路是:选择一款内置显示控制器和USB设备控制器的单芯片方案。这类芯片能在本地驱动LCD屏,并通过USB接收经过压缩或封装的视频指令/数据,从而大幅降低对USB持续带宽的要求。
主流芯片方案对比:
| 芯片方案 | 核心优势 | 适用场景 | 开发复杂度 |
|---|---|---|---|
| CH552G | 成本极低,8位8051内核,自带USB设备控制器和LCD驱动,可通过Bit-Banging模拟8080/SPI接口驱动小屏。 | 低分辨率(320x240以下)、低刷新率(<30Hz)、对色彩和速度要求不高的静态或简单动态显示。 | 中等,需编写固件处理USB协议和图形渲染。 |
| STM32F103(配合USB库) | 资源丰富,生态成熟,有成熟的USB CDC/HID库,可通过FSMC或SPI驱动屏幕。 | 中等复杂度项目,需要较强处理能力实现自定义图形界面或简单视频解码。 | 较高,需要较强的嵌入式开发能力。 |
| 专用USB显示桥接IC(如MCP2210, FT232H+FPGA) | 性能强,带宽高,通常支持显示端口(DisplayPort Alt Mode)或自定义高速协议,接近即插即用。 | 追求高分辨率、高刷新率、即插即用的成品方案。 | 低(使用现成模块)或极高(自研FPGA逻辑)。 |
实操心得:对于入门和大多数DIY场景,CH552G是一个性价比极高的起点。它价格低廉(仅几元人民币),且社区有大量驱动小屏的示例。虽然性能有限,无法流畅播放视频,但用于显示系统监控信息、静态图片、文字终端等绰绰有余,能让你以最小成本理解整个USB显示链路的运作。
2.2 通信协议:如何让Windows“认识”这块屏
确定了硬件核心,下一个关键问题是:Windows如何将这块外接设备识别为一台“显示器”,而不是一个普通的USB串口或存储设备?
这里主要有两条技术路径:
模拟标准显示设备:使设备在Windows设备管理器中显示为“通用即插即用监视器”或类似。这通常需要芯片支持并实现USB Video Class(UVC)或DisplayLink等标准协议。
- UVC:本是为USB摄像头设计,但也可用于传输静态帧。实现较复杂,且通常用于动态视频流,对于静态画面更新效率不高。
- DisplayLink:一家公司的私有技术,需要安装特定驱动,性能好但芯片通常较贵且封闭。
- Windows USB Display Adapter:微软在Windows 10/11中引入的框架,允许通过USB-C的DisplayPort Alt Mode或特定的MIPI桥接器实现显示扩展。这对硬件有特定要求。
虚拟串口+自定义客户端软件:这是更灵活、更DIY友好的方案。让USB设备被识别为一个**USB转串口(USB to UART)**设备(例如,使用CH552模拟CDC/ACM设备,或使用FT232R、CP2102等专用桥接芯片)。然后,在Windows上运行一个自己编写的客户端软件。这个软件负责:
- 捕获屏幕特定区域(或生成自定义界面)。
- 将图像数据压缩(如转换为RLE、JPEG或自定义的二进制格式)。
- 通过虚拟串口将压缩后的图像数据发送给USB设备。
- USB设备端的MCU接收数据,解压并刷新到LCD屏幕上。
方案抉择:对于2.1寸小屏DIY项目,“虚拟串口+客户端软件”方案是实践上的首选。原因如下:
- 驱动通用:USB转串口驱动(如CH340、CP210x、FTDI)在Windows上几乎是免驱或极易安装,兼容性极佳。
- 开发可控:两端(PC软件和MCU固件)的逻辑完全自主,可以自由定义数据协议、压缩算法、更新策略。
- 资源节省:MCU端无需实现复杂的标准显示协议,只需处理串口数据和屏显刷新,对MCU要求低。
- 功能扩展:可以轻松在协议中增加触摸屏(如果屏幕带触摸)数据回传、按钮状态读取等功能。
2.3 供电设计:一根USB线的能量
USB端口除了传输数据,另一个核心功能是供电。标准USB 2.0端口能提供5V/500mA的电流。一块2.1寸的LCD屏及其驱动电路,功耗通常在200mA-400mA之间,具体取决于背光亮度。
设计要点:
- 电源路径:USB的5V VBUS直接接入板载的LDO(低压差线性稳压器),如AMS1117-3.3,为MCU和屏幕的逻辑部分提供稳定的3.3V电源。
- 背光供电:屏幕背光LED通常需要较高的电压(可能12V或以上)和恒定电流驱动。这里需要一个升压(Boost)恒流电路。可以使用专门的背光驱动芯片(如OCP8155),或者用MCU的PWM信号控制一个MOSFET开关,结合电感、二极管、电容搭建一个简单的Boost电路来驱动背光。务必根据屏幕规格书设置合适的限流电阻,避免烧毁背光LED。
- 去耦电容:在MCU的VCC附近、LDO的输入输出端、以及屏幕的电源引脚处,就近放置足够容量的瓷片电容(如100nF)和电解电容(如10uF),以滤除电源噪声,这对显示稳定性至关重要。
3. 硬件设计与关键电路解析
3.1 主控电路与USB接口
以CH552G为例,其USB接口设计非常简单。
- USB Connector:使用Micro-USB或USB-C插座。连接器的VBUS接电源电路,D+和D-分别连接CH552G的UD+和UD-引脚。
- 上拉电阻:在UD+(对于全速设备)上通过一个1.5kΩ电阻上拉到3.3V,这是USB设备枚举所必需的,用于告知主机这是一个全速设备。
- 晶振:CH552G内部有RC振荡器,但对于需要稳定USB通信的场景,建议外接12MHz晶振,连接XI和XO引脚,并搭配两个20pF的负载电容接地。
- 启动配置:CH552的RST引脚需要上拉,并确保在上电时有正确的时序。通常将RST引脚通过10kΩ电阻上拉到VCC,同时连接一个100nF电容到地,构成简单的上电复位电路。
3.2 屏幕接口与驱动电路
2.1寸屏常见接口有SPI和8080并行接口。
- SPI接口:引脚少(CS, SCK, MOSI, DC, RST),节省IO,但刷新率较低,适合静态或慢速更新。CH552的硬件SPI(MOSI/P1.5, MISO/P1.6, SCK/P1.7)可以轻松驱动。
- 8080并行接口:需要数据总线(D0-D7或D0-D15)、写使能(WR)、读使能(RD)、命令/数据选择(DC)、片选(CS)和复位(RST)。刷新率高,速度快,但占用大量IO。CH552可以通过Bit-Banging模拟时序,或者使用并口模式(如果支持)。
连接示例(SPI ILI9341屏幕):
CH552G P1.5 (MOSI) -> LCD SDI (MOSI) CH552G P1.7 (SCK) -> LCD SCK CH552G P3.2 (自定义GPIO) -> LCD DC CH552G P3.3 (自定义GPIO) -> LCD CS CH552G P3.4 (自定义GPIO) -> LCD RST背光控制引脚(BL)连接到一个GPIO,通过PWM控制亮度。
3.3 电源与背光驱动电路
这是保证稳定工作的关键。
- 3.3V LDO电路:AMS1117-3.3的Vin接USB 5V,Vout输出3.3V,前后各加一个10uF电解电容。
- 背光Boost电路:假设背光需要12V。可以使用芯片如MT3608。其典型电路是:USB 5V输入,通过电感和MT3608升压,输出通过一个采样电阻(如0.5Ω)接地,将电流信号反馈给芯片的FB引脚,实现恒流。背光LED串接在输出和采样电阻之间。用一个MOSFET(由MCU的PWM引脚控制)串联在LED回路中,实现开关和调光。
注意事项:在焊接和调试电源部分时,务必先断开屏幕连接,用万用表测量各路输出电压是否正确、稳定,特别是背光电压,避免因电压过高或短路瞬间损坏昂贵的屏幕。
4. 固件开发:让芯片“动”起来
4.1 开发环境搭建与基础工程
CH552可以使用基于Keil C51的开发环境,或者更轻量的SDCC(小型设备C编译器)。我推荐使用WCH官方提供的开发包,里面包含了库函数、示例程序和编程工具。
- 安装工具链:下载并安装WCH的CH55x系列开发包,其中包含头文件、库文件和编程软件(WCHISPTool)。
- 创建工程:以Keil为例,新建一个C51工程,选择CH552型号,添加官方库文件(如
CH552.H,DEBUG.H,USB.C等)。 - 配置时钟:在代码初始化部分,将系统时钟配置为使用外部12MHz晶振,这是USB通信稳定所必需的。
SAFE_MOD = 0x55; SAFE_MOD = 0xAA; CLOCK_CFG = CLOCK_CFG & ~MASK_SYS_CK_SEL | 0x06; // 选择外部12MHz晶振 SAFE_MOD = 0x00;
4.2 USB CDC设备模拟与通信实现
目标是让电脑将CH552识别为一个虚拟串口。
- 初始化USB:调用官方库的
USBDeviceInit()函数,并实现相关的回调函数,如EP1_OUT_Callback()(接收PC数据)和EP1_IN_Callback()(发送数据完成)。 - 配置描述符:这是USB设备的“身份证”。需要修改官方示例中的描述符,确保设备类(bDeviceClass)、接口类(bInterfaceClass)设置为
0x02(Communications Device Class),并指定CDC数据接口。详细描述符结构可参考WCH的CDC示例。 - 数据收发:在
EP1_OUT_Callback中,将主机发来的数据(即PC端串口助手发送的数据)拷贝到自定义的接收缓冲区。在主循环中,检查接收缓冲区,如果有数据,就进行解析(如图像数据包)。 - 发送数据:当MCU需要向PC发送数据(如触摸坐标)时,将数据填充到USB发送缓冲区,然后调用
UEP1_T_LEN设置长度并UEP1_CTRL ^= bUEP_T_TOG切换同步标志位,启动发送。
4.3 屏幕驱动与图形库集成
- 底层驱动函数:编写屏幕的初始化函数
LCD_Init(),以及基本的写命令LCD_Write_Cmd()和写数据LCD_Write_Data()函数。对于SPI屏幕,还需要实现SPI发送函数。 - 设置显示窗口:编写
LCD_SetWindow(x_start, y_start, x_end, y_end)函数,用于指定后续像素数据填充的区域。 - 像素填充:实现
LCD_WriteData_Color(color, length)函数,用于连续填充指定颜色的像素。这是刷屏或显示图片的基础。 - 简易图形库:基于以上函数,可以构建更高级的API,如:
LCD_DrawPixel(x, y, color)LCD_DrawLine(x0, y0, x1, y1, color)LCD_DrawRectangle(x0, y0, x1, y1, color)LCD_Fill(x0, y0, x1, y1, color)LCD_DrawChar(x, y, chr, font, color, bg_color)以及LCD_DrawString(...)
- 图像显示:实现一个解析并显示图像数据的函数。例如,定义一种简单的二进制格式:先发送图像左上角坐标和宽高,然后连续发送每个像素的RGB565颜色值(2字节)。MCU收到数据后,调用
LCD_SetWindow和LCD_WriteData_Color进行刷屏。
4.4 主程序逻辑与协议解析
主循环的核心是状态机,负责协调USB通信和屏幕刷新。
void main() { sys_init(); // 初始化时钟、GPIO等 LCD_Init(); // 初始化屏幕 USBDeviceInit(); // 初始化USB EA = 1; // 开启总中断 LCD_Fill(0, 0, LCD_WIDTH, LCD_HEIGHT, BLACK); // 清屏 while(1) { if(usb_rx_flag) { // 收到USB数据包标志 usb_rx_flag = 0; parse_image_packet(usb_rx_buffer, usb_rx_len); // 解析图像数据包 } // 可以在这里加入其他任务,如按键扫描、定时刷新等 } } void parse_image_packet(uint8_t *buf, uint16_t len) { // 假设协议格式:0xAA 0x55 [CMD] [Data...] if(buf[0]==0xAA && buf[1]==0x55) { uint8_t cmd = buf[2]; switch(cmd) { case CMD_DRAW_IMAGE: // 解析坐标、尺寸,然后刷屏 uint16_t x = (buf[3]<<8)|buf[4]; uint16_t y = (buf[5]<<8)|buf[6]; uint16_t w = (buf[7]<<8)|buf[8]; uint16_t h = (buf[9]<<8)|buf[10]; LCD_SetWindow(x, y, x+w-1, y+h-1); // 从buf[11]开始是像素数据,发送到屏幕 LCD_WriteData_Color_Buffer(&buf[11], w*h); break; case CMD_FILL_SCREEN: // 清屏命令 uint16_t color = (buf[3]<<8)|buf[4]; LCD_Fill(0,0,LCD_WIDTH,LCD_HEIGHT,color); break; // ... 其他命令 } } }5. Windows客户端软件开发
为了让PC端的图像能发送到小屏,我们需要一个运行在Windows上的“推流”客户端。
5.1 开发环境与串口通信
可以使用Python(PyQt/PySide for GUI, pyserial for COM)或C#(.NET Framework, SerialPort类)快速开发。
核心步骤:
- 枚举串口:获取系统所有可用串口列表。
- 连接设备:根据已知的VID/PID(例如CH552的VID=0x4348, PID=0x55E0)或手动选择,打开对应的串口,设置波特率(如115200或更高)、数据位、停止位、无校验。
- 数据发送:将处理好的图像数据按照与MCU约定好的协议格式,通过串口发送。
5.2 屏幕捕获与图像处理
客户端的核心功能是捕获PC屏幕的指定区域,并处理成适合小屏和串口传输的格式。
- 屏幕捕获:
- C#:使用
Graphics.CopyFromScreen方法。 - Python:使用
PIL.ImageGrab.grab(bbox=(x1, y1, x2, y2))。
- C#:使用
- 图像缩放:捕获的区域可能远大于2.1寸屏的分辨率。需要使用高质量的缩放算法(如双线性插值)将图像缩放到屏幕的精确尺寸(如320x240)。
- 颜色空间转换:PC端通常是RGB24或ARGB32,而小屏可能支持RGB565(16位色)。需要将每个像素的24位颜色转换为16位:
R5 = (R8 >> 3), G6 = (G8 >> 2), B5 = (B8 >> 3),然后组合成uint16_t:(R5 << 11) | (G6 << 5) | B5。 - 图像压缩(可选):对于115200波特率,每秒最多传输约11.5KB数据。一帧320x240的RGB565图像大小为150KB,直接传输需要13秒,完全不可用。因此必须压缩。
- 简单差分更新:只发送与上一帧相比发生变化的像素区域。适合内容变化不大的场景(如仪表盘)。
- RLE行程编码:对于大面积纯色块非常有效。
- 色深优化:可以尝试转换为8位索引色(256色)甚至更低位深,配合调色板。
- 自定义二进制格式:将图像分块,每块先发送一个头部(坐标、压缩类型),再发送压缩后的数据。MCU端按块解压刷新。
5.3 性能优化与用户体验
- 双缓冲与多线程:GUI界面一个线程,屏幕捕获和串口发送放在另一个工作线程,避免界面卡顿。
- 可配置区域:允许用户在PC屏幕上拖拽选择一个矩形区域作为镜像源。
- 帧率控制:提供滑块让用户限制最大帧率(如1fps, 5fps, 10fps),在流畅度和CPU/带宽占用间取得平衡。
- 协议优化:设计高效的二进制协议。例如,协议帧可以设计为:
[帧头0xAA 0x55] [命令字] [数据长度高字节] [数据长度低字节] [数据...] [校验和]。校验和可以用简单的累加和或CRC8。
6. 系统集成、调试与问题排查
6.1 组装、烧录与首次上电
- 焊接与检查:仔细焊接所有元件,特别是USB接口、芯片和屏幕FPC座。焊接后,用放大镜检查有无短路、虚焊。用万用表测量电源对地电阻,排除短路。
- 烧录固件:CH552支持通过USB口直接ISP烧录。连接USB线,按住板上的“下载”按钮(或将BOOT引脚拉低)再上电,芯片进入Bootloader模式。使用WCHISPTool选择编译好的
.hex或.bin文件进行烧录。 - 首次上电测试:
- 不接屏幕,先给板上电,测量3.3V LDO输出是否正常。
- 正常后,连接屏幕,再次上电。观察屏幕背光是否点亮(可能需在固件中初始化背光GPIO为高)。
- 打开Windows设备管理器,检查是否出现新的COM端口(如“USB-SERIAL CH340”或“WCH USB to UART”)。这表明USB CDC设备枚举成功。
6.2 通信联调与图像显示测试
- 串口通信测试:使用串口助手(如Putty、SecureCRT或自写测试程序)打开识别到的COM口,设置正确的波特率。从串口助手发送一个简单的测试命令(如
FF 00 00代表清屏为红色),观察屏幕是否有反应。同时,可以在MCU端编程,定时通过串口发送一些调试信息(如“OK\n”),在串口助手中查看是否接收正常。 - 基础显示测试:在固件中编写一个测试图案显示函数(如交替显示红、绿、蓝三色全屏),在启动时调用,验证屏幕驱动是否正常。
- 客户端联调:运行自己编写的Windows客户端,选择正确的COM口,设置捕获区域为一个小方块,点击“开始镜像”。观察屏幕是否出现PC端的内容。首次很可能出现花屏、错位或颜色异常。
6.3 常见问题与排查实录
以下是我在项目中实际遇到并解决的问题:
| 现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 电脑无法识别USB设备 | 1. USB线仅供电无数据线。 2. USB D+/D-线接反或虚焊。 3. CH552的UD+引脚缺少1.5k上拉电阻。 4. 晶振未起振或频率偏差大。 5. 固件未正确配置USB描述符。 | 1. 更换已知良好的USB数据线。 2. 检查PCB走线和焊接。 3. 测量UD+引脚电压,确认上拉。 4. 用示波器测晶振引脚波形,更换晶振及负载电容。 5. 使用USB分析软件(如USBlyzer)查看枚举过程,对比官方示例修改描述符。 |
| 串口能识别,但通信不稳定,数据乱码 | 1. 波特率不匹配。 2. USB CDC驱动问题。 3. MCU端USB中断处理时间过长,导致数据丢失。 4. 电源噪声大,影响信号完整性。 | 1. 确保两端波特率、数据位、停止位、校验位完全一致。 2. 尝试更新或重新安装CH340/CP210x驱动。 3. 优化固件,在USB中断回调函数中只做最必要的数据搬运,标志位设置,复杂处理放到主循环。 4. 检查电源电路,在关键位置增加去耦电容,USB线缆不要过长。 |
| 屏幕白屏或背光不亮 | 1. 屏幕供电异常(3.3V或背光电压)。 2. 屏幕复位时序不对。 3. 屏幕初始化序列错误。 4. 背光电路故障或控制引脚未使能。 | 1. 测量屏幕连接器各引脚电压,对照规格书检查。 2. 用逻辑分析仪或示波器抓取RST、CS、DC、WR等控制信号时序,与屏幕数据手册对比。 3. 逐行检查 LCD_Init()函数中的初始化命令和参数,确保与屏幕型号匹配。很多屏幕供应商会提供示例代码。4. 检查背光控制引脚输出电平,测量背光驱动电路输出电压电流。 |
| 显示图像错位、撕裂、颜色错误 | 1. 显示窗口(Window)设置错误。 2. 像素数据格式(RGB565/BGR565/RGB888)不匹配。 3. 图像数据发送速度超过串口或MCU处理能力,导致缓冲区溢出。 4. 内存不足,显示缓冲区数据被覆盖。 | 1. 确认LCD_SetWindow函数的参数计算正确,特别是结束坐标是x+width-1。2. 确认PC端颜色转换代码和屏幕驱动芯片要求的像素格式一致。常见错误是R和B分量反了。 3. 在客户端降低帧率,或在MCU端增加流控(如缓冲区快满时通过串口发送暂停命令)。 4. 优化MCU端图像解码逻辑,使用更小的行缓冲区而非全图缓冲区。 |
| 刷新率极低,卡顿严重 | 1. 串口波特率过低。 2. 图像数据未压缩,数据量太大。 3. MCU刷屏速度慢(如使用软件SPI)。 4. PC端客户端软件效率低,捕获和编码耗时过长。 | 1. 尝试提高波特率到921600甚至更高(需双方硬件支持)。 2.必须实施图像压缩。即使是简单的“仅发送变化区域”也能极大提升帧率。 3. 优化屏写函数,使用硬件SPI,或提高GPIO翻转速度(对于8080并口)。 4. 对PC客户端进行性能分析,优化图像缩放和编码算法,或使用多线程。 |
一个关键的避坑技巧:在固件开发初期,实现一个简单的“回声”测试和日志输出功能至关重要。例如,让MCU将收到的每一个数据包的长度和首字节,通过串口打印回PC(可以用另一个串口助手查看)。这能立刻帮你定位问题是出在PC发送端、USB传输、还是MCU解析端。没有日志,调试就像在黑暗中摸索。
7. 进阶玩法与应用场景拓展
当基础显示功能稳定后,这块小屏的潜力才真正开始发挥。
- 系统监控仪表盘:客户端软件不捕获屏幕,而是读取系统信息(CPU/内存/网络/GPU使用率, 温度等),生成简洁的图表和数字,发送到小屏显示。可以将其放在机箱旁或笔记本边缘,实时掌控系统状态。
- 硬件信息屏:结合传感器(如温湿度传感器DHT22、大气压传感器BMP280),让MCU自主采集数据并显示,USB仅用于供电。或者通过USB接收来自PC的其他数据(如股票行情、天气预报)进行显示。
- 迷你游戏机/电子相册:利用MCU内置的Flash或外接TF卡(如果CH552 IO足够),存储简单的游戏资源或图片,实现脱机运行。通过USB可以更新存储的内容。
- 辅助输入设备:如果屏幕带触摸功能或外接按键,可以将它变成一个自定义的控制面板。通过USB HID协议,向PC发送键盘快捷键或鼠标点击事件,用于控制音乐播放、视频剪辑、或游戏宏。
- 网络信息终端:让MCU通过ESP-01S这类Wi-Fi模块连接网络,获取时间、新闻头条等,USB依然只供电。或者,让PC客户端通过网络获取数据(如服务器监控数据)再通过USB转发给小屏。
这个项目的魅力在于,它从一个简单的“USB小屏”想法出发,贯穿了硬件设计、嵌入式固件开发、PC端软件开发、通信协议设计等多个领域。每一个问题的解决,都让你对“显示”这个看似简单的功能背后复杂的系统工程有了更深的理解。当你最终看到电脑桌面的一角,稳定地出现在这块自己亲手打造的小屏幕上时,那种成就感远非购买一个成品所能比拟。