视频时序基础:逐行扫描与隔行扫描、行场同步信号、像素时钟计算、常见分辨率时序参数

做HDMI采集,说白了就是跟视频时序打交道。你想想看,摄像头采集到的图像数据,怎么才能正确地写入DDR?显示器又是怎么知道什么时候该刷新哪一行像素?这一切的答案,都藏在视频时序里。

我个人习惯把视频时序比作「阅兵式」。像素就是士兵,行同步信号是「向右看齐」的口令,场同步信号是「齐步走」的号令。没有这些口令,队伍就乱了套。

逐行扫描 vs 隔行扫描

先说说两种扫描方式。这俩概念,我当年刚入行时也迷糊过一阵子。

逐行扫描,就是从上到下,一行接一行地扫过去。第1行扫完扫第2行,第2行扫完扫第3行……直到最后一行。然后跳回顶部,重新开始。这种扫描方式画面稳定,没有闪烁感。现在的显示器、手机屏幕,基本都是逐行扫描。

隔行扫描,就有点意思了。它先把奇数行(1、3、5、7……)扫一遍,这叫「奇数场」;再扫偶数行(2、4、6、8……),这叫「偶数场」。两场合起来才是一帧完整的画面。

为什么要搞这么复杂?因为早期的CRT电视带宽有限。隔行扫描可以用一半的带宽,达到接近逐行扫描的刷新率。但代价也很明显——画面边缘会有「梳齿效应」,尤其是快速移动的物体。

我在项目中遇到过一个问题:用隔行扫描的摄像头采集运动物体,结果画面边缘全是锯齿。后来换成逐行扫描的传感器,问题就解决了。所以现在做HDMI采集,我建议优先选逐行扫描。除非你是在做老式电视信号的采集,那才需要考虑隔行。

行同步信号与场同步信号

这两个信号,是视频时序的核心。说白了,它们就是告诉显示器:「该换行了」和「该换帧了」。

行同步信号(HSYNC):每扫完一行像素,就拉一个脉冲。显示器看到这个脉冲,就知道该把电子枪(或者像素指针)移到下一行的起始位置了。

场同步信号(VSYNC):每扫完一帧(或一场),就拉一个脉冲。显示器看到这个脉冲,就知道该把指针移回屏幕左上角,开始新的一帧。

但这里有个坑——同步信号不是紧挨着有效像素的。在行同步和有效像素之间,还有一段「消隐区」。为什么要有消隐区?

嗯,这里要注意。消隐区是给显示器留的「准备时间」。CRT时代,电子枪需要时间从右边缘移回左边缘。LCD时代虽然不需要物理移动了,但时序标准保留了下来。而且,消隐区还可以用来传输音频、字幕等辅助数据——这就是HDMI的「数据岛」时期。

我曾经调试一个HDMI采集卡,发现画面总是偏左几个像素。查了半天,原来是行同步的前肩(HFP)参数配错了。所以,做时序配置时,前肩、同步脉冲、后肩这三个参数,一个都不能错。

像素时钟计算

像素时钟,就是每个像素的「心跳」。它决定了数据能跑多快。

计算公式很简单:

像素时钟 = 水平总像素 × 垂直总行数 × 刷新率

注意,这里的「水平总像素」和「垂直总行数」是包含消隐区的。不是有效分辨率。

举个例子,1080p60的标准时序:

  • 水平总像素:2200(有效1920 + 消隐280)
  • 垂直总行数:1125(有效1080 + 消隐45)
  • 刷新率:60Hz

算一下:2200 × 1125 × 60 = 148,500,000 Hz = 148.5 MHz

这就是为什么HDMI的TMDS时钟是148.5MHz。你想想看,如果直接用有效像素算:1920 × 1080 × 60 = 124.4 MHz,那就不对了。

千万别用有效分辨率去算像素时钟!我见过有新手工程师这么干,结果PLL锁不住,画面一直闪。记住,一定要用「总像素」和「总行数」。

常见分辨率时序参数

下面是我整理的一些常用分辨率时序参数。这些数据,我建议你收藏起来,做项目时直接查表用。

分辨率 刷新率 像素时钟 H总 H有效 HFP HSYNC HBP V总 V有效 VFP VSYNC VBP
640×480 60Hz 25.175 MHz 800 640 16 96 48 525 480 10 2 33
1280×720 60Hz 74.25 MHz 1650 1280 110 40 220 750 720 5 5 20
1920×1080 60Hz 148.5 MHz 2200 1920 88 44 148 1125 1080 4 5 36
3840×2160 60Hz 594 MHz 4400 3840 176 88 296 2250 2160 8 10 72

表中HFP是行前肩,HSYNC是行同步脉冲宽度,HBP是行后肩。VFP、VSYNC、VBP同理。

我个人习惯在做FPGA工程时,把这些参数定义成常量。比如:

// 1080p60 时序参数
localparam H_ACTIVE    = 1920;
localparam H_FP        = 88;
localparam H_SYNC      = 44;
localparam H_BP        = 148;
localparam H_TOTAL     = H_ACTIVE + H_FP + H_SYNC + H_BP; // 2200

localparam V_ACTIVE    = 1080;
localparam V_FP        = 4;
localparam V_SYNC      = 5;
localparam V_BP        = 36;
localparam V_TOTAL     = V_ACTIVE + V_FP + V_SYNC + V_BP; // 1125

这样写代码,可读性高,也方便后期改分辨率。你只需要改这几个参数,整个时序生成模块就自动适配了。

核心逻辑:视频时序生成状态机

理解了上面的参数,我们就可以用状态机来生成视频时序了。下面这张图,是我画的一个简化版时序生成流程:

视频时序生成状态机 行有效区 H_ACTIVE 像素 行前肩 HFP 消隐开始 行同步脉冲 HSYNC 拉低 行后肩 HBP 准备下一行 下一行开始 场同步判断 V行数 == V_TOTAL? VSYNC 脉冲 每行循环:有效像素 → 前肩 → 同步 → 后肩 → 下一行 每帧循环:V_TOTAL 行后,拉 VSYNC 开始新帧

这个状态机,说白了就是数数。每来一个像素时钟,计数器加1。计数到H_ACTIVE时,进入前肩;计数到H_FP结束,拉低HSYNC;同步脉冲结束后,进入后肩;后肩结束,一行完成。然后重复,直到V_TOTAL行都扫完,拉一个VSYNC脉冲,开始新的一帧。

嗯,这里要注意。场同步期间,行同步仍然在正常工作。也就是说,VSYNC拉低的那几行里,HSYNC该拉低还是拉低。两者是独立工作的,只是时序上要满足一定的相位关系。

调试时,我习惯用示波器同时抓HSYNC和VSYNC。如果看到VSYNC脉冲宽度不对,或者HSYNC在VSYNC期间消失了,那肯定是状态机写错了。别问我怎么知道的——调了三天才找到bug的痛,记忆犹新。

好了,视频时序的基础就这些。掌握了逐行/隔行的区别,理解了行场同步信号的作用,会算像素时钟,手头有常见分辨率的时序参数表——做HDMI采集的时序部分,你就已经上路了。

可可精品资料库