目次
前
次
シリアル送信
プロトタイピングに利用しているXilinxのFPGA基板には
レガシータイプのシリアルインタフェースがあります。
マイクロコンピュータあるいはパーソナルコンピュータと
FPGAを接続するには、シリアルインタフェースがまだまだ
現役です。
はじめて、FPGAとパーソナルコンピュータを接続すること
挑戦したときは、冗長なVHDLコードを作成しました。
自分の理解できる範囲で、初学者でも少しの努力で内容を
把握できるVHDLコードを書くことに挑戦してみます。
通信のためには、プロトコルを決めなければならないので
次の仕様としました。
- データ通信速度 9600bps
- データ長 8ビット
- ストップビット 1ビット
- パリティ なし
- フロー制御 なし
自分の十八番であるシーケンサで、シリアル送信処理を
記述します。
次の手順でシリアル送信のVHDLコードを作成しました。
- シーケンサによる並列直列変換で10ビットデータ出力
- シリアル送信するデータをトグルスイッチで設定
- シーケンサを動かすトリガーを生成回路記述
- ダブルバッファ構成でデータを処理
- 各シーケンサ用クロック生成
順を追って、回路を定義していきます。
並列直列変換
文字定数'A'を送信するときは、01000001にスタートビット
ストップビットを加えて1010000010の10ビットにします。
トリガーを貰ったなら、10ビットをスタートビットを出力後
LSBから8ビット、ストップビット1ビットを出力するように
シーケンサを構成すれば充分と判断しました。
状態遷移図を描いてみると、以下。
シーケンサの動作を書きます。
- トリガー待ち(トリガーが来たなら10ビットデータをコピー)
- スタートビット送信
- LSB送信
- 2^1ビット送信
- 2^2ビット送信
- 2^3ビット送信
- 2^4ビット送信
- 2^5ビット送信
- 2^6ビット送信
- MSB送信
- ストップビット送信
- 1に戻る
クロックをiCLK、トリガーをiTRGとし
ビットカウンタiTXCNTを使いシーケンサ
を記述します。
process (nRESET,iCLK)
begin
if ( nRESET = '0' ) then
iSTATE <= 0 ;
iDINXX <= (others => '1') ;
elsif rising_edge( iCLK ) then
case iSTATE is
-- wait trigger
when 0 => if ( iTRG = '1' ) then
iSTATE <= 1 ;
iDINXX <= iDINX ;
else
iSTATE <= 0 ;
end if ;
-- start bit
when 1 => iTXCNT <= 0 ;
iSTATE <= 2 ;
-- 2^0 (LSB)
when 2 => iTXCNT <= 1 ;
iSTATE <= 3 ;
-- 2^1
when 3 => iTXCNT <= 2 ;
iSTATE <= 4 ;
-- 2^2
when 4 => iTXCNT <= 3 ;
iSTATE <= 5 ;
-- 2^3
when 5 => iTXCNT <= 4 ;
iSTATE <= 6 ;
-- 2^4
when 6 => iTXCNT <= 5 ;
iSTATE <= 7 ;
-- 2^5
when 7 => iTXCNT <= 6 ;
iSTATE <= 8 ;
-- 2^6
when 8 => iTXCNT <= 7 ;
iSTATE <= 9 ;
-- 2^7 (MSB)
when 9 => iTXCNT <= 8 ;
iSTATE <= 10;
-- stop bit
when 10 => iTXCNT <= 9 ;
iSTATE <= 11;
-- return first state
when 11 => iSTATE <= 0 ;
iDINXX <= (others => '1') ;
-- default
when others =>
iSTATE <= 0 ;
end case ;
end if ;
end process;
トリガー生成
データ設定に、トグルスイッチを
送信にはプッシュスイッチを利用
します。
プッシュスイッチは、シンクロナイザを利用
して、データ入力のトリガーを実現。
-- input trigger (invert logical level)
iSEND <= not SEND ;
-- debouncing with 10kHz (synchronizer)
process (nRESET,iSCLK)
begin
if (nRESET = '0') then
iSEND_SFT <= "000";
elsif rising_edge(iSCLK) then
iSEND_SFT <= iSEND_SFT(1 downto 0) & iSEND ;
end if;
end process;
iSEND_TRG <= '1' when ( iSEND_SFT = "011" or iSEND_SFT = "001" ) else '0';
このままでは、何度も送信シーケンサにトリガーを
与えてしまうので、ワンショットになるような回路
を入れます。タイミングチャートでみると以下。
iSEND_TRGから、iTRGを生成する
には、シーケンサを利用します。
ハザードが出ないように、シーケンサは
ジョンソンカウンタを使います。
process (nRESET,iSCLK)
begin
if ( nRESET = '0' ) then
iTSTATE <= "00";
elsif rising_edge(iSCLK) then
case conv_integer(iTSTATE) is
-- wait trigger
when 0 => if ( iSEND_TRG = '1' ) then
iTSTATE <= "01" ;
else
iTSTATE <= "00" ;
end if ;
-- skip (send pulse)
when 1 => iTSTATE <= "11" ;
-- wait (release)
when 3 => if ( iSEND_TRG = '0' ) then
iTSTATE <= "10" ;
else
iTSTATE <= "11" ;
end if ;
-- return first state
when 2 => iTSTATE <= "00" ;
-- default
when others =>
iTSTATE <= "00" ;
end case ;
end if;
end process;
iTRG <= '1' when ( iTSTATE = "01" ) else '0' ;
状態遷移図で描くと、次のようになります。
データバッファリング
トグルスイッチにより送信データを設定するので
PLD内部にデータバッファを用意します。
データバッファは、10ビットレジスタとして
スタート、ストップビットを加えます。
トリガーを貰ってから、10ビットデータを
生成する回路を定義します。
process (nRESET, CLOCK)
begin
if (nRESET = '0') then
iDINX <= (others => '0');
elsif rising_edge(CLOCK) then
if ( iSEND_TRG = '1' ) then
iDINX(0) <= '0'; -- start bit
iDINX(8 downto 1) <= (not DINX); -- data
iDINX(9) <= '1'; -- stop bit
end if;
end if;
end process;
iLOUT <= iDINX(8 downto 1);
設定データが、内部レジスタに入っていることを
確認するため、基板上のLEDに8ビットデータを
表示します。
LOUT <= iLOUT;
クロックジェネレータ
利用しているFPGA基板は、50MHzクロックで
動作させるので、分周しシーケンサに必要な
クロックを生成します。
9600bpsは9.6kHzに相当するので、次の計算で
カウンタの最大値とDUTY比を50%にする値を
求めます。
9600bpsを生成する分周回路を定義します。
constant CNT_MAX : integer := 5208 ;
constant CNT_HALF : integer := 2603 ;
process (nRESET, CLOCK)
begin
if (nRESET = '0') then
iCNT <= 0;
iCLK <= '0' ;
elsif rising_edge(CLOCK) then
if ( iCNT < CNT_MAX ) then
iCNT <= iCNT + 1;
if ( iCNT < CNT_HALF ) then
iCLK <= '1' ;
else
iCLK <= '0' ;
end if ;
else
iCNT <= 0;
end if;
end if;
end process;
スイッチからデータを入力するトリガーの
ためにシンクロナイザを使います。それに
使うクロックを10kHzにします。
constant SCNTX_MAX : integer := 4999 ;
process (nRESET,CLOCK)
begin
if (nRESET = '0') then
iSCNTX <= 0 ;
elsif rising_edge(CLOCK) then
if ( iSCNTX < SCNTX_MAX ) then
iSCNTX <= iSCNTX + 1 ;
else
iSCNTX <= 0 ;
end if ;
end if;
end process;
iSCLK <= '1' when ( iSCNTX = 0 ) else '0' ;
ブロック図
シリアル送信の内部回路を
ブロック図でまとめます。
全ソースコード
ブロック図から、VHDLの最終ソースコードを作成します。
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity test701a is
port (
-- system
nRESET : in std_logic ;
CLOCK : in std_logic ;
-- trigger
SEND : in std_logic ;
-- data
DINX : in std_logic_vector(7 downto 0) ;
-- serial data
TxD : out std_logic ;
-- monitor
LOUT : out std_logic_vector(7 downto 0) --;
);
end test701a;
architecture Behavioral of test701a is
-- constant values
constant SCNTX_MAX : integer := 4999 ;
constant CNT_MAX : integer := 5208 ;
constant CNT_HALF : integer := 2603 ;
-- buffer
signal iDINX : std_logic_vector(9 downto 0); -- input buffer
signal iDINXX : std_logic_vector(9 downto 0); -- serial buffer
signal iLOUT : std_logic_vector(7 downto 0); -- LED monitor
-- trigger
signal iSEND : std_logic;
signal iSEND_TRG : std_logic;
signal iSEND_SFT : std_logic_vector(2 downto 0);
signal iCNT : integer range 0 to CNT_MAX ;
-- sequencer
signal iCLK : std_logic ;
signal iTRG : std_logic;
signal iSTATE : integer range 0 to 11;
signal iTXCNT : integer range 0 to 9;
-- divider
signal iSCLK : std_logic ;
signal iSCNTX : integer range 0 to SCNTX_MAX ;
-- one shot pulse controller
signal iTSTATE : std_logic_vector(1 downto 0) ;
begin
-- output
TxD <= iDINXX(iTXCNT) ;
LOUT <= iLOUT;
-- input trigger (invert logical level)
iSEND <= not SEND ;
-- clock divider (50MHz -> 10kHz)
process (nRESET,CLOCK)
begin
if (nRESET = '0') then
iSCNTX <= 0 ;
elsif rising_edge(CLOCK) then
if ( iSCNTX < SCNTX_MAX ) then
iSCNTX <= iSCNTX + 1 ;
else
iSCNTX <= 0 ;
end if ;
end if;
end process;
iSCLK <= '1' when ( iSCNTX = 0 ) else '0' ;
-- debouncing with 10kHz (synchronizer)
process (nRESET,iSCLK)
begin
if (nRESET = '0') then
iSEND_SFT <= "000";
elsif rising_edge(iSCLK) then
iSEND_SFT <= iSEND_SFT(1 downto 0) & iSEND ;
end if;
end process;
iSEND_TRG <= '1' when ( iSEND_SFT = "011" or iSEND_SFT = "001" ) else '0';
-- generate one shot pulse
process (nRESET,iSCLK)
begin
if ( nRESET = '0' ) then
iTSTATE <= "00";
elsif rising_edge(iSCLK) then
case conv_integer(iTSTATE) is
-- wait trigger
when 0 => if ( iSEND_TRG = '1' ) then
iTSTATE <= "01" ;
else
iTSTATE <= "00" ;
end if ;
-- skip (send pulse)
when 1 => iTSTATE <= "11" ;
-- wait (release)
when 3 => if ( iSEND_TRG = '0' ) then
iTSTATE <= "10" ;
else
iTSTATE <= "11" ;
end if ;
-- return first state
when 2 => iTSTATE <= "00" ;
-- default
when others =>
iTSTATE <= "00" ;
end case ;
end if;
end process;
iTRG <= '1' when ( iTSTATE = "01" ) else '0' ;
-- data buffering
process (nRESET, CLOCK)
begin
if (nRESET = '0') then
iDINX <= (others => '0');
elsif rising_edge(CLOCK) then
if ( iSEND_TRG = '1' ) then
iDINX(0) <= '0'; -- start bit
iDINX(8 downto 1) <= (not DINX); -- data
iDINX(9) <= '1'; -- stop bit
end if;
end if;
end process;
iLOUT <= iDINX(8 downto 1);
-- generate 9600bps clock
process (nRESET, CLOCK)
begin
if (nRESET = '0') then
iCNT <= 0;
iCLK <= '0' ;
elsif rising_edge(CLOCK) then
if ( iCNT < CNT_MAX ) then
iCNT <= iCNT + 1;
if ( iCNT < CNT_HALF ) then
iCLK <= '1' ;
else
iCLK <= '0' ;
end if ;
else
iCNT <= 0;
end if;
end if;
end process;
-- TxD data send sequencer
process (nRESET,iCLK)
begin
if ( nRESET = '0' ) then
iSTATE <= 0 ;
iDINXX <= (others => '1') ;
elsif rising_edge( iCLK ) then
case iSTATE is
-- wait trigger
when 0 => if ( iTRG = '1' ) then
iSTATE <= 1 ;
iDINXX <= iDINX ;
else
iSTATE <= 0 ;
end if ;
-- start bit
when 1 => iTXCNT <= 0 ;
iSTATE <= 2 ;
-- 2^0 (LSB)
when 2 => iTXCNT <= 1 ;
iSTATE <= 3 ;
-- 2^1
when 3 => iTXCNT <= 2 ;
iSTATE <= 4 ;
-- 2^2
when 4 => iTXCNT <= 3 ;
iSTATE <= 5 ;
-- 2^3
when 5 => iTXCNT <= 4 ;
iSTATE <= 6 ;
-- 2^4
when 6 => iTXCNT <= 5 ;
iSTATE <= 7 ;
-- 2^5
when 7 => iTXCNT <= 6 ;
iSTATE <= 8 ;
-- 2^6
when 8 => iTXCNT <= 7 ;
iSTATE <= 9 ;
-- 2^7 (MSB)
when 9 => iTXCNT <= 8 ;
iSTATE <= 10;
-- stop bit
when 10 => iTXCNT <= 9 ;
iSTATE <= 11;
-- return first state
when 11 => iSTATE <= 0 ;
iDINXX <= (others => '1') ;
-- default
when others =>
iSTATE <= 0 ;
end case ;
end if ;
end process;
end Behavioral;
VHDLコードに続けて、ピンアサインを考えた
UCFを定義します。
# system
NET "nRESET" LOC = "P107" ;
NET "CLOCK" LOC = "P56" ;
# transmitt data
NET "TxD" LOC = "P66" ;
# monitor
NET "LOUT<0>" LOC = "P132" ;
NET "LOUT<1>" LOC = "P124" ;
NET "LOUT<2>" LOC = "P113" ;
NET "LOUT<3>" LOC = "P112" ;
NET "LOUT<4>" LOC = "P117" ;
NET "LOUT<5>" LOC = "P116" ;
NET "LOUT<6>" LOC = "P123" ;
NET "LOUT<7>" LOC = "P122" ;
# data
NET "DINX<0>" LOC = "P111" ;
NET "DINX<1>" LOC = "P114" ;
NET "DINX<2>" LOC = "P136" ;
NET "DINX<3>" LOC = "P141" ;
NET "DINX<4>" LOC = "P120" ;
NET "DINX<5>" LOC = "P119" ;
NET "DINX<6>" LOC = "P129" ;
NET "DINX<7>" LOC = "P128" ;
# trigger
NET "SEND" LOC = "P18" ;
#(P18は、プッシュスイッチPB2に接続されている)
トグルスイッチで01000001、01000010、01000011を
設定すると、次の表示に。
(端末ソフトはTeraTerm。OSはWindows7)
目次
前
次