FPGA构造IIC时序——switch1848为例

一.对IIC的时序理解

       IIC接口是一种总线结构,只能有一个主控器件,挂在IIC的两根线上其余都为从器件。主控,发送数据和接收数据都由主器件控制。主控器件要遵从标准的IIC时序和从器件的芯片手册时序。标准的IIC时序的工作流程为:


       首先要发送数据,需要发送一个开始位,然后传送八位数据,从机在第九个时钟会回复一个应答信号主机可以选择继续发送数据和拉一个停止位。这是标准的IIC时序流程,这个时序还要和1848手册上的时序图做个对比。



       主机首先要发送一个起始位,然后发送要访问的7bits的设备ID,在1848电路中是由七个电阻上拉下拉决定ID值的,经过确认,ID值为1100000,但是一次要发送八个数据,最后bit是1就是读请求,0就是写请求。然后第九个时钟芯片在数据线SDA回复一个应答。然后发送24bit的要操作的寄存器值,每8bit从机都会回复一个ACK应答信号,发送完毕之后还要输入32bit的寄存器的数值随后接收应答之后,发送停止位。
       但是IIC时序,也有严格的时序要求。

开始位:


       开始位的时序要求为:在SCL高电平期间,主机将数据线拉低,拉低的整个过程称为IIC的起始位。

停止位:



停止位是SCL高电平器,将数据线拉高,这个拉高的过程称为停止位。

发送数据的时:



       发送数据,时钟沿的要求为,SCL高电平开始接收数据,在高电平达到之前,数据就要求稳定,在时钟SCL的低电平器件,允许数据线发生变化。
二.FPGA的原理设计
  1. 调用Ip核


       控制模块在调用此IP核的时候,需要先装填好所有要发送的值,然后启动一次写使能信号,根据1848手册,共需要写入8个8bit的数据(1的设备地址3个寄存器地址4个数据),所以每次装填好数据之后等待忙线,当检测到IIC总线不忙的时候再启动下一次的写。一共需要写入四套数据,每套是8个8bit的数据。

三.软件仿真测试
1.首先看发送数据的第一个起始位:

起始位在最初的时候scl是高,数据线被主机拉低。我发送的第一个是设备地址C0也就是
1100000 0,数据在SCL上升沿到来之前已经被赋值上,到第九个时钟三态门的dir是低准备接收应答。数值都是对的也是符合时序要求的
2.再看看停止位:

dir为低的时候是三态门方向,可以看到,dir为低接收应答之后,再SCL为高的时候,SDA也拉高。这就开启了停止位。代表一次传输的8个8bit传输结束。
3.总体来看:

busy信号共BUSY了四段每次都传输32个8bit.符合胡佳顺师兄给的寄存器的值,这32个8bit 是:
memory_initialization_radix=16;

memory_initialization_vector=c0 38 60 09 00 0000 09 c0 38 64 08 00 00 00 08 c0 00 00 97 d0 60 00 01 c0 00 00 9f d0 60 00 01;是和STM32发的值是一样的。而且软仿的时候我都是一个一个查的,时序是符合逻辑和满足要发的数据都是对。可以说明,保证FPGA是按时序输出的数据。
四.程序源码
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: fzh
// 
// Create Date: 2018/01/23 20:34:23
// Design Name: 
// Module Name: iic_drive
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////




module iic_drive( 
input clk, 
input reset_n,


// 与控制器通信信号
input   [31:0]      slv_reg0,//地址


input   [31:0]      slv_reg1,//reg地址'
input   [31:0]      slv_reg1a,//reg地址'
input   [31:0]      slv_reg1b,//reg地址'


input   [31:0]      slv_reg2,//待发送的数据
input   [31:0]      slv_reg2a,//待发送的数据
input   [31:0]      slv_reg2b,//待发送的数据
input   [31:0]      slv_reg2c,//待发送的数据


input   [31:0]      slv_reg3,//写使能
input   [31:0]      slv_reg4,//读使能
output  reg [ 7:0]  iic_rddb,
output              iic_busy,


// 外部信号
output              iic_scl,
input               iic_sda_in,
output              sda_dir,
output              sda_r
);


//  SCL 分频系数
// 产生IIC时钟  100M/20K = 5000
parameter  SCL_SUM = 13'd500;


// 仿真时
//parameter  SCL_SUM = 13'd64;


//***************************************************************************************
// iic读写状态机


reg  [12:0]     scl_cnt;
always @ (posedge clk or negedge reset_n)
begin
    if(reset_n == 1'b0)
        scl_cnt <= 13'd0;
    else if(scl_cnt < (SCL_SUM - 1))
        scl_cnt <= scl_cnt + 1;
    else
        scl_cnt <= 13'd0;
end 




// 不同的时钟阶段
assign  iic_scl = (scl_cnt <= (SCL_SUM >> 1));                       // 初始为高电平
wire    scl_hs = (scl_cnt == 13'd1);                                // scl high start
wire    scl_hc = (scl_cnt == ((SCL_SUM >> 1)-(SCL_SUM >> 2)));      // scl high center
wire    scl_ls = (scl_cnt == (SCL_SUM >> 1));                       // scl low start
wire    scl_lc = (scl_cnt == ((SCL_SUM >> 1)+(SCL_SUM >> 2)));      // scl low center






//IIC状态机控制信号                
reg     iicwr_req;                                          // IIC写请求信号,高电平有效
reg     iicrd_req;                                          // IIC读请求信号,高电平有效 
reg     [2:0]   bcnt;
wire    [7:0]   slave_addr_w = slv_reg0[7:0];               // slave地址,写数据
wire    [7:0]   slave_addr_r = slv_reg0[7:0];             // slave地址,读数据slave_addr_r = slv_reg0[7:0]+1


wire    [7:0]   reg_addr = slv_reg1[7:0];                  // reg地址
wire    [7:0]   reg_addra=slv_reg1a[7:0];
wire    [7:0]   reg_addrb=slv_reg1b[7:0];


wire    [7:0]   iic_wrdb = slv_reg2[7:0];                   // 待发送的数据 
wire    [7:0]   iic_wrdba= slv_reg2a[7:0];                // 待发送的数据 
wire    [7:0]   iic_wrdbb= slv_reg2b[7:0];              // 待发送的数据 
wire    [7:0]   iic_wrdbc= slv_reg2c[7:0];              // 待发送的数据 


wire            iic_wr_en = slv_reg3[0];                    // 写使能
wire            iic_rd_en = slv_reg4[0];                    // 读使能


//****************************************************************************
// 读写使能上升沿信号
reg     iic_wr_en_r0,iic_wr_en_r1;
reg     iic_rd_en_r0,iic_rd_en_r1;
always @  (posedge clk or negedge reset_n)
begin
    if(reset_n == 1'b0)
    begin
        iic_wr_en_r0 <= 1'b0;
        iic_wr_en_r1 <= 1'b0;
    end 
    else
    begin
        iic_wr_en_r0 <= iic_wr_en;
        iic_wr_en_r1 <= iic_wr_en_r0;
    end 
end 
wire    iic_wr_en_pos = (~iic_wr_en_r1 && iic_wr_en_r0);    // 写使能上升沿


always @  (posedge clk or negedge reset_n)
begin
    if(reset_n == 1'b0)
    begin
        iic_rd_en_r0 <= 1'b0;
        iic_rd_en_r1 <= 1'b0;
    end 
    else
    begin
        iic_rd_en_r0 <= iic_rd_en;
        iic_rd_en_r1 <= iic_rd_en_r0;
    end 
end 
wire    iic_rd_en_pos = (~iic_rd_en_r1 && iic_rd_en_r0);    // 读使能上升沿
//****************************************************************************
// IIC状态 
parameter       IDLE        = 6'd0, 
                START0      = 6'd1,
                WRSADDR0    = 6'd2,//写从地址
                ACK0        = 6'd3,
                WRRADDR     = 6'd4,//写寄存器地址
                ACK1        = 6'd5,
                WRRADDRA=6'd6,
                ACK1A=6'd7,
                WRRADDRB=6'd8,
                ACK1B=6'd9,
                WRDATA      = 6'd10,//写数据
                ACK2        = 6'd11,
                WRDATAA=6'd12,
                ACK2A=6'd13,
                WRDATAB=6'd14,
                ACK2B=6'd15,
                WRDATAC=6'd16,
                ACK2C=6'd17,
                STOP        = 6'd18,
                START1      = 6'd19,
                WRSADDR1    = 6'd20,
                ACK3        = 6'd21,
                RDDATA      = 6'd22,
                NOACK       = 6'd23;


// 状态跳转
reg [5:0]   c_state;
reg [5:0]   n_state;


always @ (posedge clk or negedge reset_n)
begin
    if(reset_n == 1'b0)
        c_state <= IDLE;
    else
        c_state <= n_state;
end 


// 组合逻辑触发
always @ (*)
begin
    case(c_state)
        IDLE:       // 初始化
        begin
            if(((iicwr_req == 1'b1)||(iicrd_req == 1'b1))&&(scl_hc == 1'b1))
                n_state = START0;
            else
                n_state = IDLE;
        end 


        START0:     // 起始
        begin
            if(scl_lc == 1'b1)
                n_state = WRSADDR0;
            else
                n_state = START0;
        end 


        WRSADDR0:   // 写slave地址
        begin
            if((scl_lc == 1'b1)&&(bcnt == 3'd0))
                n_state = ACK0;
            else
                n_state = WRSADDR0;
        end 


        ACK0:       // 接收应答
        begin
            if(scl_lc == 1'b1)
                n_state = WRRADDR;
            else
                n_state = ACK0;
        end 


        WRRADDR:    // 写寄存器地址
        begin
            if((scl_lc == 1'b1)&&(bcnt == 3'd0))
                n_state = ACK1;
            else
                n_state = WRRADDR;
        end 


        ACK1:       // 接收应答
        begin
            if(scl_lc == 1'b1)
                n_state = WRRADDRA;
            else
                n_state = ACK1;
        end 
        
        WRRADDRA:    // 写寄存器地址第二个八位
        begin
            if((scl_lc == 1'b1)&&(bcnt == 3'd0))
                n_state = ACK1A;
            else
                n_state = WRRADDRA;
        end 
        
        ACK1A:       // 接收应答
        begin
            if(scl_lc == 1'b1)
                n_state = WRRADDRB;
            else
                n_state = ACK1A;
        end 
        
        WRRADDRB:    // 写寄存器地址第二个八位
        begin
            if((scl_lc == 1'b1)&&(bcnt == 3'd0))
                n_state = ACK1B;
            else
                n_state = WRRADDRB;
        end 
        
        ACK1B:       // 接收应答
        begin
            if(scl_lc == 1'b1)
            begin
                if(iicwr_req == 1'b1)
                    n_state = WRDATA;
                else if(iicrd_req == 1'b1)
                    n_state = START1;
                else
                    n_state = IDLE;
            end 
            else
                n_state = ACK1B;
        end 

        //**************
        // 写数据
        WRDATA:
        begin
            if((scl_lc == 1'b1)&&(bcnt == 3'd0))
                n_state = ACK2;
            else
                n_state = WRDATA;
        end 


        ACK2:   // 接收应答
        begin
            if(scl_lc == 1'b1)
                n_state = WRDATAA;
            else
                n_state = ACK2;
        end 
        
        WRDATAA: // 写数据
        begin
            if((scl_lc == 1'b1)&&(bcnt == 3'd0))
                n_state = ACK2A;
            else
                n_state = WRDATAA;
        end 
        
        ACK2A:   // 接收应答
        begin
            if(scl_lc == 1'b1)
                n_state = WRDATAB;
            else
                n_state = ACK2A;
        end 
        
        WRDATAB: // 写数据
        begin
            if((scl_lc == 1'b1)&&(bcnt == 3'd0))
                n_state = ACK2B;
            else
                n_state = WRDATAB;
        end 
        
        ACK2B:   // 接收应答
        begin
            if(scl_lc == 1'b1)
                n_state = WRDATAC;
            else
                n_state = ACK2B;
        end 
        
        WRDATAC: // 写数据
        begin
            if((scl_lc == 1'b1)&&(bcnt == 3'd0))
                n_state = ACK2C;
            else
                n_state = WRDATAC;
        end 
        
        ACK2C:   // 接收应答
        begin
            if(scl_lc == 1'b1)
                n_state = STOP;
            else
                n_state = ACK2C;
        end 
        //**************
        // 读数据过程
        START1:
        begin
            if(scl_lc == 1'b1)
                n_state = WRSADDR1;
            else
                n_state = START1;
        end 


        WRSADDR1:
        begin
            if((scl_lc == 1'b1)&&(bcnt == 3'd0))
                n_state = ACK3;
            else
                n_state = WRSADDR1;
        end 


        ACK3:   // 接收应答    
        begin
            if(scl_lc == 1'b1)
                n_state = RDDATA;
            else
                n_state = ACK3;
        end 


        RDDATA:
        begin
            if((scl_lc == 1'b1)&&(bcnt == 3'd0))
                n_state = NOACK;
            else
                n_state = RDDATA;
        end


        NOACK:  
        begin
            if(scl_lc == 1'b1)
                n_state = STOP;
            else
                n_state = NOACK;
        end 
        //**************


        STOP:
        begin   
            if(scl_lc == 1'b1)
                n_state = IDLE;
            else
                n_state = STOP;
        end 


        default:  n_state = IDLE;  
    endcase 
end 




// 计数器控制
always @ (posedge clk or negedge reset_n)
begin
    if(reset_n == 1'b0)
        bcnt <= 3'd0;
    else
    begin
        case (n_state)
            WRSADDR0,WRRADDR,WRRADDRA,WRRADDRB,WRDATA,WRDATAA,WRDATAB,WRDATAC,WRSADDR1,RDDATA:
            begin
                if(scl_lc == 1'b1)
                    bcnt <= bcnt + 1;    
            end 
            default: bcnt <= 3'd0;
        endcase 
    end 
end 




reg     sda_dir;        // 控制数据方向,高电平为数据输出
reg     sda_r;          // 输出数据




// 控制数据输出
always @ (posedge clk or negedge reset_n)
begin
    if(reset_n == 1'b0)
    begin
        sda_dir <= 1'b1;
        sda_r <= 1'b1;
    end 
    else
    begin
        case (n_state)
            IDLE,NOACK:
            begin
                sda_dir <= 1'b1;
                sda_r <= 1'b1;
            end 


            START0:
            begin
                sda_dir <= 1'b1;
                sda_r <= 1'b0;          // 进入开始状态后,数据拉低
            end 


            START1:
            begin
                sda_dir <= 1'b1;
                if(scl_lc == 1'b1)
                    sda_r <= 1'b1;
                else if(scl_hc == 1'b1)
                    sda_r <= 1'b0;
            end 




            WRSADDR0:
            begin
                sda_dir <= 1'b1;
                if(scl_lc == 1'b1)
                    sda_r <= slave_addr_w[7-bcnt];
            end 


            WRSADDR1:
            begin
                sda_dir <= 1'b1;
                if(scl_lc == 1'b1)
                    sda_r <= slave_addr_r[7-bcnt];
            end 




            ACK0,ACK1,ACK1A,ACK1B,ACK2,ACK2A,ACK2B,ACK2C,ACK3:  sda_dir <= 1'b0;      // 接收总线数据


            WRRADDR:
            begin
                sda_dir <= 1'b1;
                if(scl_lc == 1'b1)
                    sda_r <= reg_addr[7-bcnt];
            end 
                     
             WRRADDRA:
            begin
                sda_dir <= 1'b1;
                if(scl_lc == 1'b1)
                    sda_r <= reg_addra[7-bcnt];
            end 
            
            WRRADDRB:
            begin
                sda_dir <= 1'b1;
                if(scl_lc == 1'b1)
                    sda_r <= reg_addrb[7-bcnt];
            end 
            
            WRDATA:
            begin
                sda_dir <= 1'b1;
                if(scl_lc == 1'b1)
                    sda_r <= iic_wrdb[7-bcnt];
            end 
            
            WRDATAA:
            begin
                sda_dir <= 1'b1;
                if(scl_lc == 1'b1)
                    sda_r <= iic_wrdba[7-bcnt];
            end 
            
            WRDATAB:
            begin
                sda_dir <= 1'b1;
                if(scl_lc == 1'b1)
                    sda_r <= iic_wrdbb[7-bcnt];
            end 
            
             WRDATAC:
            begin
                sda_dir <= 1'b1;
                if(scl_lc == 1'b1)
                    sda_r <= iic_wrdbc[7-bcnt];
            end 
            
            RDDATA:
            begin
                sda_dir <= 1'b0;
                if(scl_lc == 1'b1)
                    iic_rddb[7-bcnt] <= iic_sda_in;
            end 


            STOP:
            begin
                sda_dir <= 1'b1;
                if(scl_lc == 1'b1)
                    sda_r <= 1'b0;
                else if(scl_hc == 1'b1)
                    sda_r <= 1'b1;
            end 


        endcase 
    end 
end 


// assign iic_sda = sda_dir ? sda_r : 1'bz; 
wire iic_ack = (c_state == STOP) && scl_hc; //IIC操作响应,高电平有效


// 确定读写过程标志
always @ (posedge clk or negedge reset_n)
begin
    if(reset_n == 1'b0)
        iicwr_req <= 1'b0;
    else
    begin
        if(iic_wr_en_pos == 1'b1)
            iicwr_req <= 1'b1;
        else if(iic_ack == 1'b1)            // IIC过程结束
            iicwr_req <= 1'b0;    
    end 
end 


always @ (posedge clk or negedge reset_n)
begin
    if(reset_n == 1'b0)
        iicrd_req <= 1'b0;
    else
    begin
        if(iic_rd_en_pos == 1'b1)
            iicrd_req <= 1'b1;
        else if(iic_ack == 1'b1)            // IIC过程结束
            iicrd_req <= 1'b0;    
    end 
end 


// 当有请求时一直忙,完成过程后忙结束
assign  iic_busy = (iicwr_req || iicrd_req);

endmodule



猜你喜欢

转载自blog.csdn.net/fzhykx/article/details/79653677