背景
最近想用FPGA数字图像处理加速,Camera采集到图像后直接在硬件上处理后再交给软件做二次处理。所以首先是要把Sensor给跑起来,目前也没有太多要求,采用OV2640 Sensor,IIC+DVP的接口,所以第一步要把I2C调通。虽然FPAG上也带有I2C IP,但自己写一个应该会更有乐趣,所以就有了本文。
IIC时序介绍
IIC其实时序非常简单,就俩根线SCL和SDA,逻辑0时电平为GND,逻辑高时为高阻态,一个完整时序如下:
一个完整的时序可以拆分为三个步骤:
(1)开始信号S:保证SCL为逻辑1时,SDA从逻辑1跳变为逻辑0。(后文标识位[S])
(2)数据位+ACK位: SCL下降沿时SDA准备好数据,SCL上升沿时采样。(后文标志[DA])
(3)停止信号P:保证SCL为逻辑1时,SDA从逻辑0跳变为逻辑1。(后文标志[P])
电路设计思想
分析IIC, 一般有如下几种组合:
1. 单byte读/写: [S]开始 + [DA]器件地址 + [DA]数据 + [P]停止
2. 多byte读/写: [S]开始 + [DA]器件地址 + [DA]数据*n个 + [P]停止
3. 单byte复合读/写: [S]开始 + [DA]器件地址 + [DA]片内地址 + [DA]数据 + [P]停止
4. 单byte复合读/写: [S]开始 + [DA]器件地址 + [DA]片内地址 + [DA]数据*n个 + [P]停止
5. 寻址扫描: [S]开始 + [DA]器件地址 + [P]停止
根据上述分析,可以设计一个状态机,状态图如下:
在软件上,只需要控制 [S]开始信号、[DA]数据和ACK位、[P]停止信号 状态的切换即可即可实现常用组合
Verilog实现
/**
* qinhuqiang @2022-3-20
* 后续需要考虑到的问题:
* (2) DMA 自动传输
* (3) 考虑10bit iic器件地址
* (4) 增加slave模式
*/
module iic (
inout wire iic_sda, /* serial date line */
inout wire iic_scl, /* serial clock line */
input wire mod_rst, /* i2c module reset signal */
input wire mod_clk, /* i2c module clock signal */
input wire [15:0] mod_div, /* iic dividing frequency signal */
output wire [7:0] mod_sta, /* iic status signal */
/*
* mod_sta [only read] 位分布:
* [0]: 1:iic总线空闲 0:iic总线忙
* [1]: 1:slave被寻址 0:slave没有被寻址
* [2]: 1:收到ACK 0:没有收到ACK
* [3]: 1:mod_cmd[1]未完成 0:mod_cmd[1]操作完成
* [4]: 1:mod_cmd[3]未完成 0:mod_cmd[3]操作完成
* [5]: 1:mod_cmd[5]未完成 0:mod_cmd[5]操作完成
* [6]: 1:mod_cmd[6]未完成 0:mod_cmd[6]操作完成
*/
input wire [7:0] mod_wdata, /* iic write date */
output wire [7:0] mod_rdata, /* iic read date */
input wire [7:0] mod_cmd /* iic command input */
/*
* mod_cmd [only write] 位分布:
* [0]: 0->1 跳变时将mod_div设置到reg_div
* [1]: 0->1 跳变时发送start信号, 开始占用总线 (自动从slave模式切换到master模式)
* [2]: 0->1 跳变时将mod_wdata内容锁存到移位寄存器
* [3]: 0->1 跳变时将产生9bit写总线时钟 (8bit 数据 + 1bit ACK)
* [4]: 0->1 跳变时, 设置读总线时, stop信号前不需要发送ACK (master模式和slave模式都可用)
* [5]: 0->1 跳变时将产生9bit读总线时钟 (8bit 数据 + 1bit ACK)
* [6]: 0->1 跳变时发送stop信号, 释放总线 (自动从master模式切换到slave模式)
*/
);
/* iic输出参考状态, iic标准是高阻态, 如果设置wei1, 就只是为了调试方便, 仅此而已 */
localparam IIC_OUTPUT_VER = 1'bz;
// localparam IIC_OUTPUT_VER = 1'b1;
/* 配置分频寄存器 */
reg [15:0] reg_div;
always @(posedge mod_cmd[0] or posedge mod_rst) begin
if (mod_rst)
reg_div <= 16'd0;
else
reg_div <= mod_div;
end
/* 分频器比较逻辑 */
reg [15:0] reg_div_cmp;
wire wire_scl_filp;
assign wire_scl_filp = reg_div == reg_div_cmp;
always @(posedge mod_clk or posedge mod_rst) begin
if (mod_rst)
reg_div_cmp <= 16'd0;
else if(wire_scl_filp)
reg_div_cmp <= 16'd0;
else
reg_div_cmp <= reg_div_cmp + 1'b1;
end
/* 当前iic模式 */
reg reg_iic_mode;
localparam IIC_MODE_SLAVE = 1'b0; /* slave模式 */
localparam IIC_MODE_MASTER = 1'b1; /* master模式 */
/* 构建iic master时钟 */
reg reg_master_scl;
always @(posedge mod_clk or posedge mod_rst) begin
if (mod_rst)
reg_master_scl <= 1'b1;
else if (wire_scl_filp)
reg_master_scl <= ~reg_master_scl;
else;
end
/* iic master状态机 */
reg [3:0] reg_iic_state;
localparam IIC_STATE_HSCL = 4'd0; /* 发送停止位2, 拉高SCL */
localparam IIC_STATE_LALL = 4'd1; /* 发送停止位1, 俩跟线拉低 */
localparam IIC_STATE_ACK = 4'd2; /* 操作ACK位 */
localparam IIC_STATE_BIT0 = 4'd3; /* 操作第0位 */
localparam IIC_STATE_BIT1 = 4'd4; /* 操作第1位 */
localparam IIC_STATE_BIT2 = 4'd5; /* 操作第2位 */
localparam IIC_STATE_BIT3 = 4'd6;