SPI 发送模块在这里,我们要在主机上建立,一个向从机写入数据的SPI发送模块,首先我们先从C语言上了解几个主机在SPI写操作上容易被疏忽的小细节:我们知道SPI设备在传输都有一个规则,SCL 时钟信号在“上升沿”的时候是“锁存数据”,SCL时钟信号在“下降沿”是“设置数据”。在这里我们SPI 主机(FPGA),写操作要干的工作就是在“拉高SCL时钟信号之前”设置数据(移位数据),设置数据之后,再拉高时钟信号。但是我们常常会忽略了一些具体的细节。
module spi_write_module
(
CLK,RSTn,Start_Sig,SPI_Data,Done_Sig,SPI_Out
);
input CLK;
input RSTn;
input Start_Sig;
output [9:0] SPI_Data;
output Done_Sig;
output [3:0]SPI_Out;//[3]CS,[2]A0[1]CLK [0]D0
parameter T0P5US=4'd9;//0.5us
always@(posedge CLK or negedgeRSTn)
if(!RSTn)
Count1<=4'd0;
else if(Count1==T0P5US)
Count1<4'd0;
else if(Start_Sig)
Count1<=Count1+1'b1;
else
Count1<=4'd0;
reg[4:0]i;
reg rCLK;
reg rDO;
reg isDone;
always@(posedge CLK or negedgeRSTn)
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)beginrCLK<=1'b0;rDO<=SPI_Data[7-(i>>1)];i<=1+1'b1;end
5'd1,5'd3,5'd5,5'd7,5'd9,5'd11,5'd13,5'd15:
if(Count==T0P5US)beginrCLK<=1'b1;i<=i+1'b1;end
5'd16:
beginisDone<=1'b1;i<=i+1'b1;end
5'd17:
beginisDone<=1'b0;i<=5'd0;end
endcase
assign Done_Sig=isDone;
assignSPI_Out={SPI_Data[9],SPI_Data[8],rCLK,rDO};
endmodule
SCL 的时钟频率定义为1Mhz , 也就是说周期时间是1us,半周期就是0.5us 。如果以20Mhz 来定时,那么计数的结果是10。在19行定义了0.5us 的常量,第23~33 行是0.5us的定时器。但是比较不同的是,这个定时器平时不工作,当Start_Sig拉高的时候才开始计数(第30 行)。第37~65 行是spi_write_module.v的核心功能。i 寄存器表示操作步骤,rCLK 寄存器表示SCL 然而rDO 寄存器表示SI。如同前面所述那样,SCL 时钟信号,处于空闲状态
时是出于高电平,所以rCLK 复位值是逻辑1(46 行)。
SPI_Data : 第9 位表示CS,第8 位表示A0,第7 .. 0位表示一字节数据。
SPI_Out : 第3 位表示CS,第2 位表示A0,第1位表示SCL,第0 位表示SI。
当Start_Sig 拉高的同时,定时器开始计数(30行),该模块也开始执行(50 行)。
当第一个0.5us 定时产生的时候(54行),也就是第一个时钟的前半周期,亦即下降沿,
rCLK 设置为逻辑0。根据SPI传输的规则,下降沿的时候主机设置数据,rDO 赋予SPI_Data 信号的第7 位(SPI传输是从最高位开始,最低位结束),最后i 递增以示下一个步骤。当i 等于1 的时候并且定时产生(56行),这表示第一个时钟的后半周期,亦即上升沿,rCLK 设置为逻辑1。在SPI传输的规则中上升沿的时候,从机锁存数据。然后i 递增以示下一个步骤。
上述的步骤会一直重复到第八次,直到一字节的数据发送完毕。最后会产生一个完成信号(59~63 行)。
这里有一个表达式需要说明一下:
i >> 1 : 表示i / 2。右移操作也代表了“i /j2”(j 是右移次数)。
假设8 >> 2 ,亦即8 / 22 等于2。8>> 3, 亦即8 / 23 等于1。
最后还有一个重点就是SPI_Out 的驱动(70行)。在上面我已经重复过SPI_Out 是占4位的输出。而且每一个位都有意义。
SPI_Out 第3 位:表示了CS,所以直接由SPI_Data 的第9位驱动。
SPI_Out 第2 位:表示了A0,同样也是直接由SPI_Data的第8 位驱动。
SPI_Out 第1 位:表示了SCL,以寄存器rCLK来驱动。
SPI_Out 第0 位:表示了SI,以寄存器rDO来驱动。
这样的目的是简化连线的复杂度。我们知道Verilog HDL语言的位操作是很强大。懂得善用,会对建模提到很大的帮助。
|