基於FPGA的SPI主機數據接收發送控制器

引言:SPI是常用的板級通信協議,在FPGA板級通信中,許多重要的從器件都對SPI協議有所支持。因此,掌握SPI通信的FPGA片上實現對於FPGA工程開發具有重要的意義。本文設計了一個基於SPI模式0的主機通信控制器,系統性闡明瞭SPI設計的全流程。希望本文的設計能夠對更多的人有所幫助。

一、SPI通信協議簡介

1.物理層引線

SPI通信的最小結構爲一主一從結構,主機向從機提供信號發送接收時鐘SCLK。在物理實現中,主機與從機之間存有四根引線,即MOSI(主收從發)、MISO(主發從收)、SCK(通信時鐘)、nSS(從機片選信號)。其中,通過在主機上增加片選信號輸出端的數目,可以使得主機控制更多的從機,實現一主多從的SPI通信。

2.SPI四種模式

SPI通信協議規定了4鍾工作模式,在實際應用中應當保證主機和從機工作在相同的工作模式下。SPI工作模式通過時鐘極性CPOL和時鐘相位CPHA聯合指定。其中沒時鐘極性CPOL指定SCK在空閒狀態時的電平,時鐘極性CPHA指定在SCK的何種邊緣進行數據採樣。其標識如下:

  CPOL=1 CPOL=0
CPHA=1 MODE3:SCK空閒爲高,上升沿採樣 MODE1:SCK空閒爲低,下降沿採樣
CPHA-0 MODE2:SCK空閒爲高,下降沿採樣 MODE0:SCK空閒爲低,上升沿採樣

 

其中,SPI中最常用的時模式0和模式2.

二、FPGA實現設計

由於我們設計的目標爲SPI模式0主機控制器,因此我需要實現在SCK上升沿時對數據進行採樣。由於在時鐘上升沿進行數據採樣,那麼,接收/發送狀態機的狀態應當早於SCK時鐘上升沿提前準備好接收/發送狀態。

對於發送狀態機,必須在SCK時鐘上升沿到來前將需發送的數據在MOSI信號線上準備好。因此其狀態的跳變應當較時鐘上升沿到來更早。因此,本文設定發送狀態機的在SCK時鐘的下降沿進行狀態變換,在SCK的低電平中心將需發送的數據壓至MOSI線上。

對於接收狀態機,爲了保證狀態跳變的一致性,也採用SCK下降沿進行狀態跳變時刻。接收狀態機在SCK上升沿時將數據採集至接收緩衝口,並在採集完一字節數據後生成信號標誌脈衝。這個脈衝將保持一個SCK週期以供FPGA其餘模塊識別。

對於SPI主機控制器,另一個重要的模塊是稱之爲control的控制模塊,它是用來生成SCK時鐘與nSS片選信號的模塊。由於本文設計只有一個從機,因此只單純的控制nSS的電平,不對多條nSS進行選擇控制。SCK時鐘的生成也是依靠狀態機,其機理爲當狀態機處於IDLE空閒狀態時,檢測到讀/寫請求,在時鐘相位變爲下降沿時進入工作狀態,並輸出8個完整的SCK時鐘。此後,狀態機轉入STOP狀態,並在一個週期內檢測是否有新的請求信號到來,若無抵達空閒狀態,若有,轉入工作狀態。

本文設計的代碼如下,如果要修改SPI工作模式,請根據頭文件的相位定義修改代碼:

module SPI(
    clk,rst_n,miso,mosi,sclk,ss_n,wr,rd,data_wr,data_rd,wr_req,rd_req
    );
input clk;                                          //時鐘信號,設定爲50MHz
input rst_n;                                        //異步復位同步釋放
//SPI接口信號
input miso;                                         //主收從發信號
output mosi;                                        //主發從收信號
output sclk;                                        //SPI發送接收時鐘SCLK
output ss_n;                                        //SPI從設備片選信號
//功能指示與數據傳輸信號
output wr;                                          //SPI主機發送空閒信號
output rd;                                          //SPI主機接收緩衝區數據可讀信號
input[7:0]data_wr;                                  //需通過SPI主機發送的數據
input wr_req;                                       //SPI主機數據發送請求信號
input rd_req;                                       //SPI主機讀讀從機數據請求信號
output[7:0]data_rd;                                 //SPI主機接收機緩衝區數據
//////////////////////////////////////////////////////////////////////////////////
wire[8:0]phase;                                     //SCLK時鐘相位值
wire[3:0]cnt;                                       //發送/接收字節計數器
//////////////////////////////////////////////////////////////////////////////////
//控制模塊:片選信號及SCLK時鐘信號控制
control U1(
    .clk(clk),                                      //時鐘信號
    .rst_n(rst_n),                                  //全局復位信號
    .sclk(sclk),                                    //SCLK時鐘信號
    .ss_n(ss_n),                                    //從機片選信號
    .phase(phase),                                  //SCLK時鐘相位信息
    .wr_req(wr_req),                                //主機寫請求
    .rd_req(wr_req),                                //主機讀請求
    .cnt(cnt)                                       //字節計數器
    );
//////////////////////////////////////////////////////////////////////////////////
//發送模塊
spi_tx U2(
    .clk(clk),                                      //
    .rst_n(rst_n),                                  //
    .wr_req(wr_req),                                //寫請求信號
    .data_wr(data_wr),                              //寫數據
    .phase(phase),                                  //SCLK相位
    .wr(wr),                                        //SPI主機發送空閒信號
    .mosi(mosi),                                    //主發從收信號
    .cnt(cnt)                                       //字節計數器
    );
//////////////////////////////////////////////////////////////////////////////////
//接收模塊
spi_rx U3(
    .clk(clk),                                      //
    .rst_n(rst_n),                                  //
    .rd_req(rd_req),                                //讀請求信號
    .data_rd(data_rd),                              //讀數據
    .phase(phase),                                  //SCLK相位
    .rd(rd),                                        //SPI主機接收緩衝可讀信號
    .miso(miso),                                    //主收從發信號
    .cnt(cnt)                                       //字節計數器
    );
//////////////////////////////////////////////////////////////////////////////////
endmodule
`include "global_definition.v"
module control(
    clk,rst_n,sclk,ss_n,phase,wr_req,rd_req,cnt
    );
input clk;
input rst_n;
output reg sclk;                            //SCLK時鐘信號
output reg ss_n;                            //從機片選信號
output reg[8:0]phase;                       //SCLK時鐘相位
input wr_req;                               //主機寫請求
input rd_req;                               //主機讀請求
output cnt;                                 //字節計數器
///////////////////////////////////////////////////////////////////////////////////
reg[1:0]state;                               //狀態機
reg[1:0]next_state;                          //下一狀態
//狀態機狀態變換儘可能貼合BCD碼變換順序,以減少電路成本並降低組合邏輯風險
parameter IDLE = 2'b00;                     //空閒狀態
parameter WORK = 2'b01;                     //工作狀態
parameter STOP = 2'b11;                     //停止狀態
reg[3:0]cnt;                                 //並串轉換計數器
///////////////////////////////////////////////////////////////////////////////////
//相位計數器
always@(posedge clk or negedge rst_n)
    if(!rst_n)  phase <= 9'd0;
    else if(phase == 9'd499) phase <= 9'd0;     //分頻數500,SPI時鐘速率100KHz
    else phase <= phase + 9'd1;
///////////////////////////////////////////////////////////////////////////////////
//狀態機轉移
always@(posedge clk or negedge rst_n)
    if(!rst_n)  state <= IDLE;
    else if(`SCLK_NEG)state <= next_state;      //狀態只在SCLK時鐘下降沿時改變
    else state <= state;
///////////////////////////////////////////////////////////////////////////////////
//狀態組合判定
always@(wr_req or rd_req or cnt)
    case(state)
    IDLE : if(wr_req||rd_req)  next_state <= WORK;          //空閒狀態遭遇讀/寫請求,下次狀態發生改變
            else next_state <= IDLE;
    WORK : if(cnt == 4'd8)  next_state <= STOP;             //發送/接收一字節數據後,狀態轉移至STOP
            else next_state <= WORK;
    STOP : if(wr_req||rd_req)   next_state <= WORK;         //在STOP狀態下,若觀測到請求,則繼續工作,否則休眠
            else next_state <= IDLE;
    default : next_state <= IDLE;
    endcase
///////////////////////////////////////////////////////////////////////////////////
//狀態機輸出
always@(posedge clk or negedge rst_n)
    if(!rst_n)begin
        sclk <= 1'b0;
        ss_n <= 1'b1;
        cnt <= 4'd0;
    end
    else begin
        case(state)
        IDLE : begin
                    sclk <= 1'b0;
                    ss_n <= 1'b1;
                    cnt <= 4'd0;
                end
        WORK : begin
                    ss_n <= 1'b0;
                    if(`SCLK_POS)begin                  //時鐘上升沿
                        sclk <= 1'b1;                   //SCLK時鐘拉高
                        cnt <= cnt + 4'd1;              //字節計數器加一
                    end
                    else if(`SCLK_NEG)sclk <= 1'b0;     //
                    else sclk <= sclk;         
                end
        STOP : begin
                    cnt <= 4'd0;
                    sclk <= 1'b0;
                    ss_n <= 1'b1;
                end
        default : begin
                        cnt <= 4'd0;
                        sclk <= 1'b0;
                        ss_n <= 1'b1;
                    end
        endcase
    end
        
///////////////////////////////////////////////////////////////////////////////////
endmodule
`include "global_definition.v"

module spi_tx(
    clk,rst_n,wr_req,data_wr,phase,wr,mosi,cnt
    );
input clk;
input rst_n;
input wr_req;               //主機寫請求信號
input[7:0]data_wr;          //寫數據
input[8:0]phase;            //SCLK相位
output reg wr;              //寫空閒標誌
output reg mosi;            //主發從收信號
input[3:0]cnt;              //字節計數器
////////////////////////////////////////////////////////////////////
reg[1:0]state;
reg[1:0]next_state;
parameter IDLE = 2'b00;
parameter WORK = 2'b01;
parameter STOP = 2'b11;
////////////////////////////////////////////////////////////////////
//狀態機轉移
always@(posedge clk or negedge rst_n)
    if(!rst_n) state <= IDLE;
    else if(`SCLK_NEG) state <= next_state;
////////////////////////////////////////////////////////////////////
//狀態組合判定
always@(wr_req or cnt)
    case(state)
    IDLE : if(wr_req) next_state <= WORK;
            else next_state <= IDLE;
    WORK : if(cnt == 4'd8)  next_state <= STOP;
            else next_state <= WORK;
    STOP : if(wr_req) next_state <= WORK;
           else next_state <= IDLE;
    default :   state <= IDLE;
    endcase   
////////////////////////////////////////////////////////////////////
//狀態機輸出
always@(posedge clk or negedge rst_n)
    if(!rst_n)begin
        mosi <= 1'b0;
        wr <= 1'b1;
    end
    else begin
    case(state)
    IDLE :  begin
                mosi <= 1'b0;
                wr <= 1'b1;
            end
    WORK :  begin                               //在SCLK下降沿狀態改變後,緊跟
                if(`SCLK_LOW)begin              //SCLK低電平時將數據放置總線上
                    case(cnt)
                    4'd0 : mosi <= data_wr[7];
                    4'd1 : mosi <= data_wr[6];
                    4'd2 : mosi <= data_wr[5];
                    4'd3 : mosi <= data_wr[4];
                    4'd4 : mosi <= data_wr[3];
                    4'd5 : mosi <= data_wr[2];
                    4'd6 : mosi <= data_wr[1];
                    4'd7 : mosi <= data_wr[0];
                    default : mosi <= 1'b0;
                    endcase
                end
                wr <= 1'b0;
            end
    STOP :  begin
                wr <= 1'b1;
                mosi <= 1'b0;
            end
    default :   begin
                    wr <= 1'b1;
                    mosi <= 1'b0;
                end
    endcase
    end
////////////////////////////////////////////////////////////////////
endmodule
`include "global_definition.v"

module spi_rx(
    clk,rst_n,rd_req,data_rd,phase,rd,miso,cnt
    );
input clk;
input rst_n;
input rd_req;               //主機讀請求信號
output reg[7:0]data_rd;     //讀出數據
input[8:0]phase;            //SCLK相位
output reg rd;              //讀數據有效標誌
input miso;                 //主收從發信號
input[3:0]cnt;              //字節計數器
////////////////////////////////////////////////////////////////////
reg[1:0]state;
reg[1:0]next_state;
parameter IDLE = 2'b00;
parameter WORK = 2'b01;
parameter STOP = 2'b11;
////////////////////////////////////////////////////////////////////
//狀態機轉移
always@(posedge clk or negedge rst_n)
    if(!rst_n) state <= IDLE;
    else if(`SCLK_NEG) state <= next_state;
////////////////////////////////////////////////////////////////////
//狀態組合判定
always@(rd_req or cnt)
    case(state)
    IDLE : if(rd_req) next_state <= WORK;
            else next_state <= IDLE;
    WORK : if(cnt == 4'd8)  next_state <= STOP;
            else next_state <= WORK;
    STOP : if(rd_req) next_state <= WORK;
           else next_state <= IDLE;
    default :   state <= IDLE;
    endcase   
////////////////////////////////////////////////////////////////////
//狀態機輸出
always@(posedge clk or negedge rst_n)
    if(!rst_n)begin
        rd <= 1'b0;
        data_rd <= 8'd0;
    end
    else begin
    case(state)
    IDLE :  begin
                rd <= 1'b0;
                data_rd <= 8'd0;
            end
    WORK :  begin                               //在SCLK下降沿狀態改變後,緊跟
                if(`SCLK_POS)begin              //SCLK上升沿時讀取總線上數據
                    case(cnt)
                    4'd0 : data_rd[7] <= miso;
                    4'd1 : data_rd[6] <= miso;
                    4'd2 : data_rd[5] <= miso;
                    4'd3 : data_rd[4] <= miso;
                    4'd4 : data_rd[3] <= miso;
                    4'd5 : data_rd[2] <= miso;
                    4'd6 : data_rd[1] <= miso;
                    4'd7 : data_rd[0] <= miso;
                    default : data_rd <= 8'd0;
                    endcase
                end
                rd <= 1'b0;
            end
    STOP :  begin
                rd <= 1'b1;
            end
    default :   begin
                    rd <= 1'b0;
                    data_rd <= 8'd0;
                end
    endcase
    end
////////////////////////////////////////////////////////////////////
endmodule

頭文件如下:

`define SCLK_POS (phase == 9'd499)                 //定義SCLK上升沿
`define SCLK_HIG (phase == 9'd124)                 //定義SCLK高電平
`define SCLK_NEG (phase == 9'd249)                 //定義SCLK下降沿
`define SCLK_LOW (phase == 9'd374)                 //定義SCLK低電平

三、仿真結果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章