Rockchip DVP 视频采集入门笔记
面向初学者整理。代码基于orangepi 内核源码
kernel-5.10(RK3588 CIF 驱动),示例以 OV5640读完后你应该能回答:
- 摄像头的数据是怎么从 sensor 走到
/dev/video0的?- BT.601 / BT.656 / BT.1120 到底差在哪?
- 那张 "EAV/SAV、3/2/1/0" 的时序图在说什么?
0. 一句话理解
传感器(sensor)负责把光变成数字像素,并按某种"约定"把像素+同步信息从一排并行线上吐出来;SoC 的 CIF 负责按这个约定去采样,把有效像素搬进内存,成为一帧图像。
传感器不写内存、不产生中断,它只负责"按时序输出像素"。
1. 整条链路
镜头 → Sensor(光电转换 + 片内 ISP)→ DVP 并口线 → RK3588 CIF → DMA → DDR → /dev/video0
↑
PCLK / 数据线 / (同步线)
| 角色 | 负责 | 不负责 |
|---|---|---|
| Sensor | 曝光、成像、白平衡、格式转换、按行/场时序输出像素 | 不分配内存、不做 DMA |
CIF(rkcif) |
用 PCLK 采样数据、识别行/场边界、裁剪、写 DDR、发中断 | 不决定曝光、不产生像素 |
| 用户态 | S_FMT 设格式、REQBUFS 申请缓冲、DQBUF 取图 |
--- |
对应驱动目录:
- 接收端:drivers/media/platform/rockchip/cif
- 发送端(sensor 类):drivers/media/i2c
2. 三种"同步方式"(整个笔记最核心的概念)
视频是一行一行传的,接收端必须知道"这一行从哪开始、这一帧从哪开始"。业界有三种做法:
| 方式 | 同步信息在哪 | 代表 | 用到的线 |
|---|---|---|---|
| 外同步 | 单独的 HSYNC / VSYNC / HREF 引脚 | BT.601 并口、RAW 并口 | PCLK + 数据 + 2 根同步线 |
| 内嵌同步 | 塞在数据流里的特殊字节(SAV/EAV) | BT.656、BT.1120 | PCLK + 数据(省掉同步线) |
| 包(Packet) | 数据包头部的 data type | MIPI CSI-2 | 差分对 |
一个类比:
- 外同步:旁边有个人喊"开始!",你跟着数像素。喊的人迟到一点,你就会数错。
- 内嵌同步:每行开头都印着"第 N 行开始",你只需要找到这行字,永远不数错。
- 包:像快递,每个包裹上贴着面单(格式、尺寸、通道),自己描述自己。
关键结论:BT.656/1120 并没有改变像素内容,只是把同步信息编码进了数据流。
⚠️ 注意:MIPI 不属于 DVP。 上表列出 MIPI 只是为了对比"同步机制",两者是物理层和协议都完全不同的两套接口:
- DVP = 并行单端总线:PCLK + 8/16 根数据线 +(可选)HREF/VSYNC,1.8V/3.3V 单端信号;
- MIPI CSI-2 = 串行差分总线:1~4 对 Data Lane + 1 对 Clock Lane,LVDS 差分,数据以"包"为单位,包头自带格式类型和虚拟通道。
rkcif驱动同时支持这两条输入通路,所以DVP_FOR的输入模式枚举里有INPUT_MODE_MIPI------但它只表示"DVP 通路不接,数据从 MIPI 前端进来",不代表 MIPI 是 DVP 的一种模式 。格式表里带dvp_fmt_val的才是 DVP,只有csi_fmt_val的(如 RGB888/RGB565)走 MIPI。
3. 读懂 "EAV/SAV" 时序图

3.1 一行数据的结构
1 digital line = EAV | 行消隐 (Digital line blanking) | SAV | 有效像素 (Active video)
↑ H=1 ↑ H=0
SAV(Start of Active Video)和 EAV(End of Active Video)都是 4 个字节:
FF 00 00 XY
XY 字节的位定义:
| 位 | 含义 |
|---|---|
| bit7 | 固定为 1(用来识别这是同步码) |
| bit6 | F:场标志,0 = 第 1 场,1 = 第 2 场 |
| bit5 | V:场消隐标志,0 = 有效行,1 = 场消隐行 |
| bit4 | H:0 = SAV,1 = EAV |
| bit3:0 | 保护位(由 F/V/H 计算,用于校验) |
提示:
FF 00 00这个前导序列在有效像素里是不允许出现 的。所以规范要求把数据里的0x00/0xFF分别裁剪成0x01/0xFE,OV5640 的注释里也写了这一点(ov5640.c 约 2030 行)。
3.2 图上的数字 0~7 是怎么来的
把 (F, V, H) 当二进制读,正好得到 0~7:
| 行类型 | F | V | H | 值 |
|---|---|---|---|---|
| 场 1 有效行 | 0 | 0 | 0 / 1 | SAV=0 ,EAV=1 |
| 场 1 场消隐行 | 0 | 1 | 0 / 1 | SAV=2 ,EAV=3 |
| 场 2 有效行 | 1 | 0 | 0 / 1 | SAV=4 ,EAV=5 |
| 场 2 场消隐行 | 1 | 1 | 0 / 1 | SAV=6 ,EAV=7 |
所以图里那些 3/2/1/0、7/6/5/4 就是 EAV/SAV 的 XY 值,不是像素值。
3.3 隔行(Interlaced)与逐行(Progressive)
隔行(图 37-2):1 frame = Field 1 + Field 2
┌──────────── 一帧 ────────────┐
│ Field1 (F=0) │ Field2 (F=1) │ ← 两场在时间上分开,各占一半行数
│ 奇/偶行交替 │ 另一组行 │
└──────────────────────────────┘
逐行(图 37-3):1 frame = 1 field,F 恒为 0
图像一次传完,没有 F 位翻转
驱动侧为什么特别关心这个:
V4L2_FIELD_NONE(逐行)→ 走TRANSMIT_PROGRESS,一行就是内存里一行;V4L2_FIELD_INTERLACED(隔行)→ 走TRANSMIT_INTERFACE,并且两场要写进同一个 buffer。
先纠正一个常见误解:1080i 不是"分辨率减半"。1080i 的 1080 行是两场合计,每场 540 行(一场偶数行、一场奇数行),拼起来仍是完整 1080 行;只是运动画面的"瞬时垂直分辨率"约等于 540 线。
3.3.1 代码逐行解析:两场如何"织"成一帧
以下代码在 rkcif_csi_channel_init() 里(capture.c L2717),以 1920×1080i、YUV422(每像素 2 字节) 为例:
| 变量 | 初值 | 含义 |
|---|---|---|
channel->width |
1920 | 图像宽度(像素) |
channel->height |
1080 | 图像高度(行) |
channel->virtual_width |
ALIGN(width × 每像素字节数, 8) |
DDR 里一行的跨度(stride),单位字节 |
c
if (stream->cif_fmt_in->field == V4L2_FIELD_INTERLACED) {
channel->virtual_width *= 2; /* 3840 → 7680 */
channel->height /= 2; /* 1080 → 540 */
}
-
height /= 2:一个场只有 540 行,写入通道控制寄存器width | (height << 16)(L2893-2894); -
virtual_width *= 2:同一个场相邻两行之间空出一行的位置 留给另一个场。这个值写进 4 个 "virtual line width" 寄存器(L2896-2903),分别对应帧 0/帧 1 的 Y/UV。一个场的行跨度变成 7680:
|<-------------- 7680 字节 -------------->|
|<-- 本场这一行 3840 字节 -->|<- 空 3840 ->|
buffer 总大小没有变 :540 行 × 7680 = 4,147,200 = 3840 × 1080。跨度翻倍、行数减半,乘积不变。
两个场的 DMA 起始地址(L2032-2040):
c
rkcif_write_register(dev, frm1_addr_y,
next_buf->buff_addr[RKCIF_PLANE_Y] + (channel->virtual_width / 2));
frm0(场 1)地址 = buffer 基址 Bfrm1(场 2)地址 = B + virtual_width/2 = B + 7680/2 = B + 3840
注意 virtual_width / 2 恰好等于一行有效数据的字节数(3840),不是"半帧"。两个场都按 7680 跨度逐行递增:
场1:B+0, B+7680, B+15360, B+23040, ...
场2:B+3840, B+11520, B+19200, B+26880, ...
合起来看内存地址:
偏移 0 ← 场1 第 0 行 (逻辑第 0 行)
偏移 3840 ← 场2 第 0 行 (逻辑第 1 行)
偏移 7680 ← 场1 第 1 行 (逻辑第 2 行)
偏移 11520 ← 场2 第 1 行 (逻辑第 3 行)
...
场 1 占偶数行、场 2 占奇数行,正好填满彼此空出的位置 ------这就是"按行交织写成一整帧"。所以用户态拿到的已经是完整 1080 行,不需要自己拼;sequence /= 2(L7436-7437)让应用看到的帧率是 25/30fps,V4L2_FIELD_INTERLACED 标记则提示"这是织出来的帧,运动可能有梳状"。
DVP 通道的差异 :上面这段属于 MIPI/CSI 通道。DVP 走另一套寄存器(L4881-4909),stride 直接来自用户态
bytesperline,高度来自pixm.height(DVP_VIR_LINE_WIDTH/DVP_SET_SIZE);换 buffer 时的第二场偏移在 L2224-2230,同样是+ virtual_width/2。调试 DVP 隔行源(如 NVP6158)时,按整帧大小申请 buffer,再 hexdump 看奇偶行是否来自两个场即可确认。
3.3.2 隔行帧怎么显示
三种主流做法:
| 方案 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| A. Weave(直接当逐行帧) | 把已交织的 buffer 直接送 VO/RGA/编码器,按 1080p 输出 | 零成本,静止画面清晰度最高 | 运动物体边缘有梳状/锯齿 |
| B. Bob(只取一场) | 只取 540 行,纵向放大 2 倍 | 运动无梳状,最简单 | 垂直分辨率减半,静态发虚 |
| C. 去隔行(Deinterlace) | 用相邻场/帧插值生成逐行帧 | 清晰度和流畅度兼顾 | 需要缓存一场 + 额外算力 |
方案 C 的常见实现:
| 手段 | 说明 |
|---|---|
| 硬件 | RK3588 的 VOP 可直接输出 1080i 隔行时序(由 HDMI 端电视去隔行);RGA 可做简单 weave/bob 或双场混合 |
| GStreamer | deinterlace 元素(内部 yadif/bwdif,运动自适应) |
| FFmpeg | -vf yadif / bwdif |
| 自研 | 场间插值 + 运动检测,静止区域 weave、运动区域插值 |
两个必须注意的坑:
- 场序(Top/Bottom Field First) :先采的场与"奇/偶行"约定搞反,weave 后整帧会上下错一行,表现为轻微上下抖动或细密锯齿。V4L2 用
V4L2_FIELD_INTERLACED_TB/_BT表示。 - 帧率语义 :
sequence已除以 2,上层看到的是 25/30fps(不是 50/60),做时间戳同步时别按场率算。
3.4 多通道复用(图的最后一行)
BT.656 的 SAV/EAV 里还能带 ID 位 ,因此一条并口可以并排传 2 路或 4 路视频(车载 AHD 常见)。CIF 用 MULTI_ID_MODE_1/2/4 分流,见 regs.h L628-638。
4. DVP 的物理连线
┌──────────── I2C (SCL/SDA,配寄存器) ────────────┐
│ │
┌──────────┐ │ ┌──────────────────────────────┐ │
│ Sensor │◄──┴───────►│ SoC I2C │ │
│ │ └──────────────────────────────┘ │
│ │── XCLK/MCLK ◄────── cif_clkout(可选,SoC 供 24MHz)│
│ │── PCLK ───────────► cif_clkin (采样时钟) │
│ │── D[7:0] ──────────► cif_d0~d7 (BT656 只用这 8 根)│
│ │── D[15:8] ─────────► cif_d8~d15 (BT1120 追加) │
│ │── HREF ────────────► cif_href (601/RAW 用) │
│ │── VSYNC ───────────► cif_vsync (601/RAW 用) │
│ │◄─ RESETB/PWDN/电源 ── GPIO/regulator │
└──────────┘ │
| 信号 | 方向 | 作用 |
|---|---|---|
| XCLK / MCLK | SoC → Sensor | 传感器 PLL 的参考时钟,典型 24MHz |
| PCLK | Sensor → SoC | 像素采样时钟(源同步,SoC 不输出) |
| D7:0 | Sensor → SoC | 8 位数据总线 |
| D15:8 | Sensor → SoC | 高 8 位,BT.1120 用 |
| HREF | Sensor → SoC | 行有效(这行数据有效) |
| VSYNC | Sensor → SoC | 帧/场开始 |
| SCL/SDA | 双向 | I2C/SCCB 配寄存器 |
4.1 RK3588 的实际引脚
见 rk3588s-pinctrl.dtsi L131-189:
| pin group | 引脚 | 用途 |
|---|---|---|
cif_clk |
GPIO4_B4 | cif_clkout(给 sensor 的时钟) |
cif_dvp_clk |
GPIO4_B0 / B2 / B3 | cif_clkin / cif_href / cif_vsync |
cif_dvp_bus8 |
GPIO4_A0~A7 | D0~D7 |
cif_dvp_bus16 |
GPIO3_C4~D3 | D8~D15 |
注意 cif_dvp_bus16 只包含高 8 位,所以 BT.1120 要同时引用 bus8 + bus16。
DTS 写法(参考 NVP6158 的板级文件):
dts
pinctrl-0 = <&cif_clk &cif_dvp_clk &cif_dvp_bus8 &cif_dvp_bus16>;
/* endpoint */
bus-width = <16>;
4.2 MIPI CSI-2 的物理连线(对比参考,不属于 DVP)
MIPI 与 DVP 是两套独立接口(见第 2 节的提示)。这里放它的连接与 DTS 配置,只是为了对照理解 rkcif 为什么同时支持两条通路。
Sensor(如 OV13850 / IMX219) RK3588 CSI D-PHY
┌───────────────┐ ┌──────────────┐
│ CLK+ ────────┼──── 100Ω 差分对 ─────────────►│ CSI_CLK+ │
│ CLK- ────────┼─────────────────────────────►│ CSI_CLK- │
│ D0+ ────────┼──── Lane 0 ─────────────────►│ CSI_D0+ │
│ D0- ────────┼─────────────────────────────►│ CSI_D0- │
│ D1+ ────────┼──── Lane 1 ─────────────────►│ CSI_D1+ │
│ D1- ────────┼─────────────────────────────►│ CSI_D1- │
│ D2/D3 ... │ (4-lane 时追加 2 对) │ │
│ SCL/SDA ◄───►│──── I2C ────────────────────►│ I2C │
│ MCLK ◄───────┼──── 24MHz(模块自带晶振则可不接) │
│ RESETB/PWDN ◄┼──── GPIO ───────────────────►│ GPIO │
│ AVDD/DVDD/DOVDD/GND │ │
└───────────────┘ └──────────────┘
高速部分只有两类线:
| 线 | 数量 | 说明 |
|---|---|---|
| Clock Lane(CLK+/CLK−) | 1 对 | 差分时钟,DDR |
| Data Lane(D0±...D3±) | 1~4 对 | 可配置 1/2/4 lane |
| I2C(SCL/SDA) | 2 根 | 低速单端,配寄存器 |
| MCLK | 1 根 | sensor 主时钟(模组自带晶振时可不接) |
| RESETB / PWDN | 各 1 根 | 复位、掉电 |
| 电源 | 2~4 路 | AVDD / DOVDD(1.8V) / DVDD + GND |
2-lane 只需 6 根高速线(时钟 2 + 数据 4),16 位 DVP 却要 17 根(PCLK + 16 数据 + 同步),这是 MIPI 的核心优势。
- 完整链路是
sensor → csi2_dphy0 → mipi2_csi2 → rkcif_mipi_lvds2 →(可选)rkisp。
完整源码见 rk3588-orangepi-5-plus-camera1.dtsi L7-155。
驱动里的对应关系:
| DTS / 概念 | 驱动里 |
|---|---|
data-lanes |
V4L2_MBUS_CSI2_DPHY + lane 数解析 |
link-frequencies |
link_freq 控制项,sensor 按 lane 速率算 pixel_rate(OV13850 驱动内部固定 300MHz,DTS 可不写) |
| 像素格式 | MEDIA_BUS_FMT_SBGGR10_1X10、UYVY8_2X8 等(1X16 = 每时钟 1 个 16bit 采样) |
| CSI 包类型 | CIF 的 get_data_type() 把 mbus code 映射成 data type(RAW10=0x2B、RGB565=0x22,见 capture.c L601-645) |
与 DVP 的直观对比:
| DVP | MIPI CSI-2 | |
|---|---|---|
| 信号 | 单端并行 | 差分串行 |
| 走线 | PCLK + 8/16 数据 + 2 同步 | 时钟 1 对 + 数据 1~4 对 |
| 板级限制 | 短、难等长、易受干扰 | 可较长、抗干扰、FPC 友好 |
| 速率上限 | 约 100~150MHz PCLK | 每 lane 1~2.5Gbps,多 lane 叠加 |
| 同步 | 时钟沿 + HREF/VSYNC 或 SAV/EAV | 包边界 + 包头类型/虚拟通道 |
一句话:MIPI 用"更少的线、更高的单线速率、差分抗干扰"取代 DVP 的"多根并行线 + 显式同步",代价是两端都要有 D-PHY SerDes 和包协议处理。
5. 五种数据格式
5.1 BT.601:外同步的 YUV422
数据流(8bit,UYVY 顺序):
PCLK _/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_
HREF ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\_________
D[7:0] <U0><Y0><V0><Y1><U2><Y2><V2><Y3> 0x10 0x80 ...
└── 像素0 ──┘└── 像素1 ──┘ └── 行消隐 ──┘
- 每 2 个像素 4 个字节(色度共享)→ 16 bit/像素;
- 8bit 总线:2 个 PCLK 传一组
U Y V Y;16bit 总线:1 个 PCLK 传 1 像素; - 消隐期间线上市固定电平(8bit:Y=0x10,C=0x80);
- 没有 SAV/EAV,同步全靠 HREF/VSYNC。
代码对应:
- 接收端格式表:capture.c L346-403
c
{
.mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
.dvp_fmt_val = INPUT_MODE_YUV | YUV_INPUT_422 | YUV_INPUT_ORDER_UYVY,
.fmt_type = CIF_FMT_TYPE_YUV,
}
- 输入模式宏:regs.h L590-604(
INPUT_MODE_YUV;RK3588 上叫INPUT_BT601_YUV422,见 L649) - 发送端(OV5640):ov5640.c L2316-2326,
0x4300 = 0x3f(UYVY) - 并口模式要打开 HREF/VSYNC 输出:ov5640.c L2087-2088,
bt656 ? 0x1f : 0x7f
5.2 BT.656 / BT.1120:内嵌同步的 YUV422
BT.1120 传的同样是 YCbCr 4:2:2(YUV422),只是接口形式和位宽与 BT.656 不同。
BT.1120(16 位,4:2:2)------ 一个 PCLK 传一个像素
PCLK _/‾\_/‾\_/‾\_
Y[7:0] <--- Y0 ---><--- Y1 ---><--- Y2 --->
C[7:0] <--- Cb0 ---><--- Cr0 ---><--- Cb2 --->
└─ 两个像素共享一组 Cb/Cr ─┘
对比 BT.656(8 位,Y/C 复用在同 8 根线上,2 个 PCLK 才凑一对像素):
D[7:0] <U0><Y0><V0><Y1> ← UYVY,2 个 PCLK 传 2 个像素
两者的像素语义完全一样,差别只在"包装":
| BT.656 | BT.1120 | |
|---|---|---|
| 数据线 | 8 位(Y/C 复用) | 16 位(Y、C 分开)或 20 位(10bit 分量) |
| 像素率 | PCLK ≈ 2× 像素率 | PCLK ≈ 1× 像素率(可双边沿再翻倍) |
| 典型分辨率 | SD 720×576 / 720×480 | HD 1080i / 1080p |
| 同步 | EAV/SAV 内嵌 | EAV/SAV 内嵌(同样 FF 00 00 XY) |
| 色度 | 4:2:2 | 4:2:2(另有 4:4:4 双链路扩展,工程上少见) |
注意:BT.1120 标准名义上是 10 bit 分量 (20 位总线),但绝大多数 SoC 实现用 8 bit 截断的 16 位总线,Rockchip CIF 属于后者。
代码对应:
- 输入模式宏:
INPUT_BT1120_YUV422,名字就写明了 YUV422,见 regs.h L652 - 选择逻辑:capture.c L4285-4298(双边沿 + 指定 field → 走 BT1120)
- YC 交换位:
BT1120_YC_SWAP(regs.h L627 / L648),用于交换高/低 8 位中的 Y 和 C,见 capture.c L4830 - 格式仍归为 YUV 类:
fmt_type = CIF_FMT_TYPE_YUV,写内存时按 YUV422 的 bpp 计算,不做 RGB 转换 - 发送端实例:RK628 的 BT.1120 发送端固定用
MEDIA_BUS_FMT_UYVY8_2X8(rk628_bt1120_v4l2.c L1243、L1912),2X8即"每时钟 2 个 8bit 采样"(Y 和 C 各 8 位),正好对应 16 位 BT.1120 - 该驱动还有
SW_YUV2VYU_SWP寄存器(L655-662)用来调整 UYVY ↔ VYUY 顺序
三个容易混淆的点:
- BT.1120 不是 RGB 接口。RGB 在 Rockchip 上走 MIPI(
CSI_WRDDR_TYPE_RGB888),RAW 走INPUT_MODE_RAW。 - 16 位线不代表 16bit 色深,而是 Y 8bit + C 8bit 各占一半,色度仍是每两个像素共享一组,属于 4:2:2。
- BT.1120 的 EAV/SAV 内容与 BT.656 一致,只是 Y 和 C 同时送出,所以硬件解析更快、PCLK 更低。
一句话:BT.656 和 BT.1120 是同一套 YUV422 数据的两种包装------656 用 8 位线分时传 Y/C,1120 用 16 位线同时传 Y/C;同步码格式相同。
5.3 RAW Bayer 8 / 10 / 12
RAW 数据里没有任何同步码,所以必须用 HREF + VSYNC。
c
{ .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_8, },
{ .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_10, },
{ .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, .dvp_fmt_val = INPUT_MODE_RAW | RAW_DATA_WIDTH_12, },
- 位宽宏:regs.h L609-611
- 发送端最典型的例子是 mt9m001.c L73-86(10 位原生),它的总线声明直接写明是并行 + HREF/VSYNC:
c
cfg->flags = V4L2_MBUS_PCLK_SAMPLE_FALLING |
V4L2_MBUS_HSYNC_ACTIVE_HIGH |
V4L2_MBUS_VSYNC_ACTIVE_HIGH |
V4L2_MBUS_DATA_ACTIVE_HIGH | V4L2_MBUS_MASTER;
cfg->type = V4L2_MBUS_PARALLEL; /* 不是 BT656 */
- OV5640 也支持 RAW8(L2343-2362),但它只支持 8 位。
5.4 RGB565 / RGB888
- sensor 端有 :OV5640 支持
RGB565_2X8_LE/BE(L2327-2336),MT9M111 也支持(L560-590)。 - RK3588 的 CIF 并口没有 RGB 通路 :格式表里 RGB 条目只有
csi_fmt_val,没有dvp_fmt_val(capture.c L475-482)。 - 所以在本平台上:接 DVP 摄像头时不要选 RGB565,用 YUV422(BT.601/BT.656)或 RAW。RGB 只能从 MIPI CSI 进来。
6. 关键寄存器 DVP_FOR
DVP_FOR 是 "DVP FOR mat" 寄存器:一个寄存器描述"这一路输入长什么样、怎么解析" 。驱动在 capture.c L4854-4879 把各个位拼好后整体写入。
老款 CIF 的位定义(regs.h L586-642):
| 位 | 字段 | 含义 |
|---|---|---|
| bit0 | VSYNC 极性 | VSY_HIGH/LOW_ACTIVE |
| bit1 | HSYNC 极性 | HSY_HIGH/LOW_ACTIVE |
| bit3:2 | 输入模式 | BT601-YUV / BT656 / BT1120 / RAW / JPEG / MIPI |
| bit6:5 | YUV 字节顺序 | UYVY / YVYU / VYUY / YUYV |
| bit7 | YUV 422 / 420 | YUV_INPUT_422/420 |
| bit12:11 | RAW 位宽 | 8 / 10 / 12 |
| bit13 | 同步码检测 | BT656_DETECT_SAV / ..._SAV_EAV |
| bit24 | BT1120 单/双边沿 | BT1120_CLOCK_SINGLE/DOUBLE_EDGES |
| bit25 | 逐行 / 隔行 | TRANSMIT_PROGRESS / INTERFACE |
| bit26 | YC 交换 | BT1120_YC_SWAP |
| bit28~30 | 多通道 ID | 1/2/4 路、LSB/MSB |
RK3588 版本的编码不同(见 regs.h L644-653):输入模式改为 INPUT_BT601_YUV422 / BT656 / BT1120 / SONY_RAW,另有 bit9(逐行/隔行)、bit11(双边沿)、bit12(YC swap)、bit13(SAV 检测)、bit21(高对齐)。
记住: BT.656/1120 时序图里的所有"设置",最终都落在这个寄存器里。
7. 分辨率与时序参数:hact / htot / vact / vtot
以 OV5640 的 VGA 模式为例(ov5640.c L553-583):
c
{OV5640_MODE_VGA_640_480, SUBSAMPLING,
640, 1896, 480, 1080, /* hact, htot, vact, vtot */
ov5640_setting_VGA_640_480, ...}
| 参数 | 值 | 含义 |
|---|---|---|
hact |
640 | 每行有效像素(画面宽度) |
htot |
1896 | 每行总像素时钟数(含行消隐) |
vact |
480 | 每帧有效行数(画面高度) |
vtot |
1080 | 每帧总行数(含场消隐) |
一行:|◄──────────────── 1896 PCLK ────────────────►|
|◄─前肩─►|◄──── hact 640 ────►|◄── 消隐 1256 ──►|
一帧:|◄──────────────── 1080 行 ──────────────────►|
|◄── 场消隐 600 行 ──►|◄──── vact 480 行 ────►|
这 4 个数最终写到 4 个 16 位寄存器(ov5640.c L1094-1106):TIMING_DVPHO / DVPVO / HTS / VTS。
7.1 公式
帧率 fps = PCLK / (htot × vtot)
行频 = fps × vtot
驱动的等效像素率(ov5640.c L1548-1556):
c
rate = vtot * htot * fps; /* VGA@60 → 1896×1080×60 ≈ 122.86 MHz */
7.2 为什么 htot 远大于 640?
因为 OV5640 的行时间由内部时钟决定,不能压缩到 640 个时钟 。小分辨率模式是"子采样"(SUBSAMPLING):行周期固定,只输出其中一部分像素。这也解释了为什么几乎所有小分辨率模式都是 htot = 1896,只有 vtot 随帧率变化(984 / 1080)------htot 是硬件下限,vtot 是帧率调节旋钮。
7.3 谁来关心这些数?
| 参数 | Sensor | CIF |
|---|---|---|
| hact / vact | ✅ 决定输出窗口 | ✅ 写 DVP_SET_SIZE |
| htot / vtot | ✅ 决定 PCLK、帧率 | ❌ 不需要 |
| 行消隐长度 | 隐含在 htot 里 | ❌ 601 靠 HREF 跳过;656 靠 SAV 间距跳过 |
CIF 只写有效宽高(capture.c L4906-4912):
c
rkcif_write_register(dev, CIF_REG_DVP_VIR_LINE_WIDTH, val); /* stride */
rkcif_write_register(dev, CIF_REG_DVP_SET_SIZE,
width | (height << 16)); /* 有效宽高 */
7.4 设置流程
分辨率不写在 DTS 里,而是运行时协商:
用户态 v4l2-ctl --set-fmt-video=width=1280,height=720,pixelformat=UYVY
↓ S_FMT
sensor subdev:ov5640_set_fmt() → 从 mode 表选最接近的模式 → 写寄存器
↓ 格式传到 CIF
rkcif:rkcif_set_fmt() → 记录 width/height/field
↓ STREAMON
rkcif_stream_start() → 写 DVP_SET_SIZE / DVP_VIR_LINE_WIDTH / DVP_FOR
调试时先看 media-ctl -p 里 sensor pad 和 CIF pad 的格式是否一致。
注意:BT.656 的 SAV/EAV 不携带宽高信息,所以两端必须显式配置成一致;656 只解决"从哪开始",不解决"搬多少"。
8. 常见问题(FAQ)
Q1:为什么 RGB565 在 DVP 下不支持,而 YUV422 支持?
不是 DVP 协议的限制,而是 Rockchip CIF 的 DVP 硬件解析器没有实现 RGB:DVP_FOR 的输入模式枚举里没有 RGB 选项(regs.h L590-598)。MIPI 能支持是因为 CSI-2 的 data type 里直接定义了 RGB565=0x22、RGB888=0x24(capture.c L628-645),接收端只需按包搬运。
另外从工程上看:RGB565 与 YUV422 同为 16bit/像素,接 RGB565 不省带宽,画质还更低(色度只有 5/6 bit),厂商没动力做。
Q2:BT.601 和 BT.656 的本质区别?
| BT.601 | BT.656 | |
|---|---|---|
| 同步 | HSYNC/VSYNC 独立引脚 | 数据流里的 EAV/SAV |
| 消隐 | 数据线上是 0x10/0x80 | 数据线上是同步码 |
| 像素内容 | 4:2:2 YUV | 完全相同 |
| 抗干扰 | 依赖同步线时序 | 同步点绝对 + 保护位校验 |
| 多路复用 | 不支持 | 支持(ID 位) |
| 隔行场信息 | 靠 VSYNC | 靠 XY 的 F 位 |
Q3:BT.656/1120 的优势只是为了省引脚吗?
省引脚只是附带(只省 2 根)。更重要的是:同步点绝对(不怕消隐长度偏差)、自带保护位、天然支持隔行、支持多路复用、走线少更易等长。代价是两端要有同步码检测状态机、每行多 8 字节开销、数据要裁剪 0x00/0xFF、示波器调试不直观。
Q4:用 BT.601/656 的 sensor 需要设置分辨率吗?
需要,而且两端都要设 。sensor 通过 mode 表设定 hact/vact/htot/vtot 并写寄存器;CIF 通过 DVP_SET_SIZE 设定有效宽高。SAV/EAV 不含宽高,硬件不会自动识别。
Q5:图像出现"斜切"或"上下撕裂"是什么原因?
- 斜切:CIF 的宽度和 sensor 的有效宽度不一致(多搬/少搬了消隐字节);
- 撕裂:field 设置不一致(sensor 隔行、CIF 按逐行),或两场写入 buffer 的偏移算错。
Q6:为什么 BT.656 模式下 VSYNC/HREF 不用接?
因为同步信息已经编码进数据流,sensor 在 656 模式下会主动关闭这两根引脚的输出(OV5640:PAD_OUTPUT_ENABLE01 = 0x1f,见 L2087-2088)。
9. 术语表
| 术语 | 解释 |
|---|---|
| DVP | Digital Video Port,并行单端摄像头接口的统称(PCLK + 数据线 + 可选同步线) |
| MIPI CSI-2 | 串行差分摄像头接口(Data/Clock Lane + 包协议),与 DVP 是两套独立接口,不属于 DVP |
| PCLK | Pixel Clock,像素采样时钟 |
| HREF | Horizontal Reference,行有效区间 |
| SAV / EAV | Start/End of Active Video,内嵌同步码 FF 00 00 XY |
| F / V / H | XY 字节里的场标志 / 场消隐标志 / 行标志 |
| BT.601 | 数字分量视频采样标准(4:2:2、13.5MHz) |
| BT.656 | 8bit 内嵌同步接口,BT.601 数据 + SAV/EAV |
| BT.1120 | 16bit 内嵌同步接口,用于高清 |
| hact/htot | 有效宽度 / 含消隐的总宽度 |
| vact/vtot | 有效行数 / 含消隐的总行数 |
| field | 逐行(NONE)或隔行(INTERLACED) |
| mbus code | V4L2 描述总线上像素格式的枚举,如 UYVY8_2X8 |
| subdev | 子设备,sensor/桥接芯片在 V4L2 里的抽象 |
10. 一句话总结
- BT.656/1120 = 把"同步信号"变成"数据流里的文字",因此接收端更稳、可复用、可隔行。
- CIF 只做三件事:认同步、按配置的宽高搬像素、写内存。
- 分辨率不来自同步码,必须两端显式配置成一致。
- 在本平台上,DVP 只走 YUV422 / RAW / BT656 / BT1120,RGB 走 MIPI。