zoukankan      html  css  js  c++  java
  • SPI以及IIC的verilog实现以及两者之间的对比

    一、SPI是一种常用的串行通信接口,与UART不同的地方在于。SPI可以同时挂多个从机,但是UART只能点对点的传输数据,此外SPI有四条线实现数据的传输,而UART采用的是2条实现串行数据的传输

    1.SPI的主从机的接口模型

    master和slave在时钟的上升沿采样,下降沿发送数据。数据从最高位(MSB)开始发送。)

     

    用3条通讯总线和1条片选线。

    • MOSI:Master Output Slave Input,顾名思义,即主设备输出/从设备输入。数据从主机输出到从机,主机发送数据。
    • MISO:Master Iutput Slave Onput,主设备输入/从设备输出,数据由从机输出到主机,主机接收数据。
    • SCK:即时钟信号线,用于通讯同步。该信号由主机产生,其支持的最高通讯速率为fpclk/2,即所挂载总线速率的一半。如SPI2挂载在APB1总线上,则其最高速率为36MHz / 2 = 18MHz。类似木桶效应,两个设备之间通讯时,通讯速率受限于较低速的设备。
    • NSS:即片选信号线,用于选择通讯的从设备,也可用CS表示。每个从设备都有一条独立的NSS信号线,主机通过将某个设备的NSS线置低电平来选择与之通讯的从设备。所以SPI通讯以NSS线电平置低为起始信号,以NSS线电平被拉高为停止信号。

    2.SPI如何使用,以及对应的有几种配置模式(相位、极性)
    SPI配置模式分类根据的是时钟信号空闲状态。、以及上升沿采样还是下降沿采样,
    CPOL=0表示的是时钟空闲的时候为低电平,反之是高电平
    CPHA=0表示的是时钟信号的第一个边沿是采样沿
    CPHA=1表示的是时钟信号的第二个边沿是采样沿
    对应的时序图如下:

     

    CPOL、CPHA

    • CPOL:即在没有数据传输时,时钟的空闲状态的电平。
    • CPHA:即数据的采样时刻。

    有一点需要注意的是,主机和从机需要工作在相同的模式下才能正常通讯。

    3.起始、停止信号(转于知乎)

    如上图,编号1和6即为起始和停止信号的发生区域。NSS电平由高变低,则产生起始信号;NSS电平由低变高,则产生停止信号。从机检测到自己的NSS线电平被置低,则开始与主机进行通讯;反之,检测到NSS电平被拉高,则停止通讯。

    4.数据有效性

    MOSI和MISO线在SCK的每个时钟周期传输一位数据,开发者可以自行设置MSB或LSB先行,不过需要保证两个通讯设备都使用同样的协定。从图16-1看到,在SCK的上升沿和下降沿时进行触发和采样。

    SPI有四种通讯模式,在SCK上升沿触发,下降沿采样只是其中一种模式。四种模式的主要区别便是总线空闲时SCK的状态及数据采样时刻。这涉及到“时钟极性CPOL”和“时钟相位CPHA”,由CPOL和CPHA的组合而产生了四种的通讯模式。

    5.SPI的verilog实现:结合实际的应用场景对该通信协议进行分析:在一个网络通信模型中,可以将基带部分作为主控,RF部分作为受控部分,把SPI接口作两者之间传输数据的接口,它完成的主要工作是

    (1)将从base band接收到的16位的并行数据,转换为RF所能接收的串行数据,并将该数据根据SPI协议送给RF。

    (2)产生RF所需的时钟信号SCLK,使能信号CSB。

    (3)接收从RF传回的串行数据,并将其转换为并行数据。

    (4)将base band发送的数据,与RF返回的数据进行比较,并把比较结果传给base band。

    module Serial2Parallel_Master #(
        parameter SCLK_DIVIDER = 8'd0 //Sclk Freq = Clk/2 / (SCLK_DIVIDER + 1)
        )(
        input rst_n,
        input clk,
        input sDataRd,
        input [15:0] pDataWr,
        output dataCS,
        output dataSclk,
        output sDataWr,
        output [15:0] pDataRd
    );
    
                
        // counter,used to generate dataSclk signal
        reg dataCS_reg;
        reg dataSclk_reg;
        reg[7:0] Count1;
        always @(posedge clk or negedge rst_n)
            if(!rst_n)
                Count1 <= 8'd0;
            else if(Count1 == SCLK_DIVIDER)
                Count1 <= 8'd0;
            else
                Count1 <= Count1 + 1'b1;
                
        // generate CS and Sclk sequence        
        reg [5:0] i;//Step number 
        always @(posedge clk or negedge rst_n)
            if(!rst_n)begin
                i <= 6'd0;
                dataSclk_reg <= 1'b1;//Sclk high at reset
                dataCS_reg <= 1'b1;     //CS high at reset
            end
            else begin
                case(i)
                    //pull down CS at the beginning
                    6'd0:
                    if(Count1 == SCLK_DIVIDER) begin  
                        i <= i + 1'b1;
                        dataSclk_reg <= 1'b1;
                        dataCS_reg <= 1'b0;    
                    end
                    else;       
                    //generate 1st to 17th Sclk falling edge
                    6'd1,6'd3,6'd5,6'd7,6'd9,6'd11,6'd13,6'd15,6'd17,6'd19,6'd21,6'd23,6'd25,5'd27,6'd29,6'd31,6'd33:
                    if(Count1 == SCLK_DIVIDER) begin
                        dataSclk_reg <= 1'b0;
                        dataCS_reg <= 1'b0;
                        i <= i + 1'b1;
                    end
                    else;
                    //generate 1st to 16th Sclk rising edge
                    6'd2,6'd4,6'd6,6'd8,6'd10,6'd12,6'd14,6'd16,6'd18,6'd20,6'd22,6'd24,6'd26,6'd28,6'd30,6'd32:
                    if(Count1 == SCLK_DIVIDER) begin
                        dataSclk_reg <= 1'b1;
                        dataCS_reg <= 1'b0;    
                        i <= i + 1'b1;
                    end
                    else; 
                    6'd34://CS and Sclk go high
                    if(Count1 == SCLK_DIVIDER) begin
                        dataSclk_reg <= 1'b1;
                        dataCS_reg <= 1'b1;
                        i <= i + 1'b1;
                    end
                    else;
                    6'd35://CS keep high, Sclk go low
                    if(Count1 == SCLK_DIVIDER) begin
                        dataSclk_reg <= 1'b0;
                        dataCS_reg <= 1'b1;
                        i <= 6'd0;
                    end
                    else ;
                    default ;
                endcase
            end ;
            
                
        // - receive and send SPI data    
        reg sDataWr_reg;
        reg [15:0] pDataRd_reg;
        reg rxDone_reg;
        reg [5:0] j;   
        always @(negedge dataSclk or negedge rst_n)
            if(!rst_n) begin
                j <= 6'd0;
                sDataWr_reg <= 1'b0;
                pDataRd_reg <= 16'd0;
                rxDone_reg <= 1'b0;
            end
            // - CS high,clear j & AD data
            else if(dataCS) begin 
                j <= 6'd0;
                sDataWr_reg <= 1'b0;
                pDataRd_reg <= 16'd0;
                rxDone_reg <= 1'b0;
            end
            else begin 
                // - first falling of Sclk, send MSB of send data
                if(j == 6'd0) begin    
                    j <= j + 1'b1;
                    sDataWr_reg <= pDataWr[15];//send data 
                    pDataRd_reg <= 16'd0;//receive data clear
                    rxDone_reg <= 1'b0;
                end
                
                // - 2nd to 16th falling of Sclk
                else if(j <= 6'd15) begin    
                    j <= j + 1'b1;
                    sDataWr_reg <= pDataWr[15-j];//send data 
                    pDataRd_reg[16-j] <= sDataRd;//receive data
                    rxDone_reg <= 1'b0;
                end
                
                // - at 17th falling of sclk_fbk, CS is still low, receive LSB of receive data
                else if(j == 6'd16) begin 
                    j <= j + 1'b1;
                    sDataWr_reg <= 1'b0;//send data clear
                    pDataRd_reg[0] <= sDataRd;//receive data
                    rxDone_reg <= 1'b1;//receive done
                end             
                else begin
                    j <= j;
                    sDataWr_reg <= sDataWr_reg;
                    pDataRd_reg <= pDataRd_reg;
                    rxDone_reg <= rxDone_reg;
                end 
            end
        // - data latch for pDataRd
        reg [15:0] pDataRd_l;
        always @(posedge clk or negedge rst_n)
            if(!rst_n)
                pDataRd_l <= 16'd0;
            else if(rxDone_reg) begin
                pDataRd_l <= pDataRd_reg;
            end
            else begin
                pDataRd_l <= pDataRd_l;
            end       
        // - delay sDataWr for 1 main clk(10ns)
        reg sDataWr_dly;
        always @(posedge clk or negedge rst_n)
            if(!rst_n)
                sDataWr_dly <= 1'b0;
            else if(sDataWr_reg) begin
                sDataWr_dly <= 1'b1;
            end
            else begin
                sDataWr_dly <= 1'b0;
            end        
        // - output assignment
        assign dataCS = dataCS_reg;
        assign dataSclk = dataSclk_reg;
        assign sDataWr = sDataWr_dly;
        assign pDataRd = pDataRd_l;
    endmodule
    Serial2Parallel_Master
    module Serial2Parallel_Slave (
    
        input rst_n,
        input clk,
         
        input dataCS,
        input dataSclk,
         
        input sDataRd,
        input [15:0] pDataWr,
    
        output sDataWr,
        output [15:0] pDataRd
    );
                
        // - SPI read and write
        reg sDataWr_reg;
        reg [15:0] pDataRd_reg;
        reg rxDone_reg;
        reg [5:0] j;//operation steps
        always @(negedge dataSclk or negedge rst_n)
            if(!rst_n) begin
                j <= 6'd0;
                sDataWr_reg <= 1'b0;
                pDataRd_reg <= 16'd0;
                rxDone_reg <= 1'b0;
            end
            // - CS high,clear j & AD data
            else if(dataCS) begin 
                j <= 6'd0;
                sDataWr_reg <= 1'b0;
                pDataRd_reg <= 16'd0;
                rxDone_reg <= 1'b0;
            end
            else begin 
                // - first falling of Sclk, send MSB of send data
                if(j == 6'd0) begin    
                    j <= j + 1'b1;
                    sDataWr_reg <= pDataWr[15];//send data 
                    pDataRd_reg <= 16'd0;//receive data clear
                    rxDone_reg <= 1'b0;
                end
                
                // - 2nd to 16th falling of Sclk
                else if(j <= 6'd15) begin    
                    j <= j + 1'b1;
                    sDataWr_reg <= pDataWr[15-j];//send data 
                    pDataRd_reg[16-j] <= sDataRd;//receive data
                    rxDone_reg <= 1'b0;
                end
                
                // - at 17th falling of sclk_fbk, CS is still low, receive LSB of receive data
                else if(j == 6'd16) begin 
                    j <= j + 1'b1;
                    sDataWr_reg <= 1'b0;//send data clear
                    pDataRd_reg[0] <= sDataRd;//receive data
                    rxDone_reg <= 1'b1;//receive done
                end             
                else begin
                    j <= j;
                    sDataWr_reg <= sDataWr_reg;
                    pDataRd_reg <= pDataRd_reg;
                    rxDone_reg <= rxDone_reg;
                end 
            end
    
        // - data latch for pDataRd
        reg [15:0] pDataRd_l;
        always @(posedge dataCS or negedge rst_n)
            if(!rst_n)
                pDataRd_l <= 16'd0;
            else if(rxDone_reg) begin
                pDataRd_l <= pDataRd_reg;
            end
            else begin
                pDataRd_l <= pDataRd_l;
            end       
        // - output assignment
        assign sDataWr = sDataWr_reg;
        assign pDataRd = pDataRd_l;
     
    endmodule
    Serial2Parallel_Slave

    二、IIC通信

    IIC的通信模式示意图:

    IIC的verilog实现

    `timescale 1ns / 1ps
    
    module IIC_AD(
                clk,rst_n,
                scl,sda);
    
    
    input clk;        // 50MHz
    input rst_n;    //��λ�źţ�����Ч
    output scl;        // 24C02��ʱ�Ӷ˿�
    inout  sda;        // 24C02�����ݶ˿�
     
    reg[2:0] cnt;    // cnt=0:scl上升沿,cnt=1:scl高电平中间,cnt=2:scl下降沿,cnt=3:scl低电平中间
    reg[9:0] cnt_delay;    //500循环计数,产生iic所需要的时钟
    reg scl_r=1;        //时钟脉冲寄存器
    
    always @ (posedge clk or negedge rst_n)
        if(!rst_n) 
        cnt_delay <= 9'd0;
        else if(cnt_delay == 9'd499) 
        cnt_delay <= 9'd0;    //计数到10us为scl的周期,即100KHz 
        else 
        cnt_delay <= cnt_delay+1'b1;    
    
    always @ (posedge clk or negedge rst_n) 
    begin
        if(!rst_n) cnt <= 3'd5;
        else 
          begin
           case (cnt_delay)
                9'd124:    cnt <= 3'd1;    //cnt=1:scl高电平中间,用于数据采样 
                9'd249:    cnt <= 3'd2;    //cnt=2:scl下降沿
                9'd280:    cnt <= 3'd3;    //cnt=3:scl低电平中间,用于数据变化
                9'd499:    cnt <= 3'd0;    //cnt=0:scl上升沿
                default: cnt <= 3'd5;
            endcase
          end
    end
    `define SCL_POS        (cnt==3'd0)        
    `define SCL_HIG        (cnt==3'd1)        
    `define SCL_NEG        (cnt==3'd2)        
    `define SCL_LOW        (cnt==3'd3)        
    
    
    always @ (posedge clk or negedge rst_n)
        if(!rst_n) 
           scl_r <= 1'b1;
        else if(cnt==3'd0) 
           scl_r <= 1'b1;    //scl上升沿
           else if(cnt==3'd2) 
              scl_r <= 1'b0;    //scl下降沿
    
    assign scl = scl_r;    //产生iic所需要的时钟
    //---------------------------------------------
    reg[3:0] num;
    reg [7:0] db_r; //在IIC上传送的数据寄存器   
    reg sda_link;   //输出数据sda信号inout方向控制  0z-c,1c-z
    reg sda_r=1; //输出数据寄存器
    
    //需要写入24C02的地址和数据 
    parameter    DEVICE_WRITE=8'b0101_1000;//被寻址器件地址(写操作)
    parameter   BYTE_ADDR=8'b0000_0011;//写入/读出EEPROM的地址寄存器
    parameter   WRITE_DATA=8'b0001_1011;//写入EEPROM的数据
    
    reg [3:0] cstate;//状态寄存器
    
    parameter     START = 4'd0;//状态机的进行步骤编号
    parameter     ADD1     = 4'd1;
    parameter     ACK1     = 4'd2;
    parameter     ADD2     = 4'd3;
    parameter     ACK2     = 4'd4;
    parameter     DATA     = 4'd5;
    parameter     ACK3     = 4'd6;
    parameter     STOP1 = 4'd7;
    //---------------------------------------------
    always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) begin
              num <= 3'd0;
                sda_link <= 1'b1;
                sda_r <= 1'b1;
               cstate <= START;
            end
    
        else begin              
          case (cstate)            
                START: begin
                        if(`SCL_HIG) begin        //scl为高电平期间
                            sda_link <= 1'b1;    //确定数据传输方向,数据线sda为output
                            db_r <= DEVICE_WRITE;
                            sda_r <= 1'b0;        //拉低数据线sda,产生起始位信号
                            cstate <= ADD1;
                            num <= 4'd0;        //num计数清零
                            end
                        else 
                            cstate <= START; //等待scl高电平中间位置到来 等待数据开始传输
                    end
                ADD1:    begin
                        if(`SCL_LOW) begin
                                if(num == 4'd8) 
                                begin    
                                        num <= 4'd0;            //num清零
                                        sda_r <= 1'b1;      //提高数据线sda,开始数据变化
                                        sda_link <= 1'b0;    //确定数据传输方向,数据线sda为input
                                        cstate <= ACK1;
                                end
                                else begin
                                        cstate <= ADD1;
                                        num <= num+1'b1;
                                        case (num)
                                  4'd0: sda_r <= db_r[7];
                                            4'd1: sda_r <= db_r[6];
                                            4'd2: sda_r <= db_r[5];
                                            4'd3: sda_r <= db_r[4];
                                            4'd4: sda_r <= db_r[3];
                                            4'd5: sda_r <= db_r[2];
                                            4'd6: sda_r <= db_r[1];
                                            4'd7: sda_r <= db_r[0];
                                           default: ;
                                        endcase
                              end
                                end
                    else cstate <= ADD1;
                 end                     
             ACK1:    begin
                            if(/*!sda*/`SCL_NEG) //注:24C01/02/04/08/16器件可以不考虑应答位 
                            begin 
                                    cstate <= ADD2;    //从机响应信号
                                    db_r <= BYTE_ADDR;    // 1地址    
                            end  
                            else 
                                    cstate <= ACK1;        //等待从机响应
                        end
             ADD2:    begin
                        if(`SCL_LOW) 
                        begin
                                if(num == 4'd8) begin    
                                        sda_link <= 1'b0;
                                        sda_r <= 1'b1;
                                        num <= 4'd0;            
                                        cstate <= ACK2;
                                end
                                else begin
                                        sda_link <= 1'b1;//sda作为output  
                                        cstate <= ADD2;
                                        num <= num+1'b1;
                                        case (num)
                                  4'd0: sda_r <= db_r[7];
                                            4'd1: sda_r <= db_r[6];
                                            4'd2: sda_r <= db_r[5];
                                            4'd3: sda_r <= db_r[4];
                                            4'd4: sda_r <= db_r[3];
                                            4'd5: sda_r <= db_r[2];
                                            4'd6: sda_r <= db_r[1];
                                            4'd7: sda_r <= db_r[0];
                                           default: ;
                                            endcase
                            end
                                end
                    else cstate <= ADD2;
                 end                    
             ACK2:    begin
                        if(/*!sda*/`SCL_NEG) begin        //从机响应操作
                                    cstate <= DATA;     //写操作
                                    db_r <= WRITE_DATA;    //写入数据    
                                    
                            end    
                        else cstate <= ACK2;    //等待从机响应
                    end
             DATA:    begin
                    if(`SCL_LOW) 
                        begin
                                if(num == 4'd8) begin    
                                        sda_link <= 1'b0;
                                        sda_r <= 1'b1;
                                        num <= 4'd0;                                        
                                        cstate <= ACK3;
                                end
                                else begin
                                        sda_link <= 1'b1;
                                        cstate <= DATA;
                                        num <= num+1'b1;
                                        case (num)
                                  4'd0: sda_r <= db_r[7];
                                            4'd1: sda_r <= db_r[6];
                                            4'd2: sda_r <= db_r[5];
                                            4'd3: sda_r <= db_r[4];
                                            4'd4: sda_r <= db_r[3];
                                            4'd5: sda_r <= db_r[2];
                                            4'd6: sda_r <= db_r[1];
                                            4'd7: sda_r <= db_r[0];
                                           default: ;
                                            endcase
                            end
                                end
                    else cstate <= DATA;
                 end                    
             ACK3: begin
                        if(/*!sda*/`SCL_NEG) begin
                            sda_r <= 1'b0;                        //拉低数据线sda,产生停止信号
                            sda_link <= 1'b1;//sda作为output 
                            cstate <= STOP1;                        
                            end
                        else cstate <= ACK3;
                    end
                STOP1: begin
                         if(`SCL_HIG) begin
                                sda_link <= 1'b1;
                                sda_r <= 1'b1;                       //拉低数据线sda,产生停止信号
                                cstate <= START;
                            end
                        else cstate <= STOP1;
                    end
                default: cstate <= START;
                endcase
    
                end
    end
    assign sda = sda_link ? sda_r:1'bz;
    
    endmodule 
    View Code

    IIC的关键词:两线和低速,该总线采用开漏的结构可以很好地实现数据的双向传输,也就是说在要用到sda或者scl线的时候,可以通过内部的NMOS下拉为零,否则上拉为高电平

     

    还需要注意的就是开始和结束条件,以及IIC协议的要求:

    scl为高的器件,sda必须保持稳定,sda变化相对于scl变高有建立时间的要求,而sda变化相对于scl变低有保持时间的要求。scl低电平期间,数据sda才可以发生变化。而这里的建立保持时间是微秒级别的,所以IIC的速度慢,1MHz左右。从器件不适合高速数字逻辑单路。

    然后具体的执行顺序为:

    s1:Start+器件地址+应答信号+要发送的数据+应答信号。

    通过scl和sda两条线的控制来实现数据的传输,其中的sda信号线是inout,因为作为串行数据传输线,它不仅要传输上位机的数据到下位机,此外还需要下位机发送一个响应到上位机去,所以实际上需要它是inout。

    IIC通过器件的地址来区分从机,而SPI主要是通过作为主机的CS来区分从机的编码。

    三、关于两者的对比:

    首先是SPI的优点在于:

    1.利于硬件实现,不需要多个器件地址,只用到4根数据线,封装简单

    2.全双工传输,可以同时发送和接收数据

    3.三态输出端口,相对IIC的开漏输出,抗干扰能力强,传输稳定

    4.传输数据的速度在几百MHz远远高于IIC的几十Mhz

    5.输入输出的比特数没有限制

    缺点

    1.信号线多,而且随着从器件的个数增加,芯片选择线会增加

    2.传输过程没有确认信号,不知道从器件的接收情况。

    3.没有校验机制,传输错误不会有提示。

     IIC的优点:

    控制线少,结构简单

    IIC的缺点

    传输速度慢而且麻烦。

  • 相关阅读:
    C connect实现Timeout效果(Linux)
    QSS网址
    C实现读写文件
    crond守护进程实现定时监控某进程占有内存的大小
    Ubuntu17安装Chrome有效
    Ubuntu16安装wine(转)
    直方图均衡化
    函数后面的const修饰符的作用
    C 线程学习记录
    Override Fuction 调用——到底使用的是谁的函数
  • 原文地址:https://www.cnblogs.com/Dinging006/p/9494418.html
Copyright © 2011-2022 走看看