SPI,Serial Peripheral Interface,串行外设接口,高速的、全双工、同步通信总线。SPI以主从方式工作,一般需要至少4根线(单向传输时可用3根):
(1)MISO– Master Input Slave Output,主设备数据输入,从设备数据输出;
(2)MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入;
(3)SCLK – Serial Clock,时钟信号,由主设备产生;
(4)CS – Chip Select,从设备使能信号,由主设备控制。
SPI共有4种工作模式,常用的是模式0和模式3,具体如下:
以下为Verilog实现的SPI主机程序,系统时钟为24MHz,分频成1MHz作为SCLK时钟。收发8位数据时,至少需要8个时钟,考虑到CS使能(作为同步信号)以及双向通信的需要,设置10个节拍。起始CS=0使能主机发送,此后1-8节拍按照从高位到低位时钟上升沿依次发送数据,同时在时钟下降沿1-8节拍,读取从机数据。
//非常简单的SPI通信协议,上升沿发送,下降沿接收。
//双工工作。CS=0使能发送。8位数据。无校验。
//实现功能:主机拨码开关控制从机LED,从机拨码开关控制主机LED
module SPI_Master (
input reset, //reset
input clk_24M, //时钟24M
output MOSI, //主机发送
input MISO, //主机接收
input [7:0] Data_SW , //发送的8位数据,开关实现
output CS, //CS
output SCLK, //SPI时钟
output [7:0] Data_LED //接收的8位数据,发光管实现
);
reg [7:0] rData_LED;
reg [3:0] N;//分频计数器
reg rSCLK;
reg rMOSI;
reg rCS;
reg [3:0] cnt; //发收状态计数器
assign SCLK=rSCLK;
assign MOSI=rMOSI;
assign CS=rCS;
assign Data_LED=rData_LED;
always@(posedge clk_24M or negedge reset) //分频到1MHz
if(!reset)
begin rSCLK<=1; N<=0; end
else if(N>=11)
begin N<=0; rSCLK<=~rSCLK; end
else
N<=N+1;
always @(posedge SCLK or negedge reset) //主机发送
if (!reset)
begin rMOSI<=1; rCS<=1;cnt<=0; end
else
case(cnt)
0:begin rCS<=0;cnt<=1; end //使能主机发送
1:begin rMOSI<=Data_SW[7];cnt<=2; end
2:begin rMOSI<=Data_SW[6];cnt<=3; end
3:begin rMOSI<=Data_SW[5];cnt<=4; end
4:begin rMOSI<=Data_SW[4];cnt<=5; end
5:begin rMOSI<=Data_SW[3];cnt<=6; end
6:begin rMOSI<=Data_SW[2];cnt<=7; end
7:begin rMOSI<=Data_SW[1];cnt<=8; end
8:begin rMOSI<=Data_SW[0];cnt<=9; end
default:begin rCS<=1;cnt<=0; end //关闭发送
endcase
always @(negedge rSCLK ) //主机接收
case(cnt)
1:rData_LED[7]<=MISO;
2:rData_LED[6]<=MISO;
3:rData_LED[5]<=MISO;
4:rData_LED[4]<=MISO;
5:rData_LED[3]<=MISO;
6:rData_LED[2]<=MISO;
7:rData_LED[1]<=MISO;
8:rData_LED[0]<=MISO;
default: ;
endcase
endmodule
从机SCLK和CS均来自主机,0-7节拍上升沿时钟向主机发送数据,当CS=0下降沿1-8节拍读取主机数据。
//非常简单的SPI通信协议,从机程序,上升沿发送,下降沿接收。
//双工工作。8位数据,无校验。
//主机拨码开关控制从机LED,从机拨码开关控制主机LED
//从机时钟来源于主机
module SPI_Slave (
input MOSI, //从机接收
output MISO, //从机发送
input [7:0] Data_SW , //发送的8位数据,开关实现
input CS, //CS
input SCLK, //SPI时钟
output [7:0] Data_LED //接收的8位数据,发光管实现
);
reg [3:0] cnt; //发收状态计数器
reg rMISO;
reg[7:0] rData_LED;
assign Data_LED=rData_LED;
assign MISO=rMISO;
always @(posedge SCLK ) //从机发送
case(cnt)
0: rMISO<=Data_SW[7]; //由高到低发
1: rMISO<=Data_SW[6];
2: rMISO<=Data_SW[5];
3: rMISO<=Data_SW[4];
4: rMISO<=Data_SW[3];
5: rMISO<=Data_SW[2];
6: rMISO<=Data_SW[1];
7: rMISO<=Data_SW[0];
default:rMISO<=1;
endcase
always @(negedge SCLK ) //从机接收
if (CS) cnt<=0;
else
case(cnt)
0:cnt<=1;
1:begin rData_LED[7]<=MOSI; cnt<=2; end
2:begin rData_LED[6]<=MOSI; cnt<=3; end
3:begin rData_LED[5]<=MOSI; cnt<=4; end
4:begin rData_LED[4]<=MOSI; cnt<=5; end
5:begin rData_LED[3]<=MOSI; cnt<=6; end
6:begin rData_LED[2]<=MOSI; cnt<=7; end
7:begin rData_LED[1]<=MOSI; cnt<=8; end
8:begin rData_LED[0]<=MOSI; cnt<=9; end
default:cnt<=0;
endcase
endmodule
作为主从机的两块开发板通信测试时,注意要共地,即GND需用线连接。此代码在安陆ELF1A650、高云GW1NSR开发板上获得通过。
切记,以上为非标准的SPI协议,若与其他设备通信时,有可能失败。有时候不需要双向传输,此时仅需MOSI、CS、SCLK三根线即可,为方便计,从机端可进一步译码,用数码管显示。
//非常简单的SPI通信协议,上升沿发送,无接收。
//CS=0使能发送。8位数据。无校验。
//实现功能:主机拨码开关发送数据。
module SPI_Master (
input reset, //reset
input clk_24M, //时钟24M
output MOSI, //主机发送
input [7:0] Data_SW , //发送的8位数据,开关实现
output CS, //CS
output SCLK //SPI时钟
);
reg [3:0] N;//分频计数器
reg rSCLK;
reg rMOSI;
reg rCS;
reg [3:0] cnt; //状态计数器
assign SCLK=rSCLK;
assign MOSI=rMOSI;
assign CS=rCS;
always@(posedge clk_24M or negedge reset) //分频到1MHz
if(!reset)
begin rSCLK<=1; N<=0; end
else if(N>=11)
begin N<=0; rSCLK<=~rSCLK; end
else
N<=N+1;
always @(posedge SCLK or negedge reset) //主机发送
if (!reset)
begin rMOSI<=1; rCS<=1;cnt<=0; end
else
case(cnt)
0:begin rCS<=0;cnt<=1; end //使能主机发送
1:begin rMOSI<=Data_SW[7];cnt<=2; end
2:begin rMOSI<=Data_SW[6];cnt<=3; end
3:begin rMOSI<=Data_SW[5];cnt<=4; end
4:begin rMOSI<=Data_SW[4];cnt<=5; end
5:begin rMOSI<=Data_SW[3];cnt<=6; end
6:begin rMOSI<=Data_SW[2];cnt<=7; end
7:begin rMOSI<=Data_SW[1];cnt<=8; end
8:begin rMOSI<=Data_SW[0];cnt<=9; end
default:begin rCS<=1;cnt<=0; end //关闭发送
endcase
endmodule
//非常简单的SPI通信协议,从机程序,下降沿接收。
//8位数据,无校验。
//主机拨码开关发送数据,从机接收并译码数码管显示。
//从机时钟来源于主机
module SPI_Slave (
input MOSI, //从机接收
input CS, //CS
input SCLK, //SPI时钟
output [1:0] DG, //两位数码管位码
output [7:0] SEG //数码管段码
);
reg [3:0] cnt; //状态计数器
reg[7:0] rData;
reg[3:0] count; //要显示的数,四位2进制
reg[1:0] rDG;
reg[7:0] rSEG;
assign DG=rDG;
assign SEG=rSEG;
always @(negedge SCLK ) //从机接收
if (CS) cnt<=0;
else
case(cnt)
0:cnt<=1;
1:begin rData[7]<=MOSI; cnt<=2; end
2:begin rData[6]<=MOSI; cnt<=3; end
3:begin rData[5]<=MOSI; cnt<=4; end
4:begin rData[4]<=MOSI; cnt<=5; end
5:begin rData[3]<=MOSI; cnt<=6; end
6:begin rData[2]<=MOSI; cnt<=7; end
7:begin rData[1]<=MOSI; cnt<=8; end
8:begin rData[0]<=MOSI; cnt<=9; end
default:cnt<=0;
endcase
always @ (posedge SCLK)
case(DG)
2'b10: begin rDG<=2'b01;count=rData[7:4];end //高四位
2'b01: begin rDG<=2'b10;count=rData[3:0];end //低4位
default: begin rDG<=2'b10;end
endcase
always@(count) //数码管译码输出
case(count)
0:rSEG=8'b11000000;
1:rSEG=8'b11111001;
2:rSEG=8'b10100100;
3:rSEG=8'b10110000;
4:rSEG=8'b10011001;
5:rSEG=8'b10010010;
6:rSEG=8'b10000010;
7:rSEG=8'b11111000;
8:rSEG=8'b10000000;
9:rSEG=8'b10010000;
10:rSEG=8'b10001000;
11:rSEG=8'b10000011;
12:rSEG=8'b11000110;
13:rSEG=8'b10100001;
14:rSEG=8'b10000110;
default:rSEG=8'b10001110;
endcase
endmodule
附:ESP8266(Node MCU)有SPI的Demo,包括Master和Slave,如下图。不过需要注意的是,其引脚应接HSCLK(GPIO14,D5)、HMISO(GPIO12,D6)、HMOSI(GPIO13,D7)、HCS(GPIO15,D8)。