FPGA学习之SPI通讯--MASTER以及SLAVE端数据收发

本文详细介绍了SPI通讯中的Master和Slave模式0工作原理,重点讲述了代码实现,包括SCLK和CS信号的控制,以及数据的发送和接收过程。作者还提到代码中的状态机设计和调试问题,以及SPI通讯相对于串口的优点。
摘要由CSDN通过智能技术生成

SPI通讯–MASTER && SLAVE

背景

一转眼到了三月,前段时间出了段时间的差,终于趁这个周末有空对SPI通讯进行了复现。自己在着手编写代码前,也看了很多的博客以及多次查看了SPI的通讯时序图,始终觉得还是很多地方无法理解,其实主要就是纠结于SCLK的产生。最终,通过参考了 jgliu的博客完成了此次代码的编写与验证。但目前代码还处于功能仿真完成阶段,后续还需在板上进行验证(flash读写)。

jgliu博客地址

https://www.cnblogs.com/liujinggang/p/9609739.html
该博客对SPI的介绍可以说是目前为止我看到比较详细、易懂且全面的了,感谢这位大神的分享。

备注:本文章代码为参考博客代码的更改版

代码功能介绍

1、MASTER端SPI代码:配置为模式0,一次传输8bit数据,可实现数据的收发;sclk,cs信号均由MASTER端产生。
2、SLAVE端SPI代码:配置为模式0,一次传输8bit数据。

模式0时序图

在这里插入图片描述

具体代码

SPI通讯之MASTER端代码

`timescale 1ns / 1ps
//
// 
// 						SPI通讯--8位数据--模式0--上升沿采样、下降沿数据切换
// 
//


module spi_master(
	input 				clk,
	input 				rst_n,
	input		[7:0]	tx_data,
	input 				spi_en,
	input 				miso,
	output 	reg			mosi,
	output 	reg			cs,
	output 	reg			sclk,
	output 	reg			tx_done,
	output 	reg			rx_done,
	output	reg	[7:0]	TEM_rx_data
    );
	
	
//
// 
// 									传输开始
// 
//

	reg spi_start_flag;
	always @(posedge clk, negedge rst_n) begin
		if(!rst_n) begin
			spi_start_flag <= 1'b0;
		end else begin
			if(spi_en) begin
				spi_start_flag <= 1'b1;
			end else begin
				if(rx_done) begin
					spi_start_flag <= 1'b0;
				end
			end
		end
	end

//
// 
// 									兼容低速率从设备
// 
//
	
	parameter SLAVE_FREQUENCY 	= 5_000_000, 	//假设从设备的最大频率为5Mhz
			  MASTER_FREQUENCY 	= 50_000_000,	//主机的时钟频率为50MHZ
			  RATE_NUM			= MASTER_FREQUENCY / SLAVE_FREQUENCY;
	
	reg	[3:0]	rate_cnt;
	reg rate_flag;
	always @(posedge clk, negedge rst_n) begin
		if(!rst_n) begin
			rate_cnt <= 'd0;
			rate_flag <= 'd0;
		end else begin
			if(spi_start_flag) begin
				if(rate_cnt <= RATE_NUM - 1) begin
					rate_cnt <= rate_cnt + 1'b1;
					rate_flag <= 'd0;
				end else begin
					rate_flag <= 'd1;
					rate_cnt <= 'd0;
				end
			end else begin
				rate_flag <= 'd0;
				rate_cnt <= 'd0;
			end
			
		end
	end
//
// 
// 									sclk计数器 0-16
// 
//	
	reg	[4:0]	sclk_cnt;
	always @(posedge clk, negedge rst_n) begin
		if(!rst_n) begin
			sclk_cnt <= 'd0;
		end else begin
			if(rate_flag) begin
				if(sclk_cnt <= 16 - 1) begin
					sclk_cnt <= sclk_cnt + 1'b1;
				end else begin
					sclk_cnt <= 'd0;
				end		
			end else begin
				if(!spi_start_flag) begin
					sclk_cnt <= 'd0;
				end
			end
		end
	end
	
//
// 
// 							SPI发送--上升沿采样、下降沿数据切换
// 
//

	reg [7:0]rx_data;
	always @(posedge clk, negedge rst_n) begin
		if(~rst_n) begin
			mosi <= 0;
			tx_done <= 'd0;
			sclk <= 'd0;
			rx_data <= 'd0;
			rx_done <= 'd0;
			cs <= 'b1;
			TEM_rx_data <= 'b0;
		end else begin
			if (spi_start_flag) begin
				case(sclk_cnt)
					'd0: begin 
						cs <= 1'b0;
						rx_data <= 'd0;
						mosi <= tx_data[7];
						sclk <= 'd0;
						tx_done <= 1'b0;
						rx_done <= 1'b0;	
					end
					'd1: begin
						mosi <= mosi;
						sclk <= 'd1;
						rx_data[7] <= miso;
					end
					'd2: begin
							mosi <= tx_data[6];
							sclk <= 'd0;
					end
					'd3: begin
						mosi <= mosi;
						tx_done <= 'd0;
						sclk <= 'd1;
						rx_data[6] <= miso;
					end
					'd4: begin
							mosi <= tx_data[5];
							sclk <= 'd0;
					end
					'd5: begin
						mosi <= mosi;
						tx_done <= 'd0;
						sclk <= 'd1;
						rx_data[5] <= miso;
					end
					'd6: begin
							mosi <= tx_data[4];
							sclk <= 'd0;
					end
					'd7: begin
						mosi <= mosi;
						tx_done <= 'd0;
						sclk <= 'd1;
						rx_data[4] <= miso;
					end
					'd8: begin
							mosi <= tx_data[3];
							sclk <= 'd0;
					end
					'd9: begin
						mosi <= mosi;
						tx_done <= 'd0;
						sclk <= 'd1;
						rx_data[3] <= miso;
					end
					'd10: begin
							mosi <= tx_data[2];
							sclk <= 'd0;
					end
					'd11: begin
						mosi <= mosi;
						tx_done <= 'd0;
						sclk <= 'd1;
						rx_data[2] <= miso;
					end
					'd12: begin
							mosi <= tx_data[1];
							sclk <= 'd0;
					end
					'd13: begin
						mosi <= mosi;
						tx_done <= 'd0;
						sclk <= 'd1;
						rx_data[1] <= miso;
					end
					'd14: begin
							mosi <= tx_data[0];
							sclk <= 'd0;
					end
					'd15: begin
						mosi <= mosi;
						sclk <= 'd1;
						rx_data[0] <= miso;
						//rx_done <= 1'b1;
					end
					'd16: begin
						sclk <= 1'b0;
						TEM_rx_data <= rx_data;
						if(rate_cnt >= RATE_NUM-1) begin
							rx_done <= 1'b1;
							tx_done <= 1'b1;
							cs <= 1'b1;
						end
					end
					default: sclk_cnt <= 'd0;
				endcase
			end else begin
				mosi <= 0;
				tx_done <= 'd0;
				sclk <= 'd0;
				rx_data <= 'd0;
				rx_done <= 'd0;
				cs <= 'b1;
			end
		end
	end
	
endmodule

SPI通讯之SLAVE端代码

SLAVE_TOP
`timescale 1ns / 1ps
//
// 
// 			SPI从机TOP模块--模式0--8位数据
//  
//


module spi_slave_top(
		input clk,
		input rst_n,
		input [7:0]need_tx_data,
		input cs,
		input sclk,
		input mosi,
		output miso,
		output tx_done,
		output rx_done,
		output [7:0]rx_data
    );
	
	spi_slave_send slave_send_inst(
		.clk(clk),
		.rst_n(rst_n),
		.need_tx_data(need_tx_data),
		.sclk(sclk),
		.cs(cs),
		.miso(miso),
		.tx_done(tx_done)
		);
	
	spi_slave_receive slave_rece_inst(
		.clk		(clk),
		.rst_n		(rst_n),
		.sclk		(sclk),
		.cs			(cs),
		.mosi		(mosi),
		.rx_done	(rx_done),
		.TEM_rx_data	(rx_data)
    );
	
endmodule
SLAVE_RECEIVE
`timescale 1ns / 1ps
//
// 
//          				spi从端接收数据
// 
//

module spi_slave_receive(
    input clk,
    input rst_n,
    input sclk,
    input cs,
    input mosi,
    output reg rx_done,
    output reg [7:0]TEM_rx_data
    );
    
//
// 
//          			片选拉低检测--下降沿选通
// 
//   
    
    reg cs_0;
    reg cs_1;
	wire cs_neg;
    assign cs_neg = !cs_0 && cs_1;
	
	reg sclk_0;
	reg sclk_1;
	wire sclk_pos;
	assign sclk_pos = sclk_0 && !sclk_1;
	
    always @(posedge clk, negedge rst_n) begin
        if(~rst_n) begin
            cs_0 <= 1'b0;
            cs_1 <= 1'b0;
			sclk_0 <= 1'b0;
			sclk_1 <= 1'b0;
        end else begin
            cs_0 <= cs;
            cs_1 <= cs_0;  
			sclk_0 <= sclk;
			sclk_1 <= sclk_0;
        end 
    end 
   
//
// 
//          数据接收状态机--数据上升沿被采样、下降沿数据切换--SPI模式0
// 
//
    
    reg [3:0]current_state;
    reg [3:0]rx_cnt;
    
    parameter IDLE = 4'd0,
               RECEIVE_STATE = 4'd1;
    
    always @(posedge clk, negedge rst_n) begin
        if(~rst_n) begin
            current_state <= IDLE;
        end else begin
            case(current_state) 
                IDLE: begin
                    if(cs_neg) begin
                        current_state <= RECEIVE_STATE;
                    end
                end 
                
                RECEIVE_STATE: begin
                    if(rx_cnt > 8 - 1 && cs_neg) begin
                        current_state <= IDLE;
                    end
                end 
            endcase
        end
    end 
    
	reg [7:0]rx_data;
	always @(posedge clk, negedge rst_n) begin
        if(~rst_n) begin
            rx_data <= 8'b0;
			rx_cnt <= 4'b0;
			rx_done <= 1'b0;
			rx_data <= 'd0;
        end else begin
            case(current_state) 
                IDLE: begin
					rx_cnt <= 4'b0;
					rx_done <= 1'b0;
					rx_data <= 'd0;
                end 
                
                RECEIVE_STATE: begin
                    if(!cs) begin
						if(rx_cnt <= 8 - 1) begin
							if(sclk_pos) begin
								rx_data[7-rx_cnt] <= mosi;
								rx_cnt <= rx_cnt + 1'b1;
							end
						end else begin
							rx_done <= 1'b1;
						end
                    end else begin
						rx_cnt <= 4'b0;
						rx_done <= 1'b0;
					end
                end 
            endcase
        end
    end 

//
// 
//              			最终接收数据
// 
//

	always @(posedge clk, negedge rst_n) begin
		if(!rst_n) begin
			TEM_rx_data <= 'b0;
		end else begin
			if(rx_done) begin
				TEM_rx_data <= rx_data;
			end
		end
	end
endmodule
SLAVE_SEND
`timescale 1ns / 1ps
//
// 
//          SPI从端--数据发送--发送由高到低
// 
//

module spi_slave_send(
    input clk,
    input rst_n,
    input [7:0]need_tx_data,
    input sclk,
    input cs,
    output reg miso,
    output reg tx_done
    );
    
//
// 
//          片选拉低检测--下降沿选通
// 
//   
    
    reg cs_0;
    reg cs_1;
	wire cs_neg;
	wire cs_pos;
    assign cs_neg = !cs_0 && cs_1;
	assign cs_pos = !cs_1 && cs_0;
	
	reg sclk_0;
	reg sclk_1;
	wire sclk_neg;
	assign sclk_neg = !sclk_0 && sclk_1;
	
    always @(posedge clk, negedge rst_n) begin
        if(~rst_n) begin
            cs_0 <= 1'b0;
            cs_1 <= 1'b0;
			sclk_0 <= 1'b0;
			sclk_1 <= 1'b0;
        end else begin
            cs_0 <= cs;
            cs_1 <= cs_0;  
			sclk_0 <= sclk;
			sclk_1 <= sclk_0;
        end 
    end 

//
// 
//          数据发送状态机--数据上升沿被采样、下降沿数据切换--SPI模式0
// 
//
    
    reg [3:0]current_state;
    parameter  IDLE = 4'd0,
			   SEND_FIRST = 4'd1,
               SEND_STATE = 4'd2;  
    reg [3:0] send_cnt;  
    always @(posedge clk, negedge rst_n) begin
        if(~rst_n) begin
            current_state <= IDLE;
        end else begin
            case(current_state)
                IDLE: begin
					if(!cs && sclk_neg) begin
						current_state <= SEND_FIRST;      
                    end
                end
                
				SEND_FIRST: begin
					current_state <= SEND_STATE;
				end
				
                SEND_STATE: begin
					if(send_cnt > 7) begin
						current_state <= IDLE; 
					end
                end
				
                default: current_state <= IDLE;     
            endcase
        end 
    end 
    
    always @(posedge clk, negedge rst_n) begin
        if(~rst_n) begin
            miso <= 1'b0;
			tx_done <= 1'b0;
			send_cnt <= 4'd0;
        end else begin
            case(current_state)
                IDLE: begin
                    miso <= need_tx_data[7];
                    tx_done <= 1'b0;
					send_cnt <= 4'd0;
                end      
				
				SEND_FIRST: begin
					send_cnt <= send_cnt + 1'b1;
					miso <= need_tx_data[6];
				end
				
                SEND_STATE: begin
				    if(!cs) begin    
                        if(send_cnt <= 8 - 1) begin
							if(sclk_neg) begin
								send_cnt <= send_cnt + 1'b1;
								miso <= need_tx_data[6 - send_cnt];
							end 
                        end else begin
							tx_done <= 1'b1;
                        end
                    end else begin
                        send_cnt <= 4'd0;
						tx_done <= 1'b0;
                    end
                end                
            endcase
        end 
    end
    
endmodule

代码功能仿真

在这里插入图片描述
仿真波形图中,tx_data为SPI主端以及从端互相发送的数据,slave_rx_data以及master_rx_data为接收到的数据;从时序图中的sclk,cs以及mosi、miso波形可以看出,与SPI波形原理图基本一致。

问题总结

1、由于参考的博客中,主端的发送以及接收是分别使能进行的,而我的代码是将数据的收发合到一起的,这也导致原博客中的线性序列机('d0-'d15)在我这并不能直接使用;在本代码中,额外增加了状态’d16以满足片选拉高时间的需求。

2、本次调试的主要问题就在于需发送数据切换时,切换间隔会发生误读数据,必须等到下一次完整读取才会更新为正确的数据。

3、在理解SPI通讯后,实际上个人感觉比串口通讯还更为简单方便,而且在这一次的代码复现中,自己也首次学习了线性状态机的使用,实际上在UART、SPI通讯中使用线性状态机更为方便、快捷。

后续安排

后续会看看手里的开发板资源是否有用SPI通讯驱动的芯片,从而完成代码的板级验证。

  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值