资讯动态

FPGA串口传图到VGA显示:UART、双口RAM与时序控制全解析

发布时间:2026/10/19 7:59:10 来源:尧图企业网站定制
简介面向FPGA初学者与数字系统设计者的完整工程实例展示如何通过UART串口接收彩色图片数据经内部存储与时序控制后驱动VGA显示器实时显示涵盖串行通信、RGB编码、块RAM存储、VGA行场同步信号生成等核心知识点。压缩包共366个文件约25.79MB以v/vhdl硬件描述语言源码、xdc约束文件、bit比特流、rpt报告、tcl脚本及仿真日志为主并附有工程配置与批处理文件便于直接打开工程或命令行复现。已有1546人学习下载。资料包含完整Vivado工程目录结构、串口接收模块、内存管理模块与VGA控制器设计文件以及仿真波形与综合实现结果可供读者对照学习模块间握手与数据流控制适合课程设计、毕业设计或竞赛作品参考。1. 项目定位串口喂图、VGA显示一个最小可用的图像通路调试FPGA图像算法时经常遇到一个尴尬手边没有摄像头板子上的VGA接口空着想验证一下图像缩放或边缘检测的效果不知道该把图片数据从哪喂进去。这个基于FPGA的串口发送彩色图片数据至VGA显示工程恰好填上了这个空子用一根USB转串口线把电脑里的图片数据逐字节发给FPGAFPGA把数据存进片上RAM再通过VGA接口实时扫描输出让图片在显示器上稳定显示。整个链路覆盖UART串口通信、双口RAM读写、VGA同步时序三个模块是一个麻雀虽小五脏俱全的FPGA数字系统。适合两类人刚学完Verilog语法、想做一个能跑完整流程的小项目的新手以及正在做图像处理、想先把图像数据通路搭通再投入算法的开发者。跑通这个工程之后换成摄像头或HDMI都只用替换输入源一层核心存储和扫描逻辑可以复用。2. 整体架构数据从PC串口到VGA像素的三个关键模块2.1 模块划分与内部连接关系系统顶层由三大部分组成UART接收模块uart_rx、图像缓冲模块image_buffer、VGA控制模块vga_controller外加一个时钟生成模块。把这四个模块画在纸上数据流是单向的PC串口到UART_RX再到image_buffer的双口RAM最后经过VGA控制器输出到接口。具体到每个模块的职责uart_rx把单根数据线上的异步串行信号解码成8位字节同时给出一个单周期的写有效信号image_buffer内部维护一个写地址指针每收到两个字节就拼接成一个16位RGB565像素写入RAM而读端口由VGA侧提供地址输出对应的像素数据vga_controller按标准640x48060Hz时序产生行场同步信号在显示有效区生成地址并读取像素最后把RGB数据输出。时钟是这套系统最容易乱的地方。开发板晶振一般是50MHzVGA像素时钟要求25MHzUART采样时钟要求波特率的16倍。我通常用一个PLL把50MHz分频出25MHz给VGA和RAM读时钟同时产生比如1.8432MHz给UART采样。如果开发平台没有PLL用一个计数器分频产生25MHz也够用但要注意输出时钟的相位抖动VGA对时钟质量要求不算高串口只要误差小于5%即可。2.2 串口接收模块为什么用16倍过采样UART是异步协议发送端和接收端各自用自己的时钟。为了在正确的位置采样每个bit接收端需要提供比波特率高整数倍的时钟通常是16倍。理由是16倍频能保证在bit周期的中心附近找到采样点且对多数波特率误差容忍度较好采样时钟误差累计半个bit不会错。实际计算分频系数时以50MHz晶振、115200波特率为例16倍采样频率是1.8432MHz分频系数 50_000_000 / 1_843_200 27.126取27后实际采样频率是1.85185MHz误差仅0.47%。如果波特率换成9600分频系数变成325.5四舍五入取326误差更小。用这种分频后的时钟直接当作采样时钟状态机里对每个bit计数16次在第8次时采样正好在bit正中间。注意如果分频后时钟不是由PLL产生而是计数翻转产生的方波那它的占空比会影响采样时刻我见过有人用50%占空比计数器分频导致采样点偏移到bit边界所以时钟模块里最好用相位可控的PLL资源。2.3 数据通路为什么必须用双口RAM而不是FIFO图像缓冲最核心的决策是选用双口RAM。原因很简单VGA需要按任意行列顺序读像素FIFO只能先进先出。如果图片数据是逐行连续放入FIFOVGA扫描时按同一顺序读出来看似也能匹配但VGA在行消隐和场消隐期间不读数据FIFO会积压而串口写入是均速的两边速率和突发模式不一致最终导致读写水位不可控。双口RAM用独立地址解决了这个问题写地址由串口字节数单调递增读地址由VGA扫描行列计算二者互不干扰。RAM的宽度和深度需要与图片分辨率匹配。我要做640x48060Hz的RGB565显示像素用16位那么RAM总容量是64048016bit 4915200bit。按FPGA内部的Block RAM资源来算一个Block RAM通常是36Kbit一个工程大概需要2到3个。如果用的芯片资源较小可以把分辨率降为320x240容量变成原来的四分之一就能用1个Block RAM实现。这里也解释了为什么串口传图慢到需要几十秒一张640x480的RGB565裸数据是614400字节115200波特率下有效数据率是11520字节/秒理论上需要53.3秒考虑起始位和停止位实际一个字节要10个bit所以传输时间约53秒这个速度注定它只能做静态图显示。2.4 VGA时序参数与设计框架VGA信号有五个行同步、场同步、红、绿、蓝。红绿蓝采用模拟电压FPGA端用数字引脚通过电阻网络转换成模拟电平但时序部分都是数字信号。以640x48060Hz为例标准参数如下时序项水平方向垂直方向显示区640像素480行同步起始第656像素第490行同步结束第751像素第491行同步宽度96像素2行整周期800像素525行实际代码中不需要背这些值直接定义四个边界常量H_TOTAL800、H_SYNC_START656、H_SYNC_END751以及V_TOTAL525、V_SYNC_START490、V_SYNC_END491。只要这些边界满足标准显示器就稳定识别。在每一行显示区中hsync先保持高电平到达同步起始位置时拉低持续96个像素时钟后拉高vsync同理。行场消隐期间不输出像素所以RGB数据在显示区外都要置零。2.5 地址映射与多行拼接读地址由行列计算addr y * 640 x。这个等式看似简单注意x、y必须在显示区内有效。写地址则由字节计数决定每收两个字节写成16位像素地址自动加一。由于写地址和读地址都用无符号整数最终数值不会超过614399用19bit即可表示。如果发现地址溢出整个RAM回绕会覆盖已有数据花屏也就显而易见了。串口字节到像素的对应关系也很重要。RGB565的一像素高8位是RGB[15:8]低8位是RGB[7:0]。发送顺序我定为高字节在前、低字节在后。这样拼接操作就是一个简单的移位和或pixel {high_byte, low_byte}。如果两个字节顺序反了画面不仅颜色错位整个图的每一行都会出现左移或右移半个像素的视觉错乱排查起来会非常费劲。3. 核心代码拆解UART字节流如何变成屏幕上的彩色像素3.1 顶层模块与信号连接打开工程顶层文件首先看到的是下面这些例化代码。信号命名尽量直接方便和板卡原理图对应module top_uart_vga ( input wire clk_50m, input wire rst_n, input wire uart_rx, output wire vga_hsync, output wire vga_vsync, output wire [4:0] vga_r, output wire [5:0] vga_g, output wire [4:0] vga_b ); wire clk_vga; wire clk_uart; wire [7:0] rx_byte; wire rx_valid; wire [15:0] pixel_data; wire [18:0] vga_addr; wire [4:0] rgb_r; wire [5:0] rgb_g; wire [4:0] rgb_b; clk_gen u_clk_gen ( .clk_in (clk_50m), .clk_vga (clk_vga), .clk_uart(clk_uart) ); uart_rx #(.DIV_CLK(27)) u_uart_rx ( .clk (clk_uart), .rst_n (rst_n), .rx (uart_rx), .rx_byte (rx_byte), .rx_valid (rx_valid) ); image_buffer u_image_buffer ( .clk_wr (clk_uart), .wr_en (rx_valid), .wr_data (rx_byte), .clk_rd (clk_vga), .rd_addr (vga_addr), .rd_data (pixel_data) ); vga_controller u_vga_controller ( .clk (clk_vga), .rst_n (rst_n), .pixel_in (pixel_data), .hsync (vga_hsync), .vsync (vga_vsync), .addr (vga_addr), .rgb_r (rgb_r), .rgb_g (rgb_g), .rgb_b (rgb_b) ); assign vga_r rgb_r; assign vga_g rgb_g; assign vga_b rgb_b; endmodule逻辑说明顶层把四个模块实例连接起来uart_rx输出的rx_byte和rx_valid直接连到image_buffer的写数据与写使能vga_controller输出的addr连到image_buffer的读地址读出的pixel_data再返回给vga_controller。这里读写两侧时钟不同写侧用clk_uart读侧用clk_vga。两个时钟频率不同但只要双口RAM各自端口独立跨时钟域读写就不存在问题。外部按键复位是异步信号直接使用容易在释放时出现亚稳态。我在实际工程里会把复位信号打两拍同步后再分发到各模块保证所有寄存器是在时钟沿统一释放。如果用的是开发板上的按键还需要考虑按键抖动但这类低速系统抖动影响不大靠打拍就能滤掉大部分毛刺。3.2 UART接收状态机逐段分析UART_RX模块我平时会写成参数化的用DIV_CLK参数控制采样时钟分频系数。状态机包含IDLE、START、DATA、STOP四种状态。下面是一份完整可综合的核心代码我在末尾加上了rx_busy输出方便调试时观察接收状态module uart_rx #( parameter DIV_CLK 27 )( input wire clk, input wire rst_n, input wire rx, output reg [7:0] rx_byte, output reg rx_valid, output reg rx_busy ); localparam IDLE 2d0; localparam START 2d1; localparam DATA 2d2; localparam STOP 2d3; reg [1:0] state; reg [4:0] clk_cnt; reg [2:0] bit_cnt; reg [7:0] shift; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; rx_valid 0; rx_busy 0; end else begin rx_valid 0; rx_busy 1b0; case (state) IDLE: begin clk_cnt 0; bit_cnt 0; rx_busy 1b0; if (!rx) state START; end START: begin rx_busy 1b1; if (clk_cnt 8) begin if (!rx) state DATA; else state IDLE; end else clk_cnt clk_cnt 1; end DATA: begin rx_busy 1b1; if (clk_cnt 15) begin clk_cnt 0; shift {shift[6:0], rx}; if (bit_cnt 7) begin bit_cnt 0; state STOP; rx_byte shift; rx_valid 1; end else bit_cnt bit_cnt 1; end else clk_cnt clk_cnt 1; end STOP: begin rx_busy 1b1; if (clk_cnt 15) begin state IDLE; clk_cnt 0; end else clk_cnt clk_cnt 1; end endcase end end endmodule逻辑说明IDLE状态检测到rx拉低后进入START此时并不意味着一定有效起始位可能只是毛刺。在START状态的clk_cnt 8时再次采样rx如果此时为低才确认是真正的起始位否则回到IDLE。进入DATA后每个bit持续16个采样周期clk_cnt从0到15循环计到15时采集rx并移入shift寄存器。bit_cnt记录当前采的是第几个数据位当采完第8位bit_cnt 7时拉高rx_valid并锁存rx_byte。STOP状态只在停止位结束时清空计数器回到IDLE。参数说明DIV_CLK是采样时钟分频上限。在50MHz、115200波特率下16倍采样时钟1.8432MHz计数器每计数到27就翻转输出clk_uart。这里clk_uart在模块外部生成uart_rx本身只拿clk_uart作为时钟所以DIV_CLK参数实际用于时钟模块不在uart_rx内部。如果直接在uart_rx内部计数分频就要把这个参数转成采样时钟的周期宽度两种写法都行但内部计数分频会让时钟域复杂化我更喜欢外部PLL或时钟模块生成干净的采样时钟。3.3 字节拼接和双口RAM的推断实现image_buffer模块负责把连续两个字节拼接成一个像素。为了简洁我直接用Verilog推断一个双口RAM下面的代码在多数FPGA综合器中都能映射成Block RAMmodule image_buffer #( parameter DATA_WIDTH 16, parameter ADDR_WIDTH 19 )( input wire clk_wr, input wire wr_en, input wire [7:0] wr_data, input wire clk_rd, input wire [ADDR_WIDTH-1:0] rd_addr, output wire [DATA_WIDTH-1:0] rd_data ); reg high_valid; reg [7:0] high_byte; reg [ADDR_WIDTH-1:0] wr_addr; reg [DATA_WIDTH-1:0] ram_mem [0:(1ADDR_WIDTH)-1]; wire [DATA_WIDTH-1:0] pixel_wr {high_byte, wr_data}; wire we_a high_valid wr_en; always (posedge clk_wr) begin if (wr_en) begin if (!high_valid) begin high_valid 1; high_byte wr_data; end else begin high_valid 0; wr_addr wr_addr 1b1; ram_mem[wr_addr] pixel_wr; end end end always (posedge clk_rd) begin rd_data ram_mem[rd_addr]; end endmodule逻辑说明这里有两个always块分别对应写时钟和读时钟。写侧wr_en到来时判断high_valid状态如果高字节还没存就把当前字节存为high_byte并拉高high_valid如果已经有高字节说明当前字节是低字节用{pixel_wr}写入RAM写地址加1同时拉低high_valid等待下一像素。因此每两个字节完成一次16位写入wr_addr按像素数量递增而不是按字节递增。读侧是标准的异步读转同步读写法rd_data在rd_addr变化后的下一个时钟沿更新。这意味着VGA控制器在发出读地址后需要过一个时钟周期才能拿到数据。我在VGA模块里专门用pixel_reg打了一拍把数据和同步信号对齐。如果你直接读RAM组合输出虽然省了一个周期但时序和时钟频率都难以保证不建议。3.4 VGA控制器计数器产生同步和地址VGA控制器是纯时序逻辑围绕两个计数器工作。下面的代码展示了行场计数和像素读取的关键部分reg [9:0] h_cnt 0, v_cnt 0; reg [15:0] pixel_reg; always (posedge clk or negedge rst_n) begin if (!rst_n) begin h_cnt 0; v_cnt 0; end else begin if (h_cnt 799) begin h_cnt 0; if (v_cnt 524) v_cnt 0; else v_cnt v_cnt 1; end else h_cnt h_cnt 1; end end always (posedge clk or negedge rst_n) begin if (!rst_n) begin hsync 1; vsync 1; end else begin hsync (h_cnt 656 h_cnt 751) ? 0 : 1; vsync (v_cnt 490 v_cnt 491) ? 0 : 1; if (h_cnt 640 v_cnt 480) begin addr v_cnt * 640 h_cnt; pixel_reg pixel_in; end end end always (posedge clk) begin if (h_cnt 640 v_cnt 480) begin {rgb_r, rgb_g, rgb_b} pixel_reg; end else begin rgb_r 5d0; rgb_g 6d0; rgb_b 5d0; end end参数说明h_cnt从0到799v_cnt从0到524分别代表每个像素时钟和每行时间。行同步信号在656到751期间拉低恰好96个像素时钟场同步在490到491行拉低2行时间。只要这些数值与VGA标准一致显示器就能正确识别。addr计算用了乘法综合器会把它变成累加器或乘加器对于640x480这种小分辨率没有性能压力。RGB输出有一段组合逻辑判断显示区把pixel_reg拆成RGB565消隐区输出全0。注意addr和pixel_reg的延迟关系pixel_reg在h_cnt640且v_cnt480条件下更新所以在同一时钟沿读取RAM下一个时钟沿pixel_reg才是有效数据RGB输出在下一个时钟沿锁存pixel_reg。这样RGB数据比地址晚两个时钟沿但同步信号也要相应打拍。为了简化实际工程里我把hsync/vsync也打了两拍输出保证RGB和同步边沿对齐。当然也可以把RAM读延迟通过地址来补偿但那样设计不够清晰。3.5 复位、上电初始化和帧头处理除了核心链路实际工程还应该考虑上电复位时RAM里的初始内容。一个便宜的方案是给RAM写一个复位标志上电帧头未检测到之前VGA读端输出背景色。我在image_buffer里加了一条enable_frame信号只有收到帧头0xAA55后wr_addr才从0开始递增在未收到帧头时读端照常扫描RAMRAM内容是什么显示什么可能会出现雪花。帧头检测逻辑可以放在PC端脚本和FPGA端各做一半PC端在bin文件最前面写入0xAA 0x55FPGA端在uart_rx输出的字节流里检测连续两个字节为0xAA、0x55后拉高frame_align信号同时wr_addr清零。后续字节正常拼接。检测到下一个帧头时说明PC端开始发下一帧就把wr_addr再次清零。这样即使传输中途断线或插入了错误字节也能在下一帧开始处恢复同步不会一路错到底。4. 从仿真到上板让VGA真正亮起来的一整套运行流程4.1 仿真UART接收写好一个可复用的发送tasktestbench是调试这套系统最关键的环节。写UART验证时我习惯在testbench里写一个task来模拟串口发送这样可以按任意字节顺序灌入数据。示例代码如下task send_byte; input [7:0] data; integer i; begin rx_line 1b1; #(DIV_CLK*16*1); rx_line 1b0; #(DIV_CLK*16); for (i 0; i 8; i i 1) begin rx_line data[i]; #(DIV_CLK*16); end rx_line 1b1; #(DIV_CLK*16); end endtask这个task里的DIV_CLK是采样时钟周期数。要注意仿真时钟是采样时钟16倍波特率所以一个bit是16个采样周期起始位、数据位、停止位各占16周期。发送时rx_line从高电平拉低然后按位发送最后拉高停止位。用这个task发送0xAA、0x55之后检查image_buffer的wr_addr在帧头之后才开始递增。仿真时先连续发送几个普通字节观察uart_rx的rx_valid时序再发送完整的图片数据在波形窗口里检查RAM写地址是否按2字节递增VGA读地址是否按行列扫描。如果RAM写地址和读地址有重叠通过计算地址差可以判断是否超出了RAM容量。这些检查在仿真里远比上板方便值得花半小时写全。4.2 综合时设置引脚约束避免引脚乱分配FPGA工程的引脚约束文件需要手动填写。把顶层接口名映射到具体器件引脚例如我会写set_location_assignment PIN_AA18 -to vga_hsync set_location_assignment PIN_AB19 -to vga_vsync set_location_assignment PIN_AK21 -to vga_r[4]但不同板卡引脚差别很大这只能靠查阅原理图完成。如果没有约束文件综合器随机分配引脚烧录后VGA肯定不亮。尤其是RGB数据位如果高字节和低字节分配反了颜色会完全错位。我一般先把所有输出引脚设置为3.3V CMOS电平并检查串口的rx引脚是否有上拉电阻如果板上没有上拉UART空闲态可能漂到0会持续误检起始位。4.3 上位机发送工具的选择与使用发送图片的工具有很多我常用的是串口助手但要注意三点选择波特率115200、8N1打开文件时选择二进制模式不要勾选“发送新行”。很多串口助手默认在发送内容后追加回车换行这会往数据里插入0D 0A两个字节FPGA把这两个字节当成像素数据造成整个图像错位。另外如果串口助手一次发送整个614KB文件可能会卡住或发送不完整。我通常用支持文件分块发送的小工具或者用Python脚本自己写一个串口发送器控制在几百字节每秒配合循环校验打印确认发送进度。其实用Python脚本更可控因为可以加上帧头、帧尾和CRC方便FPGA端判断完整帧但作为基础工程串口助手已经够用。4.4 板级调试分步点亮VGA板级调试我把流程拆成三步。第一步亮同步只烧录VGA控制模块屏幕会出现黑屏有些显示器会显示“无信号”但实际上已经锁定了场频用逻辑分析仪抓一下hsync确认频率在31.5kHz。第二步看噪声在没有发送数据时VGA屏幕会出现雪花点这很正常因为RAM里是未初始化的数据说明读写链路在工作。第三步传图PC端发送图片bin文件观察屏幕从上到下逐渐刷出画面。如果前两步都通过第三步一般不会出大问题最多是位序或颜色问题按第5章的排查方法处理。我还习惯在顶层加两个测试IO一个LED接到uart_rx的rx_busy发送时能看到LED闪烁一个LED接到frame_align标志校验到帧头时点亮。这两个信号能帮我快速判断串口链路是否在动。在调试阶段把内部信号引到引脚上多看几眼比盲猜状态机有效得多。5. 避坑记录黑屏、花屏、丢字节的排查手册5.1 VGA显示器完全黑屏连光标都没有现象是接上VGA线之后显示器提示无信号或直接黑屏hsync/vsync引脚用示波器看不到同步脉冲。原因一般是时钟没有起来、同步极性反了或者引脚约束没做对。我见过最多的是PLL输出接错比如给VGA控制模块用的是50MHz时钟导致像素时钟是要求的2倍hsync频率变成2倍行频显示器就拒绝同步。还有开发板VGA接口上hsync和vsync是经过三态缓冲器的要额外配置输出使能否则引脚没输出。解决先确认像素时钟频率。用示波器量hsync频率640x48060Hz应该约31.5kHz。如果量到63kHz把时钟源或分频系数改回去。然后检查引脚约束文件确保vga_hsync、vga_vsync、rgb各引脚分配的FPGA管脚和原理图一致没有和板上其他外设冲突。上述都正确仍黑屏用串口助手发送一个字节观察图像是否突然闪出一行如果有说明RAM数据已经驱动屏幕问题只在于同步信号。5.2 花屏、错位、图像呈斜条纹现象是发送完图片后画面像打碎了一样颜色杂乱或者图像明显错位。原因分两类。第一类是写地址没有对齐例如帧头检测逻辑无效或者复位信号在发送开始后触发了地址清零导致pixel 0没有落到RAM的0地址。第二类是UART字节错位PC端用的小端和大端不匹配或者发送工具在二进制文件里自动插入了0D 0A。解决先检查上位机bin文件大小是否是64048022如果多出几个字节说明串口工具加了换行。其次在FPGA端把读地址、写地址和有效字节计数在仿真里拉出来对比。更彻底的预防方案是在数据流里加入简单的帧头识别并在拼接两个字节前先验证高字节的合法性。比如帧头0xAA55后紧接着的数据如果出现0xAA不要当作新帧头要设置成只有连续收到两个帧头字节才重置写地址。5.3 串口接收丢字节画面隔几行就缺数据现象是图像中有些行是旧内容或马赛克发送完成后重新发一次缺的内容位置不同。原因通常不是FPGA端丢数据而是PC端发送太快或者串口助手发送缓冲区不够大。UART_RX模块每字节需要10个bit时间115200波特率下一个字节约86.8微秒意味着接收速度上限约11.5KB/s。如果上位机用USB转串口且驱动缓冲较大一次性把614KB数据全部灌入芯片串口FIFO会溢出但PC端的发送速度本身不会超过接口速率。真正的风险是PC端软件分包发送之间的间隔不够导致FPGA的rx_valid没有及时被消费。因为本设计中rx_valid直接连到RAM写使能如果写使能时RAM正在处理上一字节虽然RAM接口是同步的通常不会丢但如果UART_RX模块状态机在停止位结束后没有留出足够恢复时间或时钟约束不够就会出现偶发丢失。解决在PC端发送前加延时让每包数据在几十毫秒内发送。更稳妥的做法是把UART_RX输出的rx_valid打两拍同步到写时钟域。如果VGA显示时发现缺行还可以提高串口波特率到460800并重新计算DIV_CLK。顺带提醒一句不要盲目提高波特率要确认USB转串口芯片和FPGA板载电平转换都支持否则误码率会飙升。5.4 颜色不对整个画面偏色或者红蓝互换现象是图片能显示但发红色却是蓝色发绿色偏暗或者每个像素颜色有阵点。原因很直接RGB位数分配错误或者VGA电阻网络的阻值接错。我提供的RGB565颜色定义已经明确高5位是红、中间6位是绿、低5位是蓝。很多开发板的VGA引脚是RGB各6位或各8位此时要把绿坐标接上相应的高位低两位置零同时检查板卡上VGA输出端的电阻网络是否按1、2、4……权重连接。如果你用的是24位色输出直接把RGB565扩展成888在仿真里没问题但VGA物理接口可能没有对应引脚。解决先用纯色测试图排查。例如发送全红像素观察屏幕是否全红全绿、全蓝同理。如果全蓝显示成红色就把RGB三个信号在顶层交换。如果是某一个颜色有噪点检查该颜色的低位是不是没有置零或悬空VGA输入高阻会把该引脚拉成随机值。5.5 复位时序导致图像只显示一半或者有横向白条现象是发送完图片数据后画面只有上半个区域或者中上部分有一条明显的横贯白线数据重新发送依旧如此。原因通常是上电后复位释放的时机不对。如果frame_align没有在发送数据前拉高那么第一帧的帧头可能被当成像素数据写进RAM写地址发生了偏移而帧头检测只在复位期间有效错过之后就再也不会复位写地址。另一种情况是PC端发送时FPGA正好在VGA扫描到中间区域但RAM写入不受影响只是显示时写地址和读地址相差很多表现为白条或错位。解决在顶层加一个上电延时等PLL锁定后再释放复位。同时让frame_align在每次复位后重新等待帧头。PC端发送数据前先发送一次全0或全1的测试序列让FPGA重新对帧。最好的办法是在PC端脚本里加一个“发送前重发帧头”的按钮手动同步。这个坑我自己踩过两次后来固定把帧头设计成四字节0xAA 55 00 00避免误判。6. 二次开发从静态图到动态视频流的升级思路6.1 修改分辨率与RAM容量如果你要显示800x600首先改VGA时序参数行总数为1056行显示800行同步宽度128场总数为628场显示600场同步宽度4。像素时钟从25MHz改为40MHz。RAM容量要扩大到80060016bit按8bit字节数是960000字节。同时PC端脚本的resize目标尺寸和地址映射都要改。改完先仿真确认hsync频率为约37.9kHz。整个改动成本不大但每一步都要同步否则显示会错位。6.2 加入乒乓缓存解决画面撕裂如果要显示动态视频瓶颈在于串口速率。一个可行的过渡方案是用乒乓RAM两块RAM轮流接收和显示。例如PC端发送第一帧时RAM A正在被VGA读取第一帧传完后交换角色VGA读RAM A串口写RAM B。这样读写不再竞争但RAM容量会翻倍614400字节变成1.2MB。对于资源充足的FPGA可以考虑。具体实现时每个RAM的读写时钟还是原来的只是RAM A和RAM B的写使能轮流开放读地址则根据当前选择信号路由到对应RAM的输出。帧切换信号最好在vsync有效时更新避免显示过程中突然切换。6.3 用USB或以太网接口提升传输速率如果不想双倍RAM也可以用USB高速串口适配器把波特率提高到几Mbps或者直接换USB/UART芯片的并行接口。由于UART_RX模块的输出仍然是字节流和valid你只需把数据源从UART_RX换成来自USB芯片的fifo接口image_buffer不用改动。这时要注意异步FIFO深度USB芯片的fifo读端会一次性读出整包数据如果FPGA端处理不及时会溢出FIFO深度至少设为2048。并行接口的时钟频率和数据宽度要和芯片手册匹配最好让提供时钟的芯片工作在FPGA同一个时钟域。如果换成摄像头模块UART_RX整个模块都会被替代但帧拼接、双口RAM、VGA控制器可以原样保留。这种方式更适合做视频流因为CMOS摄像头输出的像素时钟和行场信号可以让image_buffer直接使用不再需要串口拼接。我从这个工程做二次开发得到的最大体会是把“数据源”和“显示目标”解耦设计后续迭代真的省很多时间。那次改完每帧也就几十毫秒我再也不回串口传图了。希望我这套拆解能帮到你少走弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑