简介:面向FPGA学习者和数字系统设计爱好者的完整工程资源。项目以UART串口接收PC端图片数据、经FPGA内部缓存与处理后输出至VGA显示器为核心,完整覆盖串行通信协议参数设置、RGB三通道图像编码、显存组织方式以及VGA行同步与场同步时序生成等关键知识点,可作为课程设计或电子竞赛项目的直接参考。压缩包共366个文件、约25.79MB,包含17个Verilog源文件、6个VHDL文件、10个XDC约束文件,另有Tcl/Bat自动化脚本、仿真波形与综合日志、Vivado工程配置及可直接烧录验证的bit比特流,从源码到上板文件一应俱全,目录结构清晰。目前已有1546人学习浏览。借助该资源可对照完整设计流程理解串口收发、图像缓存与VGA驱动模块间的协同机制,学习跨模块时序约束、上板调试与仿真比对思路,适合正在开展FPGA图像显示项目、需要边读源码边实践的中高级学习者。
1. 基于FPGA的串口发送彩色图片数据至VGA显示:一个能跑通的完整图像链路
做嵌入式图像项目最怕的不是算法难,而是数据链路断在半路。串口发数据、FPGA收数据、FIFO缓存、VGA时序输出,这条链路里任何一个环节卡住,屏幕上就是雪花或者黑屏。很多开发者一开始用IO翻转模拟时序,刷个320x240都卡成PPT,换FPGA配合串口和FIFO之后,640x480@60Hz的彩色图片能稳定显示,而且整个工程代码量不到一千行。这套方案的适用人群是学过Verilog但没完整做过图像采集显示链路的开发者,以及想用现有开发板快速验证图像处理算法的工程师。它能帮你把PC端图片数据通过串口灌进FPGA,经过缓存和时序生成,最终在VGA显示器上还原出完整画面,整个过程不依赖价格昂贵的HDMI方案,一块入门级FPGA开发板就能搞定。
2. 架构设计与硬件选型:为什么串口+FIFO是目前最稳的落地组合
2.1 系统数据流与模块划分
整个系统的数据流向非常明确:PC上位机先把BMP格式的彩色图片解析成RGB数据,通过UART串口按字节发出;FPGA内部由串口接收模块(UART_RX)把串行数据还原成8位并行数据,写入异步FIFO;VGA时序生成模块(VGA_Controller)按固定分辨率从FIFO读出像素数据,同步产生行场同步信号和使能信号,送往板载VGA接口。显示器端看到的就是一幅完整的彩色图片。
这里为什么选串口而不是USB或者以太网?原因很直白:串口协议简单,三根线(TXD、RXD、GND)就能通信,调试时用串口助手就能发数据,不需要在PC端写复杂的驱动。而且大多数开发板都板载USB转串口芯片,插上USB线就能用,省去硬件接线这一步。串口带宽虽然低,但对于静态图片传输足够,640x480分辨率一帧数据量约300KB,115200波特率下大约需要25秒,这个时间在开发调试场景下完全能接受。
FPGA内部模块划分需要遵循单一职责原则,每个模块只干一件事:
- uart_rx模块:解析串口波形,输出8位数据字节和接收有效标志
- vga_controller模块:产生行同步/场同步信号,计算当前像素坐标,从FIFO读数据
- async_fifo模块:跨时钟域缓冲,避免串口波特率时钟和VGA像素时钟不同步导致的数据错位
- pll模块:用板载晶振产生VGA所需的像素时钟,必要时分频给其他模块
2.2 时钟方案:单时钟域还是PLL分频
很多初学者犯的第一个错误是直接用板载50MHz时钟去驱动VGA时序。VGA时序对像素时钟非常敏感,640x480@60Hz的标准像素时钟是25.175MHz。用50MHz时钟计数模拟25MHz时序虽然能显示,但会出现行同步信号占空比不对的隐患,显示器可能拒绝锁定信号。
我一般会直接在FPGA内部例化PLL,把50MHz晶振时钟分频成25MHz作为VGA像素时钟,同时把原始50MHz时钟用于串口接收模块。这里有一个细节:串口接收模块的时钟频率必须是波特率的整数倍,一般用16倍频采样,即115200bps对应1.8432MHz采样时钟,而50MHz时钟需要分频成1.8432MHz,用计数器做分频时要注意误差控制在2%以内,否则串口字节容易错位。
异步FIFO在这里起到了关键的桥接作用。串口写入数据用的是系统时钟域(50MHz),VGA读取数据用的是像素时钟域(25MHz),两者没有相位关系,如果不用跨时钟域FIFO,直接用一个双端口RAM不加同步处理,会出现读空或者读错数据的情况。选择FIFO深度时,需要考虑VGA一帧扫描时间内串口能传入多少数据。640x480分辨率下,VGA扫描一帧需要约16.6ms(不含消隐期),而串口在115200波特率下大约只能传192字节,所以FIFO深度不需要很大,一般256字节就够,但如果你打算边接收边显示(流水线式),建议直接配1024字节的FIFO,留足余量。
2.3 VGA接口硬件连接与电阻网络
VGA接口的硬件连接比很多人想象中简单,但也是最容易翻车的地方。常见开发板上VGA接口的RGB信号线并不是直连FPGA引脚,而是通过电阻网络做DAC转换。原因很简单:VGA接口的模拟输入电压范围是0V到0.7V(对应RGB各通道),FPGA的GPIO只能输出0V或3.3V的数字电平,不能直接驱动。
常见做法是每个颜色通道用3个或者4个电阻组成简单的电阻分压网络,FPGA输出3位或4位数字信号,经过电阻网络后合成模拟电压。比如4位电阻网络对应16级灰度,三通道组合就是4096色,虽然不如8位RGB的1670万色丰富,但肉眼看起来已经很接近真实彩色效果了。公司在做原型验证时,如果开发板上的电阻网络已经焊好,直接查原理图确认各颜色通道对应的FPGA引脚号就行,不用自己搭电路。
这里要记住一个关键点:VGA接口的HSYNC和VSYNC信号是TTL电平直接输出的,不需要转换,但极性非常讲究,标准VGA时序要求这两个信号是负极性(同步脉冲为低电平有效),如果你的代码写成高电平有效,显示器大概率黑屏或者提示无信号。
3. VGA时序详解:从行场同步到像素地址映射的完整参数
3.1 640x480@60Hz标准时序参数表
VGA时序本质上是严格的计数器,只要把行同步、行后沿、有效像素、行前沿、场同步这几个阶段的总像素数和总行数配置正确,显示器就能正确锁定时钟信号。以下是640x480@60Hz的标准参数,每个数值都来自VESA标准,不能随意改动:
| 时序阶段 | 行方向(像素时钟周期) | 列方向(行数) |
|---|---|---|
| 同步脉冲 | 96 | 2 |
| 后沿 | 48 | 33 |
| 有效区域 | 640 | 480 |
| 前沿 | 16 | 10 |
| 总周期 | 800 | 525 |
像素时钟为25.175MHz,一行的总周期是800个像素时钟(即大约31.8微秒),一帧的总周期是525行,其中有效显示区域是640x480。熟练掌握这个参数表,后续无论换什么分辨率(比如800x600、1024x768)都只需要改这四组数。
3.2 Verilog实现:行场计数与同步信号生成
把时序参数翻译成Verilog并不难,核心是两个计数器。行计数器h_count在0到799之间循环,场计数器v_count在0到524之间循环。行计数器每循环一次,场计数器加1。这样通过比较计数值就可以生成同步信号和使能信号。
// VGA时序生成核心代码 module vga_controller ( input clk_25m, // 25MHz像素时钟 input rst_n, // 低电平复位 output reg hsync, // 行同步信号 output reg vsync, // 场同步信号 output reg video_en, // 数据有效使能 output [9:0] pixel_x, // 像素横坐标(0~639) output [9:0] pixel_y, // 像素纵坐标(0~479) input [15:0] pixel_data, // 从FIFO读出的RGB565数据 output reg [15:0] rgb_out // 输出到电阻网络的RGB数据 ); parameter H_TOTAL = 800; // 行总周期 parameter H_SYNC = 96; // 行同步脉冲宽度 parameter H_BACK = 48; // 行后沿 parameter H_ACTIVE = 640; // 有效像素数 parameter H_FRONT = 16; // 行前沿 parameter V_TOTAL = 525; // 场总周期 parameter V_SYNC = 2; // 场同步脉冲宽度 parameter V_BACK = 33; // 场后沿 parameter V_ACTIVE = 480; // 有效行数 parameter V_FRONT = 10; // 场前沿 reg [9:0] h_count; reg [9:0] v_count; // 行计数器,循环周期为800 always @(posedge clk_25m or negedge rst_n) begin if (!rst_n) h_count <= 0; else if (h_count == H_TOTAL - 1) h_count <= 0; else h_count <= h_count + 1; end // 场计数器,循环周期为525 always @(posedge clk_25m or negedge rst_n) begin if (!rst_n) v_count <= 0; else if (h_count == H_TOTAL - 1) begin if (v_count == V_TOTAL - 1) v_count <= 0; else v_count <= v_count + 1; end end // 生成行同步信号:前96个像素周期为低电平 always @(posedge clk_25m or negedge rst_n) begin if (!rst_n) hsync <= 1; else if (h_count < H_SYNC) hsync <= 0; else hsync <= 1; end // 生成场同步信号:前2行为低电平 always @(posedge clk_25m or negedge rst_n) begin if (!rst_n) vsync <= 1; else if (v_count < V_SYNC) vsync <= 0; else vsync <= 1; end // 数据有效区域:排除同步、后沿、前沿时段 assign video_en = (h_count >= H_SYNC + H_BACK) && (h_count < H_SYNC + H_BACK + H_ACTIVE) && (v_count >= V_SYNC + V_BACK) && (v_count < V_SYNC + V_BACK + V_ACTIVE); // 有效区域内像素坐标 assign pixel_x = video_en ? h_count - H_SYNC - H_BACK : 10'd0; assign pixel_y = video_en ? v_count - V_SYNC - V_BACK : 10'd0; // 有效区域输出FIFO数据,非有效区域输出黑色 always @(posedge clk_25m or negedge rst_n) begin if (!rst_n) rgb_out <= 16'd0; else if (video_en) rgb_out <= pixel_data; // 从FIFO读取像素并输出 else rgb_out <= 16'd0; // 消隐期间输出黑色 end endmodule这段代码里的关键点在坐标映射上。VGA的有效区域其实并不是从(0,0)开始的,因为它前面还有96个像素的行同步和48个像素的行后沿,所以有效区域是从第144个像素时钟(96+48)开始的。pixel_x的计算方式是把当前h_count减去偏移量,这样FIFO读地址才和屏幕坐标保持一一对应关系。
参数H_TOTAL这些用了parameter定义,这意味着你后续想改分辨率时,不需要去改动计数器的比较逻辑,只需要替换这组参数的值。比如换成1024x768时,行总周期变成1344,有效行变成768,像素时钟变成65MHz,把这几个数改掉就行。
3.3 像素数据格式选择:RGB565是串口传输的最优解
VGA显示彩色图像最常用的是RGB888(每像素3字节),但在串口带宽有限的前提下,RGB565是更理性的选择。RGB565把红色用5位、绿色用6位、蓝色用5位,共16位(2字节)表示一个像素,相比RGB888每像素节省了1字节,一帧640x480图就能省大约300KB的数据量。
你可能会问:RGB565的绿色为什么是6位而不是5位?原因很简单,人眼对绿色最敏感,人眼的视锥细胞对绿光的响应峰值最高,所以标准里把绿色通道多分配了一位。开发板的电阻网络如果每个通道只接了4位,直接使用RGB565的高位比特也可以:红色取高4位,绿色取高4位,蓝色取高4位,这样颜色精度从RGB565降为RGB444,但显示效果依然能接受。
从上位机角度,解析BMP图片时可以直接提取每个像素的RGB565值(BMP里存储的是BGR888),然后按行序发送原始字节。FPGA收到两个字节后拼接成16位像素数据写入FIFO,VGA侧直接读取就行,不需要做任何格式转换。
4. 串口接收与FIFO缓存:从字节流到像素流的完整落地方案
4.1 UART接收模块的状态机设计
UART接收的协议很简单:空闲时数据线为高电平,起始位(低电平)到来后,按波特率采样8个数据位,最后是一个停止位(高电平)。FPGA内部的做法是用波特率时钟的16倍频进行采样,每16个采样点取中间那一个,这样能有效避开数据线边沿的毛刺干扰。
// UART接收状态机核心代码 module uart_rx ( input clk_50m, input rst_n, input uart_rxd, output reg [7:0] rx_data, output reg rx_done ); parameter BAUD_DIV = 1085; // 50MHz / 115200bps ≈ 434 用于时钟分频(实际16倍采样需要除以16) // 实际工程中用以下两个parameter parameter CLK_FREQ = 50_000_000; parameter BAUD_RATE = 115_200; // 16倍过采样,分频系数计算:CLK_FREQ / (BAUD_RATE * 16) localparam BAUD_16 = CLK_FREQ / (BAUD_RATE * 16); // ≈ 27 reg [4:0] sample_cnt; // 16倍采样计数器 reg [3:0] bit_cnt; // 当前接收位计数 reg rx_state; // 0=空闲 1=接收中 reg [7:0] shift_reg; // 移位寄存器 // 状态机主逻辑 always @(posedge clk_50m or negedge rst_n) begin if (!rst_n) begin rx_state <= 0; sample_cnt <= 0; bit_cnt <= 0; rx_done <= 0; end else begin case (rx_state) 0: begin // 空闲状态 rx_done <= 0; if (uart_rxd == 0) begin // 检测到起始位 rx_state <= 1; sample_cnt <= 0; end end 1: begin // 接收状态 if (sample_cnt == BAUD_16 - 1) begin sample_cnt <= 0; if (bit_cnt <= 7) begin // 取每个bit的中间采样点 if (bit_cnt == 0) ; // 跳过起始位 else shift_reg[bit_cnt-1] <= uart_rxd; bit_cnt <= bit_cnt + 1; end else begin // 停止位结束后完成一字节接收 rx_data <= shift_reg; rx_done <= 1; rx_state <= 0; bit_cnt <= 0; end end else sample_cnt <= sample_cnt + 1; end endcase end end endmodule这里的核心逻辑是在空闲状态检测到下降沿(uart_rxd == 0)时启动接收,然后在每个bit的中间采样点采集数据。其中BAUD_16这个参数的值决定了采样精度,公式是CLK_FREQ除以(BAUD_RATE乘以16),50MHz时钟下取整为27,即每27个时钟周期采样一次。如果波特率改成9600,这个分频系数就是要重新计算,否则会出现采样点完全错位的情况。
一个容易被忽略的细节是起始位的检测问题。在空闲状态下,如果数据线上的毛刺恰好表现为一个短低脉冲,会导致错误启动一次接收。所以工程上通常会先做一次电平消抖——把uart_rxd信号打两拍,再用打拍后的信号做边沿检测。这个处理虽然不是必须的,但在长线缆或者电磁环境差的场景下能显著降低误码率。
4.2 FIFO深度计算与读写时钟同步
从串口接收模块到VGA显示模块之间的FIFO设计是整个系统的承重墙。由于读写时钟来源不同,必须使用异步FIFO。数据写入侧时钟是50MHz系统时钟,数据读取侧时钟是25MHz像素时钟。虽然写入速率远低于读取速率,但FIFO的深度仍然要根据一帧数据量和突发情况来确定。
一个常见思路:VGA每秒刷新60帧,但你的串口传输一帧图片需要25秒,这意味着VGA会重复扫描同一帧FIFO内容很多次。只要FIFO不读空,画面就能持续显示。FIFO深度的下限是VGA一帧时间(16.6ms)内从串口收到的字节数,115200波特率下约192字节,所以配256字节的FIFO在理论上是够的。
但实践里我建议直接上1024字节的FIFO。原因有三个:一是FIFO读写指针的地址位宽按2的幂次设计比较简单;二是串口接收的字节是间隙式到达的(两个字节之间有大约87微秒的间隔),FIFO需要一定的缓冲来吸收这种间隙抖动;三是如果你后续把波特率提高到921600,1024字节的FIFO依然能覆盖突发情况。
// 异步FIFO例化(使用IP核方式) fifo_generator_1024 u_fifo ( .wr_clk(clk_50m), // 写时钟:系统时钟 .wr_en(rx_done), // 写使能:串口每接收一个字节拉高 .din({rx_data[1:0], 8'd0, rx_data[7:2]}), // 字节到RGB565拼接,按需求调整 .rd_clk(clk_25m), // 读时钟:VGA像素时钟 .rd_en(video_en_core), // 读使能:在有效显示区域持续读 .dout(fifo_dout), .full(fifo_full), .empty(fifo_empty), .rd_data_count(vga_valid_count) );这里需要特别说明的是RGB565拼接方式。uart_rx模块每次输出的是一个8位字节,而VGA需要一次读取16位像素数据,所以上位机发送时必须按低字节在前、高字节在后的顺序传。也就是说,对于一个RGB565像素(比如0xF81C),上位机先发送0x1C,再发送0xF8。FPGA端收到两个字节后需要拼接成一个16位值写入FIFO,而这个拼接过程有两种方案:一种是串口接收模块内部增加一个字节计数器,奇偶字节做拼接;另一种是直接用两个8位FIFO分时写入,读出来之后再合并。
实际验证下来,第一种方案更简单,但代码执行时序上多了一个restricted条件:rx_done拉高时,你需要在下一个时钟上升沿把数据拼好,这要求对时序收敛有充足余量。第二种方案看起来多用了硬件资源,但逻辑更直白,不容易踩坑。
4.3 上位机发送协议设计
PC端发送图片数据不是简单地把BMP字节流直接甩给串口,而是需要设计一个简单的数据帧协议。我习惯的协议格式是:帧头(2字节,如0xAA 0x55) + 图片宽度(2字节) + 图片高度(2字节) + 像素数据 + 帧尾(2字节,如0x0D 0x0A)。FPGA收到帧头后开始缓存有效数据,收到帧尾后通知显示模块刷新。
这个协议的好处在于:FPGA不需要知道图片的固定分辨率,它只需要从帧头帧尾的字段里读出当前图片的尺寸,然后调节VGA时序或者数据的读地址映射即可。如果你固定只显示640x480的图片,可以省略宽度高度字段,把上位机发送逻辑写得更简单——直接发帧头和像素数据。
还有一个细节:串口发送数据时默认是LSB先行,UART协议本身规定的就是低位先发,这会让RGB565的低字节低位先上线路。对于静态图片这完全没问题,因为最终拼接到FPGA侧是按字节对齐的,并不关心线路上的位序。但如果你遇到的不是标准UART而是SPI之类的同步串口,就需要额外处理位序问题,VGA显示图像时会呈现明显的按位错位花屏现象(颜色完全错乱)。
5. 避坑手册:从图像花屏到显示器无信号的五个实战排查记录
5.1 现象:显示器无信号且指示灯不亮
这个是最常见的入门问题,现象是接上VGA线后显示器一直待机,没有画面,也没有任何提示。排查过程先查代码里的行场同步信号极性。标准VGA时序要求HSYNC和VSYNC都是负极性,即空闲为高电平、同步脉冲为低电平。很多初学者按直觉写成高电平脉冲,显示器检测不到同步信号就会拒绝显示。解决方法是把always块里的hsync和vsync初始值改成1,然后计数区间内输出0。还要检查PLL是否真的输出25MHz时钟,用仿真工具抓PLL锁定信号(locked)是否为高,没有锁定时VGA模块拿到的时钟是悬空的。
如果是开发板的VGA接口经过IIC电平转换芯片,还要确认这个芯片的使能引脚是否按要求拉高或拉低。某公司的开发板就出过这种情况:VGA芯片的使能脚默认配置成输出高电平,但代码里没处理这个信号,导致电平时序异常,用户折腾了很久才发现是硬件上拉电阻缺失。
5.2 现象:画面显示出来了但整体偏色,红色通道变绿色
这个问题的典型特征是画面能显示,但颜色明显不对,比如红色方块变成了绿色方块。原因基本都是RGB引脚映射到电阻网络时搞反了,属于硬件连接层面的问题。开发板原理图上FPGA的PIN_C19对应的是VGA_R_3(红色通道高位),如果你把代码里RGB565的高位(红色)绑定到了PIN_D19(绿色通道),那结果就是红绿互换。解决方法是翻阅开发板原理图确认每个通道的引脚编号,然后重新编辑管脚约束文件(xdc或者qsf),把颜色通道对齐。
这个过程不是一次就能成功的。公司以前有个项目为了省事,直接把生成了一个引脚约束模板,结果dout[15:12]映射到VGA_R,dout[11:5]映射到VGA_G,dout[4:0]映射到VGA_B,最后发现VGA_G在板子上其实只接了4位电阻网络,导致绿色通道偏低。你需要做一次全引脚扫描测试:让FPGA依次输出纯红、纯绿、纯蓝画面,对照实际显示结果来校准引脚约束。
5.3 现象:图像能显示但有明显的横条纹,像隔行扫描一样
这个现象经常发生在上位机通过串口发送图片的过程中。因为VGA模块是以25MHz的频率持续刷新显示,而串口接收数据的速度是每秒不到12KB,刷新频率远大于数据到达频率,画面会出现边接收边显示的撕裂感:上半部分是最新数据,下半部分还是上一帧的老数据。
有两种解决思路:一是彻底等数据传完再开始显示,即FPGA内部做一个帧同步标志——当帧尾字节到达时,把FIFO读指针清零并刷新起始地址,确保每一帧都是从同一基地址开始读取的;二是用双缓冲方式,接收侧写入一个FIFO,显示侧读另一个FIFO,等当前显示帧扫完后再切换读指针到新数据。双缓冲在最坏情况下会浪费一帧的存储空间,但能彻底避免撕裂问题。FPGA资源里BRAM够用的话,直接配置两个1024字节的FIFO即可。
5.4 现象:传输完一整帧图后,画面只显示了上半部分,下半部分是灰色
这个问题指向FIFO的读空现象。串口一帧数据是连续写入FIFO的,但FIFO读侧只要有video_en就会持续读数据。如果写入速率跟不上读取速率,FIFO会变空,读出数据变成0或者高阻态,整帧画面变成上半有图下半黑。严格来说FIFO读空本身不会导致显示停止,但读出的无效数据会被当作有效像素显示出来。
解决方法是让FIFO在数据量达到一定阈值后再开始允许读取(这个一般叫"FIFO预加载"),且在数据读取过程中检查empty信号,一旦FIFO为空,就把当前行的输出强制为黑色。这样至少能避免花屏。真正的根治手段还是提高串口波特率或者压缩传输数据量:RGB565已经是压缩过的,另外一种思路是降低分辨率到320x240,一帧数据量缩小到四分之一,传输时间从25秒减少到6秒左右。
5.5 现象:图像上下颠倒但左右正常
这个问题的原因很直接:BMP图片格式本身存储的是从下到上的行序,即第一行像素在文件尾部。如果你直接按BMP文件字节序发送,FPGA按顺序写入FIFO并按顺序读取,那么屏幕上显示的就是上下颠倒的图像,因为VGA扫描是从左上角开始逐行往下的。
解决方法是上位机在解析BMP时做一次垂直翻转:从文件末尾开始往前读行,或者按行逆序发送。这种转换在Python或者C#上位机里就是一个小循环,但经常被人忽略。还有一个相关的坑是BMP的像素存储是从左到右、从上到下(实际是从上到下反转写入),如果你的图片原始数据来自别的采集方式(比如摄像头捕获的RAW数据),行序可能就是正序的,直接发送会变成倒像,需要根据图像源格式灵活处理。
6. 进阶验证与调优:用测试彩条验证链路、突破串口传输瓶颈的两种手段
一个工程从写完到真正显示出来,中间最好设计一套自检流程。我现在做这套系统时会分三级验证:第一级是VGA直接输出逻辑分析仪验证时序信号,把HSYNC和VSYNC接到逻辑分析仪上,对照标准时序参数检查脉冲宽度是否符合要求;第二级是FPGA内部生成一幅测试彩条图,不依赖串口数据,直接由VGA模块读取ROM里的固定图像数据,验证时序和像素颜色是否正确;第三级才接上串口发真实的BMP图像数据。
这种分级验证能帮你快速定位问题出在链路哪一段。如果彩条显示正常,说明VGA模块和外部硬件没问题,问题大概率在串口接收或者FIFO拼接;如果彩条都不出来,那问题主要在VGA时序或者引脚约束上,不用去看串口代码。
关于串口带宽的瓶颈,两个实用技巧值得一提。一是把波特率调到921600,这是USB转串口芯片常常支持的最高速率(FT232和CH340都能支持),此时一帧640x480的RGB565数据大约3秒传完,调试效率直接提升一个数量级。调整时只需要修改UART模块的BAUD_16分频系数,但要注意9600和115200用同一个系数会出错,快速换波特率时最容易栽在这里。二是用RLE压缩像素数据,但注意RLE在重复颜色少的自然图像上压缩效果很差,适合UI截图那种大块纯色区域,建议加上压缩标志位,让FPGA能区分压缩和未压缩数据。
这套工程做完整之后,最大感受是:所有看起来玄学的花屏问题,归根结底都是时序和跨时钟域的问题。从那以后我每次做图像链路都会强制走一遍三级自检流程:ROM彩条测显示、串口小图测链路、全分辨率测极限,这个习惯帮我排掉过至少三次引脚错绑的坑。希望帮到你,这套流程值得在你自己的FPGA板子上完整跑一遍。
本文还有配套的精品资源,点击获取