目次
前
次
シリアル受信
プロトタイピングに利用しているFPGA基板で
シリアル送信ができたので、次は受信処理を
扱います。
次のような接続を想定してみます。
通信のためには、プロトコルが必要なので
次の仕様としました。
- データ通信速度 9600bps
- データ長 8ビット
- ストップビット 1ビット
- パリティ なし
- フロー制御 なし
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基板は、写真のようになっています。
目次
前
次