FPGA SPI总线实现及仿真
一、SPI简介
SPI(Serial Perripheral Interface)是由Motorola公司推出的一种高速、全双工的总线协议。SPI采用主从方式工作,主机通常为FPGA、MCU或DSP等可编程控制器,从机通常为EPROM、Flash、AD/DA、音视频处理芯片等设备。
SPI可以分为4线和3线2种形式:
4线制:SPI总线有SCLK,MOSI,MISO和CS4根数据线,可以实现数据的全双工传输。
各数据线的介绍如下:
SCLK,时钟信号,时钟频率即SPI速率,和SPI模式有关
MOSI,(Master out Slave in)主机输出,从机输入
MISO,(Master in Slave out)主机输入,从机输出
CS,从机设备选择,低电平有效。
3线制:根据不同的应用场景,可以分为以下2种类型:
1):SCLK,MOSI和CS,没有MISO数据线,适用于单工通讯,主机只发送或只接收从机的数据。
2):SCLK,SDIO和CS,SDIO作为双向端口,适用于半双工通讯。
二、SPI基本时序
SPI协议的基本时序:
CS为低电平时,表示对应的从机设备被使能,在每个SCLK周期传输1Bit数据。根据不同SPI器件的控制方法,在进行正式的数据读写操作前,一般需要先写入控制字,然后再写入寄存器地址和数据。
SPI写时序如下图所示:
SPI读时序如下图所示:
SPI的读写时序相对IIC简单,IIC的读写时序及实现方法在上篇文章已经介绍过(FPGA IIC 总线实现及仿真)。下面以93C46B EEPROM为例,实现FPGA对93C46芯片的SPI读写。
三、93C46B
93C46是一款基于SPI的EEPROM芯片,存储大小1kbit,即总共64*16bit,寻址需要6位地址线(A5-A0)。
93C46B管脚如下图所示,从图中可以看出其SPI总线为4根。
93C46B 操作指令如下图所示,其中ERASE为擦除数据指令;ERAL为擦除全部数据指令;EWDS为擦除/写保护指令;EWEN为擦除/写使能;READ为读数据指令;WRITE为写数据指令;WRAL为写所有数据指令。93C46B上电时为EWDS状态,在擦除、写数据指令操作前,必须先发送EWEN指令,之后才能对芯片进行操作。
93C46B读数据时序和写数据时序如下图所示。
三、FPGA实现
结合93C46B SPI总线的特点,完成SPI总线编码及仿真
输入输出接口
module SPI_Master(
input clk, //工作时钟 40M
input rst_p, //复位 1:复位 0:正常工作
output CS, //SPI CS选通信号 1:选通 1:未选通
output SCLK, //SPI 时钟信号
output MOSI, //SPI MOSI信号
input MISO //SPI MISO信号
);
parameter Baud_clk = 10 ; //SPI _clk=clk/clk_div 400k
parameter time_1us = 40 ; //1us时间
reg [24:0] SPI_data = 0;
reg [2:0] Opcode = 9'h00;//SPI输出数据
reg [5:0] Data_Addr = 6'h0F; //SPI读写地址
reg [15:0] data_out = 16'h5AA5;//SPI输出数据
reg [15:0] data_in = 0; //SPI接收的数据
reg [15:0] Bit_cnt = 0;
reg [15:0] Wait_cnt = 0; //等待时间计数器
reg [15:0] clk_cnt = 0;
reg [3:0] SPI_state = 0;
reg [3:0] SPI_next_state = 0;
localparam [3:0] Idle_state = 0,
EWEN_state = 1,//写、擦除使能状态
WrData_state = 2,//写数据
RdData_state = 3,//读数据
send_state = 4,
Wait_state = 5,
End_state = 6;//结束
reg SPI_CS = 0;
reg SPI_SCLK = 0;
reg SPI_SCLK_r = 0;
reg SPI_MOSI = 0;
assign CS = SPI_CS;
assign SCLK = SPI_SCLK;
assign MOSI = SPI_MOSI;
SPI状态机
SPI 读写时序///
always @ (posedge clk) begin
case(SPI_state)
Idle_state:begin
SPI_state<= Wait_state;
SPI_next_state<=EWEN_state;
end
EWEN_state:begin
SPI_state<= send_state;
SPI_next_state<=WrData_state;
end
WrData_state:begin
SPI_state<= send_state;
SPI_next_state<=RdData_state;
end
RdData_state:begin
SPI_state<= send_state;
SPI_next_state<=Idle_state;
end
send_state:begin
if(SPI_SCLK_r==1 && SPI_SCLK==0) //下降沿计数
Bit_cnt<= Bit_cnt+1;
if(Bit_cnt ==25 ) //计数25bit
SPI_state<= Wait_state;
end
Wait_state:begin
Bit_cnt<=0;
if(Wait_cnt==time_1us*2) //每次操作后 等待2us
begin
Wait_cnt<=0;
SPI_state<= SPI_next_state;
end
else
Wait_cnt<= Wait_cnt+1;
end
End_state:begin
SPI_state<=End_state;
end
endcase
end
SPI输入输出时序
always @ (posedge clk) begin
if(SPI_state== send_state) begin
if(clk_cnt==Baud_clk-1)
clk_cnt<=0;
else
clk_cnt<=clk_cnt+1;
end
else
clk_cnt<=0;
end
always @ (posedge clk) begin
if(SPI_state== send_state )
SPI_CS<=1;
else
SPI_CS<=0;
end
always @ (posedge clk) begin
SPI_SCLK_r<=SPI_SCLK;
if(clk_cnt<Baud_clk/2)
SPI_SCLK<=0;
else
SPI_SCLK<=1;
end
always @ (posedge clk) begin
if(SPI_state==send_state)
SPI_MOSI<=SPI_data[24];
else
SPI_MOSI<=0;
end
//
always @ (posedge clk) begin
case(SPI_state)
EWEN_state:SPI_data<={3'h4,6'h30,16'h0000};
WrData_state:SPI_data<={3'h5,6'h0f,data_out};
RdData_state:SPI_data<={3'h6,6'h0f,16'h0000};
send_state:begin
if(clk_cnt==Baud_clk-1)
SPI_data[24:1]<=SPI_data[23:0];
end
default:SPI_data<=25'h0;
endcase
end
always @ (posedge clk) begin
if(SPI_state== send_state )
begin
if(Bit_cnt>=9 && SPI_SCLK_r==0 && SPI_SCLK==1)//上升沿收数
begin
data_in[0]<=MISO;
data_in[15:1]<=data_in[14:0];
end
end
end
endmodule
四、SPI仿真
仿真文件顶层模块,在顶层模块例化了一个SPI 从模式模块,用来模拟93C46B的数据读写
module Test_spi(
);
reg clk;
reg rst_p;
wire CS;
wire SCLK;
wire MOSI;
wire MISO;
SPI_Master SPI_Master(
.clk(clk),
.rst_p(rst_p),
.CS(CS),
.SCLK(SCLK),
.MOSI(MOSI),
.MISO(MISO)
);
initial begin
clk = 0;
rst_p = 1;
#100
rst_p = 0;
end
always #10 clk = ~clk ;
SPI_Slave SPI_Slave(
.clk(clk),
.rst_p(rst_p),
.CS(CS),
.SCLK(SCLK),
.MOSI(MOSI),
.MISO(MISO)
);
endmodule
SPI Slave模块
module SPI_Slave(
input clk, //工作时钟 40M
input rst_p, //复位 1:复位 0:正常工作
input CS, //SPI CS选通信号 1:选通 1:未选通
input SCLK, //SPI 时钟信号
input MOSI, //SPI MOSI信号
output MISO //SPI MISO信号
);
parameter Baud_clk = 10 ; //SPI _clk=clk/clk_div 400k
parameter time_1us = 40 ; //1us时间
//reg [24:0] SPI_data = 25'h00;
reg [8:0] Opcode = 9'h00;//SPI输出数据
reg [5:0] Data_Addr = 6'h0F; //SPI读写地址
reg [15:0] data_out = 16'h5AA5;//SPI输出数据
reg [15:0] data_in = 0; //SPI接收的数据
reg [15:0] Bit_cnt = 0;
reg [15:0] Data_cnt = 0;
reg [15:0] data_mem [0:63];
integer i;
initial begin
for(i=0;i<=63;i=i+1)begin
data_mem[i]=0;
end
end
reg [3:0] SPI_state = 0;
// reg [3:0] SPI_next_state = 0;
localparam [3:0] Idle_state = 0,
RecvOpc_state = 1,//接收命令
WrData_state = 2,//写数据
WrDataAll_state= 3,//写所有地址数据
RdData_state = 4,//读数据
Wait_state = 5,
End_state = 6;//结束
reg SPI_CS = 0;
reg SPI_CS_r = 0;
reg SPI_SCLK = 0;
reg SPI_SCLK_r = 0;
reg SPI_MISO = 0;
assign MISO = SPI_MISO;
SPI 读写时序///
always @ (posedge clk) begin
SPI_CS<=CS;
SPI_CS_r<=SPI_CS;
SPI_SCLK<=SCLK;
SPI_SCLK_r<=SPI_SCLK;
case(SPI_state)
Idle_state:begin
Bit_cnt<=0;
if(SPI_CS_r==0 && SPI_CS==1)
SPI_state<= RecvOpc_state;
end
RecvOpc_state:begin
if(SPI_CS==1 && SPI_SCLK_r==0 && SPI_SCLK==1)//上升沿计数
Bit_cnt<= Bit_cnt+1;
if( Bit_cnt ==9 && SPI_SCLK_r==1 && SPI_SCLK==0) //9个时钟周期
begin
Bit_cnt<=0;
case(Opcode[8:6])
4:begin
if(Data_Addr[5:4]==1)
SPI_state<= WrDataAll_state; //写所有地址数据
else
SPI_state<=Idle_state; //其他指令 暂时不响应
end
5:SPI_state<= WrData_state;//写数据指令
6:SPI_state<= RdData_state;//读数据指令
7:SPI_state<= Idle_state;//擦除指令 暂时不响应
default:SPI_state<= Idle_state;
endcase
end
end
WrData_state:begin
if( SPI_CS==1 && SPI_SCLK_r==0 && SPI_SCLK==1) //上升沿计数
Bit_cnt<= Bit_cnt+1;
if(Bit_cnt ==16 && SPI_SCLK_r==1 && SPI_SCLK==0) //16bit 时钟周期
begin
SPI_state<= Idle_state;
end
end
RdData_state:begin
if(SPI_CS==1 && SPI_SCLK_r==0 && SPI_SCLK==1)
Bit_cnt<= Bit_cnt+1;
if(Bit_cnt ==16 && SPI_SCLK_r==1 && SPI_SCLK==0) //写25bit 时钟周期
begin
SPI_state<= Idle_state;
end
end
WrDataAll_state:begin
if(SPI_CS==1 && SPI_SCLK_r==0 && SPI_SCLK==1)
Bit_cnt<= Bit_cnt+1;
if(Bit_cnt ==16 && SPI_SCLK_r==1 && SPI_SCLK==0) //读25bit 时钟周期
begin
if(Data_cnt==63)
begin
Data_cnt<=0;
SPI_state<= Idle_state;
end
else
Data_cnt<=Data_cnt+1;
end
end
endcase
end
always @ (posedge clk) begin
if(SPI_state== RecvOpc_state )
begin
if(SPI_SCLK_r==0 && SPI_SCLK==1) //上升沿接数
begin
Opcode[0]<=MOSI;
Opcode[8:1]<= Opcode[7:0];
end
end
end
always @ (posedge clk) begin
if(SPI_state== WrData_state || SPI_state== WrDataAll_state)
begin
if(SPI_SCLK_r==0 && SPI_SCLK==1)
begin
data_in[0]<=MOSI;
data_in[15:1]<=data_in[14:0];
end
end
end
always @ (posedge clk) begin
if(SPI_state== WrData_state || SPI_state== WrDataAll_state)
begin
data_mem[Data_Addr]<=data_in;
end
end
always @ (posedge clk) begin
if(SPI_state== RecvOpc_state )
begin
if(Opcode[8:4]==5'h11)//写全部地址时,Data_Addr从0开始
Data_Addr<=0;
else
Data_Addr<=Opcode[5:0];
end
else if(SPI_state== WrDataAll_state )
begin
if(Bit_cnt==16 && SPI_SCLK_r==1 && SPI_SCLK==0) //下降沿 地址+1
Data_Addr<=Data_Addr+1;
end
end
always @ (posedge clk) begin
if(SPI_state== RecvOpc_state)
data_out<=data_mem[Data_Addr];
else if(SPI_state== RdData_state )
begin
SPI_MISO<=data_out[15];
if(SPI_SCLK_r==1 && SPI_SCLK==0)//下降沿数据改变
data_out[15:1]<=data_out[14:0];
end
else
SPI_MISO<=0;
end
endmodule
五、SPI仿真结果
仿真结果如下图所示,从仿真结果看出FPGA通过SPI总线向SPI Slave模块的0x0F写入数据0x5AA5.然后又从相同的地址读出数据0x5AA5。