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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM