FPGA--SPI通信
一,SPI说明:
1、什么是SPI?
SPI是串行外设接口(Serial Peripheral Interface)的缩写。是 Motorola 公司推出的一 种同步串行接口技术,是一种高速的,全双工,同步的通信总线。
2、SPI优点
支持全双工通信、通信简单、数据传输速率块
3、缺点
没有指定的流控制,没有应答机制确认是否接收到数据,所以跟IIC总线协议比较在数据 可靠性上有一定的缺陷。
4、特点
1):高速、同步、全双工、非差分、总线式
2):主从机通信模式
5、协议通信时序详解
1):SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多 个从设备,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,它们是
SDI(数据输入)、SDO(数据输出)、SCLK(时钟)、CS(片选)。
(1)SDO/MOSI ((master out slaver in))– 主设备数据输出,从设备数据输入;
(2)SDI/MISO – 主设备数据输入,从设备数据输出;
(3)SCLK – 时钟信号,由主设备产生;
(4)CS/SS – 从设备使能信号,由主设备控制。当有多个从设备的时候,因为每个从设 备上都有一个片选引脚接入到主设备机中,当我们的主设备和某个从设备通信时将需 要将从设备对应的片选引脚电平拉低或者是拉高。
二,协议举例说明(某通过SPI通信的芯片):
1,芯片管脚说明:
MISO:output data for spi port,output on falling edge of sclk;
MOSI:input data for spi port,latches on rising edge of sclk,MSB first;
CSB:chip select input,active low,CSB frames SPI commands and enables SPI port;
SCLK:clock for SPI;
SPI Protocol:
• SPI MOSI data is read on SCLK rising edge.
• MISO data is changed on SCLK falling edge.
• SPI communication is “MSB first”.
• SPI command is composed of a multiple of 24 SCLK cycles.
• The number of clock cycles occurring on the pin SCLK while the CSB pin is asserted low must be a multiple of 24.
• The serial output data is available on the rising edge of SCLK and transitions on the falling edge of SCLK .
2,时序图:
3,FPGA 代码举例
A:作为主机发送数据:
发送模块:
`timescale 1ns / 1ps module clk( clk50,//系统时钟 clkout,//输出时钟,sclk管脚 send_flag,//发送数据指令 send_over,//数据发送完毕提示 sdo, DATA ); input clk50; output clkout; input send_flag; output send_over; input [23:0] DATA;//要发送的数据 output sdo; //寄存器型数据对象的定义 reg clkout; reg [15:0] cnt;//分频因子 reg send_over; reg [7:0] clock_num;//clkout的数目,没24个clk后清零一次 reg [7:0] cnt1;//发送数据位寄存器 reg sdo; //50mhz时钟100分频,sclk时钟500k always @(posedge clk50) begin if((send_flag==1'b1)&&(clock_num<8'd24))begin send_over<=1'b1;//正在发送数据 if(cnt == 16'd1) begin clkout <= 1'b0;//下降沿sdo管脚数据change cnt <= cnt + 16'd1; sdo<=DATA[23-cnt1]; //MSB前 cnt1<=cnt1+8'd1; end else if(cnt == 16'd49) begin clkout <= 1'b1; cnt <= cnt + 16'd1; end else if(cnt == 16'd99) begin cnt <= 16'd0; clock_num<=clock_num+8'b1; end else cnt <= cnt + 16'd1; end else begin clock_num<=8'b0; send_over<=1'b0; clkout<=1'b0; sdo<=1'b0; cnt1<=8'd0; end end endmodule
顶层模块:
`timescale 1ns / 1ps module SPI( clk, // 输入时钟: 50Mhz clkout, sdo, sdi, cs ); input clk; output clkout; output sdo; output cs; input sdi; reg [15:0] count;//cs,clk开始发出的时序控制寄存器 reg send_flag; wire send_over; wire [23:0] DATA; reg cs; reg [23:0] DATA1; reg [7:0] cycle;//发送的数据选择位 reg [31:0] delay_count;//延时计数器 reg delay_flag;//延时结束标志位 parameter cs_num=8'd6;//发送的数据的个数 assign DATA=DATA1; always @(posedge clk) begin if(delay_flag==1'b0)begin//上电延时一段时间 delay_count<=delay_count+1'b1; if(delay_count==32'd50_000) delay_flag<=1'b1; end if((send_over==1'b0)&&(cycle<(cs_num+1))&&(delay_flag==1'b1))begin//cs,clk开始发出的时序生成代码 count<=count+16'b1; end else if(send_over==1'b1)begin count<=16'b0; end if(count==16'd100)begin cs<=1'b1; end else if(count==16'd2800)begin case(cycle) 8'd0:begin DATA1<=24'h555555; end 8'd1:begin DATA1<=24'hAAAAAA; end 8'd2:begin DATA1<=24'h5A5A5A; end 8'd3:begin DATA1<=24'hA5A5A5; end 8'd4:begin DATA1<=24'hDDDDDD; end 8'd5:begin DATA1<=24'hb81d69; end endcase cycle<=cycle+8'b1; end else if(count==16'd2900)begin cs<=1'b0; end else if(count==16'd3000)begin send_flag<=1'b1; end if(delay_flag==1'b0)begin cs<=1'b1; send_flag<=1'b0; end else if((send_over==1'b0)&&(count<16'd10)&&(delay_flag==1'b1))begin//发送完成后send_flag为清零,为下一次发送做准备 send_flag<=1'b0; end end //例化时钟模块 clk clk_SPI( .clk50(clk), .clkout(clkout), .send_flag(send_flag), .send_over(send_over), .sdo(sdo), .DATA(DATA) ); endmodule
B,接收数据,并发出:
发送模块:
`timescale 1ns / 1ps module clk( clk50,//系统时钟 clkout,//输出时钟 send_flag, send_over, sdo, DATA ); input clk50; output clkout; input send_flag; output send_over; input [23:0] DATA; output sdo; //寄存器型数据对象的定义 reg clkout; reg [15:0] cnt; reg send_over; reg [7:0] clock_num; reg [7:0] cnt1; reg sdo; //initial begin //clkout<=0; //cnt<=0; //send_over<=0; //clock_num<=0; //cnt1<=0; //sdo<=0; // //end //50mhz时钟100分频 always @(posedge clk50) begin if((send_flag==1'b1)&&(clock_num<8'd24))begin send_over<=1'b1; if(cnt == 16'd1) begin clkout <= 1'b0; cnt <= cnt + 16'd1; sdo<=DATA[23-cnt1]; cnt1<=cnt1+8'd1; end else if(cnt == 16'd49) begin clkout <= 1'b1; cnt <= cnt + 16'd1; end else if(cnt == 16'd99) begin cnt <= 16'd0; clock_num<=clock_num+8'b1; end else cnt <= cnt + 16'd1; end else begin clock_num<=8'b0; send_over<=1'b0; clkout<=1'b0; sdo<=1'b0; cnt1<=8'd0; end end endmodule
接收模块:
`timescale 1ns / 1ps module read( clkin,//系统时钟 cs, sdi, DATA, flag//一个24bit读取完毕标志位,读取下一个字节到一半的时候清零 ); input clkin; input cs; output [23:0] DATA; input sdi; output flag; reg [7:0] cnt2; reg [23:0] DATA; reg [23:0] data_receive; reg flag; //仿真使用 //initial begin //cnt2<=0; //DATA<=0; //data_num<=0; //data_receive<=0; //flag<=0; //end // // always @(posedge clkin) begin if(cs==1'b0) begin data_receive[23-cnt2]<=sdi; if(cnt2==8'd23)begin cnt2<=1'b0; DATA<=(data_receive&24'hFFFFFE)|sdi;//最后一位这里读不到,所以直接把sdi给他 flag<=1'b1; end else if(cnt2==8'd11) begin flag<=1'b0; cnt2<=cnt2+1'b1; end else begin cnt2<=cnt2+1'b1; end end end endmodule
顶层模块:
`timescale 1ns / 1ps module SPI( clk, // 输入时钟: 50Mhz clkout, sdo, sdi, cs, clk_in, sdi_in, cs_in ); input clk; output clkout; output sdo; output cs; input sdi; input clk_in; input sdi_in; input cs_in; ///////////////////////////////////////////// wire [23:0] data_in; wire read_flag; reg [15:0] count; reg send_flag; wire send_over; reg [23:0] DATA; reg cs; reg [7:0] cycle; reg delay_flag;//这里变成了开启发送数据的标志位 reg [7:0] cs_num; reg [7:0] read;//保证每次读取数据的时候,顶层里面的处理代码只执行一次,类似状态机 reg [7:0] read_num;//读取数据的个数 ///////////////////////////////////////////// //数据存储寄存器 reg [23:0] DATAIN_1; reg [23:0] DATAIN_2; reg [23:0] DATAIN_3; reg [23:0] DATAIN_4; reg [23:0] DATAIN_5; reg [23:0] DATAIN_6; //仿真使用 initial begin //count<=0; //send_flag<=0; //cs<=0; //cycle<=0; delay_flag<=0; //cs_num<=0; //DATAIN_1<=0; //DATAIN_2<=0; //DATAIN_3<=0; //DATAIN_4<=0; //DATAIN_5<=0; //DATAIN_6<=0; end // always @(posedge clk) begin if((read_flag==1'b1)&&(read<=4))begin read<=read+1'b1; if(read==1)begin read_num=read_num+1'b1;end else if(read==2)begin if(data_in==24'hB81D69)begin//B81D69 if(read_num>4'd6) begin cs_num<=4'd6;end else begin cs_num<=read_num-4'd1;end end end else if(read==3)begin if(data_in==24'hB81D69)begin delay_flag<=1'b1;read_num<=0;end end else if(read==4)begin case(read_num) 4'd1:begin DATAIN_1<=data_in;end 4'd2:begin DATAIN_2<=data_in;end 4'd3:begin DATAIN_3<=data_in;end 4'd4:begin DATAIN_4<=data_in;end 4'd5:begin DATAIN_5<=data_in;end 4'd6:begin DATAIN_6<=data_in;end endcase end end else if(read_flag==0) begin read<=0;end ////////////////////////////////// if((send_over==1'b0)&&(cycle<(cs_num+1))&&(delay_flag==1'b1))begin count<=count+16'b1;end else if(send_over==1'b1)begin count<=16'b0;end if(count==16'd100)begin cs<=1'b1;end else if(count==16'd2800)begin case(cycle) 8'd0:begin DATA<=DATAIN_1;end 8'd1:begin DATA<=DATAIN_2;end 8'd2:begin DATA<=DATAIN_3;end 8'd3:begin DATA<=DATAIN_4;end 8'd4:begin DATA<=DATAIN_5;end 8'd5:begin DATA<=DATAIN_6;end endcase cycle<=cycle+8'b1; end else if(count==16'd2900)begin cs<=1'b0;end else if(count==16'd3000)begin send_flag<=1'b1; if(cycle==cs_num) begin cycle<=8'b0;delay_flag<=1'b0;end end if((send_over==1'b0)&&(count<16'd10))begin send_flag<=1'b0;end end // //例化时钟模块 clk clk_SPI( .clk50(clk), .clkout(clkout), .send_flag(send_flag), .send_over(send_over), .sdo(sdo), .DATA(DATA) ); // // read read_SPI( .clkin(clk_in), .cs(cs_in), .sdi(sdi_in), .DATA(data_in), .flag(read_flag) ); endmodule