Verilog实现SPI通信(包括对任务和函数用法的讲解)

一、基本知识

   1、SPI

      SPI是串行外设接口(Serial Peripheral Interface)的缩写。它是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线。

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

(1)SDI – SerialData In,串行数据输入;

(2)SDO – SerialDataOut,串行数据输出;

(3)SCLK – Serial Clock,时钟信号,由主设备产生;

(4)CS – Chip Select,从设备使能信号,由主设备控制。

2、SPI的工作模式

    

3、Verilog中的任务

        任务是通过调用来执行的,而且只有在调用时才执行。在定义任务时,设计者可以为其添加输入和输出端口,用于在任务调用时传递参数。任务可以包含带时序控制的语句,当调用带时序控制的任务时,任务返回时的时间和调用时的时间可能不相同。任务可以彼此调用,而且任务还可以调用函数。

(1)任务的定义

task  <任务名>
    端口及数据类型声明语句
     其他语句


endtask

(2) 任务的特点

  • 任务的定义与调用需在一个module模块内
  • 定义任务时,没有端口名列表,但需要紧接着进行输入、输出端口和数据类型的说明。
  • 当任务被调用时,任务被激活。调用时,须列出端口名列表,端口名的排序和类型必须与任务定义中的相一致。
  • 一个任务可以调用别的任务和函数,可以调用的任务和函数个数不限
  • 为了提高任务的模块化程度,传递给任务的参数名通常不使用与任务内部I/O声明的参数名相同的参数名。
  • 任务定义结构中不能出现initial和always过程块

(3)任务使用

module mult(clk,a,b,out,en_mult)

    input clk,en_mult;
    input [3:0] a,b;
    output [7:0] out;
    reg [7:0] out;
       task muotme;//任务定义
         input [3:0] xme,tome;
         output [7:0] result;
         wait(en_mult)  //电平敏感事件触发
         result=xme*tome;
       endtask
       always @(posedge clk)
          muotme(a,b,out);//任务调用时传递给任务的参数与任务I/O声明时的参数顺序相同
endmodule

4、verilog中的函数

       函数和任务一样,也是定义一个可重复调用的模块,但是函数可以返回一个值,因此可以出现在等号右边的表达式中,而任务的返回值只能通过任务的输出端口来获得。

    (1)  函数的定义:

function <返回值位宽或类型说明> 函数名;
    输入端口与类型说明
    局部变量说明
    块语句
endfunction

   (2)  函数的特点:

  • 函数定义不能包含任何时序控制语句。
  • 函数必须包含输入,但不能有输出或双向信号。
  • 函数中不能使用非阻塞赋值语句。
  • 一个函数只能返回一个值,该值的变量名与函数名同名。
  • 传递给函数参数的顺序与函数定义时输入参数声明的顺序相同。
  • 函数定义必须包含在模块定义之内。
  • 函数不能调用任务,但任务可以调用函数。

(3)举例用函数定义一个8-3编码器

module code_83(din,dout)

   input  [7:0] din;
   output [2:0] dout;
        function [2:0] code;  //函数定义
           input [7:0] din;//函数只有输入,输出为函数本身
            casex(din)
                 8'b1xxx_xxxx:cade=3'h7;
                 8'b01xx_xxxx:cade=3'h6;
                 8'b001x_xxxx:cade=3'h5;
                 8'b0001_xxxx:cade=3'h4;
                 8'b0000_1xxx:cade=3'h3;
                 8'b0000_01xx:cade=3'h2;
                 8'b0000_001x:cade=3'h1;
                 8'b0000_0001:cade=3'h0;
                 default:code=3'hx;
            endcase
         endfuntion
          assign dout=code(din);//函数调用

endmodule

二、实现过程

设计两段式状态机,完成SPI的模式1

一个模块控制读还是写,一个完成对SPI引脚控制

1、定义模块输入输出引脚

module tem_rec(
input clk_10m,//输入10Mhz的时钟
input rst, //高电平有效
input spidi,//spi数据输入端 
output spiclk,//spi中的时钟
output spido,//spi的发送端 
output spics,//片选信号
output [7:0] data,
    );
reg spics;//spi片选信号
reg spiclk;//spi时钟
reg spido;////spi写信号
reg [7:0] data;//读的数据转换成8位并行数据输出

2、读写控制模块编写:

//总状态控制
parameter ini=4'd0, state_1=4'd1, state_2=4'd2, state_3 =4'd3,state_4=4'd4,state_5=4'd5,
state_6=4'd6,state_7=4'd7,state_8=4'd8,state_9=4'd9,state_10=4'd10,state_11=4'd11,
state_12=4'd12,state_13=4'd13,state_14=4'd14,state_end=4'd15;//使用循环码
reg wr;
reg rd;//读写命令
reg [3:0] next_state;//状态转移
always@(posedge clk_10m or posedge rst)
begin
   if(rst) begin
       next_state<=ini;
       wr<=0;
       rd<=0;
       data<=8'd0;//待发送
   end
   else	begin
       case(next_state)
       ini://初始化 
       begin
		  next_state<=state_1;
       end
       state_1: //写
       begin
          if(send_rdy) begin       			 
			wr<=0;
            next_state<=state_2;
          end	
		 else//写寄存器
			wr<=1;
       end
       state_2://读
	   begin
			if(rec_rdy) begin
				rd<=0;
			 end
		   else begin
				rd<=1;
		   end
	  end
      endcase
   end
end

3、SPI引脚控制模块编写:

//*spi的数据接收和发送*//
reg send_rdy;//发送完一字节标志位
reg rec_rdy;//接收完成一字节数据标志位
always @(posedge clk_10m)
begin
   if(rst) begin
	  spistate<=idle;
      spics<=1'b1;
	  spiclk<=1'b1;
	  spido<=1'b1;
	  send_rdy<=1'b0;
	  rec_rdy<=1'b0;
   else 
   begin
      case(spistate)//读写状态控制
	  idle:
      begin
         if((wr==1'b1)&&(rd==1'b0)) begin//发送数据转换
			spistate<=send_data;//转发送状态
			dsend<=datain;//准备待发送数据
			send_rdy<=1'b0;
			rec_rdy<=1'b0;   
		end
        else if((wr==1'b0)&&(rd==1'b1) begin
            spistate<=receive_data;//转接收转态
            dstate<=8'd0;
			rec_rdy<=1'b0;
			send_rdy<=1'b0;
        end
      end
       send_data://发送数据状态
	   begin
          case(dstate)
			8'd0://产生片选信号有效
			begin
				spics<=1'b1;
				spiclk<=1'b1;
				spido<=1'b1;//发送出去的数据
				dstate<=8'd1;
			end
			8'd1:
			begin
				spics<=1'b1;
				spiclk<=1'b1;
				spido<=1'b1;
				dstate<=8'd2;
			end
			8'd2:
			begin
				spics<=1'b0;
				spiclk<=1'b1;
				spido<=1'b1;
				dstate<=8'd3;
			end
			8'd3:
			begin
				spics<=1'b0;
				spiclk<=1'b0;
				spido<=datain[7];//发送数据最高位
				dstate<=8'd4;
			end
			8'd4:
			begin
				spics<=1'b0;
				spiclk<=1'b1;
				spido<=datain[7];
				dstate<=8'd5;
			end
			8'd5:
			begin
				spics<=1'b0;
				spiclk<=1'b0;
				spido<=datain[6];
				dstate<=8'd6;
			end
			8'd6:
			begin
				spics<=1'b0;
				spiclk<=1'b1;
				spido<=datain[6];
				dstate<=8'd7;
			end
			8'd7:
			begin
				spics<=1'b0;
				spiclk<=1'b0;
				spido<=datain[5];
				dstate<=8'd8;
			end
			8'd8:
			begin
				spics<=1'b0;
				spiclk<=1'b1;
				spido<=datain[5];
				dstate<=8'd9;
			end
			8'd9:
			begin
				spics<=1'b0;
				spiclk<=1'b0;
				spido<=datain[4];
				dstate<=8'd10;
			end
			8'd10:
			begin
				spics<=1'b0;
				spiclk<=1'b1;
				spido<=datain[4];
				dstate<=8'd11;
			end
			8'd11:
			begin
				spics<=1'b0;
				spiclk<=1'b0;
				spido<=datain[3];
				dstate<=8'd12;
			end
			8'd12:
			begin
				spics<=1'b0;
				spiclk<=1'b1;
				spido<=datain[3];
				dstate<=8'd13;
			end
			8'd13:
			begin
				spics<=1'b0;
				spiclk<=1'b0;
				spido<=datain[2];
				dstate<=8'd14;
			end
			8'd14:
			begin
				spics<=1'b0;
				spiclk<=1'b1;
				spido<=datain[2];
				dstate<=8'd15;
			end
			8'd15:
			begin
				spics<=1'b0;
				spiclk<=1'b0;
				spido<=datain[1];
				dstate<=8'd16;
			end
			8'd16:
			begin
				spics<=1'b0;
				spiclk<=1'b1;
				spido<=datain[1];
				dstate<=8'd17;
			end
			8'd17:
			begin
				spics<=1'b0;
				spiclk<=1'b0;
				spido<=datain[0];
				dstate<=8'd18;
			end
			8'd18:
			begin
				spics<=1'b0;
				spiclk<=1'b1;
				spido<=datain[0];
				dstate<=8'd19;
			end
			8'd19:
			begin
				spics<=1'b1;
				spiclk<=1'b1;
				spido<=1'b1;
				dstate<=8'd20;
			//  	send_rdy<=1'b1;
			end
			8'd20://一个字节数据发送完成
			begin
				spics<=1'b1;
				spiclk<=1'b1;
				spido<=1'b1;
				dstate<=8'd0;
				spistate<=idle;
				send_rdy<=1'b1;//发送完成一帧数据标志位
			end
			default    
			begin
				spics<=1'b1;
				spiclk<=1'b1;
				spido<=1'b1;
				spistate<=idle;
			end
			endcase//对应发送状态
       end
       receive_data://接收数据状态
       begin
           case (dstate) //片选信号有效
			8'd0:
			begin
				spics <= 1'b1;
				spiclk <= 1'b1;
				spido <= 1'b1;
				dstate <= 8'd1;
			end
			8'd1:
			begin
				spics <= 1'b1;
				spiclk <= 1'b1;
				spido <= 1'b1;
				dstate <= 8'd2;
			end
			8'd2:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				spido <= 1'b1;
				dstate <= 8'd3;
			end
			8'd3:
			begin
				spics <= 1'b0;
				spiclk <= 1'b0;
            dreceive[7] <= spidi;				
				dstate <= 8'd4;
			end
			8'd4:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1; //紧接着上升沿的下降沿数据被读取       
				dreceive[7] <= spidi; //接收数据最高位
				dstate <= 8'd5;
			end
			8'd5:
			begin
				spics <= 1'b0;
				spiclk <= 1'b0;
				dstate <= 8'd6;
				dreceive[6] <= spidi;
			end
			8'd6:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				dreceive[6] <= spidi;
				dstate <= 8'd7;
			end
			8'd7:
			begin
				spics <= 1'b0;
				spiclk <= 1'b0;
				dstate <= 8'd8;
				dreceive[5] <= spidi;
			end
			8'd8:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				dreceive[5] <= spidi;
				dstate <= 8'd9;
			end
			8'd9:
			begin
				spics <= 1'b0;
				spiclk <= 1'b0;
				dstate <= 8'd10;
				dreceive[4] <= spidi;
			end
			8'd10:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				dreceive[4] <= spidi;
				dstate <= 8'd11;
			end
			8'd11:
			begin
				spics <= 1'b0;
				spiclk <= 1'b0;
				dstate <= 8'd12;
				dreceive[3] <= spidi;
			end
			8'd12:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				dreceive[3] <= spidi;
				dstate <= 8'd13;
			end
			8'd13:
			begin
				spics <= 1'b0;
				spiclk <= 1'b0;
				dstate <= 8'd14;
				dreceive[2] <= spidi;
			end
			8'd14:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				dreceive[2] <= spidi;
				dstate <= 8'd15;
			end
			8'd15:
			begin
				spics <= 1'b0;
				spiclk <= 1'b0;
				dstate <= 8'd16;
				dreceive[1] <= spidi;
			end
			8'd16:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				dreceive[1] <= spidi;
				dstate <= 8'd17;
			end
			8'd17:
			begin
				spics <= 1'b0;
				spiclk <= 1'b0;
				dstate <= 8'd18;
				dreceive[0] <= spidi; 
			end
			8'd18:
			begin
				spics <= 1'b0;
				spiclk <= 1'b1;
				dreceive[0] <= spidi; //接收数据最低位
				dstate <= 8'd19;
			 end
			8'd19:
			begin
				spics <= 1'b1;
				spiclk <= 1'b1;
				spido<= 1'b1;
				dstate <= 8'd20;
				dataout[7:0]<= dreceive[7:0];
				//rec_rdy<=1'b1;
			end
			8'd20:
			begin
				spics <= 1'b1;//片选信号无效
				spiclk <= 1'b1;     
				spido <= 1'b1;
				dstate <= 8'd0;
				spistate <= idle;
				rec_rdy<=1'b1;//接收完一个字节标志位有效
			end
			default
			begin
				spics<=1'b1;
				spiclk<=1'b1;
				spido<=1'b1;
		
			end
			endcase//对应接收状态
       end
      endcase
   end
end

            

        

发布了141 篇原创文章 · 获赞 194 · 访问量 25万+

猜你喜欢

转载自blog.csdn.net/kenjianqi1647/article/details/86554209
今日推荐