iT邦幫忙

2021 iThome 鐵人賽

DAY 19
1

SPI是什麼?

SPI(Serial Peripheral Interface),是一種同步的傳輸協定,主要應用於單晶片系統中。類似 I2C(之後會提到),它的應用有快閃記憶體、EEPROM、SD 卡與顯示器等等....

圖片出處


介面

SPI協定規定了4個邏訊號介面(也有三線的,但那就是單工了):

  • SCLK(Serial Clock,會由 master 端發出)
  • MOSI(Master Out,Slave In)
  • MISO(Master In, Slave Out)
  • CS(Chip Select,因為一個 master 可以跟數個 slave 做通訊,因此需要 CS 來選擇要通訊的 slave,而通常 CS 為低電位致能)

SPI 的 Timing Diagram

開始傳輸資料時,CS 要拉低,而我的模式是:mosi 在 SCLK 的負緣時值會改變,而 miso 則是在 SCLK 正緣時值會改變,與 Uart 不同的是:SPI 並沒有起始位元,slave 單純靠 SCLK 的正負緣讀取資料(正或負要看如何設計的),在這邊,資料位寬的話我是做 16bit;傳輸頻率 1MHZ,因為想說可以試著與 max7219 做通訊,有興趣的可以去查一下 max7219 的 datasheet。


設計SPI狀態機

跟 UART 一樣,先定義四個狀態:

/*---------parameter---------*/
localparam IDLE  = 2'd0;
localparam START = 2'd1;
localparam SHIFT = 2'd2;
localparam STOP  = 2'd3;

再來定義輸入輸出
輸入:

  • clk_sys
  • rst_n
  • SCLK_temp
  • ready(狀態機 enable)
  • tick_SPI
  • count(計數移位 16 次的計數器,由外模組來數所以這裡是輸入)

輸出:

  • CS(Chip Select)
  • SCLK(總輸出的 SCLK)
  • LDEN(LoadEnable,告訴外部模塊要 Load 資料)
  • SHEN(ShiftEnable,告訴外部模塊要移位)
  • rstcount(reset count,告訴外部模塊將計數器歸零)
  • countEN(CountEnable,告訴外部模塊將計數器往上計數)
  • finish(告訴外部模塊現在傳輸結束了)
module SPI(
  clk_sys, 
  SCLK_temp, 
  tick_SPI, 
  rst_n, 
  SCLK, 
  CS, 
  count, 
  countEN, 
  rstcount, 
  ready, 
  finish, 
  SHEN, 
  LDEN
);
/*-----------ports declaration-----------*/
input       clk_sys; 
input       SCLK_temp; 
input       rst_n;
input       ready;
input       tick_SPI;
input [3:0] count;
output      CS; 
output      countEN; 
output      rstcount; 
output      SHEN; 
output      LDEN;
output      finish;
output      SCLK;
reg         CS; 
reg         countEN; 
reg         rstcount; 
reg         SHEN; 
reg         LDEN;
reg         finish;
wire        SCLK;
/*-----------finish-----------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)begin
    finish <= 1'b0;
  end
  else begin
    if(fstate==STOP&&tick_SPI)finish <= 1'b1;
    else                      finish <= 1'b0;
  end
end

宣告跑狀態的變數:

/*---------variables---------*/	
reg [1:0] fstate;

狀態邏輯:

  • 重置時要能回到 IDLE 狀態。
  • IDLE(收到 ready(enable) 訊號後開始到下一個狀態)
  • START(只有一個傳輸週期,用來 Load 資料用)
  • SHIFT(count 數滿 15 次後並且最後一次也要待滿一個傳輸週期,狀態才往 STOP)
  • STOP(再次回到 IDLE,等待下一次傳輸)
/*-----------state-----------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)fstate <= IDLE;
  else begin
    case(fstate)
      IDLE:begin
        if(ready)fstate <= START;
        else     fstate <= IDLE;
      end
      START:begin
        if(tick_SPI)fstate <= SHITF;
        else        fstate <= START;
      end
      SHITF:begin
        if(count==4'd14&&tick_SPI)fstate <= STOP;
        else                      fstate <= SHITF;
      end
      STOP:begin
        if(tick_SPI)fstate <= IDLE;
        else        fstate <= STOP;
      end
      default:fstate <= IDLE;
    endcase
  end
end

輸出邏輯:

  • 重置時
    • CS = 1(不選晶片)
    • 其餘都是 0
  • IDLE
    • CS = 1(不選晶片)
    • 其餘都是 0
  • START
    • 此時要 Load 要傳的 16 bit 資料,所以LDEN = 1
    • 此時 CS 要拉到 0,致能 slave
    • 其餘都是 0
  • SHIFT
    • 這個狀態要開始數 bit 數,也要在最後一個 bit 將計數暫存器歸零。
    • countEN = 1
    • 在最後一 bit 時 countEN = 1
    • 移位訊號 SHEN = 1
  • STOP
    • CS 再拉回 1(不選晶片)
    • 其餘都是 0
/*-----------output-----------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)begin
    CS       <= 1'b1;
    countEN  <= 1'b0;
    rstcount <= 1'b0;
    SHEN     <= 1'b0;
    LDEN     <= 1'b0;
  end
  else begin
    if(tick_SPI)begin
      case(fstate)
        IDLE:begin
          CS       <= 1'b1;
          countEN  <= 1'b0;
          rstcount <= 1'b0;
          SHEN     <= 1'b0;
          LDEN     <= 1'b0;
        end
        START:begin
          CS       <= 1'b0;
          countEN  <= 1'b0;
          rstcount <= 1'b0;
          SHEN     <= 1'b0;
          LDEN     <= 1'b1;
        end
        SHITF:begin
          CS       <= 1'b0;
          countEN  <= 1'b1;
          if(count==4'd14)    rstcount  <= 1'b1;
          else if(count<4'd14)rstcount  <= 1'b0;
          else                rstcount  <= 1'b0;//prevent latch
          SHEN     <= 1'b1;
          LDEN     <= 1'b0;
        end
        STOP:begin
          CS       <= 1'b1;
          countEN  <= 1'b0;
          rstcount <= 1'b0;
          SHEN     <= 1'b0;
          LDEN     <= 1'b0;
        end
        default:begin
          CS       <= 1'bx;
          countEN  <= 1'bx;
          rstcount <= 1'bx;
          SHEN     <= 1'bx;
          LDEN     <= 1'bx;
        end
      endcase
    end
    else begin
      CS       <= CS;
      countEN  <= countEN;
      rstcount <= rstcount;
      SHEN     <= SHEN;
      LDEN     <= LDEN;
    end
  end
end

endmodule

這裡也一樣,要數到 15 卻 14 就結束是因為外部模組會延後一個 clk,如果 15 才停,那外部模組它數到 16 才停下,shift 的次數也會多一次,這種情況是我們不樂見的~!

那麼今天的的教學就到這邊,下一篇我們會完成整個 SPI 模組~~~~


上一篇
【Day18】Uart_TX 的實現
下一篇
【Day20】SPI的實現
系列文
verilog or very lag30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言