第三章 图像传感器驱动设计:I2C配置接口实现、传感器寄存器读写、PLL时钟配置、帧同步信号解析
图像传感器驱动,说白了就是让摄像头芯片听你的话。
我刚开始做这个模块时,总觉得不就是写几个寄存器嘛,能有多难?结果第一次上板,画面全是雪花。嗯,后来才发现,I2C时序稍微偏一点,传感器就不理你了。这一章,我把这些年踩过的坑和总结的经验,一次性讲清楚。
3.1 I2C配置接口实现
图像传感器几乎清一色用I2C做配置接口。为什么?因为引脚少,两根线搞定所有寄存器读写。我个人习惯用FPGA的GPIO模拟I2C,而不是调用硬核IP。原因很简单——灵活。你可以随时调整时序,适配不同厂家的传感器。
3.1.1 I2C时序基础
I2C总线就两根线:SCL(时钟)和SDA(数据)。空闲时,两根线都被上拉电阻拉到高电平。启动信号:SCL高电平时,SDA从高变低。停止信号:SCL高电平时,SDA从低变高。
我曾经在一个项目中,因为上拉电阻选错了值,导致I2C通信时好时坏。后来查了三天,才发现是电阻太大,信号上升沿太慢。嗯,这里要注意:上拉电阻一般选4.7kΩ,总线电容大时可以降到2.2kΩ。
3.1.2 FPGA模拟I2C的Verilog实现
下面是我常用的I2C主控模块框架。注意,这里只展示了核心状态机,完整代码需要处理时钟分频和超时检测。
module i2c_master (
input wire clk, // 系统时钟,50MHz
input wire rst_n, // 异步复位
input wire start, // 启动传输
input wire [6:0] dev_addr, // 设备地址
input wire rw, // 0:写, 1:读
input wire [7:0] reg_addr, // 寄存器地址
input wire [7:0] wdata, // 写数据
output reg [7:0] rdata, // 读数据
output reg done, // 传输完成
output reg ack_err, // 应答错误
inout wire scl, // I2C时钟
inout wire sda // I2C数据
);
// 状态定义
localparam IDLE = 4'd0;
localparam START = 4'd1;
localparam SEND_ADDR = 4'd2;
localparam WAIT_ACK1 = 4'd3;
localparam SEND_REG = 4'd4;
localparam WAIT_ACK2 = 4'd5;
localparam SEND_DATA = 4'd6;
localparam WAIT_ACK3 = 4'd7;
localparam STOP = 4'd8;
reg [3:0] state, next_state;
reg [7:0] bit_cnt; // 位计数器
reg [7:0] shift_reg; // 移位寄存器
reg scl_out, sda_out;
reg sda_oe; // SDA输出使能
// 时钟分频:50MHz -> 100kHz
reg [8:0] clk_div;
reg scl_en;
// ... 状态机逻辑和时序控制代码 ...
// 实际项目中,这里大约200行代码
endmodule
3.2 传感器寄存器读写
每个图像传感器内部都有一堆寄存器,控制着曝光时间、增益、输出格式、窗口大小等等。说白了,你要通过I2C往这些寄存器里写值,传感器才会按你的要求工作。
3.2.1 典型的寄存器读写流程
以OV5640为例,写一个寄存器的流程是这样的:
- 发送启动信号
- 发送设备地址(0x3C,写操作)
- 等待传感器应答
- 发送寄存器高8位地址
- 等待传感器应答
- 发送寄存器低8位地址
- 等待传感器应答
- 发送要写入的8位数据
- 等待传感器应答
- 发送停止信号
读寄存器稍微复杂一点,需要先写寄存器地址,然后重新发送启动信号,再读数据。我记得第一次做读操作时,忘了重新发送启动信号,结果读回来的数据全是0xFF。你想想看,传感器根本没理你。
3.2.2 寄存器配置表的管理
实际项目中,传感器初始化需要配置几十甚至上百个寄存器。我习惯把这些配置做成一个表格,用ROM或者BRAM存储。这样修改配置时,只需要改ROM内容,不用动逻辑代码。
| 寄存器地址 | 寄存器名称 | 配置值 | 说明 |
|---|---|---|---|
| 0x3008 | SYSTEM_CTRL0 | 0x82 | 软件复位 |
| 0x3103 | CLOCK_ENABLE | 0x11 | 使能MIPI时钟 |
| 0x3034 | PLL_CONFIG | 0x1A | PLL倍频设置 |
| 0x3035 | PLL_CONFIG2 | 0x21 | PLL分频设置 |
| 0x3036 | PLL_MULTIPLIER | 0x46 | PLL倍频系数 |
| 0x3037 | PLL_DIVIDER | 0x08 | PLL分频系数 |
3.3 PLL时钟配置
图像传感器内部通常有一个PLL,用来产生像素时钟、MIPI时钟等。PLL配置错了,传感器要么不工作,要么输出时钟频率不对,导致图像采集失败。
3.3.1 PLL配置的核心公式
PLL输出频率 = 输入频率 × 倍频系数 / 分频系数。这个公式看起来简单,但实际配置时有很多坑。
举个例子,OV5640的输入时钟是24MHz,我需要输出84MHz的像素时钟。那么:
- 倍频系数 = 84 / 24 × 分频系数
- 如果分频系数选4,倍频系数 = 84 / 24 × 4 = 14
- 但倍频系数必须是整数,且不能超过PLL的允许范围
我曾经在一个项目中,算出来的倍频系数是13.5,取整后时钟频率偏差太大,导致图像颜色不对。后来换了分频系数,才找到一组整数解。嗯,这里要注意:PLL配置一定要保证输出频率在传感器允许的范围内,偏差一般不超过±1%。
3.3.2 常见的PLL配置寄存器
不同传感器的PLL寄存器布局不同,但通常包含以下几类:
- 时钟源选择: 选择内部振荡器还是外部晶振
- 倍频系数: 决定PLL的倍频倍数
- 分频系数: 决定输出时钟的分频比例
- 输出使能: 控制各个时钟输出端口的开关
3.4 帧同步信号解析
传感器输出图像时,会伴随帧同步信号。常见的同步信号有两种:
- VSYNC(垂直同步): 一帧开始或结束的标志
- HSYNC(水平同步): 一行开始或结束的标志
3.4.1 VSYNC和HSYNC的时序关系
典型的帧同步时序是这样的:
- VSYNC拉高,表示新一帧开始
- VSYNC拉低后,开始传输第一行数据
- 每行数据开始前,HSYNC拉高
- HSYNC拉低后,像素数据开始传输
- 一行传输完毕,HSYNC再次拉高,开始下一行
- 所有行传输完毕,VSYNC再次拉高,表示帧结束
你想想看,如果VSYNC和HSYNC的极性搞反了,采集到的图像就是颠倒的。我刚开始做时,就犯过这个错误,图像上下颠倒,找了半天才发现是VSYNC极性配置错了。
3.4.2 FPGA中解析帧同步信号
在FPGA中,我通常用状态机来解析帧同步信号:
// 帧同步解析状态机
localparam WAIT_VSYNC = 3'd0;
localparam WAIT_HSYNC = 3'd1;
localparam READ_LINE = 3'd2;
localparam LINE_DONE = 3'd3;
reg [2:0] frame_state;
reg [15:0] line_cnt; // 行计数器
reg [15:0] pixel_cnt; // 像素计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
frame_state <= WAIT_VSYNC;
line_cnt <= 16'd0;
pixel_cnt <= 16'd0;
end else begin
case (frame_state)
WAIT_VSYNC: begin
if (vsync) begin
frame_state <= WAIT_HSYNC;
line_cnt <= 16'd0;
end
end
WAIT_HSYNC: begin
if (hsync) begin
frame_state <= READ_LINE;
pixel_cnt <= 16'd0;
end
end
READ_LINE: begin
// 每个时钟周期采集一个像素
pixel_cnt <= pixel_cnt + 1'b1;
if (pixel_cnt == LINE_LENGTH - 1) begin
frame_state <= LINE_DONE;
end
end
LINE_DONE: begin
line_cnt <= line_cnt + 1'b1;
if (line_cnt == FRAME_HEIGHT - 1) begin
frame_state <= WAIT_VSYNC; // 一帧结束
end else begin
frame_state <= WAIT_HSYNC; // 继续下一行
end
end
endcase
end
end
3.5 本章知识体系
下面这张图,是我总结的传感器驱动设计核心流程。你看一眼,就能明白各个模块之间的关系。
这张图展示了传感器驱动的完整链路。从I2C配置接口开始,到寄存器读写,再到PLL配置和帧同步解析,最后输出图像数据给FPGA处理。每一步都环环相扣,缺一不可。
好了,这一章就到这里。记住,驱动调试时,先用示波器看I2C波形,再用逻辑分析仪抓帧同步信号。这两步走通了,图像数据基本就稳了。
公众号:蓝海资料掘金营,微信deep3321