4、图像采集接口与时序:OV5640摄像头配置、SCCB/I2C协议、行场同步信号解析、FPGA采集模块设计
做图像加速,第一步就是要把图像「拿进来」。
我最早接触摄像头驱动时,觉得不就是配几个寄存器嘛,结果折腾了两天才把图像调出来。后来发现,真正难的不是配置本身,而是理解背后的时序逻辑。
这一章,我们就来啃这块硬骨头。
4.1 OV5640摄像头:一颗经典的500万像素传感器
OV5640是OmniVision公司的一颗CMOS图像传感器。最大支持2592x1944分辨率,也就是500万像素。我项目中用得最多的其实是720p和1080p,因为带宽和存储都更友好。
它的核心特性:
- 支持RAW RGB、RGB565、YUV422等多种输出格式
- 内置ISP(图像信号处理),可以自动曝光、自动白平衡
- 通过SCCB总线配置内部寄存器
- 输出接口是DVP(并行)或MIPI(串行)
嗯,这里要注意:FPGA采集通常用DVP模式,因为并行接口时序简单,适合入门。
| 参数 | 典型值 |
|---|---|
| 有效像素 | 2592 x 1944 |
| 像素尺寸 | 1.4 μm |
| 输出格式 | RGB565 / YUV422 / RAW10 |
| 帧率(1080p) | 30 fps |
| 供电电压 | 1.8V / 2.8V |
| 接口类型 | DVP(8/10位) / MIPI(2-lane) |
4.2 SCCB/I2C协议:配置摄像头的「语言」
OV5640的寄存器配置走的是SCCB协议。说白了,SCCB就是简化版的I2C。区别不大,但有个坑——SCCB不支持多字节连续读写,必须一地址一数据地操作。
我曾经在项目中直接用I2C控制器去配OV5640,结果发现读回来的数据全是0xFF。查了半天,原来是SCCB的停止条件比I2C多了一个额外的时钟周期。嗯,细节决定成败。
4.2.1 时序结构
SCCB的写操作分三步:
- 发送设备地址(0x3C,写方向)
- 发送寄存器地址(16位,高字节在前)
- 发送寄存器数据(8位)
读操作稍微复杂一点:先写设备地址+寄存器地址,然后重新发送设备地址(0x3D,读方向),最后读取数据。
4.2.2 FPGA实现要点
写SCCB控制器,核心就是一个状态机:
// 伪代码示例
typedef enum {
IDLE,
START,
SEND_ADDR,
SEND_REG_H,
SEND_REG_L,
SEND_DATA,
STOP
} sccb_state_t;
always @(posedge clk) begin
case(state)
IDLE: if(start) state <= START;
START: begin
scl <= 1'b0;
sda <= 1'b0;
state <= SEND_ADDR;
end
// ... 后续状态转移
endcase
end
你想想看,这个状态机其实就8个状态左右。但要注意的是,每个状态都要等SCL的上升沿和下降沿,不能跳太快。
4.3 行场同步信号解析:读懂摄像头的「呼吸」
摄像头输出图像时,不是一股脑把像素全丢出来的。它用两个信号告诉你:「一行开始了」「一帧结束了」。
- HREF(行同步):高电平时,每个PCLK输出一个有效像素
- VSYNC(场同步):高电平表示一帧开始(或结束,取决于配置)
- PCLK(像素时钟):每个上升沿锁存一个像素数据
我记得第一次用示波器抓这三个信号时,发现VSYNC的脉宽只有几行的时间。当时以为摄像头坏了,后来才知道OV5640的VSYNC默认是「高电平有效,帧开始前拉高」。嗯,多看几遍数据手册就明白了。
4.4 FPGA采集模块设计:把模拟信号变成数字帧
采集模块的核心任务就三个:
- 检测VSYNC的上升沿,开始新一帧
- 在HREF有效期间,每个PCLK锁存一个像素
- 把像素数据写入FIFO或DDR
4.4.1 模块架构
下面是我常用的采集模块结构图:
4.4.2 核心代码片段
下面是一个简化的采集状态机:
// 采集状态机核心逻辑
always @(posedge pclk or negedge rst_n) begin
if (!rst_n) begin
frame_cnt <= 0;
line_cnt <= 0;
pixel_cnt <= 0;
fifo_wr_en <= 0;
end else begin
// VSYNC上升沿检测
vsync_rise <= vsync_d1 & ~vsync_d2;
if (vsync_rise) begin
frame_cnt <= frame_cnt + 1;
line_cnt <= 0;
pixel_cnt <= 0;
end else if (href) begin
// 行有效期间,每个PCLK写一个像素
fifo_wr_en <= 1;
pixel_cnt <= pixel_cnt + 1;
if (pixel_cnt == LINE_WIDTH - 1) begin
line_cnt <= line_cnt + 1;
pixel_cnt <= 0;
end
end else begin
fifo_wr_en <= 0;
end
end
end
4.5 配置实战:让OV5640输出720p@30fps
配置OV5640,说白了就是往寄存器里写值。下面是我常用的720p配置序列(部分):
// 初始化序列(部分关键寄存器)
0x3103, 0x11 // 软件复位
0x3008, 0x82 // 进入配置模式
0x3800, 0x01 // 水平起始高字节
0x3801, 0x50 // 水平起始低字节
0x3802, 0x01 // 垂直起始高字节
0x3803, 0xD0 // 垂直起始低字节
0x3804, 0x08 // 水平结束高字节
0x3805, 0xCF // 水平结束低字节
0x3806, 0x06 // 垂直结束高字节
0x3807, 0x8F // 垂直结束低字节
// ... 更多配置省略
配置完成后,记得把0x3008设为0x02,让摄像头进入流模式。这时候VSYNC和HREF就开始跳动了。
好了,这一章的内容就到这里。图像采集是整个加速链路的起点,把这里搞扎实了,后面的处理才能站得住脚。