3、摄像头驱动与初始化:理解摄像头(如OV5640)的I2C配置协议,编写初始化序列,通过逻辑分析仪验证波形。
摄像头驱动,说白了就是让摄像头“活过来”的第一步。很多同学一上来就急着读图像数据,结果发现画面全是雪花。嗯,我当年也干过这事。后来才明白,摄像头初始化没做好,后面全是白费功夫。
今天我们就拿OV5640这个经典传感器开刀。它支持500万像素,I2C接口配置,市面上绝大多数FPGA图像项目都在用。我个人习惯,拿到一个新摄像头,第一件事不是看数据手册,而是先搞清楚它的I2C协议——这是所有配置的基础。
3.1 I2C协议基础回顾
I2C是个两线制协议:SCL(时钟)和SDA(数据)。主设备(FPGA)控制时钟,从设备(摄像头)响应地址。OV5640的I2C地址是0x78(写)和0x79(读)。
为什么是这两个地址?因为芯片的ADDR引脚电平决定了地址最低位。OV5640默认拉低,所以7位地址是0x3C,左移一位加上读写位,就成了0x78和0x79。这个细节,我刚开始做的时候踩过坑——地址写错了,逻辑分析仪抓出来全是NACK。
OV5640的寄存器是16位地址,8位数据。这和很多8位地址的传感器不一样。你想想看,如果按8位地址去发,摄像头根本不会理你。
3.2 初始化序列的编写思路
OV5640的初始化序列,说白了就是往一堆寄存器里写特定值。数据手册里有个“初始化序列表”,几百行。但别慌,我们不需要全部照搬。
我个人习惯,把初始化分成三个阶段:
- 芯片复位与时钟配置——先让摄像头“醒过来”
- 输出格式设置——告诉它你要RGB565还是JPEG
- 分辨率与帧率配置——比如640x480 @ 30fps
我曾经在项目里遇到一个问题:摄像头输出图像颜色不对。折腾了两天,最后发现是初始化序列里漏写了一行——设置色彩矩阵的寄存器。从那以后,我每次写初始化代码,都会对照数据手册逐行核对。
3.3 代码实现:FPGA端的I2C控制器
下面是一个简化的I2C写操作Verilog代码。注意,这里只展示了单次写操作,实际初始化需要循环调用。
module i2c_wr #(
parameter CLK_DIV = 250 // 50MHz -> 200kHz I2C时钟
)(
input clk, rst_n,
input start, // 启动信号
input [15:0] reg_addr, // 16位寄存器地址
input [7:0] reg_data, // 8位数据
output reg done, // 完成标志
inout sda, // I2C数据线
output reg scl // I2C时钟线
);
// 状态机定义
localparam IDLE = 4'd0;
localparam START = 4'd1;
localparam ADDR = 4'd2; // 发送设备地址
localparam ACK1 = 4'd3;
localparam REG_H = 4'd4; // 寄存器高字节
localparam ACK2 = 4'd5;
localparam REG_L = 4'd6; // 寄存器低字节
localparam ACK3 = 4'd7;
localparam DATA = 4'd8; // 发送数据
localparam ACK4 = 4'd9;
localparam STOP = 4'd10;
reg [3:0] state, next_state;
reg [7:0] clk_cnt;
reg sda_out, sda_oe; // sda输出和输出使能
// 双向SDA控制
assign sda = sda_oe ? sda_out : 1'bz;
// 时钟分频与状态机跳转(略去完整实现,重点看结构)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= IDLE;
scl <= 1;
sda_oe <= 0;
done <= 0;
end else begin
case (state)
IDLE: begin
if (start) begin
state <= START;
clk_cnt <= 0;
end
end
// ... 其他状态跳转
STOP: begin
// 产生STOP条件:SCL高时SDA从低变高
state <= IDLE;
done <= 1;
end
endcase
end
end
endmodule
这段代码的核心是状态机。每个状态对应I2C协议的一个阶段。注意看,我用了sda_oe来控制SDA方向——这是FPGA做I2C的关键。很多新手直接assign sda = sda_out,结果SDA被拉死,永远出不了数据。
3.4 逻辑分析仪验证波形
波形验证是联调中最重要的一环。我个人习惯用Saleae逻辑分析仪,采样率设到24MHz以上。为什么?因为I2C时钟200kHz,每个bit约5us,24MHz采样能保证每个bit有120个采样点,波形还原度很高。
抓波形时,重点关注以下几点:
- START条件: SCL高时,SDA从高变低。这个沿必须干净,不能有毛刺。
- 地址字节: 0x78(写)后面必须跟ACK(SDA被拉低)。如果看到NACK,检查地址对不对。
- 寄存器地址: 16位,先高后低。比如0x3008,先发0x30,再发0x08。
- 数据字节: 写完后看ACK。如果摄像头没响应,可能是寄存器地址越界了。
- STOP条件: SCL高时,SDA从低变高。这个沿也要干净。
下面是我用SVG画的一个典型I2C写操作时序图,帮你直观理解每个阶段:
- SCL频率不能超过400kHz(OV5640标准模式),我一般用200kHz,稳定可靠。
- SDA在SCL低电平时才能变化,高电平时必须稳定。逻辑分析仪上如果看到SDA在SCL高时跳变,那肯定是协议错误。
- 有些FPGA开发板的I2C上拉电阻没焊,导致波形上升沿很缓。我遇到过,补焊4.7kΩ上拉电阻就好了。
3.5 初始化序列的验证流程
写完了I2C控制器和初始化序列,怎么验证?我一般按这个步骤来:
- 单步测试: 先发一个寄存器,比如0x3008写0x80(软复位)。用逻辑分析仪抓波形,确认START、地址、数据、STOP都正确。
- 回读验证: OV5640支持读操作。写完后读同一个寄存器,看返回值是不是你写的值。如果读回来是0xFF,说明摄像头没响应——检查硬件连接。
- 批量初始化: 把初始化序列表里的寄存器逐条写入。注意,有些寄存器需要延时等待(比如软复位后要等10ms)。
- 输出检查: 初始化完成后,用示波器看摄像头的VSYNC和HREF引脚。如果有脉冲输出,说明摄像头已经开始工作了。
我记得有一次,初始化序列写完了,逻辑分析仪波形也完美,但就是没图像。折腾了半天,发现是初始化序列里有个寄存器地址写错了——0x4740写成了0x4741。差一个bit,摄像头直接罢工。从那以后,我每次都会用回读功能验证关键寄存器。
好了,摄像头驱动与初始化这部分就讲到这里。记住,I2C协议是基础,波形验证是手段,初始化序列是核心。把这三点吃透了,OV5640就能乖乖听你指挥。
可可精品资料库