Video Signals And Timing

来自osdev
Zhang3讨论 | 贡献2022年4月6日 (三) 09:09的版本 (创建页面,内容为“{{Video warning}} 为了使显卡和显示器相互独立,在通信方面有一个标准。 本页描述了该链接的技术部分,并提供了对视频卡进行编程并使其在所附屏幕上正确渲染的必要信息。 ==显示信号== 标准VGA电缆中有15个针脚。 当你的视频卡将其视频数据发送到显示器时,它使用5个数据通道: * Analog Red * Analog Green * Analog Blue * Horizontal Sync * Vertical Sync 通过在连接…”)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索

警告:不正确地更改 CRTC 或硬件设置可能对视频卡和连接的显示器有害。 配置图形硬件可能是一个值得一做的过程,许多人已经成功实现了他们的目标,甚至篡改了他们远远超出他们的设备原始规格。但是仍然要注意,有“好几个”已知的显示器实例——CRT 和 LCD 等——在输入不良数据后会烧坏。同样,视频卡也可能缺乏防止意外配置错误的类似保护措施。

免责声明:所提供的信息可能不准确,使用这些信息完全由你自己承担风险。在 OSDev.org 的整个历史中没有报告过任何事故,但万一你破坏了某些东西,我们 不负责任

为了使显卡和显示器相互独立,在通信方面有一个标准。 本页描述了该链接的技术部分,并提供了对视频卡进行编程并使其在所附屏幕上正确渲染的必要信息。

显示信号

标准VGA电缆中有15个针脚。 当你的视频卡将其视频数据发送到显示器时,它使用5个数据通道:

  • Analog Red
  • Analog Green
  • Analog Blue
  • Horizontal Sync
  • Vertical Sync

通过在连接上发送适当的RGB值,逐条扫描线构建屏幕。 然而,只有颜色流不会告诉你流的哪一部分属于屏幕上的左上角像素。 为了解决这个问题,他们又增加了两个信号: 水平同步,当一条水平线完成时发出脉冲; 垂直同步,当所有行都完成时发出脉冲。 因此,每个帧对应于单个垂直同步脉冲。 由于每个帧具有相同的像素量,因此连续脉冲之间的时间是恒定的。 监视器乘以脉冲的频率,然后基于它知道颜色数据应该在什么时候发送到屏幕上的什么位置。

然而,这个系统并没有那么简单。 第一批显示器是CRT,它使用磁场将电子束投射到磷化荧光层上,使它们在短时间内可见。 CRT的磁场具有一定的惯性-无法在一个像素可用的时间内将其从随机位置设置为另一个随机位置。 因此,视频信号必须有一些间隙,以适应监视器需要改变磁场并将电子束指向屏幕另一侧的时间。 在此期间,可能没有颜色信号,或者屏幕上可能有条纹。

自那时以来,crt已经有了很大的改进,现在被高度智能的LCD显示器所取代。 此后,信号标准一直没有改变。 由于最初的标准是为CRT制定的,因此文档的其余部分将隐含地假定为(旧的)CRT。 LCD显示器基本上是对CRT信号进行解码,然后在模拟信令方法下对其进行重组以适合自己的屏幕网格。 类似地,在许多其他参考文献中,你会看到视频卡的显示单元被称为“CRTC”或“CRT控制器”

显示组成

视频信号的所有帧都有特定的布局,视频卡对这些信号有半标准的考虑方式。 基本上,你必须向视频卡提供足够的信息,以便能够导出下图中显示的所有尺寸。 VGA Hardware说明如何将这些尺寸提供给兼容的VGA。 对于其他硬件,你应该检查卡的文档,以了解如何将这些值存储在其中。

VGA crtc.gif

在水平和垂直方向上,有6种状态,因此有6种大小。 每种尺寸都以像素为单位。 大部分发射的数据是活动显示器的一部分,即x分辨率 * y分辨率像素的区域。 每一次削减都是在:

  • 活动显示进入过扫描
  • 过扫描进入消隐的地方
  • 消隐使同步线改变的地方
  • 其中同步恢复为其原始值
  • 消隐回到过扫描的地方
  • 过扫描进入活动显示,扫描线或扫描帧完成,下一个扫描开始。

显卡通常为你提供以下可编程寄存器(水平和垂直)

  • 分辨率 (有源显示器的像素大小)
  • 总数(一次运行中的“像素”总数)
  • 消隐开始和结束(或开始和大小)-标记消隐的位置。既不消隐也不活动显示的区域变为过扫描区域。
  • 同步开始和结束 (或开始和大小)-标记同步脉冲的位置

频率

当你提高分辨率时,你将需要向显示器发送更多像素。 如果继续以相同的速率发送像素,则传输一帧的时间将增加,因此特定时间间隔内的帧数将减少。 由于CRT显示的像素在耗尽能量之前只会发出短时间的光,因此需要反复刷新。 如果这样做的速度足够快 (大约60Hz,每秒60次),屏幕对人眼来说几乎是恒定的。 当刷新率上升到对人眼无关紧要的程度时,这种情况会进一步改善。 然而,当它下降太多时,屏幕开始闪烁,导致用户头痛。 因此,为了用户的理智,我们需要将频率保持在至少60Hz,并且低于显示器功能所规定的其他速率。 为了使完整的像素帧在六十分之一秒之内,我们必须调整这些像素的传输速度。 这种速度称为像素时钟,或点时钟。 例如,VGA的点时钟为25mhz或28MHz,对应于每秒2500万像素或每秒2800万像素,后者仅足以在60hz下显示720x480的分辨率 (回想一下,活动显示只是帧的一部分)。 因此,大多数较高分辨率的视频可以使用远高于25 MHz的大范围点时钟,当前的范围允许足够的带宽在100赫兹时轻松超过1600x1200。

虽然分辨率受到视频卡的限制,但在大多数非VGA场景中,显示器无法处理信号达到的速度。 监视器具有允许的垂直频率(每秒帧数,通常以Hz为单位)和水平频率(以KHz为单位)。 有些CRT是固定频率的,只允许水平和垂直使用某些频率。 当你向旧的VGA显示器提供与这些速率不完全匹配的信号时,它们会因此烧坏。 虽然现代的CRT基本上不受不良信号的影响,但你必须知道,你可以用这种方式破坏硬件,你需要小心。

通用计时公式

为了应对所有CRT旧设备,VESA产生了一组方程式,可让你在给定所需分辨率的情况下计算所需的各种显示设置。 有三套独立的公式: 一个用于计算分辨率和所需刷新率的所有设置,一个用于计算分辨率和水平频率,还有一个用于计算分辨率和点时钟。

通常在设置模式之前,你会执行以下操作:

  • 根据分辨率和频率计算水平刷新率和像素时钟
  • 检查显卡是否能提供最接近的像素时钟
  • 计算给定分辨率和像素时钟的所有设置。
  • 检查水平和垂直频率是否在监视器允许的范围内。

如果频率恰好超出范围,你将不得不调整刷新率和/或分辨率。 因为刷新率可以稍微降低一点而不会出现问题,所以这通常是要修复的第一选择。 因此:

  • 将水平频率钳制到极限
  • 根据分辨率和新的水平频率计算像素时钟和垂直频率
  • 选择显卡具有的适当像素时钟(确保你正在舍入,如果你使用最大水平频率,则向下舍入)
  • 在给定新的点时钟的情况下计算水平和垂直频率
  • 检查频率是否在范围内。

水平频率现在应该是正确的,你可以决定是否允许新模式,因为垂直速率已降至可接受的速率以下,并且你可以通知用户不支持该模式。

VESA提供的公式使用了不同的常数标度。 Brendan正在努力把尺度换成单位。 目前,尺度空间仍然未知。

== GTF公式的通用部分

H_PIXELS_RND = ( ROUND ( H_PIXELS / CELL_GRAN_RND ) ) * CELL_GRAN_RND

if ( INTERLACE_REQUIRED == true )
{

V_LINES_RND = ROUND ( V_LINES / 2 )
V_FIELD_RATE_REQUIRED = REFRESH_RATE_REQUIRED * 2
INTERLACE = 0.5

} else {

V_LINES_RND = ROUND ( V_LINES) )
V_FIELD_RATE_REQUIRED = REFRESH_RATE_REQUIRED
INTERLACE = 0

}

if ( #MARGINS_REQUIRED == true ) {

TOP_MARGIN_LINES = ROUND ( MARGIN_PERCENT / 100 * V_LINES_RND )
BOTTOM_MARGIN_LINES = ROUND ( MARGIN_PERCENT / 100 * V_LINES_RND )
LEFT_MARGIN_PIXELS = ( ROUND ( H_PIXELS_RND * MARGIN_PERCENT / 100 / CELL_GRAN_RND ) ) * CELL_GRAN_RND
RIGHT_MARGIN_PIXELS = ( ROUND ( H_PIXELS_RND * MARGIN_PERCENT / 100 / CELL_GRAN_RND ) ) * CELL_GRAN_RND

} else {

TOP_MARGIN_LINES = 0
BOTTOM_MARGIN_LINES = 0
LEFT_MARGIN_PIXELS = 0
RIGHT_MARGIN_PIXELS = 0

}

/* 在此处使用GTF方程之一 */

V_FRONT_PORCH = MIN_PORCH_RND + INTERLACE
H_SYNC = ( ROUND ( H_SYNC_PERCENT / 100 * H_TOTAL / CELL_GRAN_RND ) ) * CELL_GRAN_RND
H_FRONT_PORCH = H_TOTAL / 2 - H_SYNC
H_BACK_PORCH = H_FRONT_PORCH + H_SYNC

使用分辨率和刷新率的GTF=

H_PERIOD_ESTIMATE = ( 1 / V_FIELD_RATE_REQUIRED - MIN_V_SYNC_AND_BACK_PORCH / 1000000 )

/ ( V_LINES_RND + 2 * TOP_MARGIN_LINES + MIN_PORCH_RND + INTERLACE ) * 1000000


V_SYNC_AND_BACK_PORCH = ROUND ( MIN_V_SYNC_AND_BACK_PORCH / H_PERIOD_ESTIMATE )

V_BACK_PORCH = V_SYNC_AND_BACK_PORCH - V_SYNC_RND

TOTAL_V_LINES = V_LINES_RND +

TOP_MARGIN_LINES + BOTTOM_MARGIN_LINES +
V_SYNC_AND_BACK_PORCH + INTERLACE + MIN_PORCH_RND


V_FIELD_RATE_ESTIMATE = 1000000 / H_PERIOD_ESTIMATE / TOTAL_V_LINES

H_PERIOD = H_PERIOD_ESTIMATE * V_FIELD_RATE_ESTIMATE / V_FIELD_RATE_REQUIRED

V_FIELD_RATE = 1000000 / H_PERIOD / TOTAL_V_LINES

if ( INTERLACE_REQUIRED == true ) {

V_FRAME_RATE = V_FIELD_RATE / 2

} else {

V_FRAME_RATE = V_FIELD_RATE

}

TOTAL_ACTIVE_PIXELS = H_PIXELS_RND + LEFT_MARGIN_PIXELS + RIGHT_MARGIN_PIXELS

IDEAL_DUTY_CYCLE = C_PRIME - M_PRIME * H_PERIOD / 1000

H_BLANK_PIXELS = ( ROUND (

( TOTAL_ACTIVE_PIXELS * IDEAL_DUTY_CYCLE / ( 100 - IDEAL_DUTY_CYCLE ) / ( 2 * CELL_GRAN_RND ) )
) ) * 2 * CELL_GRAN_RND

H_TOTAL = TOTAL_ACTIVE_PIXELS + H_BLANK_PIXELS

PIXEL_FREQ = H_TOTAL / H_PERIOD * 1000000

H_FREQ = 1 / H_PERIOD

GTF Using resolution and pixel clock

PIXEL_FREQ = PIXEL_FREQ_REQUIRED
TOTAL_ACTIVE_PIXELS = H_PIXELS_RND + RIGHT_MARGIN_PIXELS + LEFT_MARGIN_PIXELS

IDEAL_H_PERIOD = ( ( C_PRIME - 100 ) + ( SQRT ( ( ( 100 - C_PRIME ) ^ 2 ) +

( 0.4 * M_PRIME * ( TOTAL_ACTIVE_PIXELS + RIGHT_MARGIN_PIXELS + LEFT_MARGIN_PIXELS ) / PIXEL_FREQ / 1000000 ) ) )
) ) / 2 / M_PRIME * 1000

IDEAL_DUTY_CYCLE = C_PRIME - ( #M_PRIME * IDEAL_H_PERIOD / 1000 )

H_BLANK_PIXELS = ( ROUND ( TOTAL_ACTIVE_PIXELS * IDEAL_DUTY_CYCLE / ( 100 - IDEAL_DUTY_CYCLE ) / ( 2 * CELL_GRAN_RND ) ) ) * 2 * CELL_GRAN_RND

H_TOTAL = TOTAL_ACTIVE_PIXELS + H_BLANK_PIXELS
H_FREQ = PIXEL_FREQ / H_TOTAL
H_PERIOD = 1 / H_FREQ
V_SYNC_AND_BACK_PORCH = ROUND ( MIN_V_SYNC_AND_BACK_PORCH * H_FREQ / 1000000 )
V_BACK_PORCH = V_SYNC_AND_BACK_PORCH - V_SYNC_RND
TOTAL_V_LINES = V_LINES_RND + TOP_MARGIN_LINES + BOTTOM_MARGIN_LINES + INTERLACE + V_SYNC_AND_BACK_PORCH + MIN_PORCH_RND
V_FIELD_RATE = H_FREQ / TOTAL_V_LINES

if ( INTERLACE_REQUIRED == true) {

V_FRAME_RATE = V_FIELD_RATE / 2

} else {

V_FRAME_RATE = V_FIELD_RATE

}

使用分辨率和水平频率的GTF

H_FREQ = H_FREQ_REQUIRED
V_SYNC_AND_BACK_PORCH = ROUND ( MIN_V_SYNC_AND_BACK_PORCH * H_FREQ / 1000000 )
V_BACK_PORCH = V_SYNC_AND_BACK_PORCH - V_SYNC_RND
TOTAL_V_LINES = V_LINES_RND + TOP_MARGIN_LINES + BOTTOM_MARGIN_LINES + INTERLACE + V_SYNC_AND_BACK_PORCH + MIN_PORCH_RND
V_FIELD_RATE = H_FREQ / TOTAL_V_LINES

if ( INTERLACE_REQUIRED == true)
{

V_FRAME_RATE = V_FIELD_RATE / 2

} else {

V_FRAME_RATE = V_FIELD_RATE

}

TOTAL_ACTIVE_PIXELS = H_PIXELS_RND + RIGHT_MARGIN_PIXELS + LEFT_MARGIN_PIXELS
IDEAL_DUTY_CYCLE = C_PRIME - ( M_PRIME / H_FREQ )
H_BLANK_PIXELS = ( ROUND ( TOTAL_ACTIVE_PIXELS * IDEAL_DUTY_CYCLE / ( 100 - IDEAL_DUTY_CYCLE ) / ( 2 * CELL_GRAN_RND ) ) ) * 2 * CELL_GRAN_RND
H_TOTAL = TOTAL_ACTIVE_PIXELS + H_BLANK_PIXELS
H_PERIOD = 1 / H_FREQ
PIXEL_FREQ = H_TOTAL * H_FREQ

变量引用

以下是上述方程式中使用的变量:

硬件属性

对于给定的监视器,这些是恒定的。 它们描述了CRT的功能和局限性。

C_PRIME

默认为40,可以根据EDID数据计算出实际值。

M_PRIME

默认为600,可以从EDID数据计算实际值。

CELL_GRAN_RND

单元格粒度-计时应与之对齐的像素量。 例如,VGA以字符时钟的倍数使用水平计时,每个字符的宽度为8(图形和某些文本模式)或9(大多数文本模式)像素。 对于VGA图形模式,你需要在此处提供8。 根据你的特定硬件,常见的值为8或1。

MIN_PORCH_RND

通常为1。可能用于在同步周期周围强制至少一个消隐单位,以免混淆无法处理同步脉冲周围没有消隐的图形硬件。

MIN_V_SYNC_AND_BACK_PORCH

监视器需要检测垂直同步信号并随后返回屏幕左上角的时间(以微秒为单位)。默认值为550。

V_SYNC_RND

垂直同步脉冲有效的扫描线数量。 Todo 似乎取决于VESA没有告诉你的内容。 等于3,尽管在传统VGA模式12/13中使用了两条扫描线。

H_SYNC_PERCENT

应使同步脉冲处于活动状态的扫描线百分比。等于8%

视频模式输入

这些构成了用户/程序员的请求。

MARGINS_REQUIRED

页边距用于为显示的图片添加可见的黑色环境。 我假设小边距是为了确保当图片不在屏幕上完全居中时,整个图片仍然可见。

:理论上,可以使用边距在大屏幕上模拟小屏幕,或者完美地模拟不同的纵横比。 例如,如果要在16:10的显示屏上设置640*480视频模式,则可以将左右页边距分别设置为64像素(没有上边距或下边距),使其看起来与4:3显示屏上的完全相同,而不是拉伸以适应。 相反的情况也是可能的 (例如,在带有 “信箱” 的4:3显示器上显示16:10视频数据)。 不幸的是,GTF计算实际上不支持不同大小的左/右和上/下边距。

MARGIN_PERCENT

(修复:百分比!)你希望在屏幕上显示的页边距大小。 VESA建议默认1.8,以允许屏幕不完全适合图像的中心。

INTERLACE_REQUIRED

有两种视频模式,隔行模式和渐进模式。 逐行模式一次显示完整的帧,而隔行扫描模式在每次运行中显示彼此的扫描线,采用两帧来渲染完整的画面。 通常,你不想要这个,许多视频卡并不支持它,所以你通常想在这里提供 “false”。

V_LINES

所需显示器的垂直分辨率。对于640x480分辨率,请提供480。

H_PIXELS

Horizontal resolution of the display you want. 如果分辨率为640x480,请提供640。 请注意,由于 单元格粒度,视频卡通常希望这是八的倍数。

PIXEL_FREQ_REQUIRED

点时钟(或像素时钟)的速度,以像素每秒为单位。 这通常与点时钟的频率相同(以赫兹为单位),但在某些情况下存在2倍的差异 (时钟加倍时的频率为2倍,每隔一个周期发出像素时的频率为一半) 点时钟通常受到视频卡的限制,因为更高的帧率和分辨率需要更高的时钟。

REFRESH_RATE_REQUIRED

每秒field数值,单位为赫兹。 这也是非隔行模式每秒的帧数,或者隔行模式每秒的帧数的两倍。

H_FREQ_REQUIRED

所需的水平频率,单位为Hz。 在最常见的用法中,你会希望这是显示器的最大水平频率,以找到不违反显示器限制的最佳刷新率。

Outputs

GTF公式的最终结果。 这些参数与VGA CRTC型号的参数相对应。

PIXEL_FREQ

所需的点时钟(检查单位?)

H_TOTAL

扫描线中的像素量

LEFT_MARGIN_PIXELS

左边的边距量

RIGHT_MARGIN_PIXELS

右边的保证金金额

H_TOTAL

以像素为单位的水平同步持续时间

H_BACK_PORCH

水平同步脉冲与下一条扫描线开始之间的像素量

H_BACK_PORCH

活动显示结束和水平同步脉冲开始之间的像素量

TOTAL_V_LINES

帧中的扫描线数量

TOP_MARGIN_LINES

顶部的边距量。

BOTTOM_MARGIN_LINES

底部的页边距量

V_BACK_PORCH

垂直同步脉冲和下一个field开始之间的扫描线数量

V_FRONT_PORCH

field结束和垂直回程开始之间的扫描线数量

生成完整信号所需的其余三个组件是输入。 作为参考,这些是: H_PIXELS V_LINES 构成分辨率,以及 V_SYNC_RND,这是给定监视器的常数。

Intermediates

其他结果是副产品,但可能对错误和兼容性检查有用。

V_LINES_RND

每帧活动显示扫描线的数量。 等于[#V_LINES|Y分辨率]],除非使用隔行模式。

H_PIXELS_RND

使用的实际水平分辨率。基本上包含X分辨率舍入到最接近的允许边界。

V_FIELD_RATE_REQUIRED

每秒发送到监视器的完整图像的速率。除非使用隔行模式,否则等于刷新率。

H_FREQ

以赫兹为单位的水平频率

H_PERIOD

扫描线上花费的时间(秒)

H_PERIOD

在扫描线上花费的时间 (以秒为单位) 的初始估计

INTERLACE

隔行扫描模式通过跳过半条扫描线来指示,以便水平同步移动半个周期。 等于0或0.5,具体取决于是否包含隔行扫描信号。

V_SYNC_AND_BACK_PORCH

用于VSync和底部消隐的扫描线数量

V_FIELD_RATE_ESTIMATE

每秒字段的初始估计值(检查单位?)

V_FIELD_RATE

每秒的field数(检查单位?)

V_FRAME_RATE

每秒的帧数 (检查单位?)

TOTAL_ACTIVE_PIXELS

水平可见像素的数量,等于活动显示加上过扫描

IDEAL_DUTY_CYCLE

百分比。计算水平冲裁量的步骤之一。

IDEAL_H_PERIOD

在扫描线上花费的最佳时间。 这通常无法实现,因为扫描线是整数像素数。

H_BLANK_PIXELS

水平方向的空白像素总数(包括水平同步)

Brendan的旁注

引述:

我认为部分问题是VESA的原始GTF公式是从他们的电子表格中复制的,因此 (举一个愚蠢的例子) 而不是做类似的事情:

frequency = 10000000
period = 1 / frequency seconds

他们会:

frequency_in_MHz = 10
period_in_ms = 1 / (frequency_in_MHz * 1000000) * 1000

然后将其简化为:

frequency_in_MHz = 10
period_in_ms = 1 / frequency_in_MHz / 1000

然后,他们将删除他们所做的任何指示:

frequency = 10
period = 1 / frequency / 1000

最后,来自VESA的公式变成了一团混乱,你永远不会太确定是否有隐藏的比例因子。 另一个简单的例子(不是虚构的)是“MARGINS_PERCENT”,它是一个范围从0到50的值,通常除以100;它不是一个从0到0.5的值,不需要缩放。

我计划再次通过公式,并确保所有变量都是像素,扫描线,秒或赫兹 (没有隐含/隐藏毫秒,微秒,千赫兹,兆赫或 “百分比” 比例因子)。

我还注意到,每个公式中的一些计算是相同的。 与拥有3个独立的/更大的代码片段不同,它可以拆分成3个较小的代码片段,这些代码片段都使用相同的子例程来处理常见的事情。 例如,你可以用 “call do_precalculations” 替换每个公式中的大约10行,以避免代码重复 (重复三遍?)。

此外,我只发布了“基本公式”。 还有一个“二级公式”,其中第一组计算的结果被用来寻找其他参数;还有另一个计算(称为“空白公式”),用于从一些称为C、M、J和K的变量中找出“C_Prime”和“M_Prime”的值,该变量用于“二级计时”。 我想对这些公式做同样的事情,就像我对主要计算所做的那样。 然后,我需要检查所有这些参数,找出哪些参数真正有用(既可以使用VBE设置视频模式,也可以在“裸机”视频驱动程序中设置视频模式),然后反向删除冗余计算(即仅用于创建不需要的信息的计算)。 完成此操作后,我可以将其与“公共子例程(common subroutines)”思想相结合,这样整个“二级公式”最终将成为“do_postculations()”例程的一部分,并且整个空白公式将成为“do_preculations()”例程的一部分。

然后我想找到所有计算中使用的所有变量的有效范围。 这主要是取决于我计划如何实现它。 例如,如果我知道“H_FREQ”的范围是50000到1000000,那么我就可以使用这个变量的“20:12”定点来提高从32位整数得到的精度。

最后,如果你假设正在使用 “默认GTF值”,则所有这些计算都可以大大简化。 我想提供公式的简化版本,因为大多数时候你可以使用默认的GTF值,并且可以跳过很多工作。

另见

论坛

外部链接