基于Verilog的I2C_MASTER模块设计

I2C_MASTER模块设计

I2C协议简介

I2C的数据传输中,其实重要的只有4点:起始位,数据发送,应答位,结束位。

  • 起始位:SCL为高时,使SDA产生下降沿

  • 数据发送:SCL为低时,数据允许变化,SDA为高时,数据必须保持稳定

  • 应答位:每一相数据发完后,数据接收方发出ACK表示已经收到数据,数据发送方根据这一位判断是否继续发送数据。

  • 结束位:SCL为高时,使SDA产生上升沿

知道这几点后,已经足够我们设计出状态机了,而更细节的部分直接看这篇文章就可以了:I2C协议靠这16张图彻底搞懂(超详细)

I2C接口设计

接口信号及解释

对于这一模块,我们为了能够方便的使用,采用命令+请求信号相组合的方式来驱动这一模块,于是就有了如下的接口设计:

module i2c_master
(
    input           clk         ,
    input           rst_n       ,
    
    input           i2c_start   ,
    input   [7:0]   i2c_data_in ,
    input   [3:0]   i2c_cmd     ,
    
    output          i2c_scl     ,
    input           i2c_sda_in  ,
    output          i2c_sda_out ,
    output          i2c_sda_en  ,
    
    output  [7:0]   dout        ,
    output          i2c_end     
);
  • i2c_start:每次使用时,都需要给予一个周期的start信号

  • i2c_cmd:每次使用时,都需要在i2c_start前给予希望执行的操作

  • i2c_data_in:这一信号只针对写操作,如果发送了写命令,则还需要在i2c_start前给予要写的数据

  • dout:这一信号只针对写操作,如果发送了读命令,这一信号就会返回读出的数据

  • i2c_end:这是告诉控制模块此次I2C传输完成的信号,如果发送了读命令,这也是数据有效信号

I2C状态机设计

I2C状态机原理图

 

状态机简介

首先分析I2C协议中需要接口模块做什么:

  • 发起始位

  • 读/写操作

  • 读/写响应

  • 发停止位

明确需要进行的操作后,就可以确定需要几个状态了。因为接口是基于命令控制来设计的,那么状态机的状态跳转条件也要与命令相关。为了便于理解,此处贴出状态跳转的代码:

//命令
localparam  START_CMD   = 4'b0001,
            WRITE_CMD   = 4'b0010,
            READ_CMD    = 4'b0100,
            STOP_CMD    = 4'b1000;
//状态跳转条件
assign  idle_start  = (state_c == IDLE  ) && (start && (i2c_cmd & START_CMD));
assign  start_write = (state_c == START ) && (end_cnt_time && (i2c_cmd & WRITE_CMD));
assign  start_read  = (state_c == START ) && (end_cnt_time && (i2c_cmd & READ_CMD));
assign  write_wack  = (state_c == WRITE ) && (end_cnt_bit);
assign  read_rack   = (state_c == READ  ) && (end_cnt_bit);
assign  wack_stop   = (state_c == WACK  ) && (end_cnt_time && ((i2c_cmd & STOP_CMD) || slave_ack_r));
assign  rack_stop   = (state_c == RACK  ) && (end_cnt_time && (i2c_cmd & STOP_CMD));
assign  stop_idle   = (state_c == STOP  ) && (end_cnt_time);
assign  idle_write  = (state_c == IDLE  ) && (start && (i2c_cmd & WRITE_CMD));
assign  idle_read   = (state_c == IDLE  ) && (start && (i2c_cmd & READ_CMD));
assign  wack_idle   = (state_c == WACK  ) && (end_cnt_time);
assign  rack_idle   = (state_c == RACK  ) && (end_cnt_time);
//其他注释
//cnt_time是用于产生SCL的计数器,end_cnt_time表示一个SCL时钟周期的结束
//cnt_bit是用于计数发送的bit数的计数器,end_cnt_bit表示8个bit发完

模块设计时的特殊处理

START信号

在控制模块中,为了使用方便,i2c_start信号一般持续为高电平。但是在i2c_end信号发出后,控制模块还没来得及更新命令及数据,状态机就会因为i2c_start持续为高而再次启动。这显然不是我们希望的,因为这会导致一些命令发送两次,一些命令完全不发。

为了应对这种情况,我们对i2c_start进行了一些处理:

    reg         start;
​
    always@(posedge clk or negedge rst_n) begin
        if(!rst_n)
            start <= 'd0;
        else if(state_c == IDLE && i2c_start)
            start <= 'd1;
        else if(state_c != IDLE)
            start <= 'd0;
    end

这种写法达成了两个效果:

  • 通过将i2c_start延时一个周期,给了控制模块改变数据的时间

  • 保证start是一个脉冲信号,而不是hold型的信号

经过修改后,i2c_start有了更高的自由度,可兼容脉冲和hold信号。

I2C_end信号

assign  i2c_end = wack_idle || rack_idle || stop_idle;

先来分析一下原本的写法:如果发送的命令为{READ_CMD|STOP_CMD},那么状态跳转条件中的:

//部分状态跳转条件
assign  rack_stop   = (state_c == RACK  ) && (end_cnt_time && (i2c_cmd & STOP_CMD));
assign  rack_idle   = (state_c == RACK  ) && (end_cnt_time);
​
assign  stop_idle   = (state_c == STOP  ) && (end_cnt_time);

rack_stop和rack_idle会同时拉高,之后stop_idle也会拉高,这会造成一次传输中i2c_end拉高两次。这虽然可以在控制模块的状态机设计中做出错误规避,但这依旧是危险的。

因此,我们对输出的i2c_end做一些处理:

    reg         i2c_end_r       ;
    
    always@(*) begin
        if(i2c_cmd & STOP_CMD)
            if(stop_idle)
                i2c_end_r = 1'b1;
            else
                i2c_end_r = 1'b0;
        else
            i2c_end_r =  wack_idle || rack_idle;
    end
​
assign  i2c_end = i2c_end_r;

这种写法约束了三种结束信号的优先级:

  • 当有STOP命令时,只有stop_idle才能发出i2c_end

  • 没有STOP命令时,wack_idle和rack_idle才能发出i2c_end

模块完整代码

module i2c_master
(
    input           clk         ,
    input           rst_n       ,
    input           i2c_start   ,
    input   [7:0]   i2c_data_in ,
    input   [3:0]   i2c_cmd     ,
    output          i2c_scl     ,
    input           i2c_sda_in  ,
    output          i2c_sda_out ,
    output          i2c_sda_en  ,
    output  [7:0]   dout        ,
    output          i2c_end     
);
​
//输出寄存
    reg         scl_r           ;
    reg         sda_out_r       ;
    reg         sda_out_en_r    ;
    reg         slave_ack_r     ;
    reg [7:0]   dout_r          ;
​
    reg         i2c_end_r       ;
​
//参数定义
​
    parameter   SCL_TIME    = 250,//200Khz
                DATA_WIDTH  = 8;
​
    localparam  SCL_HALF    = SCL_TIME / 2,
                SCL_LOW     = SCL_TIME / 4,
                SCL_HIGH    = SCL_TIME - SCL_LOW;
​
    localparam  START_CMD   = 4'b0001,
                WRITE_CMD   = 4'b0010,
                READ_CMD    = 4'b0100,
                STOP_CMD    = 4'b1000;
//计数器定义
    reg [7:0]   cnt_time;
    wire        add_cnt_time,
                end_cnt_time;
​
    reg [2:0]   cnt_bit;
    wire        add_cnt_bit,
                end_cnt_bit;
​
//其他信号
    reg         start;
​
//状态机参数定义
    reg [6:0]   state_c,
                state_n;
​
    localparam  IDLE    = 7'b0000_001,
                START   = 7'b0000_010,
                WRITE   = 7'b0000_100,
                WACK    = 7'b0001_000,
                READ    = 7'b0010_000,
                RACK    = 7'b0100_000,
                STOP    = 7'b1000_000;
​
    wire        idle_start  ,
                start_write ,
                start_read  ,
                write_wack  ,
                read_rack   ,
                wack_stop   ,
                rack_stop   ,
                stop_idle   ,
                idle_write  ,
                idle_read   ,
                wack_idle   ,
                rack_idle   ;
​
//状态机状态转移
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)
            state_c <= IDLE;
        else
            state_c <= state_n;
    end
​
    always@(*)begin
        case (state_c)
            IDLE :begin
                if(idle_start)
                    state_n = START;
                else if(idle_write)
                    state_n = WRITE;
                else if(idle_read)
                    state_n = READ;
                else
                    state_n = state_c;
            end
            START:begin
                if(start_write)
                    state_n = WRITE;
                else if(start_read)
                    state_n = READ;
                else
                    state_n = state_c;
            end
            WRITE:begin
                if(write_wack)
                    state_n = WACK;
                else
                    state_n = state_c;
            end
            WACK :begin
                if(wack_stop)
                    state_n = STOP;
                else if(wack_idle)
                    state_n = IDLE;
                else
                    state_n = state_c;
            end
            READ :begin
                if(read_rack)
                    state_n = RACK;
                else
                    state_n = state_c;
            end
            RACK :begin
                if(rack_stop)
                    state_n = STOP;
                else if(rack_idle)
                    state_n = IDLE;
                else
                    state_n = state_c;
            end
            STOP :begin
                if(stop_idle)
                    state_n = IDLE;
                else
                    state_n = state_c;
            end
            default: state_n = IDLE;
        endcase
    end
​
assign  idle_start  = (state_c == IDLE  ) && (start && (i2c_cmd & START_CMD));
​
assign  start_write = (state_c == START ) && (end_cnt_time && (i2c_cmd & WRITE_CMD));
assign  start_read  = (state_c == START ) && (end_cnt_time && (i2c_cmd & READ_CMD));
assign  write_wack  = (state_c == WRITE ) && (end_cnt_bit);
assign  read_rack   = (state_c == READ  ) && (end_cnt_bit);
​
assign  wack_stop   = (state_c == WACK  ) && (end_cnt_time && ((i2c_cmd & STOP_CMD) || slave_ack_r));
assign  rack_stop   = (state_c == RACK  ) && (end_cnt_time && (i2c_cmd & STOP_CMD));
​
assign  stop_idle   = (state_c == STOP  ) && (end_cnt_time);
assign  idle_write  = (state_c == IDLE  ) && (start && (i2c_cmd & WRITE_CMD));
assign  idle_read   = (state_c == IDLE  ) && (start && (i2c_cmd & READ_CMD));
​
assign  wack_idle   = (state_c == WACK  ) && (end_cnt_time);
assign  rack_idle   = (state_c == RACK  ) && (end_cnt_time);
​
//计数器
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)
            cnt_time <= 1'b0;
        else if(add_cnt_time)
                if(end_cnt_time)
                    cnt_time <= 1'b0;
                else
                    cnt_time <= cnt_time + 1'b1;
            else
                cnt_time <= cnt_time;
    end
    assign add_cnt_time = (state_c != IDLE);
    assign end_cnt_time = add_cnt_time && cnt_time >= SCL_TIME - 1'b1;
​
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)
            cnt_bit <= 1'b0;
        else if(add_cnt_bit)
                if(end_cnt_bit)
                    cnt_bit <= 1'b0;
                else
                    cnt_bit <= cnt_bit + 1'b1;
            else
                cnt_bit <= cnt_bit;
    end
    assign add_cnt_bit = ((state_c == WRITE) || (state_c == READ)) && end_cnt_time;
    assign end_cnt_bit = add_cnt_bit && cnt_bit >= DATA_WIDTH - 1'b1;
​
//启动缓存
    always@(posedge clk or negedge rst_n) begin
        if(!rst_n)
            start <= 'd0;
        else if(state_c == IDLE && i2c_start)
            start <= 'd1;
        else if(state_c != IDLE)
            start <= 'd0;
    end
​
//SCL输出定义
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)
            scl_r <= 1'b1;
        else if(add_cnt_time && (cnt_time <= SCL_HALF))
                scl_r <= 1'b0;
        else if(add_cnt_time && (cnt_time > SCL_HALF))
                scl_r <= 1'b1;
        else    
            scl_r <= 1'b1;
    end
​
    assign  i2c_scl = scl_r;
​
//sda_out_en输出定义
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)
            sda_out_en_r <= 'b0;
        else if((state_c == START) || (state_c == WRITE) || (state_c == RACK) || (state_c == STOP))
            sda_out_en_r <= 'b1;
        else
            sda_out_en_r <= 'b0;
    end
​
    assign  i2c_sda_en = sda_out_en_r;
​
//sda_out输出定义
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)
            sda_out_r <= 'b1;
        else if(state_c == START)begin
                if(cnt_time <= SCL_HIGH)
                    sda_out_r <= 'b1;
                else
                    sda_out_r <= 'b0;
                end
        else if(state_c == WRITE)begin
                if(cnt_time == SCL_LOW)
                    sda_out_r <= i2c_data_in[DATA_WIDTH-1-cnt_bit];
                else
                    sda_out_r <= sda_out_r;
                end
        else if(state_c == RACK)begin
                if(cnt_time <= SCL_LOW)
                    sda_out_r <= sda_out_r;
                else if(i2c_cmd & STOP_CMD)
                    sda_out_r <= 'b1;
                else
                    sda_out_r <= 'b0;
                end
        else if(state_c == STOP)begin
                if(cnt_time <= SCL_HIGH-19)
                    sda_out_r <= 'b0;
                else
                    sda_out_r <= 'b1;
                end
        else
            sda_out_r <= 'b1;
    end
​
    assign  i2c_sda_out = sda_out_r;
​
//slave_ack输出定义
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)
            slave_ack_r <= 'd0;
        else if((state_c == WACK) && (cnt_time == SCL_HIGH))
            slave_ack_r <= i2c_sda_in;
        else
            slave_ack_r <= slave_ack_r;
    end
​
//dout输出定义
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)
            dout_r <= 'd0;
        else if((state_c == READ) && (cnt_time == SCL_HIGH))
            dout_r[DATA_WIDTH-1-cnt_bit] <= i2c_sda_in;
        else
            dout_r <= dout_r;
    end
​
    assign  dout = i2c_end ? dout_r : dout;
​
    always@(*) begin
        if(i2c_cmd & STOP_CMD)
            if(stop_idle)
                i2c_end_r = 1'b1;
            else
                i2c_end_r = 1'b0;
        else
            i2c_end_r =  wack_idle || rack_idle;
    end
​
assign  i2c_end = i2c_end_r;
​
endmodule

其他补充

这一模块只是一个I2C接口,本身不能达成什么功能,需要搭配相应的控制模块才可以正常工作。控制模块根据不同的器件有不同的要求和写法,之后以OV5640的初始化来进行实例展示。

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Verilog I2C Master是一种在数字电路设计中常用的通信协议。I2C是一种串行通信协议,用于在电路板上的各个设备之间进行通信。在Verilog中实现I2C Master的功能可以使设备能够与其他设备进行数据交换。 在Verilog中,I2C Master通常由状态机实现。状态机根据I2C协议的规定,控制时钟和数据线的状态,以进行数据传输。设备发送起始信号,然后发送设备地址,再发送要传输的数据或者要读取的数据的地址。接下来,I2C Master发送或接收数据,并在完成后发送停止信号结束本次传输。 为了实现I2C Master功能,Verilog代码需要考虑以下几个方面: 1. 发送和接收数据的寄存器:需要定义寄存器用于存储要发送或接收的数据。 2. I2C状态机:根据I2C协议的规定,设计状态机以控制时钟和数据线的状态,与其他设备进行通信。 3. 时钟和数据线的控制:通过控制时钟和数据线的电平变化,进行数据的传输和接收。 4. 适应不同数据传输速率:根据具体需求调整状态机和时钟控制来适应不同的数据传输速率。 5. 错误处理机制:设计适当的错误处理机制,确保数据的正确传输和接收。 使用Verilog实现I2C Master可以实现设备之间的数据通信,并且能够方便地与其他硬件模块进行集成。通过适当的调试和测试,可以保证I2C Master在不同场景下的稳定性和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值