三大通信协议(3)SPI——寄存器配置

目录

一、SPI通信协议简介

二、SPI通信时序

1.主从通信

2.模式选择

三、实例

总结


一、SPI通信协议简介

     SPI是串行外设接口(Serial Peripheral Interface)的缩写,是 Motorola 公司推出的一种同步串行接口技术,是一种高速的,全双工,同步的通信总线,支持全双工通信、通信速率快(可以最高可达几十兆)。适用于主机与外设之间的近距离通信,经常应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间的通信。由于其通信速率快,支持全双工通信,并且占用的引脚数目较少(4根传输线),目前市面上很多芯片都集成了这种通信协议。当然,SPI通信协议也存在自身的缺点,没有指定的流控制,同时也没有相应的应答机制,因此在通信过程中缺乏一定的可靠性。

二、SPI通信时序

1.主从通信

       SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时),也是所有基于SPI的设备共有的,它们是SDI(数据输入)、SDO(数据输出)、SCLK(时钟)、CS(片选)。

2.模式选择

      SPI通信有4种不同的模式,不同的从设备可能在出厂是就是配置为某种模式,这是不能改变的;但我们的通信双方必须是工作在同一模式下,所以我们可以对我们的主设备的SPI模式进行配置,通过CPOL(时钟极性)即在没有数据传输时,时钟的空闲状态的电平和CPHA(时钟相位)即数据的采样时刻两个信号来控制我们主设备的通信模式,

具体如下:

   如果CPOL被清0,则SCLK在空闲状态保持低电平,反之被置1则保持高电平;如果CPHA位被清0,则在SCLK每个时钟周期的第1个边沿(奇数边沿)进行数据位采样,反之被置1则在SCLK每个时钟周期的第2个边沿(偶数边沿)采样

三、实例

          该例子是学习了V3学院——尤老师的腾讯课堂SPI接口实例。主机是FPGA,从机是DAC3283,通过FPGA进行写操作,将数据通过SPI协议发送给从机,完成主机与从机之间的单向通信(实质是FPGA通过该协议对DAC中的32个寄存器进行配置)。

    DAC3283内部共有32个寄存器,下图是每个寄存器的使用手则。

  每个寄存器最多可以写入的数据为5Byte,其中第1Byte用于命令传输,包括读写控制、传输字节个数、寄存器地址;剩余Byte用于数据传输,进行寄存器配置。第一个Byte的命令配置如下图所示:

      下图是发送第一个Byte和第二个Byte的时序图:

       设计过程:首先将要发送的命令信息和数据信息存在RAM中,RAM的深度是32(供32个寄存器),位宽是16(8bite的命令和8bite的数据),一次发送16bite信息,共发送32次。每次信息发送之间需要有一定的时间间隔,因此本设计采用状态机的形式进行设计。状态机共包括以下五个状态,上电复位后处于IDLE状态,Work_en拉高,进入到WAIT状态进行等待,等待8个SCLK时钟周期后进入READ_MEM状态,在该状态下将读RAM的地址累加,并将RAM中的数据读出存入到databuff中,等待一个系统周期 后进入WRITE_REG状态,在该状态下进行串并转换,将databuff中的数据输出给spi,同时输出SCLK时钟信号和sdenb使能信号,16个SCLK时钟周期进入WAIT状态,进行下次数据的发送,如果在WRITE_REG状态中检测到32组数据全部发送完毕,则进入STOP状态。在设计过程中应注意SCLK的上升沿对应每次数据的中心位置,保证充足的建立时间和保持时间。下图是对应的状态转移图和Verilog代码。

状态转移图:

时序图:

Verilog代码如下:

module  spi_ctrl(
          input              sys_clk,
	  input              sys_res,
	  input              work_en,
//	  input              spo,
	  output     reg     sclk,
	  output     reg     spi,
	  output     reg     sdenb
);
parameter       IDLE = 5'b00001;
parameter       WAIT = 5'b00010;
parameter       READ_MEM = 5'b00100;
parameter       WRITE_REG = 5'b01000;
parameter       STOP = 5'b10000;
reg     [4:0]state;
reg     [4:0]fre_count;
wire          sclk_n;
reg          sclk_p;
reg          sclk_flag;
reg     [3:0]wait_cnt;
reg     [3:0]shift_cnt;//数据移位计数器,前八位为命令位,后八位为数据位
reg     [4:0]r_addr;//读RAM中的地址计数器   
reg     [15:0]shift_buff;//存储从RAM中读出的数据
reg           data_end;


wire         wren_sig;
wire   [15:0]r_data;
  

always@(posedge sys_clk or negedge sys_res)begin
     if(!sys_res)
	     fre_count <= 5'd0;
	 else if(fre_count == 5'd24)
	     fre_count <= 5'd0;
	 else 
	     fre_count <= fre_count + 1'd1;
end 

always@(posedge sys_clk or negedge sys_res)begin
     if(!sys_res)
         sclk_p <= 1'b0;
	 else if(fre_count == 5'd24)
	     sclk_p <= ~sclk_p;
	 else 
	     sclk_p <= sclk_p;
end 

assign sclk_n = ~sclk_p;

//sclk_flag 为分频后的时钟sclk_n一个周期的标志
always@(posedge sys_clk or negedge sys_res)begin
     if(!sys_res)
	    sclk_flag <= 1'b0;
	 else if(fre_count == 5'd24 && sclk_n == 1'b0)
	    sclk_flag <= 1'b1;
	 else 
	    sclk_flag <= 1'b0;
end 

always@(posedge sys_clk or negedge sys_res)begin
     if(!sys_res)
	wait_cnt <= 4'd0;
     else if(state == WAIT && sclk_flag)
        wait_cnt <= wait_cnt + 1'b1;
     else if(state != WAIT)
        wait_cnt <= 4'd0;
     else
        wait_cnt <= wait_cnt;
end 
always@(posedge sys_clk or negedge sys_res)begin
     if(!sys_res)
	shift_cnt <= 4'd0;
     else if(state == WRITE_REG && sclk_flag)
        shift_cnt <= shift_cnt + 1'b1;
     else if(state != WRITE_REG)
        shift_cnt <= 4'd0;
     else
        shift_cnt <= shift_cnt;
end 
always@(posedge sys_clk or negedge sys_res)begin
     if(!sys_res)
	    state <= IDLE;	
     else begin
        case(state)
		   IDLE:if(work_en)
		           state <= WAIT;
				else 
				   state <= state;
		   WAIT:if(wait_cnt[3])
		           state <= READ_MEM;	
				else
				   state <= state;
		   READ_MEM:state <= WRITE_REG;
		   WRITE_REG:if(shift_cnt == 4'd15 && sclk_flag && data_end)
						     state <= STOP;
					 else if(shift_cnt == 4'd15 && sclk_flag)
						     state <= WAIT;
					 else
					     state <= state;
		   STOP:state <= state;
           default:state <= IDLE; 		   
        endcase		
	 end 
end 

always@(posedge sys_clk or negedge sys_res)begin
     if(!sys_res)
	    r_addr <= 5'd0;
	 else if(state == READ_MEM)
	    r_addr <= r_addr + 1'b1;
	 else
	    r_addr <= r_addr; 
end  
always@(posedge sys_clk or negedge sys_res)
      if(!sys_res)
	     data_end <= 1'b0;
	  else if(state == READ_MEM && (&r_addr) == 1'b1)//等价于r_addr == 5‘d31
	     data_end <= 1'b1;
	  else
	     data_end <= data_end;


always@(posedge sys_clk or negedge sys_res)begin
   if(!sys_res)
	    shift_buff <= 16'd0;
	 else if(state == READ_MEM)
	    shift_buff <= r_data;
	 else if(state == WRITE_REG && sclk_flag)
	    shift_buff <= {shift_buff[14:0],1'b1};
	 else
	    shift_buff <= shift_buff;
end 

always@(posedge sys_clk or negedge sys_res)
     if(!sys_res)
	    spi <= 1'b0;
	 else if(state == WRITE_REG)
	    spi <= shift_buff[15];
	 else 
	    spi <= 1'b0;

always@(posedge sys_clk or negedge sys_res)
      if(!sys_res)
	   sdenb <= 1'b1;
        else if(state == WRITE_REG)
           sdenb <= 1'b0;
        else
           sdenb <= 1'b1;	  
	   
always@(posedge sys_clk or negedge sys_res)
      if(!sys_res)
	     sclk <= 1'b0;
	  else if(state == WRITE_REG)
	     sclk <= sclk_p;
	  else
	     sclk <= 1'b0;

assign wren_sig = 1'b0;
ram_16_32_r ram_16_32_r_inst (
    .address ( r_addr ),
    .clock ( sys_clk ),
    .data ( 16'd0 ),
    .wren ( wren_sig ),//写使能高有效,读使能低有效
    .q ( r_data )
    );

endmodule

仿真验证

tb测试代码:

`timescale  1ns/1ns
module tb_spi_ctrl;
reg     clk;
reg     res;
reg     work_en;
//wire    spo;
wire    sclk;
wire    spi;
wire    sdenb;
reg     [15:0]read_mem[31:0];
reg     [15:0]shift_buffer = 16'd0;
initial  begin
      clk <= 1'b0;
	  res <= 1'b0;
	  #200 res <= 1'b1;
end

initial  begin
     work_en <= 1'b0;
	 #250 work_en <= 1'b1;
end

initial begin
     $readmemb("ram_data.txt",read_mem);
end 
initial begin
    rec_spi();
end 


always #10 clk <= ~clk;

task  rec_spi();
   integer  i,j;
   begin

        for(i=0;i<32;i=i+1)begin
		    for(j=0;j<16;j=j+1)begin
			
			     @(posedge sclk)
				 shift_buffer = {shift_buffer[14:0],spi};
				 if(j == 15 && shift_buffer == read_mem[i])
				      $display("ok,data index is %d,rec_data is %d,send_data is %d",i,shift_buffer,read_mem[i]);
				 else if(j == 15)
				      $display("error");
			 	  
			end 
		end 

   end 

endtask


  spi_ctrl u_spi_ctrl(
         .sys_clk(clk),
	 .sys_res(res),
	 .work_en(work_en),
	// .spo(spo),
	 .sclk(sclk),
	 .spi(spi),
	 .sdenb(sdenb)
);
  
  
  
endmodule 

读取的数据:

0000000000011111
0000001010011001
0000000000011000
0000001011110110
0001010101000111
0000001001001010
0000001100010001
0010000101111111
0001010101011100
0000011100011100
0000000000000000
0000001100010001
0001011011110011
0000000001010110
0000000000000000
0000001100010001
0010001010101011
0000000000000100
0000001100010001
0001011011100001
1101100111101001
0000000000111000
0000000001010111
0000001101101011
0001111010101110
0000001000111111
0000000111001010
0000000000000000
0000000001010111
0000000001010110
1101010100111011
0000000111001000

仿真结果(注意由于调用了ramip核,因此,在仿真的过程中需要添加ramip核生成的.v文件以及库文件altera_mf.v文件):



总结

        SPI通信协议应用范围十分广泛,接下来将继续学习一下该通信协议的一些例子,比如在FPGA内部对外部Flash进行全擦除或扇区擦除以及FPGA对外部Flash进行读、写操作的实验。

        初次创作,难免文章中存在错误,希望读者能够及时纠正并给予私信,望大家共同进步!

  • 20
    点赞
  • 157
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刻蓇铭鑫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值