目次

シリアル受信

 プロトタイピングに利用しているFPGA基板で
 シリアル送信ができたので、次は受信処理を
 扱います。

 次のような接続を想定してみます。



 通信のためには、プロトコルが必要なので
 次の仕様としました。

 FPGA内部のブロック図を描いて考えます。



 送信処理は、すでに作成してあるので
 受信処理を設計すればよいでしょう。

 調歩同期式を利用する場合、送信側からスタートビットが
 送信されてきます。このスタートビットを捉えて、カウンタ
 で時間調整しつつ、8ビットをシフトレジスタに格納です。



 サンプリングが必要になるので、この周期を
 データ転送速度の4、8、16、32、64倍程度に
 します。

 今回は、倍率を8倍として考えてみました。

 9600Hz x 8 = 76.8kHz

 送信クロックは9.6kHz、受信クロックは76.8kHzと
 して、受信クロックから送信クロックを作ります。

 システムクロックから76.8kHzを生成し、さらに
 8分周して、9.6kHzにします。



 76.8kHzは受信に利用するクロックなので、48MHzを
 625分周します。
  constant SCNTMAX : integer := 624 ; -- 48000kHz / 625 = 76.8kHz

  process (nRESET,CLOCK)
  begin
    if ( nRESET = '0' ) then
      iSCNT  <= 0 ;
      iPHASE <= "000" ;
    elsif rising_edge(CLOCK) then
      if ( iSCNT = SCNTMAX ) then
        iSCNT  <= 0 ;
        iPHASE <= iPHASE + '1' ;
      else
        iSCNT <= iSCNT + 1 ;
      end if ;
    end if ;
  end process ;
  iCLKX <= '1' when ( iSCNT = 0 ) else '0' ;
  iPOUT <= '1' when ( iPHASE = "000" ) else '0' ;

 9.6kHzは送信に利用するクロックなので、76.8kHzを
 8分周します。

 1/(9600x8)秒ごとに、iCLKXが'1'になる他に
 位相を扱う変数iPHASEを用意し、この値が0に
 なるとき、8x1/(9600x8)秒になります。
 iPHASEを利用し、9600bpsの1クロックの時間差
 を作ります。

 受信処理はシーケンサを利用し、1ビットデータを
 シフトレジスタに入れていきます。スタートビット
 をシフトレジスタで捕捉します。



 a RxDがHからLに変化したことを捉える。
    受信クロックで、位相用カウンタの値を
    0〜7まで+1していきます。

   シフトレジスタの3ビットで、H→Lを
   検出します。そのときの位相用カウンタ
   の値に2を加算し、1/(9600x8)秒の中間
   になるようにしておきます。

 b 位相用カウンタがaで設定した値で、RxDが
   0になっていることを確認します。
   スタートビットであることを確認します。

   スタートビットの次から8ビットのデータが
   送信されてくるので、位相用カウンタの値を
   参照して、1ビットずつデータを記憶します。

 c 位相用カウンタの値が、記憶している値になる
   ごとに、シフトレジスタにデータを記録して
   1バイトに仕上げます。

 この考え方で、スタートビットは、1/(9600x8)秒ごとに
 サンプリングして確認します。

 シーケンサの定義は、以下。

  process (nRESET,iCLKX)
  begin
    if ( nRESET = '0' ) then
      iRCV_STATE <= 0 ;
      iRCV_CNT   <= 0 ;
      iRCV_PHASE <= "000" ;
      iRCV_BUF   <= X"00" ;
      iREG       <= X"00" ;
    elsif rising_edge(iCLKX) then
      case iRCV_STATE is
        -- wait RxD falling edge
        when 0 => if ( iRCV_START = '1' ) then
                    iRCV_STATE <= 1 ;
                    iRCV_PHASE <= iPHASE + "010" ; -- phase_count + 2
                  else
                    iRCV_STATE <= 0 ;
                    iRCV_PHASE <= "000" ;
                  end if ;
        -- confirm RxD = '0' (start bit) 
        when 1 => if ( iRCV_PHASE = iPHASE and iRxD = '0' ) then
                    iRCV_STATE <= 2 ;
                  else
                    iRCV_STATE <= 1 ;
                  end if ;
        -- skip same state with phase count
        when 2 => if ( iPHASE /= iRCV_PHASE ) then
                    iRCV_STATE <= 3 ;
                    iRCV_CNT   <= 0 ;
                    iRCV_BUF   <= X"00" ;
                  else
                    iRCV_STATE <= 2 ;
                  end if ;
        -- get bit data (0 -> 7)
        when 3 => if ( iRCV_PHASE = iPHASE ) then
                    iRCV_BUF   <= iRxD & iRCV_BUF(7 downto 1) ;
                    iRCV_CNT   <= iRCV_CNT + 1 ;
                    iRCV_STATE <= 4 ;
                  else
                    iRCV_STATE <= 3 ;
                  end if ;
        -- skip same state with phase count
        when 4 => if ( iRCV_PHASE /= iPHASE ) then
                    if ( iRCV_CNT = RCVCNTMAX ) then
                      iRCV_STATE <= 5 ;
                    else
                      iRCV_STATE <= 3 ;
                    end if;
                  else
                    iRCV_STATE <= 4 ;
                  end if ;
        -- confirm RxD = '1' (stop bit) 
        when 5 => if ( iRCV_PHASE = iPHASE and iRxD = '1' ) then
                    iRCV_STATE <= 6 ;
                    iREG       <= iRCV_BUF ;
                  else
                    iRCV_STATE <= 5 ;
                  end if ;
        -- post handling
        when 6 => if ( iRCV_PHASE /= iPHASE and iRxD = '1' ) then
                    iRCV_STATE <= 7 ;
                    iRCV_PHASE <= "000" ;
                  else
                    iRCV_STATE <= 6 ;
                  end if ;
        -- judge interpreter is idle
        when 7 => if ( iSTATE = 0 ) then
                    iRCV_STATE <= 8 ;
                  else
                    iRCV_STATE <= 7 ;
                  end if ;
        -- send trigger
        when 8 => iRCV_STATE <= 9 ;
        -- return first state
        when 9 => iRCV_STATE <= 0 ;
        -- default
        when others =>
                  iRCV_STATE <= 0 ;
      end case ;
    end if ;
  end process ;
  iRCV_EXIT <= '1' when ( iRCV_STATE = 7 ) else '0' ;

 受信した1バイトは、シフトレジスタに保存後
 破壊されないように、バッファレジスタに複写
 します。

 バッファレジスタに複写後、ストップビットを
 確認します。次に、コマンドインタプリタが
 待っていることを確認し、データを渡します。

 受信処理は与えられた仕事が終わったなら、コマンド
 インタプリタの状態を見て、適切なタイミングで値を
 渡します。

 PC(Personal Computer)から1文字受信したことが
 わかるように、LEDに受信文字を表示したり、エコー
 バックします。ここでは、受信文字の次の文字を
 生成して、PCに送信します。

 コマンドインタプリタのシーケンサを定義します。

  process (nRESET,iCLKX)
  begin
    if ( nRESET = '0' ) then
      iSTATE <= "000" ;
      iDATA  <= X"00" ;
    elsif rising_edge(iCLKX) then
      case conv_integer(iSTATE) is
        -- wait trigger
        when 0 => if ( iRCV_EXIT = '1' ) then
                    iSTATE <= "001" ;
                  else
                    iSTATE <= "000" ;
                  end if ;
        -- copy
        when 1 => iDATA  <= iREGX ;
                  iSTATE <= "011" ;
        -- conversion
        when 3 => iDATA  <= iDATA + '1' ;
                  iSTATE <= "111" ;
        -- judge
        when 7 => if ( iTXD_STATE = 0 ) then
                    iSTATE <= "110" ;
                    iCNT   <= 8 ;
                  else
                    iSTATE <= "111" ;
                  end if ;
        -- send trigger
        when 6 => if ( iCNT = 0 ) then
                    iSTATE <= "100" ;
                  else
                    iCNT   <= iCNT - 1 ;
                    iSTATE <= "110" ;
                  end if ;
        -- return first state 
        when 4 => iSTATE <= "000" ;
        -- default
        when others =>
                  iSTATE <= "000" ;
      end case ;
    end if ;
  end process ;
  iTXD_TRG <= '1' when ( iSTATE = "110" ) else '0' ;

 受信処理ブロックからのトリガーで、データを受取り
 バッファレジスタに保存します。

 自分の管理できるレジスタバッファを利用し
 記憶している値に+1します。

 この演算を終えたなら、送信ブロックの状態を
 確認してからトリガーで動作開始を指示して
 動作を終えます。

 データ送信処理は9.6kHzで動作しているので
 トリガーを確実に捕捉できるようにストレッチ
 しておかないと、PC側で文字化けがおきます。

 送信ブロックは、コマンドインタプリタからの
 トリガーで動作するように構成します。

  process (nRESET,iPOUT)
  begin
    if ( nRESET = '0' ) then
      iTXD_STATE <= 0 ;
      iTXD_DAT   <= X"FF" ;
      iTXD_SFT   <= (others => '1') ;
      iTXD       <= '1' ;
    elsif rising_edge(iPOUT) then
      case iTXD_STATE is
        -- wait trigger
        when 0 => if ( iTXD_TRG = '1' ) then
                    iTXD_STATE <= 1 ;
                    iTXD_DAT   <= iDATA ;
                  else
                    iTXD_STATE <= 0 ;
                  end if ;
                  iTxD <= '1' ;
        -- generate code
        when 1 => iTXD_SFT   <= iTXD_DAT ;
                  iTXD       <= '1' ;
                  iTXD_STATE <= 2 ;
        -- send data (start bit);
        when 2 => iTXD <= '0' ;
                  iTXD_STATE <= 3 ;
        -- send data (0 bit);
        when 3 => iTXD <= iTXD_SFT(0) ;
                  iTXD_STATE <= 4 ;
        -- send data (1 bit);
        when 4 => iTXD <= iTXD_SFT(1) ;
                  iTXD_STATE <= 5 ;
        -- send data (2 bit);
        when 5 => iTXD <= iTXD_SFT(2) ;
                  iTXD_STATE <= 6 ;
        -- send data (3 bit);
        when 6 => iTXD <= iTXD_SFT(3) ;
                  iTXD_STATE <= 7 ;
        -- send data (4 bit);
        when 7 => iTXD <= iTXD_SFT(4) ;
                  iTXD_STATE <= 8 ;
        -- send data (5 bit);
        when 8 => iTXD <= iTXD_SFT(5) ;
                  iTXD_STATE <= 9 ;
        -- send data (6 bit);
        when 9 => iTXD <= iTXD_SFT(6) ;
                  iTXD_STATE <= 10 ;
        -- send data (7 bit);
        when 10 => iTXD <= iTXD_SFT(7) ;
                   iTXD_STATE <= 11 ;
        -- return first state
        when 11 => iTxD       <= '1' ;
                   iTXD_STATE <= 0 ;
        -- default
        when others =>
                   iTxD       <= '1' ;
                   iTXD_STATE <= 0 ;
      end case ;
    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 scitst is
  port (
    -- system 
    nRESET : in  std_logic ;
    CLOCK  : in  std_logic ; -- 48MHz
    -- serial data
    RxD    : in  std_logic ;
    TxD    : out std_logic ;
    -- sampling clock
    RCVCLK : out std_logic ;
    POUT   : out std_logic ;
    PHASE  : out std_logic_vector(7 downto 0) ;
    -- monitor
    RxDOUT : out std_logic_vector(7 downto 0) --;
  );
end scitst;

architecture Behavioral of scitst is
  -- constant values
  constant SCNTMAX     : integer := 624 ; -- 48000kHz / 625 = 76.8kHz
  constant RCVSTATEMAX : integer := 9  ;
  constant RCVCNTMAX   : integer := 8  ;
  constant TXDCNTMAX   : integer := 9  ;
  -- internal clock
  signal iSCNT  : integer range 0 to SCNTMAX ;
  signal iCLKX  : std_logic ;
  signal iPHASE : std_logic_vector(2 downto 0) ;
  signal iPOUT  : std_logic ;
  -- serial data buffer and register
  signal iRxD       : std_logic ;
  signal iRCV_SFT   : std_logic_vector(3 downto 0) ;
  signal iRCV_PHASE : std_logic_vector(2 downto 0) ;
  signal iRCV_START : std_logic ;
  signal iRCV_BUF   : std_logic_vector(7 downto 0) ;
  signal iRCV_EXIT  : std_logic ;
  signal iREG       : std_logic_vector(7 downto 0) ;
  signal iREGX      : std_logic_vector(7 downto 0) ;
  signal iTXD       : std_logic ;
  signal iTXD_SFT   : std_logic_vector(7 downto 0) ;
  signal iDATA      : std_logic_vector(7 downto 0) ;
  -- sequencer
  signal iRCV_STATE : integer range 0 to RCVSTATEMAX ;
  signal iRCV_CNT   : integer range 0 to RCVCNTMAX   ;
  signal iTXD_STATE : integer range 0 to 11 ;
  signal iSTATE     : std_logic_vector(2 downto 0) ;
  signal iCNT       : integer range 0 to 8 ;
  signal iTXD_TRG   : std_logic ;
  signal iTXD_DAT   : std_logic_vector(7 downto 0) ;
begin
  -- output
  RxDOUT <= not iREGX ;
  PHASE  <= not ("00000" & iRCV_PHASE) ;
  RCVCLK <= iCLKX ; -- 76.8kHz
  TxD    <= iTXD ;
  POUT   <= iPOUT ; -- 9600bps

  -- input
  iRxD <= RxD ;

  -- generate 76.8kHz (9600bps x 8)
  process (nRESET,CLOCK)
  begin
    if ( nRESET = '0' ) then
      iSCNT  <= 0 ;
      iPHASE <= "000" ;
    elsif rising_edge(CLOCK) then
      if ( iSCNT = SCNTMAX ) then
        iSCNT  <= 0 ;
        iPHASE <= iPHASE + '1' ;
      else
        iSCNT <= iSCNT + 1 ;
      end if ;
    end if ;
  end process ;
  iCLKX <= '1' when ( iSCNT = 0 ) else '0' ;
  iPOUT <= '1' when ( iPHASE = "000" ) else '0' ;

  -- judge RxD falling edge
  process (nRESET,iCLKX)
  begin
    if ( nRESET = '0' ) then
      iRCV_SFT <= "1111" ;
    elsif rising_edge(iCLKX) then
      iRCV_SFT <= iRCV_SFT(2 downto 0) & iRxD ;
    end if ;
  end process ;
  iRCV_START <= '1' when ( iRCV_SFT(3 downto 1) = "100" or iRCV_SFT(3 downto 1) = "110" ) else '0' ;

  -- receive sequencer
  process (nRESET,iCLKX)
  begin
    if ( nRESET = '0' ) then
      iRCV_STATE <= 0 ;
      iRCV_CNT   <= 0 ;
      iRCV_PHASE <= "000" ;
      iRCV_BUF   <= X"00" ;
      iREG       <= X"00" ;
    elsif rising_edge(iCLKX) then
      case iRCV_STATE is
        -- wait RxD falling edge
        when 0 => if ( iRCV_START = '1' ) then
                    iRCV_STATE <= 1 ;
                    iRCV_PHASE <= iPHASE + "010" ; -- phase_count + 2
                  else
                    iRCV_STATE <= 0 ;
                    iRCV_PHASE <= "000" ;
                  end if ;
        -- confirm RxD = '0' (start bit) 
        when 1 => if ( iRCV_PHASE = iPHASE and iRxD = '0' ) then
                    iRCV_STATE <= 2 ;
                  else
                    iRCV_STATE <= 1 ;
                  end if ;
        -- skip same state with phase count
        when 2 => if ( iPHASE /= iRCV_PHASE ) then
                    iRCV_STATE <= 3 ;
                    iRCV_CNT   <= 0 ;
                    iRCV_BUF   <= X"00" ;
                  else
                    iRCV_STATE <= 2 ;
                  end if ;
        -- get bit data (0 -> 7)
        when 3 => if ( iRCV_PHASE = iPHASE ) then
                    iRCV_BUF   <= iRxD & iRCV_BUF(7 downto 1) ;
                    iRCV_CNT   <= iRCV_CNT + 1 ;
                    iRCV_STATE <= 4 ;
                  else
                    iRCV_STATE <= 3 ;
                  end if ;
        -- skip same state with phase count
        when 4 => if ( iRCV_PHASE /= iPHASE ) then
                    if ( iRCV_CNT = RCVCNTMAX ) then
                      iRCV_STATE <= 5 ;
                    else
                      iRCV_STATE <= 3 ;
                    end if;
                  else
                    iRCV_STATE <= 4 ;
                  end if ;
        -- confirm RxD = '1' (stop bit) 
        when 5 => if ( iRCV_PHASE = iPHASE and iRxD = '1' ) then
                    iRCV_STATE <= 6 ;
                    iREG       <= iRCV_BUF ;
                  else
                    iRCV_STATE <= 5 ;
                  end if ;
        -- post handling
        when 6 => if ( iRCV_PHASE /= iPHASE and iRxD = '1' ) then
                    iRCV_STATE <= 7 ;
                    iRCV_PHASE <= "000" ;
                  else
                    iRCV_STATE <= 6 ;
                  end if ;
        -- judge interpreter is idle
        when 7 => if ( iSTATE = 0 ) then
                    iRCV_STATE <= 8 ;
                  else
                    iRCV_STATE <= 7 ;
                  end if ;
        -- send trigger
        when 8 => iRCV_STATE <= 9 ;
        -- return first state
        when 9 => iRCV_STATE <= 0 ;
        -- default
        when others =>
                  iRCV_STATE <= 0 ;
      end case ;
    end if ;
  end process ;
  iRCV_EXIT <= '1' when ( iRCV_STATE = 7 ) else '0' ;

  process (nRESET,iCLKX)
  begin
    if ( nRESET = '0' ) then
      iREGX <= X"00" ;
    elsif rising_edge(iCLKX) then
      if ( iRCV_STATE = 6 ) then
        iREGX <= iREG ;
      end if ;
    end if ;
  end process ;

  -- send sequencer (9600bps)
  process (nRESET,iPOUT)
  begin
    if ( nRESET = '0' ) then
      iTXD_STATE <= 0 ;
      iTXD_DAT   <= X"FF" ;
      iTXD_SFT   <= (others => '1') ;
      iTXD       <= '1' ;
    elsif rising_edge(iPOUT) then
      case iTXD_STATE is
        -- wait trigger
        when 0 => if ( iTXD_TRG = '1' ) then
                    iTXD_STATE <= 1 ;
                    iTXD_DAT   <= iDATA ;
                  else
                    iTXD_STATE <= 0 ;
                  end if ;
                  iTxD <= '1' ;
        -- generate code
        when 1 => iTXD_SFT   <= iTXD_DAT ;
                  iTXD       <= '1' ;
                  iTXD_STATE <= 2 ;
        -- send data (start bit);
        when 2 => iTXD <= '0' ;
                  iTXD_STATE <= 3 ;
        -- send data (0 bit);
        when 3 => iTXD <= iTXD_SFT(0) ;
                  iTXD_STATE <= 4 ;
        -- send data (1 bit);
        when 4 => iTXD <= iTXD_SFT(1) ;
                  iTXD_STATE <= 5 ;
        -- send data (2 bit);
        when 5 => iTXD <= iTXD_SFT(2) ;
                  iTXD_STATE <= 6 ;
        -- send data (3 bit);
        when 6 => iTXD <= iTXD_SFT(3) ;
                  iTXD_STATE <= 7 ;
        -- send data (4 bit);
        when 7 => iTXD <= iTXD_SFT(4) ;
                  iTXD_STATE <= 8 ;
        -- send data (5 bit);
        when 8 => iTXD <= iTXD_SFT(5) ;
                  iTXD_STATE <= 9 ;
        -- send data (6 bit);
        when 9 => iTXD <= iTXD_SFT(6) ;
                  iTXD_STATE <= 10 ;
        -- send data (7 bit);
        when 10 => iTXD <= iTXD_SFT(7) ;
                   iTXD_STATE <= 11 ;
        -- return first state
        when 11 => iTxD       <= '1' ;
                   iTXD_STATE <= 0 ;
        -- default
        when others =>
                   iTxD       <= '1' ;
                   iTXD_STATE <= 0 ;
      end case ;
    end if ;
  end process ;

  -- interpreter
  process (nRESET,iCLKX)
  begin
    if ( nRESET = '0' ) then
      iSTATE <= "000" ;
      iDATA  <= X"00" ;
    elsif rising_edge(iCLKX) then
      case conv_integer(iSTATE) is
        -- wait trigger
        when 0 => if ( iRCV_EXIT = '1' ) then
                    iSTATE <= "001" ;
                  else
                    iSTATE <= "000" ;
                  end if ;
        -- copy
        when 1 => iDATA  <= iREGX ;
                  iSTATE <= "011" ;
        -- conversion
        when 3 => iDATA  <= iDATA + '1' ;
                  iSTATE <= "111" ;
        -- judge
        when 7 => if ( iTXD_STATE = 0 ) then
                    iSTATE <= "110" ;
                    iCNT   <= 8 ;
                  else
                    iSTATE <= "111" ;
                  end if ;
        -- send trigger
        when 6 => if ( iCNT = 0 ) then
                    iSTATE <= "100" ;
                  else
                    iCNT   <= iCNT - 1 ;
                    iSTATE <= "110" ;
                  end if ;
        -- return first state 
        when 4 => iSTATE <= "000" ;
        -- default
        when others =>
                  iSTATE <= "000" ;
      end case ;
    end if ;
  end process ;
  iTXD_TRG <= '1' when ( iSTATE = "110" ) else '0' ;

end Behavioral;

 ピンアサインは、以下とします。

# system
NET "CLOCK"  LOC = "P55" ;
NET "nRESET" LOC = "P73" ;

# G0 (monitor LED)
NET "RxDOUT<0>" LOC = "P1"  ;
NET "RxDOUT<1>" LOC = "P2"  ;
NET "RxDOUT<2>" LOC = "P4"  ;
NET "RxDOUT<3>" LOC = "P5"  ;
NET "RxDOUT<4>" LOC = "P6"  ;
NET "RxDOUT<5>" LOC = "P7"  ;
NET "RxDOUT<6>" LOC = "P8"  ;
NET "RxDOUT<7>" LOC = "P10" ;

# G1
NET "RCVCLK" LOC = "P11" ;
NET "POUT"   LOC = "P12" ;

# G2
# group G2 (monitor signal output)
NET "PHASE<0>" LOC = "P27" | DRIVE = 8 | SLEW = SLOW ;
NET "PHASE<1>" LOC = "P28" | DRIVE = 8 | SLEW = SLOW ;
NET "PHASE<2>" LOC = "P31" | DRIVE = 8 | SLEW = SLOW ;
NET "PHASE<3>" LOC = "P32" | DRIVE = 8 | SLEW = SLOW ;
NET "PHASE<4>" LOC = "P33" | DRIVE = 8 | SLEW = SLOW ;
NET "PHASE<5>" LOC = "P35" | DRIVE = 8 | SLEW = SLOW ;
NET "PHASE<6>" LOC = "P36" | DRIVE = 8 | SLEW = SLOW ;
NET "PHASE<7>" LOC = "P40" | DRIVE = 8 | SLEW = SLOW ;

# serial interface
NET "TxD" LOC = "P60" ;
NET "RxD" LOC = "P63" ;

 手持ちのSpartan3ボードに、VHDLコードをダウンロードし
 TeraTermで操作すると、次のようになりました。



 ローカルエコーするようにしたので、abcdの入力に
 対してbcdeが返ってくることから、表示が「abbccdde」
 となっています。

 数字の0、3には、それぞれ1、4が返ってきて「0134」になります。

 スペースには、!が返ってきます。

 Spartan3基板は、写真のようになっています。




目次

inserted by FC2 system