zoukankan      html  css  js  c++  java
  • iic协议--Verilog及仿真

    1、协议原理:

    IIC(Inter-Integrated Circuit),i2c总线由数据线sda和时钟线scl这两条构成的串行总线,主机和从机可以在i2c总线上发送和接收数据。scl时钟线作为控制,sda则包含有ack、nack、设备地址、字节地址、8bits数据。

    起始信号(scl为高电平时,sda变成低电平)与结束信号(scl为高电平时,sda变成高电平)的状态:

    IIC单字节写时序有两种:1字节地址段器件单字节写时序、2字节地址段器件单字节写时序。

     

     IIC单字节读时序有两种:1字节地址段器件单节读时序、2字节地址段器件单节读时序

     

    字节地址高三位xxx:这里使用的EEPROM的存储容量只有8192bits(1024bits*8)=210*23=213,所以16位的字节地址就多余了三位。


    2、协议代码:

    学习文章:https://www.cnblogs.com/xiaomeige/p/6509414.html、https://www.cnblogs.com/liujinggang/p/9656358.html、正点原子教程

    1、这里实现的是2字节单次读写。

    2、开始和结束时,虽然scl为高电平,sda仍要变化;接下来传输字节,scl为低电平,sda才能变化。这里采取在scl高电平和低电平中线产生标志。

    3、通过状态机来实现读写。

    综合代码:

    module IIC_AT24C64(
    input sys_clk,
    input sys_rst_n,
    input iic_en,
    input [2:0]cs_bit,//可编程地址
    input [12:0]byte_address,//字节地址
    input write,
    input read,
    input [7:0]write_data,
    output reg[7:0]read_data,
    output reg scl,
    inout sda,
    output reg done 
    );
    parameter
    SYS_CLK=50_000_000,//系统时钟50MHz
    SCL_CLK=200_000;//scl时钟200KHz
    reg [7:0]scl_cnt;//时钟计数
    
    parameter div_cnt=SYS_CLK/SCL_CLK;
    
    always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
    scl_cnt<=8'd0;
    else if(scl_cnt == div_cnt-1'b1)
    scl_cnt<=8'd0;
    else
    scl_cnt<=scl_cnt+1'b1;
    end
    
    //生成scl时钟线
    always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
    scl<=1'b1;
    else if(scl_cnt == (div_cnt>>1)-1'b1)
    scl<=1'b0;
    else if(scl_cnt == div_cnt-1'b1)
    scl<=1'b1;
    else
    scl<=scl;
    end
    
    //scl电平中线
    reg scl_high_middle;//scl高电平中线
    reg scl_low_middle;//scl低电平中线
    always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
    scl_high_middle<=1'b0;
    scl_low_middle<=1'b0;
    end
    else if(scl_cnt == (div_cnt>>2))
    scl_high_middle<=1'b1;
    else if(scl_cnt == (div_cnt>>1)+(div_cnt>>2))
    scl_low_middle<=1'b1;
    else begin
    scl_high_middle<=1'b0;
    scl_low_middle<=1'b0;
    end
    end
    
    reg [15:0]state;
    parameter 
    idle=16'd1,//空闲状态
    w_or_r_start=16'd2,//设备地址
    device_ADDR=16'd3,//发送
    ACK1=16'd4,
    byte_ADDR_high=16'd5,//字节地址高8位
    ACK2=16'd6,
    byte_ADDR_low=16'd7,//字节地址低8位
    ACK3=16'd8,
    w_data=16'd9,//写数据
    ACK4=16'd10,
    r_start=16'd11,//读开始
    device_ADDR_r=16'd12,//设备地址读
    ACK5=16'd13,
    r_data=16'd14,//读数据
    NACK=16'd15,//非应答位
    stop=16'd16;
    
    reg sda_en;//sda数据线使能
    reg sda_reg;//sda数据暂存位
    reg [7:0]sda_data_out;//sda数据发给从机暂存
    reg [7:0]sda_data_in;//sda数据取之从机暂存
    reg [3:0]bit_cnt;//每一bit
    assign sda=sda_en?sda_reg:1'bz;
    
    //读写标志位
    reg w_flag;
    reg r_flag;
    
    always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
    state<=idle;
    w_flag<=1'b0;
    r_flag<=1'b0;
    sda_reg<=1'b1;
    done<=1'b0;
    sda_en<=1'b0;
    end
    else begin 
    case(state)
    
    idle:begin
    sda_reg<=1'b1;
    w_flag<=1'b0;
    r_flag<=1'b0;
    sda_en<=1'b0;
    sda_reg<=1'b1;
    done<=1'b0;
    if(iic_en && write)begin
    w_flag<=1'b1;
    sda_en<=1'b1;
    sda_reg<=1'b1;
    state<=w_or_r_start;
    end
    else if(iic_en && read)begin
    r_flag<=1'b1;
    sda_en<=1'b1;
    sda_reg<=1'b1;
    state<=w_or_r_start;
    end
    else
    state<=idle;
    end
    
    w_or_r_start:begin
    if(scl_high_middle)begin
    sda_reg<=1'b0;
    sda_data_out<={4'b1010,cs_bit,1'b0};//在这里装好设备地址
    bit_cnt<=4'd8;
    state<=device_ADDR;
    end
    else begin
    sda_reg<=1'b1;
    state<=w_or_r_start;
    end
    end
    
    device_ADDR:begin
    if(scl_low_middle)begin
    bit_cnt<=bit_cnt-1'b1;
    sda_reg<=sda_data_out[7];
    sda_data_out<={sda_data_out[6:0],1'b0};//在这里发出设备地址。其他的也是在上一状态装好值,下一个状态发出
    if(bit_cnt==0)begin
    state<=ACK1;
    sda_en<=1'b0;
    end
    else
    state<=device_ADDR;
    end
    else
    state<=device_ADDR;
    end
    
    ACK1:begin
    if(scl_high_middle)begin
    if(sda==1'b0)begin
    state<=byte_ADDR_high;
    sda_data_out<={3'bxxx,byte_address[12:8]};
    bit_cnt<=4'd8;
    end
    else 
    state<=idle;
    end
    else
    state<=ACK1;
    end
    
    byte_ADDR_high:begin
    if(scl_low_middle)begin
    sda_en<=1'b1;
    bit_cnt<=bit_cnt+1'b1;
    sda_reg<=sda_data_out[7];
    sda_data_out<={sda_data_out[6:0],1'b0};
    if(bit_cnt==0)begin
    state<=ACK2;
    sda_en<=1'b0;
    end
    else
    state<=byte_ADDR_high;
    end
    else
    state<=byte_ADDR_high;
    end
    
    ACK2:begin
    if(scl_high_middle)begin
    if(sda==1'b0)begin
    state<=byte_ADDR_low;
    sda_data_out<=byte_address[7:0];
    bit_cnt<=4'd8;
    end
    else
    state<=idle;
    end
    else
    state<=ACK2;
    end
    
    byte_ADDR_low:begin
    if(scl_low_middle)begin
    sda_en<=1'b1;
    bit_cnt<=bit_cnt-1'b1;
    sda_reg<=sda_data_out[7];
    sda_data_out<={sda_data_out[6:0],1'b0};
    if(bit_cnt==0)begin
    state<=ACK3;
    sda_en<=1'b0;
    end
    else
    state<=byte_ADDR_low;
    end
    else
    state<=byte_ADDR_low;
    end
    
    ACK3:begin
    if(scl_high_middle)begin
    if(sda==1'b0)begin
    if(w_flag)begin
    sda_data_out<=write_data;
    bit_cnt<=4'd8;
    state<=w_data;
    end
    else if(r_flag)begin
    sda_reg<=1'b1;
    state<=r_start;
    end
    end
    else
    state<=ACK3;
    end
    end
    
    w_data:begin
    if(scl_low_middle)begin
    sda_en<=1'b1;
    bit_cnt<=bit_cnt-1'b1;
    sda_reg<=sda_data_out[7];
    sda_data_out<={sda_data_out[6:0],1'b0};
    if(bit_cnt==0)begin
    state<=ACK4;
    sda_en<=1'b0;
    end
    else
    state<=w_data;
    end
    else
    state<=w_data;
    end
    
    ACK4:begin
    if(scl_high_middle)begin
    if(sda==1'b0)
    state<=stop;
    else
    state<=idle;
    end
    else
    state<=ACK4;
    end
    
    r_start:begin
    if(scl_low_middle)begin
    sda_en<=1'b1;
    end
    else if(scl_high_middle)begin
    sda_reg<=1'b0;
    state<=device_ADDR_r;
    sda_data_out<={4'b1010,cs_bit,1'b1};
    bit_cnt<=4'd8;
    end
    else begin
    sda_reg<=1'b1;
    state<=r_start;
    end
    end
    
    device_ADDR_r:begin
    if(scl_low_middle)begin
    bit_cnt<=bit_cnt-1'b1;
    sda_reg<=sda_data_out[7];
    sda_data_out<={sda_data_out[6:0],1'b0};
    if(bit_cnt==0)begin
    state<=ACK5;
    sda_en<=1'b0;
    end
    else
    state<=device_ADDR_r;
    end
    else
    state<=device_ADDR_r;
    end
    
    ACK5:begin
    if(scl_high_middle)begin
    if(sda==1'b0)begin   
    state<=r_data;
    sda_en<=1'b0;
    bit_cnt<=4'd8;
    end
    else
    state<=idle;
    end
    else
    state<=ACK5;
    end
    
    r_data:begin
    if(scl_high_middle)begin
    sda_data_in<={sda_data_in[6:0],sda};
    bit_cnt<=bit_cnt-1'b1;
    state<=r_data;
    end
    else if(scl_low_middle && bit_cnt==0)
    state<=NACK;
    else
    state<=r_data;
    end
    
    NACK:begin
    read_data<=sda_data_in;
    if(scl_high_middle)begin
    state<=stop;
    sda_reg<=1'b0;   //为什么这里要为0?因为你发现其他的应答位都是判断if(sda==1'b0),而这里由于最后一个是主机发出的应答,而不是像之前那些是从机发出的。
    end              //也可以看出主机应答的是直接让output sda_reg为0,而前面的应答是等待从机input sda的值来判断应答位
    else
    state<=NACK;
    end
    
    stop:begin
    if(scl_low_middle)begin
    sda_en<=1'b1;
    sda_reg<=1'b1;
    state<=idle;
    done<=1'b1;
    end
    else
    state<=stop;
    end
    
    default:begin
    state<=idle;
    sda_en<=1'b0;
    sda_reg<=1'b1;
    w_flag<=1'b0;
    r_flag<=1'b0;
    done<=1'b0;
    end
    endcase
    end
    end
    
    endmodule 

    仿真代码:直接用了别人的代码。

    仿真部分结果:设备地址:1010  001  0   字节地址高8位:xxx   00000     字节地址低8位:1100  1000    数据:1100  1000

    主机是fpga,从机是EEPROM。仿真一个EEPROM存储器:

    `timescale 1ns/1ns
    `define timeslice 1250
    
    module EEPROM_AT24C64(
        scl, 
        sda
    );
        input scl;               //串行时钟线
        inout sda;               //串行数据线
        
        reg out_flag;            //SDA数据输出的控制信号
        
        reg[7:0] memory[8191:0]; //数组模拟存储器
        reg[12:0]address;        //地址总线
        reg[7:0]memory_buf;      //数据输入输出寄存器
        reg[7:0]sda_buf;         //SDA数据输出寄存器
        reg[7:0]shift;           //SDA数据输入寄存器
        reg[7:0]addr_byte_h;     //EEPROM存储单元地址高字节寄存器
        reg[7:0]addr_byte_l;     //EEPROM存储单元地址低字节寄存器
        reg[7:0]ctrl_byte;       //控制字寄存器
        reg[1:0]State;           //状态寄存器
        
        integer i;
        
        //---------------------------
        parameter  
            r7 = 8'b1010_1111,  w7 = 8'b1010_1110,   //main7
            r6 = 8'b1010_1101,  w6 = 8'b1010_1100,   //main6
            r5 = 8'b1010_1011,  w5 = 8'b1010_1010,   //main5
            r4 = 8'b1010_1001,  w4 = 8'b1010_1000,   //main4
            r3 = 8'b1010_0111,  w3 = 8'b1010_0110,   //main3
            r2 = 8'b1010_0101,  w2 = 8'b1010_0100,   //main2
            r1 = 8'b1010_0011,  w1 = 8'b1010_0010,   //main1
            r0 = 8'b1010_0001,  w0 = 8'b1010_0000;   //main0    
        //---------------------------
        
        assign sda = (out_flag == 1) ? sda_buf[7] : 1'bz;
        
        //------------寄存器和存储器初始化---------------
        initial
        begin
            addr_byte_h    = 0;
            addr_byte_l    = 0;
            ctrl_byte    = 0;
            out_flag     = 0;
            sda_buf      = 0;
            State        = 2'b00;
            memory_buf   = 0;
            address      = 0;
            shift        = 0;
            
            for(i=0;i<=8191;i=i+1)
                memory[i] = 0;  
        end
    
        //启动信号
        always@(negedge sda)
        begin
            if(scl == 1)
            begin
                State = State + 1;
                if(State == 2'b11)
                    disable write_to_eeprom;
            end 
        end
        
        //主状态机
        always@(posedge sda)
        begin
            if(scl == 1)                //停止操作
                stop_W_R;
            else
            begin
                casex(State)
                    2'b01:begin
                        read_in;
                        if(ctrl_byte == w7 || ctrl_byte == w6 
                            || ctrl_byte == w5  || ctrl_byte == w4
                            || ctrl_byte == w3  || ctrl_byte == w2
                            || ctrl_byte == w1  || ctrl_byte == w0)
                        begin
                            State = 2'b10;
                            write_to_eeprom;    //写操作                 
                        end
                        else
                            State = 2'b00;          
                    end
                    
                    2'b11:
                        read_from_eeprom;               
                    
                    default:
                        State = 2'b00;          
                endcase     
            end 
        end     //主状态机结束
        
        //操作停止
        task stop_W_R;
        begin
            State        = 2'b00;
            addr_byte_h  = 0;
            addr_byte_l  = 0;
            ctrl_byte    = 0;
            out_flag     = 0;
            sda_buf      = 0;   
        end
        endtask
        
        //读进控制字和存储单元地址
        task read_in;
        begin
            shift_in(ctrl_byte);
            shift_in(addr_byte_h);
            shift_in(addr_byte_l);      
        end 
        endtask
        
        //EEPROM的写操作
        task write_to_eeprom;
        begin
            shift_in(memory_buf);
            address = {addr_byte_h[4:0], addr_byte_l};
            memory[address] = memory_buf;       
            State = 2'b00;
        end
        endtask
        
        //EEPROM的读操作
        task read_from_eeprom;
        begin
            shift_in(ctrl_byte);
            if(ctrl_byte == r7 || ctrl_byte == w6 
                || ctrl_byte == r5  || ctrl_byte == r4
                || ctrl_byte == r3  || ctrl_byte == r2
                || ctrl_byte == r1  || ctrl_byte == r0)
            begin
                address = {addr_byte_h[4:0], addr_byte_l};
                sda_buf = memory[address];
                shift_out;
                State = 2'b00;
            end
        end
        endtask
        
        //SDA数据线上的数据存入寄存器,数据在SCL的高电平有效
        task shift_in;  
            output[7:0]shift;
            begin
                @(posedge scl) shift[7] = sda;
                @(posedge scl) shift[6] = sda;
                @(posedge scl) shift[5] = sda;
                @(posedge scl) shift[4] = sda;
                @(posedge scl) shift[3] = sda;
                @(posedge scl) shift[2] = sda;
                @(posedge scl) shift[1] = sda;
                @(posedge scl) shift[0] = sda;
                
                @(negedge scl)
                begin
                    #`timeslice;
                    out_flag = 1;     //应答信号输出
                    sda_buf = 0;
                end
                
                @(negedge scl)
                begin
                    #`timeslice;
                    out_flag = 0;               
                end         
            end 
        endtask
        
        //EEPROM存储器中的数据通过SDA数据线输出,数据在SCL低电平时变化
        task shift_out;
        begin
            out_flag = 1;
            for(i=6; i>=0; i=i-1)
            begin
                @(negedge scl);
                #`timeslice;
                sda_buf = sda_buf << 1;         
            end
            @(negedge scl) #`timeslice sda_buf[7] = 1;    //非应答信号输出
            @(negedge scl) #`timeslice out_flag = 0;
        end
        endtask
    
    endmodule 

    仿真iic:

    `timescale 1ns/1ns
    `define clk_period 20

    module iic_tb;

    reg clk50M;
    reg reset;
    reg iic_en;
    reg [12:0]address;
    reg write;
    reg [7:0]write_data;
    reg read;

    wire [7:0]read_data;
    wire scl;
    wire sda;
    wire done;

    integer i;

    IIC_AT24C64 u_IIC_AT24C64(
    .sys_clk(clk50M),
    .sys_rst_n(reset),
    .iic_en(iic_en),
    .cs_bit(3'b001),
    .byte_address(address),
    .write(write),
    .write_data(write_data),
    .read(read),
    .read_data(read_data),
    .scl(scl),
    .sda(sda),
    .done(done)
    );

    EEPROM_AT24C64 EEPROM(
    .scl(scl),
    .sda(sda)
    );

    initial clk50M = 1'b1;
    always #(`clk_period/2)clk50M = ~clk50M;

    initial
    begin
    reset = 1'b0;
    iic_en = 1'b0;
    address = 13'h0;
    write = 1'b0;
    write_data = 1'b0;
    read = 1'b0;

    #(`clk_period*200 + 1)
    reset = 1'b1;
    #200;

    write = 1'b1;
    address = 200;
    write_data = 200;
    iic_en = 1'b1;
    #(`clk_period)
    iic_en = 1'b0;

    for(i=199;i>0;i=i-1)
    begin
    @(posedge done);
    #2000;
    address = i;
    write_data = i;
    iic_en = 1'b1;
    #(`clk_period)
    iic_en = 1'b0;
    end
    @(posedge done);
    #2000;
    write = 1'b0;
    #5000;

    //?????????200???
    read = 1'b1;
    iic_en = 1'b1;
    #(`clk_period)
    iic_en = 1'b0;

    for(i=200;i>0;i=i-1)
    begin
    @(posedge done);
    #2000;
    address = i;
    iic_en = 1'b1;
    #(`clk_period)
    iic_en = 1'b0;
    end
    @(posedge done);
    #20000;
    read = 1'b0;
    #5000;

    $stop;
    end

    endmodule

  • 相关阅读:
    Linux下sed,awk,grep,cut,find学习笔记
    Python文件处理(1)
    KMP详解
    Java引用详解
    解决安卓中页脚被输入法顶起的问题
    解决swfupload上传控件文件名中文乱码问题 三种方法 flash及最新版本11.8.800.168
    null id in entry (don't flush the Session after an exception occurs)
    HQL中的Like查询需要注意的地方
    spring mvc controller间跳转 重定向 传参
    node to traverse cannot be null!
  • 原文地址:https://www.cnblogs.com/FPGAer/p/13788439.html
Copyright © 2011-2022 走看看