【Day19】SPI 状态机的实现

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 模组~~~~


<<:  Day19:终於要进去新手村了-javascript-回圈-break、continue

>>:  Day19 - Interpreting Machines - 2 :使用 interpret API

JQuery 学习纪录 ( 6 )

这次非常久才发布学习纪录,因为最近当兵的关系,能学习的时间又被压缩了,但我还是会努力播出时间练习的。...

#17 数据上的各种距离(2)

曼哈顿距离(Manhattan Distance) 假设你要从家里走到学校,行径的距离肯定不会是两点...

番外篇(2)一起来做 To Do List!- 实作篇(3)

不知不觉也来到最後一篇啦! 第八步 在 codepen 上可以看到一些酷炫的汉堡选单 code ,但...

Day24:老板我等等来拿

Future介面定义有get()方法以及isDone()方法,其目的就是在呼叫get()时看看能不能...

Day 30 KubeEdge的使用心得与总结

KubeEdge 使用心得 KubeEdge的使用导向应是以"云边协同"、&qu...