SPI 发送模块
在这里,我们要在主机上建立,一个向从机写入数据的SPI
发送模块,首先我们先从C
语言上了解几个主机在SPI
写操作上容易被疏忽的小细节:我们知道SPI
设备在传输都有一个规则,SCL 时钟信号在“上升沿”的时候是“锁存数据”,SCL
时钟信号在“下降沿”是“设置数据”。在这里我们SPI 主机(FPGA),写操作要干的工作就是在“拉高SCL
时钟信号之前”设置数据(移位数据),设置数据之后,再拉高时钟信号。但是我们常常会忽略了一些具体的细节。
上面有两个主机的SPI_Send
函数(写函数),左边的写法是最常用,但是也是最容易忽
略小细节。相比右边的写法比较谨慎,在最低的程度上符合一写小细节。
再来,我们继续引用ST7565P
芯片的写入时序图,分析并且区分上边的两个写法。关于
SCL
信号在空闲的时候总是处于高电平。当主机开始向从机写入数据,主机会先拉低CS 信号,再拉低SCL
信号,然后“设置”数据,亦即主机(FPGA)更新SI 的数据(主机数据移位操作),最后再拉高SCL
信号。同一时间,从机会因为SCL 的上升沿变化,从机(液晶资源)“锁存”(从机读取数据操作)SI
上的数据。很明显左边的写法没有符合这些细节,然而右边的写法却符合这些细节。无论是左边的写法还是右边的写法,都忽略了一个致命的细节,两种写法都无法确定SPI
时钟信号的时钟频率。
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 negedge
RSTn)
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 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<=1+1'b1;end
5'd1,5'd3,5'd5,5'd7,5'd9,5'd11,5'd13,5'd15:
if(Count==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};
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
语言的位操作是很强大。懂得
善用,会对建模提到很大的帮助。