目次

SPI slave handler

 マイクロコンピュータとCPLD/FPGAのインタフェースに
 SPIを利用したいことがある。

 SPIは、以下の4信号でデータ交換が可能。

 クロックを出力する側をマスター、入力する側を
 スレーブと考えていけばよい。

 CPLDに、DCモータ制御回路を2ブロック用意し
 マイコンからDUTY比を渡すため、インタフェース
 でSPIを利用してみる。

 パラレルインタフェースでDUTY比を渡すと
 10本以上のワイヤーが必要になり、ミスが
 増えると考え、SPIを採用した。

 SPIのインタフェースで、スレーブ利用するには
 2つの方式がある。

 タイミングチャートでみると、以下。



 SPIのモード2、3がスレーブモードとなるが
 シフトとラッチのどちらを先行させるかの違い
 になる。
 モード2では、falling_edgeで1ビットを記憶
 モード3では、rising_edgeで1ビットを記憶。

 今回は、モード3を採用した。
 VHDLコードで記述すると、スレーブ側のシフト
 レジスタは、次のようになる。

  process (nRESET,SCK)
  begin
    if ( nRESET = '0' ) then
      iMOSI_SFT <= X"00" ;
    elsif rising_edge(SCK) then
      if ( iSS = '1' ) then
        iMOSI_SFT <= iMOSI_SFT(6 downto 0) & iMOSI ;
      end if ;
    end if ;
  end process ;

 SSは、マイコンがマスターになる場合
 正論理、負論理のどちらもあるが負論理
 で扱うことにする。

 CPLD/FPGA内部では、正論理で動かすとして
 SSを論理反転して扱う。

iSS <= not nSS ;

 正論理で扱えば、iSSが'1'のときに
 内部シフトレジスタにMOSIの信号を
 入力していく。



 内部シフトレジスタにデータが入った後
 それを利用するためには、データ格納が
 終了したことを捉え、後処理回路を起動
 すればよい。

 SS信号は、データ転送が終わるとL→Hと
 変化するので、この変化を捉えて転送が
 終わったことを、後処理回路に通知。



 エッジを捉えるには、シフトレジスタを利用する。

  process (nRESET,CLOCK)
  begin
    if ( nRESET = '0' ) then
      iSS_SFT <= "000" ;
    elsif rising_edge(CLOCK) then
      iSS_SFT <= iSS_SFT(1 downto 0) & iSS ;
    end if ;
  end process ;
  iSTRG <= '1' when ( iSS_SFT = "110" ) else '0' ;

 iSTRGを後処理回路の動作開始トリガーに利用。

 後処理回路は、シーケンサを利用し
 与えられた情報を指定レジスタへと
 転送すればよい。

 シーケンサで、2チャネルのDCモータ制御
 回路のレジスタにDUTY比率を転送。

  process (nRESET,CLOCK)
  begin
    if ( nRESET = '0' ) then
      iSTATE <= "00" ;
      iREG   <= X"00" ;
      iLEFT  <= "0000000" ;
      iRIGHT <= "0000000" ;
    elsif rising_edge(CLOCK) then
      case conv_integer(iSTATE) is
        -- wait trigger
        when 0 => if ( iSTRG = '1' ) then
                    iSTATE <= "01" ;
                    iREG   <= iMOSI_SFT ;
                  else
                    iSTATE <= "00" ;
                  end if ;
        -- deliver
        when 1 => iSTATE <= "11" ;
                  if ( iREG(7) = '1' ) then -- left register
                    iLEFT <= iREG(6 downto 0) ;
                  else
                    iRIGHT <= iREG(6 downto 0) ;
                  end if ;
        -- clear register
        when 3 => iSTATE <= "10" ;
                  iREG   <= X"00" ;
        -- return first state
        when 2 => iSTATE <= "00" ;
        -- default
        when others => 
                  iSTATE <= "00" ;
      end case ;
    end if ;
  end process ;

 DUTY比は、0から99になるので、8ビットの
 MSBをチャネル選択ビットとして利用。

 チャネル数が多いときは、送信したデータを
 分割してタグをつけ、1バイトにまとめる。
 複数回の転送で、SPIスレーブに必要な情報を
 与える方式を採用する。

 DUTY比を制御回路に転送したなら、PWM波形
 生成は、次のように簡単に構成する。

  LPULSE <= '1' when ( iPCNT < iLEFTX  ) else '0' ;
  RPULSE <= '1' when ( iPCNT < iRIGHTX ) else '0' ;

  process (nRESET,PCLK)
  begin
    if ( nRESET = '0' ) then
      iPCNT   <= 0 ;
      iLEFTX  <= 0 ;
      iRIGHTX <= 0 ;
    elsif rising_edge(PCLK) then
      if ( iPCNT = 99 ) then
        iPCNT   <= 0 ;
        iLEFTX  <= conv_integer(iLEFT)  ;
        iRIGHTX <= conv_integer(iRIGHT) ;
      else
        iPCNT <= iPCNT + 1 ;
      end if ;
    end if ;
  end process ;

 まとめると以下。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity tstspi is
  port (
    -- system
    nRESET : in  std_logic ;
    CLOCK  : in  std_logic ; -- 4MHz
    -- SPI
    SCK    : in  std_logic ;
    nSS    : in  std_logic ;
    MOSI   : in  std_logic ;
    MISO   : out std_logic ;
    -- register select
    SEL    : in  std_logic ;
    -- pulse clock
    PCLK   : in  std_logic ; -- 10kHz
    -- pulse out
    LPULSE : out std_logic ;
    RPULSE : out std_logic ;
    -- register output
    REGOUT : out std_logic_vector(6 downto 0) --;
  );
end tstspi ;

architecture behavioral of tstspi is
  -- BUS interface
  signal iSS   : std_logic ;
  signal iMOSI : std_logic ;
  -- synchronizer 
  signal iSS_SFT : std_logic_vector(2 downto 0) ;
  signal iSTRG   : std_logic ;
  -- internal shift register
  signal iMOSI_SFT : std_logic_vector(7 downto 0) ;
  -- internal register
  signal iREG   : std_logic_vector(7 downto 0) ;
  signal iLEFT  : std_logic_vector(6 downto 0) ;
  signal iRIGHT : std_logic_vector(6 downto 0) ;
  -- sequencer
  signal iSTATE : std_logic_vector(1 downto 0) ;
  -- duty
  signal iPCNT   : integer range 0 to 99 ;
  signal iLEFTX  : integer range 0 to 99 ;
  signal iRIGHTX : integer range 0 to 99 ;
begin
  -- input
  iSS   <= not nSS ;
  iMOSI <= MOSI ;

  -- output
  MISO   <= iMOSI_SFT(7) ;
  REGOUT <= iLEFT when ( SEL = '1' ) else iRIGHT ;
  LPULSE <= '1' when ( iPCNT < iLEFTX  ) else '0' ;
  RPULSE <= '1' when ( iPCNT < iRIGHTX ) else '0' ;

  -- synchronizer
  process (nRESET,CLOCK)
  begin
    if ( nRESET = '0' ) then
      iSS_SFT <= "000" ;
    elsif rising_edge(CLOCK) then
      iSS_SFT <= iSS_SFT(1 downto 0) & iSS ;
    end if ;
  end process ;
  iSTRG <= '1' when ( iSS_SFT = "110" ) else '0' ;

  -- internal register
  process (nRESET,SCK)
  begin
    if ( nRESET = '0' ) then
      iMOSI_SFT <= X"00" ;
    elsif rising_edge(SCK) then
      if ( iSS = '1' ) then
        iMOSI_SFT <= iMOSI_SFT(6 downto 0) & iMOSI ;
      end if ;
    end if ;
  end process ;

  -- sequencer
  process (nRESET,CLOCK)
  begin
    if ( nRESET = '0' ) then
      iSTATE <= "00" ;
      iREG   <= X"00" ;
      iLEFT  <= "0000000" ;
      iRIGHT <= "0000000" ;
    elsif rising_edge(CLOCK) then
      case conv_integer(iSTATE) is
        -- wait trigger
        when 0 => if ( iSTRG = '1' ) then
                    iSTATE <= "01" ;
                    iREG   <= iMOSI_SFT ;
                  else
                    iSTATE <= "00" ;
                  end if ;
        -- deliver
        when 1 => iSTATE <= "11" ;
                  if ( iREG(7) = '1' ) then -- left register
                    iLEFT <= iREG(6 downto 0) ;
                  else
                    iRIGHT <= iREG(6 downto 0) ;
                  end if ;
        -- clear register
        when 3 => iSTATE <= "10" ;
                  iREG   <= X"00" ;
        -- return first state
        when 2 => iSTATE <= "00" ;
        -- default
        when others => 
                  iSTATE <= "00" ;
      end case ;
    end if ;
  end process ;

  -- generate pulse
  process (nRESET,PCLK)
  begin
    if ( nRESET = '0' ) then
      iPCNT   <= 0 ;
      iLEFTX  <= 0 ;
      iRIGHTX <= 0 ;
    elsif rising_edge(PCLK) then
      if ( iPCNT = 99 ) then
        iPCNT   <= 0 ;
        iLEFTX  <= conv_integer(iLEFT)  ;
        iRIGHTX <= conv_integer(iRIGHT) ;
      else
        iPCNT <= iPCNT + 1 ;
      end if ;
    end if ;
  end process ;

end behavioral;

 DUTY比を格納したレジスタの値を確認できる
 ように、6ビットの出力を用意。左右を指定
 するためのセレクタビットを入れてある。

 ピンアサインは、以下。

# system
NET "CLOCK"  LOC = "P5"  ;
NET "nRESET" LOC = "P39" ;

# SPI interface
NET "nSS"  LOC = "P1" ;
NET "MOSI" LOC = "P2" ;
NET "MISO" LOC = "P3" ;
NET "SCK"  LOC = "P6" ;
NET "PCLK" LOC = "P7" ;

# register output
NET "REGOUT<0>" LOC = "P11" ;
NET "REGOUT<1>" LOC = "P12" ;
NET "REGOUT<2>" LOC = "P13" ;
NET "REGOUT<3>" LOC = "P14" ;
NET "REGOUT<4>" LOC = "P18" ;
NET "REGOUT<5>" LOC = "P19" ;
NET "REGOUT<6>" LOC = "P20" ;
NET "SEL"       LOC = "P22" ;

# pulse output
NET "LPULSE" LOC = "P44" ;
NET "RPULSE" LOC = "P43" ;

 動作確認したボードは、XC9572を利用。



 SPIのマスターになるマイクロコンピュータは
 次の仕様でSPIインタフェースを指定。

 AVR、PICの内蔵モジュールを利用する場合
 上の仕様をレジスタに設定する。

 SPIのスレーブでD/Aコンバータを使うため
 このハンドラをテストした。下の基板にて
 実装するD/Aコンバータの動作テストとなった。




目次

inserted by FC2 system