FPGA学习之SPI

SPI协议

SPI是一种高速,全双工,同步的通信总线,在芯片上主要占用四根线(CS、MOSI、MISO、SCK),极大的节约了芯片的引脚。
CS:片选信号,空闲状态为高电平
MOSI(master output slave input):主机输出数据,从机输入数据
MISO(master input slave output):主机输入数据,从机输出数据
SCK:用于输出时钟(在从模式下,SPI相对于该时钟传输数据)或接收时钟。
基本结构如下图:
FPGA学习之SPI_第1张图片
SPI总线主要有四种工作状态,SPI0,SPI1,SPI2,SPI3,其中常用的为了SPI0和SPI3。SPI的工作状态主要由CPOL(时钟极性)和CPHA(时钟相位)决定。
CPOL=0时,SCK空闲时为低电平,CPOL=1时,SCK空闲时为高电平;
CPHA=0时,SCK的第一个跳变沿为采样数据即读取发送的数据,第二个跳变沿为输出数据,CPHA=1时,SCK的第一个跳变沿为输出数据,第二个跳变沿为采样数据。

SPI举例

当CPOL=1,CPHA=1时,即SCK空闲为高电平,第二个跳变沿为采集数据;
当主机需要发送的spi_data=8’b10101010时,在第一个跳变沿会发送mosi=1,从最高位开始输出,第二个跳变沿会读取0,第三个跳变沿输出mosi=0,以此类推;
在这个通信过程中需要三个组成部分:
计数器:计数16次,每次代表一个跳变沿;
移位器:将spi_data不断向左移,输出最高位到mosi;
分频器:生成sck波形,当空闲时拉高,启动时拉低,然后有规律的波动;
在top顶层文件中需要对状态切换进行定义,在三个子模块中,对于计数器,移位器和分频器进行编写;
SPI的输入主要有clk,rst,spi_data,spi_start;
SPI的输出主要有sck,spi_done,mosi,cs;

RTL代码

module top(			
					input clk,
					input rst,
					input [7:0] spi_data,
					input spi_start,
					output sck,
					output mosi,
					output reg cs,
					output reg spi_done
		 );
	reg a,en_a;//计数器清零和启动信号
	reg b,en_b;//分频器,拉高和使能信号
	reg c,en_c;//移位器,读取spi_data和移位标志信号
	wire [4:0] i;//跳变沿计数;
	reg [5:0] state='b0;//当前状态
	reg [5:0] next_state='b0;//下一个状态
	parameter s0='b000001;
	parameter s1='b000010;
	parameter s2='b000100;
	parameter s3='b001000;
	parameter s4='b010000;
	parameter s5='b100000;
	always@(posedge clk or negedge rst) 
		begin
			if(!rst)
				state<=s0;
			else
				state<=next_state;
		end
	always@(*)
		begin
		case(state) 
			s0: begin
				if(spi_start)
					next_state<=s1;
				else
					next_state<=s0;
				end
			s1:begin
				if(i=='b1)//开始计数,第一个跳变沿发送数据
					next_state<=s2;
				else
					next_state<=s1;
				end
			s2:begin
				if(i[0]=='b0)//偶数跳变沿为采样数据
					next_state<=s3;
				else
					next_state<=s2;
				end
			s3:begin
				if(i==5'd15)
					next_state<=s4;
				else begin
						if(i[0]=='b1)//奇数跳变沿为输出数据
							next_state<=s2;
						else
							next_state<=s3;
						end
				end
			s4:begin
				if(i=='d16)
					next_state<=s5;
				else
					next_state<=s4;
				end
			s5:begin
				if(i=='b0)
					next_state<=s0;
				else
					next_state<=s5;
				end
			default:next_state<=s0;
		endcase
	end
	always@(*)begin
		case(state)
			s0:begin
				a<='b1;//计数器清零
				en_a<='b0;
				b<='b1;//CPOL=1,空闲拉高平
				en_b<='b0;
				c<='b0;//未载入输入数据
				en_c<='b0;
				spi_done<='b0;
				cs<='b1;//空闲 片选拉高
				end
			s1:begin
				a<='b0;
				en_a<='b1;//开始计数
				b<='b0;
				en_b<='b1;//产生SCK
				c<='b1;//载入数据
				en_c<='b0;
				spi_done<='b0;
				cs<='b0;
				end
			s2:begin
				a<='b0;
				en_a<='b1;
				b<='b0;
				en_b<='b1;
				c<='b0;
				en_c<='b1;//开始移位 输出mosi
				spi_done<='b0;
				cs<='b0;
				end
			s3:begin
				a<='b0;
				en_a<='b1;
				b<='b0;
				en_b<='b1;
				c<='b0;
				en_c<='b0;//停止移位  保持数据不变  没有采样阶段
				spi_done<='b0;
				cs<='b0;
				end
			s4:begin
				a<='b0;
				en_a<='b1;
				b<='b0;
				en_b<='b0;
				c<='b0;
				en_c<='b0;
				spi_done<='b0;
				cs<='b0;
				end
			s5:begin
				a<='b0;
				en_a<='b0;
				b<='b0;
				en_b<='b0;
				c<='b0;
				en_c<='b0;
				spi_done<='b1;
				cs<='b1;
				end
			default:begin
				a<='b1;
				en_a<='b0;
				b<='b1;
				en_b<='b0;
				c<='b0;
				en_c<='b0;
				spi_done<='b0;
				cs<='b1;
				end
		endcase
	end
	count u1(
		.clk(clk),
		.i(i),
		.a(a),
		.en_a(en_a)
		);
	sck u2(
		.clk(clk),
		.b(b),
		.en_b(en_b),
		.sck(sck)
		);
	shifter u3(
		.clk(clk),
		.c(c),
		.en_c(en_c),
		.spi_data(spi_data),
		.mosi(mosi)
		);
endmodule
module count(
	input clk,
	input a,
	input en_a,
	output reg [4:0] i
	);
	always@(posedge clk)begin
		if(a)
			i<='b0;
		else 
			if(en_a) begin
				if(i=='d16)
					i<='d0;
				else
					i<=i+'d1;
			end
			else
				i<=i;
	end
endmodule
module sck(
	input clk,
	input b,
	input en_b,
	output reg sck
	);
	always@(posedge clk)begin
		if(b)
			sck<='b1;
		else	begin
			if(en_b)
				sck<=~sck;
			else
				sck<='b1;
		end
	end
endmodule
module shifter(
	input clk,
	input [7:0] spi_data,
	input c,
	input en_c,
	output mosi
	);
	reg [7:0] data;
	always@(posedge clk) begin
		if(c)
			data<=spi_data;
		else begin
			if(en_c)
				data<=	{data[6:0],1'b0};
			else
				data<=data;
		end
	end
	assign mosi=data[7];
endmodule

Testbench

module test( );
    reg clk;
    reg rst;
    reg spi_start;
    reg [7:0] spi_data;
    wire spi_done;
    wire sck;
    wire mosi;
    wire cs;
    initial begin
    	 rst<=1'b0;
    	 clk<=1'b0;
    	 spi_start<=1'b0;
    	 spi_data<=8'b0;
    	 #100
    	 rst<=1'b1;
    	 #100
    	 spi_start<=1'b1;
    	 #100
    	 spi_data<=8'b10101010;

  	end
    always #5 clk=~clk;
    top uu(
   			 .clk(clk),
   		     .rst(rst),
             .spi_data(spi_data),
             .spi_start(spi_start),
             .sck(sck),
             .cs(cs),
             .spi_done(spi_done),
             .mosi(mosi)
    );
endmodule

仿真图

FPGA学习之SPI_第2张图片
可以看到在SCK从高跳变到低时,即第一个跳变沿时mosi由高到低输出spi_data;

参考资料

CSDN-基于FPGA的SPI协议实现
spi协议(英文版)

你可能感兴趣的:(fpga)