【FPGA】verilog实现的i2c接口控制

 i2c协议规范:

 一、时钟

首先第一步是产生fast-mode的400khz的scl速率,假设方波高低电平各占一半,即1.25us,理论上不满足规范上scl低周期1.3us,但是绝大多数器件都支持稍微超过400khz的速率。

我们仍打算产生一个规范内的速率。输入时钟clk=20mhz,计数12+1次后翻转,即可产生一个周期为13x2x50ns=1.3us的方波clk_800,这个周期满足规范。

也可以改变计数方式,产生一个高低占比不一样的波形,可以适当提高速率,同时也满足规范。

//    +clk2------------------------------------------
//    1) 20mhz -> clk2 -> SCL-400khz  clk2==2xSCL
//    2) cnt = 20m/800k = 25 /2 = 12 cnt[3:0]
//    -clk2------------------------------------------
always @(posedge clk or negedge rst_n)
    if(rst_n==0)
        begin
        cnt_clk <= 0;
        clk_800k <= 0;
        end
    else if(cnt_clk==12)
        begin
        cnt_clk <= 0;
        clk_800k <= ~clk_800k;// scl@falling sda@rising
        end
    else
        cnt_clk <= cnt_clk +1;

 二、我们的规划是,scl在clk_800k的下降沿翻转,而sda在clk_800k的上升沿变化,这样就能保证在scl的高电平周期,sda是绝对稳定的,同时也能方便地产生start/stop条件。

根据上面clk的计数,可知scl的周期是1.3us x2=2.6us,速率约是384khz,没到400k。

//    +iic------------------------------------------
//    1) 
//    -iic------------------------------------------
always @(negedge clk_800k or negedge rst_n)// negedge
    if(rst_n==0)
        iic_scl <= 0;
    else
        iic_scl <= ~iic_scl;// 400k

 三、接下来就是采用状态机控制产生协议要求的时序波形。在clk_800k的rising沿动作。

IIC_start:检测到scl的高,将sda拉低,产生start条件。占用了1/4个scl周期,0.65us,完全满足start的建立条件;

IIC_data_setup:这一节拍正好是scl的低周期,不需要判断,在sda上送出数据的MSB;

IIC_data_hold:scl的高周期,sda必须保持不变。循环传出8个bit,完成一个字节后跳到下一状态;

IIC_ack_setup:先要释放sda,让从器件去拉低响应;

IIC_ack_hold:判断响应;如果不响应,或者我这里要求传完3个byte的数据,跳到下一状态;

IIC_stop_setup:这一步主要是把sda拉低,准备做stop;

IIC_stop_hold:在scl高周期,sda拉高,产生stop条件;结束。

只要第一步的start条件切入准了,后面按照节拍来,就不需要反复判断scl的高低了。

//    +iic------------------------------------------
//    1)
//    -iic------------------------------------------
always @(posedge clk_800k or negedge rst_n)
    if(rst_n==0)
        begin
        NS_iic <= IIC_idle;
        iic_sda <= 1;
        finish_iic <= 0;
        end
    else
        case(NS_iic)
            IIC_idle:
                begin
                if(start_iic==1)
                    NS_iic <= IIC_start;
                else
                    NS_iic <= IIC_idle;
                end
            IIC_start:
                begin
                if(iic_scl==1)
                    begin
                    NS_iic <= IIC_data_setup;
                    iic_sda <= 0;// start condition
                    data_iic <= bram_dout[23:0];
                    cnt_byte <= 2;
                    cnt_bit <= 0;// 0->7
                    end
            //    else 
            //        NS_iic <= IIC_start;
                end
            IIC_data_setup:// data setup
                begin
                NS_iic <= IIC_data_hold;
                iic_sda <= data_iic[23];
                cnt_bit <= cnt_bit -1;
                end
            IIC_data_hold:// data hold
                begin
                if(cnt_bit==0) NS_iic <= IIC_ack_setup;
                else NS_iic <= IIC_data_setup;
                data_iic <= {data_iic[22:0],1'b0};
                end
            IIC_ack_setup:// ack prepare
                begin
                NS_iic <= IIC_ack_hold;
                iic_sda <= 1'bz;
                end
            IIC_ack_hold:// ack response
                begin
                if(iic_sda==1||cnt_byte==0)
                    NS_iic <= IIC_stop_setup;
                else
                    NS_iic <= IIC_data_setup;
                cnt_byte <= cnt_byte -1;
                end
            IIC_stop_setup:
                begin
                NS_iic <= IIC_stop_hold;
                iic_sda <= 0;
                finish_iic <= 1;
                end
            IIC_stop_hold:
                begin
                NS_iic <= IIC_idle;
                iic_sda <= 1;
                finish_iic <= 0;
                end
            default:
                begin
                NS_iic <= IIC_idle;
                end
        endcase

每次发送3字节的数据,addr+reg+data。cnt_byte是计数3个字节的cnt_bit是计数每个字节内的bit位。蓝色是相应位。

 start条件。发送的数据是AA,即10101010,跟计数位相对应。

 stop条件。

//

原文地址:https://www.cnblogs.com/kevinchase/p/7411494.html