FPGA的SPI從機模塊實現

一. SPI總線協議

        SPI(Serial Peripheral Interface)接口,中文爲串行外設接口。它只需要3根線或4根線即可完成通信工作(這裏討論4根線的情況)。
        這4根通信線分別爲NCS/NSS(片選信號)、SCK/SCLK(串行同步時鐘)、MOSI/SDO(主機輸出從機輸入,Master Output Slave Input)、MISO/SDI(主機輸入從機輸出)。
        SPI通信有四種方式,由CPOL(時鐘極性)、CPHA(時鐘相位)的4種組合決定的。CPOL決定總線空閒時,SCK是高電平還是低電平(CPOL=,0,無數據傳輸時,SCK=0;CPOL=1,無數據傳輸時,SCK=1)。CPHA決定在數據開始傳輸時,SCK第幾個跳變沿採集數據(CPHA=0,開始傳輸時,在第一個跳變沿採集數據,第二個跳變沿改變發送數據(即改變MISO或者MOSI線上電平);CPHA=1,開始傳輸是,在第一個跳變沿改變發送的數據,在第二個跳變沿採集數據)(見圖1)。

圖1
        確立可靠通信前,必須保證主從機處於同一種的傳輸方式,這裏爲方便起見,專門以CPOL=0,CPHA=0的傳輸方式進行討論。需要注意的是:在CPOL=0,CPHA=0的情況下,主從機都在SCK上跳沿對數據進行採集,SCK下跳沿改變總線電平(見圖2)。


圖2
這裏在使用FPGA實現SPI模塊時,做一個規定:
1. 使用CPOL=0,、CPHA=0的傳輸方式;
2. 傳輸時,以最高位先輸出,最後輸出最低位;
3. FPGA實現的SPI模塊作從機,SCK由外部主機提供;
4. 通信數據長度爲8位。

二. FPGA的SPI從機實現

        實現SPI從機,可以分爲兩個模塊:一個是SPI接收模塊,另一個則是SPI發送模塊。

1. 首先確定模塊的輸出輸入管腳

        由標題一可以知道,SPI通信腳有4根線,我們還是用到時鐘總線和模塊復位腳,因此模塊管腳可以定義爲
module myspi(nrst, clk, ncs, mosi, miso, sck);
input clk, nrst;
input ncs, mosi, sck;
output miso;

2. SCK跳變沿檢測

        原理十分簡單:使用寄存器記錄SCK狀態,由狀態判斷SCK是否出現跳變沿。
reg[2:0] sck_edge;
	always @ (posedge clk or negedge nrst)
	begin
		if(~nrst)
		begin
			sck_edge <= 3'b000;
		end
		else
		begin
			sck_edge <= {sck_edge[1:0], sck};
		end
	end
wire sck_riseedge, sck_falledge;
	assign sck_riseedge = (sck_edge[2:1] == 2'b01);  //檢測到SCK由0變成1,則認爲發現上跳沿
	assign sck_falledge = (sck_edge[2:1] == 2'b10);  //檢測到SCK由1變成0,則認爲發現下跳沿

3. SPI接收部分

        SPI接收部分使用有限狀態機:
狀態1:等待SCK上跳沿,並將MOSI的數據移入移位寄存器byte_received,接收位數寄存器bit_received_cnt記錄接收到的數據位數,接收到8位數據後轉入狀態2;
狀態2:保存移位寄存器byte_received數據到接收緩存器rec_data,接收標誌位/接收緩存器非空標誌位rec_flag置高4個clk時鐘週期後轉入狀態3;
狀態3:清除rec_flag並轉入狀態1。
reg[7:0] byte_received;
	reg[3:0] bit_received_cnt;
	reg rec_flag;
	reg[1:0] rec_status;  //SPI接收部分狀態機
	reg[7:0] rec_data;
	reg[2:0] rec_flag_width;  //SPI接收完成標誌位脈衝寬度寄存器
	always @ (posedge clk or negedge nrst)  //每次sck都會接收數據,spi的頂端模塊狀態機決定是否取用
	begin
		if(~nrst)
		begin
			byte_received <= 8'h00;
			bit_received_cnt <= 4'h0;
			rec_flag <= 1'b0;
			rec_status <= 2'b00;
			rec_flag_width <= 3'b000;
		end
		else
		begin
			if(~ncs)
			begin
				case (rec_status)
				2'b00: begin
					if(sck_riseedge)
					begin
						byte_received <= {byte_received[6:0], mosi};
						if(bit_received_cnt == 4'h7)
						begin
							bit_received_cnt <= 4'b0000;
							rec_status <= 2'b01;
						end
						else
						begin
							bit_received_cnt <= bit_received_cnt+1;
						end
					end
				end
				2'b01: begin
					rec_data <= byte_received;
					rec_flag <= 1'b1;
					if(rec_flag_width==3'b100) begin
						rec_flag_width <= 3'b000;
						rec_status <= 2'b11;
					end
					else begin
						rec_flag_width <= rec_flag_width+1;
					end
				end
				2'b11: begin
					rec_flag <= 1'b0;
					rec_status <= 2'b00;
				end
				endcase
			end		
		end
	end

     這裏,使用rec_flag的原因是通知另一個模塊處理接收數據(後面將會提到),rec_data若在下一次數據傳輸完成前不做處理則會丟失。

4. SPI發送部分

        SPI從機一般在解析主機發送的命令後,主動發出主機所需數據,所以,SPI發送部分,需要其他模塊的觸發,並將數據送往MISO管腳。
   SPI發送部分也離不開狀態機:
狀態1:等待發送觸發標誌位send_flag置高,一旦標誌位send_flag置高,發送移位寄存器byte_sended存儲外部觸發模塊的數據send_data,miso管腳輸出發送數據最高位send_data[7],置位正在發送標誌位sending_flag,轉入狀態2;
狀態2:等待SCK上跳沿,即等待主機接收數據最高位後進入狀態3;(其實這個狀態可有可無的狀態)
狀態3:在SCK下跳沿,將發送移位寄存器byte_sended最高位移入miso管腳,當發送移位寄存器被移空,清除正在發送標誌位sending_flag,進入狀態4;
狀態4:置低miso管腳,轉入狀態1。
reg miso;
	reg sending_flag;  //正在發送標誌位
	reg[7:0] byte_sended;  //發送移位寄存器
	reg[3:0] bit_sended_cnt;  //SPI發送位計數器
	reg[1:0] send_status;  //SPI發送部分狀態機
	always @ (posedge clk or negedge nrst)
	begin
		if(~nrst)
		begin
			byte_sended <= 8'h00;
			bit_sended_cnt <= 4'b0000;
			send_status <= 2'b00;
			sending_flag <= 1'b0;
		end
		else
		begin
			if(~ncs)
			begin
				case (send_status)
				2'b00: begin
					if(send_flag) 
					begin  //鎖存發送數據
						send_status <= 2'b01;  //2'b01;
						byte_sended <= send_data;
						sending_flag <= 1'b1;
						miso <= send_data[7];
					end
				end
				2'b01: begin  //發送數據移入移位寄存器
					if(sck_riseedge) begin
						//miso <= byte_sended[7];
						//byte_sended <= {byte_sended[6:0], 1'b0};
						send_status <= 2'b11;
					end
				end
				2'b11: begin  //根據sck下降沿改變數據
					miso <= byte_sended[7];
					if(sck_falledge)   ///---------------------------------------這裏多移了一位
					begin
						//miso <= byte_sended[7];
						byte_sended <= {byte_sended[6:0], 1'b0};
						if(bit_sended_cnt == 4'b0111)
						begin
							send_status <= 2'b10;
							bit_sended_cnt <= 4'b0000;
							sending_flag <= 1'b0;
						end
						else
						begin
							bit_sended_cnt <= bit_sended_cnt+1;
						end
					end
				end
				2'b10: begin  //數據發送完畢
					send_status <= 2'b00;
					//sending_flag <= 1'b0;
					miso <= 1'b0;
				end
				endcase
			end
		end
	end

經過實測,SCK頻率低於clk頻率8倍以上,通信可靠穩定,測試芯片爲XC3S50-TQ144,平臺爲ISE,clk爲25MHz。

發佈了55 篇原創文章 · 獲贊 10 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章