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

  • 相关阅读:
    Idea中Module is not specified解决办法
    Navicat 导入数据时报Incorrect datetime value: '0000-00-00 00:00:00.000000' 错误
    SQL Server错误18456,window身份验证登录失败解决办法
    Linq
    web.config配置数据库连接
    $.ajax()方法详解
    将一张图片上传到指定的文件夹,然后在窗体上的PictrueBox控件中显示出来
    winform中picturebox自适应图片大小
    C#中产生SQL语句的几种方式
    [转]ORACLE触发器详解
  • 原文地址:https://www.cnblogs.com/FPGAer/p/13788439.html
Copyright © 2011-2022 走看看