LCD显示技术完全指南:原理·制造·驱动·FPGA实现之点屏二

篇9:数据来了怎么刷------帧缓冲与DDR控制器集成

上一篇我们用FPGA写了一个RGB接口控制器,实时生成彩条图案。但现实项目不可能永远显示彩条------你需要显示真实的图像、视频、或者来自CPU/GPU的界面。

问题来了:FPGA处理数据的速度和屏幕刷新率不一定同步。如果CPU送数据送慢了,屏幕就会出现"撕裂"(tearing);如果送快了,数据会覆盖正在显示的内容。

解决这个问题的标准方法就是 帧缓冲(Framebuffer) ------在内存中开辟一块区域,完整存储一帧图像,FPGA以固定速率从其中读取,而CPU可以异步地写入新数据。

这一篇,我们从小容量的BRAM帧缓冲开始,然后扩展到DDR,最后讨论ARM+FPGA架构下的数据流设计。


一、为什么需要帧缓冲?

1.1 同步难题

一个典型的显示系统有三个角色:

  • 数据源 :CPU、GPU、摄像头、视频解码器等,产生图像数据
  • 显示控制器 :FPGA中的RGB/LVDS/MIPI发送器,以固定时序向屏幕发送数据
  • 屏幕 :以固定的刷新率(如60Hz)逐行扫描

矛盾点 :数据源产生数据的速度和时机是不可预测的(取决于CPU负载、解码速度等),而屏幕要求数据在精确的时间点出现在数据线上。如果两者直接连接,就会出现:

  • 数据源送慢了 → 屏幕读取时数据还没准备好 → 读到旧数据 → 画面卡顿
  • 数据源送快了 → 新数据覆盖了正在被屏幕读取的区域 → 屏幕上部分画面是旧帧、部分是新帧 → 撕裂(tearing)

1.2 帧缓冲的解决方案

在内存中开辟一个 独立的空间 (帧缓冲),它有两个接口:

  • 写入端口 :数据源任意时刻写入新的一帧(或局部更新)
  • 读取端口 :显示控制器以固定时序读取,并发送给屏幕

两个端口独立操作,只要写入速度不低于读取速度,并且使用双缓冲三缓冲机制,就能保证屏幕总是读到完整的一帧。

类比 :帧缓冲就像电影院放映员和送片员的关系。送片员(数据源)可以随时把新胶片送到放映室,但放映员(显示控制器)只在固定时间从片盒里取胶片放映。只要片盒里有完整的胶片,就不会出问题。


二、BRAM帧缓冲:小分辨率的入门方案

对于低分辨率(如480×272、800×480)和低色深(如16位RGB565),FPGA内部的BRAM(块RAM) 完全可以作为帧缓冲。

2.1 容量计算

以800×480、RGB565(16位色)为例:

  • 帧缓冲大小 = 800 × 480 × 16 = 6,144,000 bits = 6.144 Mbits
  • 一块典型的BRAM(如Xilinx 7系列)为36Kbits,需要 6.144M / 36K ≈ 171 块
  • 中等规模FPGA(如Artix-7 100T)有约135块BRAM,不够;Artix-7 200T有约365块,勉强够。

对于480×272、RGB565:480×272×16 ≈ 2.09 Mbits,需要约58块BRAM,大部分小FPGA也能容纳。

工程结论 :BRAM帧缓冲只适合 小分辨率 (≤480×272)或 高成本FPGA 。800×480已经接近BRAM容量的上限,不建议用BRAM做帧缓冲。

2.2 双端口BRAM实现

FPGA中的BRAM天然支持 真双端口(True Dual Port) :两个端口完全独立,可以同时读写不同地址。

我们利用这一点:

  • 端口A :连接数据源的写入逻辑(AXI-Stream、FIFO等)
  • 端口B :连接显示控制器的读取逻辑(像素地址发生器)

verilog

复制代码
// 双端口BRAM例化(Xilinx)
xpm_memory_tdpram #(
    .ADDR_WIDTH_A(19),  // 2^19 = 524288 地址(对于800×480,每个地址存16bit)
    .ADDR_WIDTH_B(19),
    .DATA_WIDTH_A(16),
    .DATA_WIDTH_B(16),
    .MEMORY_SIZE(800*480*16) // bits
) u_framebuffer (
    .clka(clk_wr), .ena(1'b1), .wea(wr_en), .addra(wr_addr), .dina(wr_data),
    .clkb(clk_rd), .enb(1'b1), .web(1'b0), .addrb(rd_addr), .doutb(rd_data)
);

注意 :两个端口可以使用不同时钟(异步),但要注意跨时钟域处理(BRAM内部已经处理好)。

2.3 BRAM帧缓冲的局限性

  • 容量小 :对于1080p全高清,需要 1920×1080×24 ≈ 49.8 Mbits,即约1380块BRAM,任何FPGA都装不下。
  • 不能共享 :BRAM被帧缓冲占用后,其他模块(如滤波器、DMA)就没法用了。
  • 成本高 :大容量BRAM增加FPGA成本,不如外挂DDR划算。

结论 :BRAM帧缓冲只用于原型验证极低成本低分辨率产品 。真正产品中,几乎都用DDR。


三、DDR作为帧缓冲:主流方案

3.1 为什么选DDR?

  • 容量大 :单颗DDR3/DDR4可达几GB,足以存储多帧图像
  • 带宽高 :DDR3-1600的理论带宽12.8GB/s,远超1080p@60所需(约 1920×1080×24×60 ≈ 2.99Gbps = 374MB/s)
  • 成本低 :每比特成本远低于FPGA内部BRAM

3.2 挑战:DDR不是为实时流设计的

DDR的特点是:

  • 随机访问延迟高 (几十到上百个时钟周期)
  • 突发访问效率高 (连续地址读取很快)
  • 需要刷新、仲裁、时序控制 ------必须用DDR控制器IP

显示控制器读取帧缓冲时,是 顺序访问 (逐行、逐像素),这非常适合DDR的突发模式。但难点在于:数据源可能随机写入(例如摄像头输出、CPU绘制),且多个模块可能同时访问DDR(帧缓冲、CPU、DMA等),需要仲裁。

3.3 带宽计算:1080p@60需要多少DDR带宽?

一帧数据量 = 1920×1080×24bit = 49,766,400 bits ≈ 6.22 MB。

60帧/秒 → 数据速率 = 6.22 × 60 = 373 MB/s。

这只是读取带宽。如果同时还有写入(如摄像头实时编码),总带宽要加倍。DDR3-1600的理论带宽12.8GB/s,但实际有效带宽受限于:

  • 刷新周期(约每64ms一次)
  • 行列切换开销
  • 仲裁器开销

通常 实际可用带宽约为理论值的60~70% 。1080p@60读写共约750MB/s,远低于DDR3的几GB/s,所以绰绰有余。

但对于4K@60:3840×2160×24×60 ≈ 1.2GB/s(仅读取),加上写入可达2.4GB/s,需要DDR4或更高。


四、集成DDR控制器:从FPGA到DDR

4.1 使用厂商IP

Xilinx提供 MIG(Memory Interface Generator) ,可生成DDR3/DDR4控制器;Intel有 EMIF 。这些IP封装了物理层(PHY)、时序训练、刷新、仲裁等复杂功能,对外提供类似AXI4本地接口的简单读写端口。

MIG配置要点 (以Xilinx 7系列 DDR3为例):

  • 内存型号 :根据板载DDR芯片选择(如MT41K256M16)
  • 总线宽度 :16/32/64位,决定突发粒度
  • 时钟频率 :如400MHz(DDR3-800),实际数据率800Mbps
  • 接口类型 :AXI4(推荐,方便连接其他IP)

生成MIG后,你会得到一个 UI(User Interface)

  • app_addr\[\] :地址(字节地址,注意对齐)
  • app_cmd :0=写,1=读
  • app_en :命令有效
  • app_rdy :控制器准备好接收命令
  • app_wdf_data\[\] :写数据
  • app_wdf_wren :写数据有效
  • app_wdf_rdy :控制器准备好接收写数据
  • app_rd_data\[\] :读数据
  • app_rd_data_valid :读数据有效

使用流程 :将命令(读/写+地址)发送给MIG,等待app_rdy,然后发送数据(写)或等待读数据返回。

4.2 从像素地址到DDR地址映射

帧缓冲在DDR中是连续存储的。假设每个像素用32位(ARGB8888)存储,一行有1920个像素。

地址映射公式

text

复制代码
byte_addr = (row * H_ACT + col) * BYTES_PER_PIXEL

其中BYTES_PER_PIXEL=4(32位)。

注意 :MIG的地址通常是 字节地址 ,而且要求对齐到 总线宽度/8 。例如64位总线,地址最低3位必须为0。但我们的像素地址是4字节对齐,天然满足。

4.3 显示控制器的DMA读取

显示控制器需要一个读DMA 模块,负责从DDR连续读取像素数据并填充到内部的行缓存(Line Buffer) 或直接输出到RGB接口。

设计要点

  • 突发传输 :每次读取一个突发(burst)长度(如8个像素,共32字节),利用DDR的高效特性
  • 预取 :提前读取下一行的数据,避免读取速度跟不上PCLK
  • FIFO :在DMA输出端加FIFO,平滑DDR读延迟

简单读DMA状态机

  1. 空闲:等待显示控制器发出"下一行请求"
  2. 启动读:向MIG发送读命令(行首地址),突发长度=一行像素数(若一行像素数大于最大突发长度,拆成多次)
  3. 等待数据:每次收到app_rd_data_valid,将数据写入FIFO
  4. 重复直到一行读完,返回空闲

性能分析 :一行1920像素,每次突发8像素,需240次突发。每次突发有几十个时钟的延迟,但数据返回后是连续流的。只要FIFO深度足够(如512像素),PCLK就能连续输出。

4.4 数据源写入:ARM或CPU的接口

如果你的系统是 ARM + FPGA (如Zynq),ARM通过AXI-HP端口访问DDR,可以将帧缓冲映射到ARM的内存空间。ARM的GPU或CPU绘制图像后,只需通知FPGA帧缓冲地址已更新。

如果FPGA是独立(无ARM),数据源可能是:

  • 摄像头接口 :将摄像头数据写入DDR
  • PCIe/DMA :从主机接收图像数据
  • UART/ETH :慢速接口,需要累积一帧后再显示

无论哪种,都需要一个写DMA模块,与读DMA共用MIG仲裁。


五、仲裁:谁可以访问DDR?

多个模块可能同时访问DDR:

  • 读DMA (显示控制器):实时性要求高,必须保证每行数据准时读出
  • 写DMA (数据源):实时性一般,可以等待
  • CPU (通过AXI):偶发性访问,优先级最低

仲裁策略

  • 固定优先级 :读DMA最高,写DMA次之,CPU最低。简单,但可能导致写DMA饿死。
  • 时间片轮转 :每个模块分配固定时间窗口,适合等带宽需求。
  • 基于请求的加权轮转 :读DMA权重最高,但写DMA和CPU也能分到时间。

实现 :MIG本身支持多个AXI端口,可以在MIG内部配置优先级。或者自己实现一个仲裁器,在多个主设备之间切换。

关键要求 :读DMA的 延迟必须可预测且小于行消隐时间 。假设一行总时间15μs,其中消隐期(HBP+HFP+HSYNC)约占20%,即3μs。如果一次突发读取延迟超过3μs(约300个时钟@100MHz),就会导致FIFO下溢,屏幕出现横向撕裂。解决办法:增大FIFO深度或提高DDR时钟。


六、ARM + FPGA架构:Zynq示例

Zynq系列将ARM Cortex-A核和FPGA集成在同一芯片,通过AXI互联共享DDR。

系统框图

text

复制代码
ARM CPU (PS) -- AXI-HP --> DDR Controller (PS) --> DDR3
       |                      ^
       |                      |
       +-- AXI-GP --> FPGA Fabric (PL)
                     |
                     +-- RGB Controller
                     +-- VDMA (Video DMA)

Xilinx提供的VDMA IP :一个专门用于视频帧缓冲的DMA控制器,支持:

  • 自动从DDR读取帧数据,输出AXI-Stream
  • 支持双缓冲/三缓冲(通过S2MM和MM2S通道)
  • 集成地址生成和仲裁

使用步骤

  1. 在Block Design中添加VDMA
  2. 配置帧缓冲分辨率、像素格式(RGB888)、帧数(如3)
  3. 将VDMA的MM2S(Memory Map to Stream)端口连接到RGB控制器的AXI-Stream输入
  4. 将VDMA的S_AXI_HP端口连接到Zynq的S_AXI_HP0_FPD
  5. ARM驱动中分配DDR内存,设置VDMA的起始地址

这样,ARM只需将绘制好的图像写入DDR中的帧缓冲地址,VDMA自动将数据流式传输给FPGA逻辑,无需FPGA编写复杂的DMA逻辑。


七、双缓冲与三缓冲:消除撕裂

即使有了帧缓冲,如果数据源在屏幕读取的同一时刻 更新了帧缓冲的内容,仍然会出现撕裂。解决方法是使用 多缓冲

7.1 双缓冲

  • 在DDR中分配两个缓冲区:Buffer0 和 Buffer1
  • 显示控制器始终读取其中一个(如Buffer0)
  • 数据源写入另一个(Buffer1)
  • 当数据源完成一帧后, 交换指针 :显示控制器下一帧开始读取Buffer1,数据源写入Buffer0

交换时机 :必须在垂直消隐期(VBlank) 进行,此时屏幕没有读取数据。可以通过读取VSYNC中断或检测FPGA输出的VBlank信号来实现。

7.2 三缓冲

双缓冲有一个问题:如果数据源写入速度慢于刷新率,显示控制器可能会读完Buffer0后,Buffer1还没准备好,导致重复读取旧帧或卡顿。

三缓冲增加一个Buffer2:

  • 显示控制器读取最新完成的缓冲区
  • 数据源写入空闲缓冲区
  • 可以允许数据源慢一帧,仍然输出流畅画面

工程实现 :使用信号量寄存器标志指示每个缓冲区的状态(空闲/写入中/待显示/显示中)。FPGA提供VBlank中断给ARM,ARM在中断服务程序中更新VDMA的起始地址寄存器。


八、实战:最小DDR帧缓冲系统(800×480)

假设我们使用Artix-7 + 外部DDR3,实现一个简单的帧缓冲显示系统。

模块清单

  • MIG IP(DDR3控制器)
  • 读DMA模块(从DDR读取像素到FIFO)
  • RGB时序控制器(上一篇的模块,但数据源改为从FIFO读取)
  • 写DMA模块(通过AXI-Lite或UART接收图像数据并写入DDR)
  • 仲裁器(轮转或固定优先级)

关键代码片段 :读DMA + FIFO + RGB控制器集成

verilog

复制代码
// 读DMA模块
module read_dma (
    input clk, rst,
    // MIG用户接口
    output reg [28:0] app_addr,
    output reg [2:0] app_cmd,
    output reg app_en,
    input app_rdy,
    input [511:0] app_rd_data,
    input app_rd_data_valid,
    // 输出到FIFO
    output reg [23:0] pixel_data,
    output reg pixel_valid,
    // 控制
    input frame_start,
    input [28:0] base_addr
);

// 状态机:IDLE -> READ_BURST -> WAIT_DATA -> SEND_PIXEL -> 重复
// 每行读取800个像素,突发长度=8像素(32字节),共100次突发
// ...
endmodule

// RGB控制器(FIFO读版本)
module rgb_display (
    input pclk,
    input [23:0] fifo_dout,
    input fifo_empty,
    output fifo_rd_en,
    // RGB输出
    output hsync, vsync, de,
    output [23:0] rgb
);

// 当de为高且fifo非空时,每个时钟读取一个像素并输出
assign fifo_rd_en = de & ~fifo_empty;
assign rgb = fifo_dout;

// 其余时序逻辑同篇8
endmodule

性能验证 :在Vivado中运行仿真,观察FIFO的占用率。如果FIFO从未变空,说明DDR读取速度足够。


九、☕ 工程师私房话

为什么DDR读延迟会导致屏幕撕裂,而不是卡顿?

因为显示控制器是"只管读,不管对错"。如果FIFO空了,它会继续输出时钟,但数据线上的值是不确定的(或旧值),导致屏幕出现一条 水平方向的断裂带 ------上半部分是上一帧的内容,下半部分是下一帧的部分内容。这就是撕裂(tearing)。而卡顿(stuttering)是帧率整体下降导致的视觉效果。

面试题:如何用FPGA内部的FIFO深度计算最大允许的DDR读延迟?

假设一行有效像素时间T_line = 15μs,PCLK=50MHz(周期20ns),FIFO深度D=256像素。

FIFO以PCLK速率消耗(50MHz),若DDR突发读取延迟L(时钟周期数),则FIFO能容忍的最大延迟 = D / (PCLK频率) = 256×20ns = 5.12μs。如果DDR读延迟超过5.12μs,FIFO就会下溢。

解决方案:增大FIFO深度,或提高DDR读优先级,或预取整行数据到行缓存。

冷知识:为什么有些专业显卡使用"GDDR"而不是普通DDR?

GDDR(Graphics DDR)针对图形应用优化,具有更宽的接口(32位每通道)、更高的时钟频率和更大的突发长度,适合GPU的极高带宽需求。但GDDR延迟比普通DDR高,且功耗大,不适合CPU主存。我们做FPGA显示驱动,用普通DDR足够。

相关推荐
汽车仪器仪表相关领域19 小时前
南华 NHA-604/605 汽车排放气体测试仪:国六b全适配高精度便携检测设备
大数据·人工智能·功能测试·深度学习·安全·fpga开发·压力测试
一口一口吃成大V1 天前
使用PLL的lock信号作为复位信号
fpga开发
hexiaoyan8271 天前
图像分析与测试卡学习资料第216篇:基于FMC接口的1路full Camera Link输入 1路HDMI(DVI)输出子卡
fpga开发·图像分析与测试·数字成像
zlinear数据采集卡2 天前
电源纹波杀手:LDO线性稳压电路的“降噪哲学”——基于ZLinear数据采集卡的深度解析
单片机·嵌入式硬件·fpga开发·硬件架构
lf2824814312 天前
08 AD9361自发自收PS工程搭建
fpga开发
zlinear数据采集卡2 天前
电源纹波无处遁形!工业采集卡电源去耦与滤波电路深度解析
c语言·嵌入式硬件·fpga开发·自动化·硬件架构
通信小呆呆2 天前
单端口RAM、伪双端口RAM、真双端口RAM:功能详解与应用选型指南
fpga开发
s09071363 天前
【FPGA实战】基于Verilog的MCP2515 CAN控制器SPI驱动详解 | 附完整代码
fpga开发·硬件设计·can通信·mcp2515