欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

通信协议篇——SPI通信

程序员文章站 2022-07-09 12:53:47
...

通信协议篇——SPI通信

1.简介

SPI(Serial Peripheral Interface)是一种高速、同步、全双工串行通信总线,采用主从机通信模式,主要应用在EEPROM,FLASH,实时时钟,AD转换器等。

2.原理

通信方式

SPI通信属于串行通信,利用芯片选择/使能线CS、串行时钟线SCLK、数据输入线DATAIN、数据输出线DATAOUT四线实现同步全双工通信。

通信模式

SIP通信有四种模式,由时钟极性和时钟相位设置不同模式;

CPOL:时钟极性选择,为0时SPI总线空闲为低电平,为1时SPI总线空闲为高电平;

CPHA:时钟相位选择,为0时在SCLK第一个跳变沿采样,为1时在SCLK第二个跳变沿采样;

Mode0: CPOL=0,CPHA=0;SPI总线空闲状态为低电平,SCLK第一个跳变沿是上升沿,所以在时钟上升沿对数据采样,在时钟下降沿发送数据;

Mode1: CPOL=0,CPHA=1;SPI总线空闲状态为低电平,SCLK第一个跳变沿是上升沿,所以在时钟上升沿发送数据,在时钟下降沿对数据采样;

Mode2: CPOL=1,CPHA=0;SPI总线空闲状态为高电平,SCLK第一个跳变沿是下降沿,所以在时钟上升沿发送数据,在时钟下降沿对数据采样;

Mode3: CPOL=1,CPHA=1;SPI总线空闲状态为高电平,SCLK第一个跳变沿是下降沿,所以在时钟上升沿对数据采样,在时钟下降沿发送数据;
通信协议篇——SPI通信

数据格式

SPI通信并没有固定的数据格式,可以根据不同的应用进行灵活地运用。数据内容大致可以分为三类——指令、地址、数据。以FLASH中的SPI为例,指令长度为8位,地址长度为24位,数据长度以字节为单位,数据格式主要有指令、指令+地址、指令+数据、指令+地址+数据。

操作时序

以FLASH的操作控制为例,展示几种SPI读写时序(采用Mode0或Mode3):

  1. 写使能:发送命令字0X06

通信协议篇——SPI通信

  1. Sector擦除:发送命令字0X20,再发送24位地址

通信协议篇——SPI通信

  1. 数据读:发送命令字0X03,再发送24位地址,然后接收数据

通信协议篇——SPI通信

标准接口

name description direction length
clk 系统时钟 input 1
rst 复位信号 input 1
spi_cs 从机选择信号 output 1
spi_clk SPI时钟信号 output 1
spi_mosi SPI数据输出 output 1
spi_miso SPI数据输入 input 1

3.程序实现

RTL视图

通信协议篇——SPI通信

spi模块

`timescale 1ns/1ps
////////////////////////////////////////////
//Module Name	:	spi
//Description	:	spi communication module
//Editor		:	Yongxiang
//Time			:	2020-02-03
////////////////////////////////////////////
module spi
	(
		output	wire	flash_clk,
		output 	reg		flash_cs,
		output	reg		flash_datain,
		input	wire	flash_dataout,
		
      	input	wire	clock25M,
		input	wire	flash_rstn,
		input	wire[3:0]	cmd_type,
		output	reg		Done_Sig,
		input	wire[7:0]	flash_cmd,
		input	wire[23:0]	flash_addr,
		output 	reg[7:0]	mydata_o,
		output	wire	myvalid_o
	);

assign myvalid_o = myvalid;
assign flash_clk = spi_clk_en ? clock25M : 1'b0;

reg myvalid;
reg[7:0] mydata;
reg spi_clk_en = 1'b0;
reg data_come;

parameter idle = 3'b000;
parameter cmd_send = 3'b001;
parameter address_send = 3'b010;
parameter read_wait = 3'b011;
parameter write_data = 3'b101;
parameter finish_done = 3'b110;

reg[2:0] spi_state;
reg[7:0] cmd_reg;
reg[23:0] address_reg;
reg[7:0] cnta;
reg[8:0] write_cnt;
reg[7:0] cntb;
reg[8:0] read_cnt;
reg[8:0] read_num;
reg read_finish;

//发送读flash命令
always @(negedge clock25M)
begin
	if(!flash_rstn)begin
		flash_cs <= 1'b1;		
		spi_state <= idle;
		cmd_reg <= 8'd0;
		address_reg <= 24'd0;
	   spi_clk_en <= 1'b0;		//SPI clock输出不使能
		cnta <= 8'd0;
		write_cnt <= 9'd0;
		read_num <= 9'd0;	
		Done_Sig <= 1'b0;
	end
	else begin
		case(spi_state)
			idle:begin	//idle 状态		  
				spi_clk_en <= 1'b0;
				flash_cs <= 1'b1;
				flash_datain <= 1'b1;	
			   cmd_reg <= flash_cmd;
            address_reg <= flash_addr;
		      Done_Sig <= 1'b0;				
				if(cmd_type[3] == 1'b1)begin	//bit3为命令请求,高表示操作命令请求
					spi_state <= cmd_send;
					cnta <= 8'd7;		
					write_cnt <= 9'd0;
					read_num <= 9'd0;					
				end
			end
			
			cmd_send:begin	//发送命令状态	
			   spi_clk_en <= 1'b1;	//flash的SPI clock输出
				flash_cs <= 1'b0;	//cs拉低
			   if(cnta > 8'd0)begin	//如果cmd_reg还没有发送完
					flash_datain <= cmd_reg[cnta];	//发送bit7~bit1位
               cnta <= cnta - 8'd1;
				end
				else begin	//发送bit0
					flash_datain <= cmd_reg[0];
					if((cmd_type[2:0] == 3'b001) | (cmd_type[2:0] == 3'b100))begin	//如果是Write Enable/disable instruction
						spi_state <= finish_done;
					end						 
					else if(cmd_type[2:0] == 3'b011)begin	//如果是read register1
						spi_state <= read_wait;
						cnta <= 8'd7;
						read_num <= 9'd1;	//接收一个数据
					end	 
					else begin	//如果是sector erase, page program, read data,read device ID      
						spi_state <= address_send;
						cnta <= 8'd23;
					end
				end
			end
			
			address_send:begin	//发送flash address	
			   if(cnta > 8'd0)begin	//如果cmd_reg还没有发送完
					flash_datain <= address_reg[cnta];	//发送bit23~bit1位
               cnta <= cnta - 8'd1;						
				end				
				else begin	//发送bit0
					flash_datain <= address_reg[0];   
               if(cmd_type[2:0] == 3'b010)begin	//如果是	sector erase
 						spi_state <= finish_done;	
               end
               else if(cmd_type[2:0] == 3'b101)begin	//如果是page program				
				      spi_state <= write_data;
						cnta <= 8'd7;                       
					end
					else if(cmd_type[2:0] == 3'b000)begin	//如果是读Device ID
					   spi_state <= read_wait;
						read_num <= 9'd2;		//接收2个数据的Device ID
               end						 
					else begin
					   spi_state <= read_wait;
						read_num <= 9'd256;	//如果是block读命令,接收256个数据							 
               end						 
				end
			end
			
			read_wait:begin	//等待flash数据读完成
				if(read_finish)begin
					spi_state <= finish_done;
					data_come <= 1'b0;
				end
				else begin
					data_come <= 1'b1;
				end
			end
			
			write_data:begin	//写flash block数据
				if(write_cnt < 9'd256)begin	// program 256 byte to flash
					if(cnta > 8'd0)begin	//如果data还没有发送完
						flash_datain <= write_cnt[cnta];	//发送bit7~bit1位
                  cnta <= cnta - 8'd1;						
					end				
					else begin                                 
						flash_datain <= write_cnt[0];	//发送bit0
					   cnta <= 8'd7;
					   write_cnt <= write_cnt + 9'd1;
					end
				end
				else begin
					spi_state <= finish_done;
					spi_clk_en <= 1'b0;
				end 
			end
			
			finish_done:begin	//flash操作完成
				flash_cs <= 1'b1;
				flash_datain <= 1'b1;
				spi_clk_en <= 1'b0;
				Done_Sig <= 1'b1;
				spi_state <= idle;
			end
			
			default:begin
				spi_state <= idle;
			end
			
		endcase;		
	end
end
	
//接收flash数据	
always @(posedge clock25M)
begin
	if(!flash_rstn)begin
		read_cnt <= 9'd0;
		cntb <= 8'd0;
		read_finish <= 1'b0;
		myvalid <= 1'b0;
		mydata <= 8'd0;
		mydata_o <= 8'd0;
	end
	else begin
		if(data_come)begin
			if(read_cnt < read_num)begin	//接收数据			  
				if(cntb < 8'd7)begin	//接收一个byte的bit0~bit6		  
					myvalid <= 1'b0;
					mydata <= {mydata[6:0], flash_dataout};
					cntb <= cntb + 8'd1;
				end
				else begin
					myvalid <= 1'b1;	//一个byte数据有效
					mydata_o <= {mydata[6:0], flash_dataout};	//接收bit7
					cntb <= 8'd0;
					read_cnt <= read_cnt + 9'd1;
				end
			end				 			 
			else begin 
				read_cnt <= 9'd0;
				read_finish <= 1'b1;
				myvalid <= 1'b0;
			end
		end
		else begin
			read_cnt <= 9'd0;
			cntb <= 8'd0;
			read_finish <= 1'b0;
			myvalid <= 1'b0;
			mydata <= 8'd0;
		end
	end
end	

endmodule

flash_control模块

`timescale 1ns/1ps
////////////////////////////////////////////
//Module Name	:	flash_control
//Description	:	flash read and write control
//Editor		:	Yongxiang
//Time			:	2020-02-03
////////////////////////////////////////////
module flash_control
    (
        input	wire	CLK,
        input	wire	RSTn,
        output	reg		clock25M,
        output	reg[3:0]	cmd_type,
        input	wire	Done_Sig,
        output	reg[7:0]	flash_cmd,
        output	reg[23:0]	flash_addr,
        input	wire[7:0]	mydata_o,
        input	wire	myvalid_o
    );
 
reg[3:0] i;
reg[7:0] time_delay;

//FLASH 擦除,Page Program,读取程序	
always @(posedge clock25M)
begin
   if(!RSTn)begin
		i <= 4'd0;
		flash_addr <= 24'd0;
		flash_cmd <= 8'd0;
		cmd_type <= 4'b0000;
		time_delay <= 8'd0;
	end
	else begin
	   case(i)
			4'd0:begin	//读Device ID
				if( Done_Sig )begin
					flash_cmd <= 8'h00;
					i <= i + 4'd1;
					cmd_type <= 4'b0000;
				end
				else begin
					flash_cmd <= 8'h90;
					flash_addr <= 24'd0;
					cmd_type <= 4'b1000;
				end	
			end
			
	      4'd1:begin	//写Write Enable instruction
				if(Done_Sig)begin
					flash_cmd <= 8'h00;
					i <= i + 4'd1;
					cmd_type <= 4'b0000;
				end
				else begin
					flash_cmd <= 8'h06;
					cmd_type <= 4'b1001;
				end
			end
			
			4'd2:begin	//Sector擦除
				if(Done_Sig)begin
					flash_cmd <= 8'h00;
					i <= i + 4'd1;
					cmd_type<=4'b0000;
				end
				else begin
					flash_cmd <= 8'h20;
					flash_addr <= 24'd0;
					cmd_type <= 4'b1010;
				end
			end
			
	      4'd3:begin	//waitting 100 clock
				if(time_delay < 8'd100)begin
					flash_cmd <= 8'h00;
					time_delay <= time_delay + 8'd1;
					cmd_type <= 4'b0000;
				end
				else begin
					i <= i + 4'd1;
					time_delay <= 8'd0;
				end	
			end
			
			4'd4:begin	//读状态寄存器1, 等待idle
				if(Done_Sig)begin 
					if(mydata_o[0] == 1'b0)begin
						flash_cmd <= 8'h00;
						i <= i + 4'd1;
						cmd_type <= 4'b0000;
					end
					else begin
						flash_cmd <= 8'h05;
						cmd_type <= 4'b1011;
					end
				end
				else begin
					flash_cmd <= 8'h05;
					cmd_type <= 4'b1011;
				end
			end
			
	      4'd5:begin	//写Write disable instruction
				if(Done_Sig)begin
					flash_cmd <= 8'h00;
					i <= i + 4'd1;
					cmd_type <= 4'b0000;
				end
				else begin
					flash_cmd <= 8'h04;
					cmd_type <= 4'b1100;
				end
			end
			
			4'd6:begin	//读状态寄存器1, 等待idle
				if(Done_Sig)begin
					if(mydata_o[0] == 1'b0)begin
						flash_cmd <= 8'h00;
						i <= i + 4'd1;
						cmd_type <= 4'b0000;
					end
					else begin
						flash_cmd <= 8'h05;
						cmd_type <= 4'b1011;
					end
				end
				else begin
					flash_cmd <= 8'h05;
					cmd_type <= 4'b1011;
				end
			end
			
	      4'd7:begin	//写Write Enable instruction
				if(Done_Sig)begin
					flash_cmd <= 8'h00;
					i <= i + 4'd1;
					cmd_type <= 4'b0000;
				end
				else begin
					flash_cmd <= 8'h06;
					cmd_type <= 4'b1001;
				end 
			end
			
	      4'd8:begin	//waitting 100 clock
				if(time_delay < 8'd100)begin
					flash_cmd <= 8'h00;
					time_delay <= time_delay + 8'd1;
					cmd_type <= 4'b0000;
				end
				else begin
					i <= i + 4'd1;
					time_delay <= 8'd0;
				end	
			end
			
	      4'd9:begin	//page program: write 0~255 to flash
				if(Done_Sig)begin
					flash_cmd <= 8'h00;
					i <= i + 4'd1;
					cmd_type <= 4'b0000;
				end
				else begin
					flash_cmd <= 8'h02;
					flash_addr <= 24'd0;
					cmd_type <= 4'b1101;
				end
			end
			
	      4'd10:begin	//waitting
				if(time_delay < 8'd100)begin
					flash_cmd <= 8'h00;
					time_delay <= time_delay + 8'd1;
					cmd_type <= 4'b0000;
				end
				else begin
					i <= i + 4'd1;
					time_delay <= 8'd0;
				end	
			end
			
			4'd11:begin	//读状态寄存器1, 等待idle
				if(Done_Sig)begin 
					if(mydata_o[0] == 1'b0)begin
						flash_cmd <= 8'h00;
						i <= i + 4'd1;
						cmd_type <= 4'b0000;
					end
					else begin
						flash_cmd <= 8'h05;
						cmd_type <= 4'b1011;
					end
				end
				else begin
					flash_cmd <= 8'h05;
					cmd_type <= 4'b1011;
				end
			end
			
	      4'd12:begin	//写Write disable instruction
				if(Done_Sig)begin
					flash_cmd <= 8'h00;
					i <= i + 4'd1;
					cmd_type <= 4'b0000;
				end
				else begin
					flash_cmd <= 8'h04;
					cmd_type <= 4'b1100;
				end		
			end
			
			4'd13:begin	//读状态寄存器1, 等待idle
				if(Done_Sig)begin
					if(mydata_o[0] == 1'b0)begin
						flash_cmd <= 8'h00;
						i <= i + 4'd1;
						cmd_type <= 4'b0000;
					end
					else begin
						flash_cmd <= 8'h05;
						cmd_type <= 4'b1011;
					end
				end
				else begin
					flash_cmd <= 8'h05;
					cmd_type <= 4'b1011;
				end
			end
			
			4'd14:begin	//read 256byte
				if(Done_Sig)begin
					flash_cmd <= 8'h00;
					i <= i + 4'd1;
					cmd_type <= 4'b0000;
				end
				else begin
					flash_cmd <= 8'h03;
					flash_addr <= 24'd0;
					cmd_type <= 4'b1110;
				end
			end
			
			4'd15:begin	//idle
				i <= 4'd15;
			end
			
		endcase
	end
end


//产生25Mhz的SPI Clock		  
always @(posedge CLK)
begin
   if(!RSTn)begin
		clock25M <= 1'b0;
	end
	else begin
		clock25M <= ~clock25M;
	end
end

endmodule

顶层模块

`timescale 1ns/1ps
////////////////////////////////////////////
//Module Name	:	flash
//Description	:	top_file
//Editor		:	Yongxiang
//Time			:	2020-02-03
////////////////////////////////////////////
module flash
    (
       	input	wire	CLK,
        input	wire	RSTn,

       	output	wire	flash_clk,		//spi flash clock 
        output	wire	flash_cs,		//spi flash cs 
        output	wire	flash_datain,	//spi flash data input  
        input	wire	flash_dataout	//spi flash data output
    );

wire[7:0] flash_cmd;
wire[23:0] flash_addr;
wire clock25M;
wire[3:0] cmd_type;
wire Done_Sig;
wire[7:0] mydata_o;
wire myvalid_o;

//spi通信
spi spi_inst
    (
        .flash_clk(flash_clk),
        .flash_cs(flash_cs),
        .flash_datain(flash_datain),  
        .flash_dataout(flash_dataout),    

        .clock25M(clock25M),		//input clock
        .flash_rstn(RSTn),		//input reset 
        .cmd_type(cmd_type),		// flash command type		  
        .Done_Sig(Done_Sig),		//output done signal
        .flash_cmd(flash_cmd),	// input flash command 
        .flash_addr(flash_addr),// input flash address 
        .mydata_o(mydata_o),		// output flash data 
        .myvalid_o(myvalid_o)	// output flash data valid 		
    );

//flash控制
flash_control flash_control_inst
    (
       	.CLK(CLK),
        .RSTn(RSTn),
        .clock25M(clock25M),
        .cmd_type(cmd_type),
        .Done_Sig(Done_Sig),
        .flash_cmd(flash_cmd),
        .flash_addr(flash_addr),
        .mydata_o(mydata_o),
        .myvalid_o(myvalid_o)
    );

endmodule