FPGA:三大协议(UART、IIC、SPI)之SPI

摘要:1、本文介绍SPI物理层面连接(通过哪几条线通信),2、本文介绍SPI时序(通过哪种方式进行器件之间交流)。3、提供主机和从机verilog代码。4、仅供自己参考

一、SPI物理层连接

(1)有四根线连接:CS_N(片选信号--主机发出)、miso(从机发出,主机接收)、mosi(主机发出,从机接收)、SCLK(时钟信号,主机发出,作为数据传输的参考时钟)。

(2)结构图:

(3)总结:

1、SPI是一种全双工,同步通信总线,需要四根信号线(可用于FLASH器件的控制);

2、SPI通信有主从之分,可以实现一主多从或者是一主多从,但是不能实现多主多从,因为从器件只有一根cs_n片选信号线,没办法知道是哪个主机发出的控制信号;

3、SCLK是主机产生的,用来同步数据;

4、SPI是以MSB方式传播(高位先发);

二、SPI协议层时序

(1)SPI要记住以下几个知识(必须死记)

1、miso是从机发送,主机接收的信号线;

2、mosi是主机发送,从机接收的信号线;

3、mosi和mosi可以同时工作;

4、SCLK是主机产生,用来同步数据;且!!!:主机接收和发送数据都是在SCLK的跳变沿

从机接收和发送数据也是在SCLK的跳变沿。

5、根据4可知,数据的接收和发送是在SCLK的上升沿,究竟是在上升沿还是下降沿是由SPI的通信模式决定。注!!!:这个通信模式是双发同时遵守的!!,不是通过什么信号线配置的!!。

6、cs_n片选信号是由主机产生的,控制从机是否开始工作。

(2)通信模式:

解释:什么叫通信模式,就是决定数据的采集是在SCLK的上升沿还是下降沿,并且还决定SCLK在空闲时候是什么状态。

1、由CPOL和CPHA决定(这两个就是两个名称,没有什么用,相当于你的名字,没有实际的用处),所以可以有四种通信模式,00 、01、 10、 11。

2、CPOL        :clock polarity 时钟极性

CPHA             :clock phase 时钟相位,表示奇偶边沿采样。

CPHA = 0      :表示在时钟(SCLK)奇数边沿对数据采样,CPHA = 1表示在SCLK时钟偶数边沿采样。

CPOL = 0       :表示SCLK空闲时低电平,CPOL = 1表示SCLK时钟空闲时是高电平。

总结表格:

时序图示意(提供11模式时序图):

 注!!!:以上的CPOL和CPHA是没有任何物理意义,仅仅用他们来表示这种通信模式,也可以用其他名字来代替CPOL和CPHA,是要双方用同一个模式就可以了。

(3)怎么通信:

1、首先要知道:miso、mosi、cs_n在空闲时是高电平,sclk空闲状态由通信模式决定。

2、由起始信号和停止信号表示传输开始和停止:

 3、sclk怎么产生:用计数器记录一段时间实现SCLK的翻转,就产生了sclk脉冲信号;

4、mosi怎么产生:mosi的产生根据通信模式产生,比如在11模式:sclk空闲为高,在上升沿采样。那么在sclk下降沿的时候更具数据改变mosi信号线的高低;

5、miso怎么接收:比如在11模式。在sclk上升沿来临的时候采集miso,把串行数据变成并行数据;

6、定位数据哪一位接收或者发送:用一个计数器记录这一次接受了好多bit数据,并且放在数据对应位上,实现串并转换。

如上图:当bit为0的时候发送data的第7位(MSB),cnt_bit的变化是当sclk记录完一个周期之后加一。

主机verilog代码:

module spi_master(
    input  					clk		, //system clock 50MHz
    input  			 		rst_n	, //reset, low valid
    
    input					start	, //按键控制数据发送
    /* input			[7:00]	send_data	, //要发送的数据 */

    input                   spi_miso,//从机发送,主机接收

    output					send_done	, //这一次发送结束
    output reg     [7:0]    rx_data,
    output			 reg   	sclk,	  //
    output           reg    spi_mosi,//主发
    output           reg    spi_cs   //片选信号
);
//Parameter Declarations
localparam
        IDLE             =  4'b0001,
        START            =  4'b0010,
        WR_RD_DATA       =  4'b0100,
        STOP             =  4'b1000;
//Internal wire/reg declarations
reg		[3:00]	state_c, state_n; //
//跳转条件定义
wire			idle2start;
wire			start2wr_rd_data;
wire			wr_rd_data2stop;
wire			stop2dile;
      
reg     [7:0]   data_tmp;//输入数据暂存
reg     [7:0]   data_tmp_rx;

reg		[5:00]	cnt_sclk		; //Counter   
wire			add_cnt_sclk ; //Counter Enable
wire			end_cnt_sclk ; //Counter Reset 

reg		[3:00]	cnt_bit	; //Counter   
wire			add_cnt_bit ; //Counter Enable
wire			end_cnt_bit ; //Counter Reset 

//Module instantiations , self-build module
//缓存数据
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        data_tmp <= 8'd20;
    end  
    else if(start)begin
        data_tmp <= data_tmp + 3;
    end 
    else begin
        data_tmp <= data_tmp;
    end
end //always end

//Logic Description      
        
//第一段设置状态转移空间
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        state_c <= IDLE;
    end
    else begin
        state_c <= state_n;
    end
end //always end
//第二段、组合逻辑定义状态转移
always@(*)begin
    case(state_c)
        IDLE:begin
            if(idle2start)begin
                state_n = START;
            end
            else begin
                state_n = state_c;
            end
        end
        START:begin
            if(start2wr_rd_data)begin
                state_n = WR_RD_DATA;
            end 
            else begin
                state_n = state_c;	
            end 
        end
        WR_RD_DATA:begin
            if(wr_rd_data2stop)begin
                state_n = STOP;
            end 	
            else begin
                state_n = state_c;	
            end 
        end 
        STOP:begin
            if(stop2dile)begin
                state_n = IDLE;
            end 
            else begin
                state_n = state_c;	
            end 
        end 
        default: begin
            state_n = IDLE;
        end
    endcase
end //always end
        
assign	idle2start	        =	state_c ==  IDLE	&& 	start;
assign	start2wr_rd_data	=	state_c	==	START	&&	spi_cs == 0;
assign	wr_rd_data2stop	    =	state_c	==	WR_RD_DATA	&&	end_cnt_bit ;
assign	stop2dile	        =	state_c	==	STOP	&&	spi_cs == 1;
        
//计数器
//cnt_sclk[5:00]
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin  
        cnt_sclk <= 6'd0; 
    end  
    else if(add_cnt_sclk)begin  
        if(end_cnt_sclk)begin  
            cnt_sclk <= 6'd0; 
        end  
        else begin  
            cnt_sclk <= cnt_sclk + 1'b1; 
        end  
    end  
    else begin  
        cnt_sclk <= 6'd0;  
    end  
end 
assign add_cnt_sclk = state_c == WR_RD_DATA; 
assign end_cnt_sclk = add_cnt_sclk && cnt_sclk >= 9; //周期是10个时钟周期,当cnt_sclk加到5的时候翻转

//cnt_bit     
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin  
        cnt_bit <= 4'd0; 
    end  
    else if(wr_rd_data2stop)begin
        cnt_bit <= 4'd0; 
    end
    else if(add_cnt_bit)begin  
        if(end_cnt_bit)begin  
            cnt_bit <= 4'd0; 
        end  
        else begin  
            cnt_bit <= cnt_bit + 1'b1; 
        end  
    end  
    else begin  
        cnt_bit <= cnt_bit;  
    end  
end 
        
assign add_cnt_bit = state_c == WR_RD_DATA && end_cnt_sclk; //在计数器为0的时候加一,表示数据变化是在下降沿,采集是在上升沿
assign end_cnt_bit = add_cnt_bit && cnt_bit >= 7; 
//第三段,定义状态机输出情况,可以时序逻辑,也可以组合逻辑
always @(posedge clk or negedge rst_n)begin  
    if(!rst_n)begin  
        sclk <= 1'b1;
        spi_cs <= 1'b1;
        spi_mosi <= 1'b1;
        rx_data <= 8'd0;
    end  
    else begin  
        case(state_c)
            IDLE          :begin//全高
                sclk <= 1'b1;
                spi_cs <= 1'b1;
                spi_mosi <= 1'b1;
            end   
            START         :begin
                spi_cs <= 1'b0;
                sclk <= 1'b1;
                spi_mosi <= 1'b1;
            end   
            WR_RD_DATA    :begin
                //发送
                spi_cs <= 1'b0;
                if(cnt_sclk < 5)begin
                    sclk <= 1'b0;
                end
                else begin
                    sclk <= 1'b1;
                end
                spi_mosi <= data_tmp[7-cnt_bit];
                //接收

                if(sclk == 1'b1)begin
                    data_tmp_rx[7-cnt_bit] <= spi_miso;
                end
            end   
            STOP          :begin
                rx_data <= data_tmp_rx;
                sclk <= 1'b1;
                spi_cs <= 1'b1;
                spi_mosi <= 1'b1;
            end   
            default:;
        endcase
        
    end  
end //always end
assign send_done = stop2dile;

endmodule 

 从机verilog代码:

module spi_slave(
    input					sclk	, //
    input			    	spi_cs	, //
    input                   spi_mosi,//接收的数据
    output		reg			spi_miso,	//输出的无数据

    output      [7:0]       rx_data,
    output                  data_vld//信号有效
);
//Parameter Declarations
reg     [7:0]   rx_data_tmp;

reg     [2:0]   cnt_bit;
reg     [7:0]   tx_data = 8'd0;
//Internal wire/reg declarations


//Module instantiations , self-build module

    
//Logic Description
always @(posedge sclk)begin
    if(spi_cs == 1'b1)begin
        rx_data_tmp <= 8'd0;
        spi_miso <= 1'b1;
        cnt_bit <= 3'b111;
    end
    else begin
        cnt_bit = cnt_bit + 1;
        if(sclk)begin
            rx_data_tmp[7-cnt_bit] <= spi_mosi;
        end
        else ;
        if(cnt_bit == 1'b1)begin
            tx_data <=  tx_data + 1;
        end
        else ;
    end
end

always @(*)begin
    if(spi_cs == 1'b1)begin
        spi_miso = 1'b1;
    end
    else   begin
        if(sclk == 1'b0)begin
            spi_miso = tx_data[7 - cnt_bit];
        end
        else begin
            spi_miso = spi_miso;
        end
    end
end

assign rx_data = rx_data_tmp;
assign data_vld = cnt_bit == 3'd7;

endmodule 

  • 24
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值