本帖最后由 何立立 于 2015-7-5 20:04 编辑
许多设备都是例如某些液晶屏是需要用到SPI协议传输数据的。 在SPI传输中有分为主机和从机之分。主机的定义,有CS使能权,产生串行时钟。反之从机的定义是CS被使能,接收串行时钟。此实验中
FPGA是主机。
在接收端很简单从机只要检测到CLK,在CLK的上升沿时采集数据即可接保存发送端发送的数据。
在这里我们要干的事情只有一件而已,就是主机(FPGA)向从机写数据。SI端,SCL端和CS端都是由主机输出,从机输入。从机读取(锁存)数据都是CS被拉低,并且发生在SCL信号的上升沿。
SCL信号在空闲的时候总是处于高电平。当主机开始向从机写入数据,主机会先拉低CS信号,再拉低SCL信号,然后“设置”数据,亦即主机(FPGA)更新SI的数据(主机数据移位操作),最后再拉高SCL信号。同一时间,从机会因为SCL的上升沿变化,从机“锁存”(从机读取数据操作)SI上的数据。
下面是此实验的RTL 原理图:
上图所示是要建立的功能模块spi_write_module.v ,亦即是主机的SPI发送模块。为了最大发挥 Verilog HDL语言特性,SPI_Data 和 SPI_Out 的位配置如下:
在对其它设备控制时候用到CS和A0,CS是使能被控制设备的信号(低电平有效)。
A0 给被控设备的命令或者数据决定信号(0 = 命令,1 = 数据 )。SPI_Data : 第9位表示CS,第8位表示A0,第7 .. 0 位表示一字节数据。
SPI_Out : 第3位表示CS,第2位表示A0,第1位表示SCL,第0位表示SI。
下面给出本实验的代码:
- module spi_write_module
- (
- CLK, RSTn,
- Start_Sig,
- SPI_Data,
- Done_Sig,
- SPI_Out,
- SPI_CLK,
- SPI_DATA
- );
- input CLK;
- input RSTn;
- input Start_Sig;
- input [9:0]SPI_Data;
- output Done_Sig;
- output [3:0]SPI_Out; // [3]CS [2]A0 [1]rCLK [0]rDO
-
- output SPI_CLK;
- output SPI_DATA;
- /*****************************/
- parameter T0P5US = 3'd6; //半周期为0.5us 这里可以根据需要来更改SPI的时钟周期大小
- /*****************************/
- reg [2:0]Count1;
- always @ ( posedge CLK or negedge RSTn )
- if( !RSTn )
- Count1 <= 3'd0;
- else if( Count1 == T0P5US )
- Count1 <= 3'd0;
- else if( Start_Sig )
- Count1 <= Count1 + 1'b1;
- else
- Count1 <= 3'd0;
- /*****************************/
- reg [4:0]i;
- reg rCLK;
- reg rDO;
- reg isDone;
- always @ ( posedge CLK or negedge RSTn )
- if( !RSTn )
- begin
- i <= 5'd0;
- rCLK <= 1'b1;
- rDO <= 1'b0;
- isDone <= 1'b0;
- end
- else if( Start_Sig )
- case( i )
- 5'd0, 5'd2, 5'd4, 5'd6, 5'd8, 5'd10, 5'd12, 5'd14:
- if( Count1 == T0P5US ) begin rCLK <= 1'b0; rDO <= SPI_Data[ 7 - ( i >> 1) ]; i <= i + 1'b1; end
- 5'd1, 5'd3, 5'd5, 5'd7, 5'd9, 5'd11, 5'd13, 5'd15 :
- if( Count1 == T0P5US ) begin rCLK <= 1'b1; i <= i + 1'b1; end
- 5'd16:
- begin isDone <= 1'b1; i <= i + 1'b1; end
- 5'd17:
- begin isDone <= 1'b0; i <= 5'd0; end
- endcase
- /*******************************************/
- assign Done_Sig = isDone;
- assign SPI_Out = { SPI_Data[9], SPI_Data[8], rCLK, rDO };
- //为了方便观察把spi时序时钟信号拎出来看
- assign SPI_DATA=rDO;
- assign SPI_CLK=rCLK;
- /*******************************************/
- endmodule
SPI_Out 第3位:表示了CS,所以直接由 SPI_Data的第9位驱动。
SPI_Out 第2位:表示了A0,同样也是直接由 SPI_Data 的第8位驱动。
SPI_Out 第1位:表示了SCL,以寄存器rCLK来驱动。
SPI_Out 第0位:表示了SI,以寄存器rDO来驱动。
下面给出
仿真代码和结果:
设置传送数据为:SPI_Data=9'b01_10000001;//传送数据129。仿真代码:
- module TB;
- // Inputs
- reg CLK;
- reg RSTn;
- reg Start_Sig;
- reg [9:0] SPI_Data;
- // Outputs
- wire Done_Sig;
- wire [3:0] SPI_Out;
- wire SPI_CLK;
- wire SPI_DATA;
- // Instantiate the Unit Under Test (UUT)
- spi_write_module uut (
- .CLK(CLK),
- .RSTn(RSTn),
- .Start_Sig(Start_Sig),
- .SPI_Data(SPI_Data),
- .Done_Sig(Done_Sig),
- .SPI_Out(SPI_Out),
- .SPI_CLK(SPI_CLK),
- .SPI_DATA(SPI_DATA)
- );
- initial begin
- // Initialize Inputs
- CLK = 0;
- RSTn = 0;
- Start_Sig = 0;
- SPI_Data = 0;
- // Wait 100 ns for global reset to finish
- #100;
-
- // Add stimulus here
- RSTn=1;
- Start_Sig = 1;
- SPI_Data=10'b01_10000001;//不妨就设传送数据为10000001(129)
- end
-
- always #83.333 CLK=!CLK;
-
- endmodule
为了方便看出传输的时序关系,特将SPI_CLK,和SPI_DATA即SPI_DATA拎出来看,仿真结果如下:
可以看出:SPI_DATA=10000001(129),SPI_CLK,都符合SPI时序关系。那么接收端的数据应该为