kivantium活動日記

プログラムを使っていろいろやります

FPGAでRS-232C通信を行う

FPGAが流行っているのでちゃんと勉強していこうシリーズです。

RS-232Cとは

シリアル通信を行う昔の規格です。送信に1本・受信に1本の信号線しか必要としないので手軽に通信を行えます。

最近のパソコンには端子がついていないのでUSBから変換して使うことが多いです。
秋月の超小型USBシリアル変換モジュールFT232RL USBシリアル変換モジュールはレベル変換も行ってくれるので便利です。

今使用しているFPGA基板(Papilio One 500K)にはFT232RLが内蔵されているので今回はこれをそのまま使いました。

回路はPC側のTXをFPGAのRXに、PC側のRXをFPGAのTXにつなぐようにし、Cピンの下位8bitにLEDをつけただけの単純なものです。
f:id:kivantium:20160225230748j:plain:w600

RS-232Cの通信は非同期式の場合とても簡単で、通常時は1(ストップビット)で、最初に1bitの0が来た後(スタートビット)に、8bit (設定による)分の信号が送られてきます。(設定によってはパリティビットなどもある)

1bit分の信号が持続する時間は1秒間に伝送するbit数を表すボーレートによって指定されます。今回は9600を使いました。

設計

  • 初期状態は待機モード
  • TXピン(受信信号が入る)が立ち下がったら受信モードに移行
  • 受信モードでは3333クロック(32MHz/9600bps)ごとに違うビットの信号だと考える
  • 各ビット信号の中間でTXピンを読み取りdataに格納(本当はノイズ対策をしっかり考えたほうがよいのだがサボった)
  • 8bit受信し終わったら送信モードに移行
  • 各bit情報を3333クロックの間RXピンに出力する
  • 8bit送信し終わったら待機モードに移行

TXピンに入った信号は一度バッファで受けていて、待機モードでは「2つ前のクロックでH and 1つ前のクロックでL」の場合立ち下がりとしています。

以下ソースコードです

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

entity main is
    Port ( A : out  STD_LOGIC_VECTOR (15 downto 0);
           B : out  STD_LOGIC_VECTOR (15 downto 0);
           C : out  STD_LOGIC_VECTOR (15 downto 0);
           TX: in STD_LOGIC;
           RX: out STD_LOGIC;
           clk : in STD_LOGIC);
end main;

architecture Behavioral of main is
    signal data: std_logic_vector(7 downto 0) := (others => '0');
    signal counter : std_logic_vector(11 downto 0) := (others => '0');
    signal current_bit : std_logic_vector(3 downto 0) := (others => '0');
    signal state : std_logic_vector(1 downto 0) := (others => '0');
    signal recv_buf : std_logic := '0';
    signal recv_buf_before : std_logic := '0';

begin
process(clk)begin
    if rising_edge(clk) then
        recv_buf <= TX;
        if state = 0 then        -- current state is wait
            RX <= '1';
            recv_buf_before <= recv_buf;
            state(0) <= (not recv_buf) and recv_buf_before;
        elsif state = 1 then     -- current state is receiving
            RX <= '1';
            if counter = 3333 then
                counter <= (others => '0');
                if current_bit = 8 then
                    current_bit <= "0000";
                    state <= "10";
                    recv_buf_before <= '0';
                else
                    current_bit <= current_bit + 1;
                end if;
            elsif counter = 1500 then
                case current_bit is
                    when "0000" => null;
                    when "0001" => data(0) <= recv_buf;
                    when "0010" => data(1) <= recv_buf;
                    when "0011" => data(2) <= recv_buf;
                    when "0100" => data(3) <= recv_buf;
                    when "0101" => data(4) <= recv_buf;
                    when "0110" => data(5) <= recv_buf;
                    when "0111" => data(6) <= recv_buf;
                    when "1000" => data(7) <= recv_buf;
                    when others => null;
                end case;
                counter <= counter + 1;
            else
                counter <= counter + 1;
            end if;
        else                   -- current state is transmitting
            if counter = 3333 then
                counter <= (others => '0');
                if current_bit = 8 then
                    current_bit <= "0000";
                    state <= "00";
                else
                    current_bit <= current_bit + 1;
                end if;
            else
                case current_bit is
                    when "0000" => RX <= '0';
                    when "0001" => RX <= data(0);
                    when "0010" => RX <= data(1);
                    when "0011" => RX <= data(2);
                    when "0100" => RX <= data(3);
                    when "0101" => RX <= data(4);
                    when "0110" => RX <= data(5);
                    when "0111" => RX <= data(6);
                    when "1000" => RX <= data(7);
                    when others => null;
                end case;
                counter <= counter + 1;
            end if;
        end if;
    end if;
end process;
A <= "0000000000000000";
B <= "0000000000000000";
C <= "00000000" & data;
end Behavioral;

FPGAに書き込んで、gtktermなどのシリアルコンソールとつなぎます。

動作の様子はこんな感じです。

画面はこんな感じになって、ちゃんと送信した通りの信号を受信していることが分かります。
f:id:kivantium:20160225232554p:plain:w600

RS-232Cで通信できるようになり計算結果などを取得しやすくなったので今度は浮動小数点数の計算とかをやろうかなと思っています。