目次

カメラ画像処理

 カメラとして、OV7670利用を考えていました。



 自分の得意分野であるFPGA/CPLD内部回路開発で
 低機能のプロセッサを設計したことから、OV7670
 の利用をひかえることに。

 MCR_VCでは、カメラ互換センサーを利用することが
 前提なので過去のmugenで使ったBacCodeScanner(BCS)
 をセンサーにすると方針転換です。



 BCSは、LED光をコース面に投射するので利用を
 考えていた高輝度LEDによる大電力消費を抑える
 こともできます。



 BCSはバーコードの縞模様を判断するほどの解像度
 をもつので、MCRのコース面程度の判定は朝飯前。

 BCSのデータシートから、どのようにコース面の
 情報を入力し、センサーデータとして抽出する
 かを考えました。

 データシートにあったタイミングチャートを眺めて
 FPGA内部に2048ビットのデータ入力を考えます。



 タイミングチャートから、次のことが理解できました。

 BCSはバーコードリーダに入っていたので、リーダの
 内蔵マイコンで画像データを取得していました。
 そのインタフェースは6ピンで次のように接続。
  1. Vcc
  2. LED_CON
  3. (no connection)
  4. Vout
  5. enable <-> P50
  6. GND
 タイミングチャートからVoutが、BCSの画像データ  出力とわかったので、FPGAに取り込むために次の  ように接続することに。
  1. Vcc
  2. SH(START)
  3. φ1(CLKA)
  4. φ2(CLKB)
  5. Vout(CDAT)
  6. GND
 START、CLKA、CLKBはrising_edgeでBCS内部の  CCDの動作タイミングを決めています。  FPGA内部とBCSを同期させるため、シフトレジスタ  を利用した同期信号を生成します。 -- BCS synchronizer process (nRESET,iBCLK) begin if ( nRESET = '0' ) then iSTART_SFT <= "000" ; iCLKA_SFT <= "00" ; iCLKB_SFT <= "00" ; elsif rising_edge(iBCLK) then iSTART_SFT <= iSTART_SFT(1 downto 0) & iSTART ; iCLKA_SFT <= iCLKA_SFT(0) & iCLKA ; iCLKB_SFT <= iCLKB_SFT(0) & iCLKB ; end if ; end process ; -- BCS generate trigger signal iSTART_TRG <= '1' when (iSTART_SFT = "011" or iSTART_SFT = "001") else '0' ; iCLKA_TRG <= '1' when ( iCLKA_SFT = "01" ) else '0' ; iCLKB_TRG <= '1' when ( iCLKB_SFT = "01" ) else '0' ;  同期信号を利用し、内部シーケンサで画像データを  取得に必要なカウンタを動かします。 process (nRESET,iMCLK) begin if ( nRESET = '0' ) then iSTATE <= 0 ; iCNT <= 0 ; iREGOUT <= (others => '0') ; elsif rising_edge(iMCLK) then case iSTATE is -- wait trigger when 0 => if ( iSTART_TRG = '1' ) then iSTATE <= 1 ; -- clear counter iCNT <= 0 ; -- get information iREGOUT <= iREG ; else iSTATE <= 0 ; end if ; -- judge last count when 1 => if ( iCNT = 2048 ) then iSTATE <= 4 ; else iSTATE <= 2 ; end if ; -- get data when 2 => if ( iSCLKA_TRG = '1' ) then iSTATE <= 3 ; iCNT <= iCNT + 1 ; else iSTATE <= 2 ; end if ; -- get data when 3 => if ( iSCLKB_TRG = '1' ) then iSTATE <= 1 ; iCNT <= iCNT + 1 ; else iSTATE <= 3 ; end if ; -- return first state when 4 => iSTATE <= 0 ; -- default when others => iSTATE <= 0 ; end case ; end if ; end process ;  カウンタの値を利用して、ビットデータをシフト  レジスタに入れ、最後にバッファに転送して  他のシーケンサが使えるようにします。 -- BCS data gatter process (nRESET,iBCLK) begin if ( nRESET = '0' ) then iBCSREGX <= (others => '0') ; iBCSREG <= (others => '0') ; elsif rising_edge(iBCLK) then case iBCSCNT is when TCNT0 => iBCSREGX <= iBCSREGX(6 downto 0) & iCDAT ; when TCNT1 => iBCSREGX <= iBCSREGX(6 downto 0) & iCDAT ; when TCNT2 => iBCSREGX <= iBCSREGX(6 downto 0) & iCDAT ; when TCNT3 => iBCSREGX <= iBCSREGX(6 downto 0) & iCDAT ; when TCNT4 => iBCSREGX <= iBCSREGX(6 downto 0) & iCDAT ; when TCNT5 => iBCSREGX <= iBCSREGX(6 downto 0) & iCDAT ; when TCNT6 => iBCSREGX <= iBCSREGX(6 downto 0) & iCDAT ; when TCNT7 => iBCSREGX <= iBCSREGX(6 downto 0) & iCDAT ; when TCNT8 => iBCSREG <= iBCSREGX ; when others => NULL ; end case ; end if ; end process ;  同期回路、シーケンサの関係は、次のブロック図で  表現できます。  同期回路、シーケンサを動かすには、クロックが必要。  オシロスコープと周波数カウンタを利用して、3信号の  周波数を測定すると、以下となっていました。  2つのクロックが100kHzなので、シーケンサは  その4倍以上のクロックで動かせば、問題なく  画像データを取得できます。  今回は、500kHzとして同期回路、シーケンサを  動かしました。  BCSは、約10msごとに画像データを更新するので  それにあわせ、サーボモータ、DCモータのパラメータ  を決定していきます。  BCSは、アナログデータを出力するので、デジタル化  するために、若干の回路が必要でした。  (波形写真は、知人のKさんから貰いました)  オシロスコープで見た波形から、BCSはオフセットを  含んだBarCodeの縞を出力しています。オフセットを  とらないと、デジタル回路で処理できません。  ここでアナログ回路の知識が役に立ちました。  オフセットがない交流信号を見たいときには  キャパシタを入れ、直流分を除去します。  回路は簡単で、キャパシタをつけておき、更に  接続するデジタル回路の電圧振幅が、デジタル  回路の電源電圧を超えないよう、抵抗を挿入。  キャパシタの静電容量、抵抗値は厳密に計算した  値ではなく、たまたま手元にあった部品を使った  だけですが、問題なく動作しました。  BCSの画像データ出力は、OPアンプU4の7ピンから  取り出しています。  BCSは、秋月電子から入手したものを分解しています。  赤LEDは常時点灯するように、6ピンに接続されている  LED点灯制御信号を'H'固定で、制御用トランジスタを  常にONさせます。
  1. Vcc
  2. LED_CON     'H'固定
  3. (no connection)
  4. Vout
  5. enable <-> P50
  6. GND
 実験用に作成したVHDLコードは、以下です。 library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity bcsx is port ( -- system nRESET : in std_logic ; CLOCK : in std_logic ; -- 10MHz -- input START : in std_logic ; -- start trigger SCLKA : in std_logic ; -- 100kHz shift clock SCLKB : in std_logic ; -- 100kHz shift clock SDAT : in std_logic ; -- data -- monitor MCLK : out std_logic ; -- internal clock monitor 500kHz MSTART : out std_logic ; -- start trigger monitor MSCLKA : out std_logic ; -- 100kHz shift clock monitor MSCLKB : out std_logic ; -- 100kHz shift clock monitor MSDAT : out std_logic ; -- data monitor -- output LOUT : out std_logic_vector(7 downto 0) --; ) ; end bcsx; architecture Behavioral of bcsx is -- constant values constant TCNT0 : integer := 160 ; constant TCNT1 : integer := 416 ; constant TCNT2 : integer := 672 ; constant TCNT3 : integer := 928 ; constant TCNT4 : integer := 1184 ; constant TCNT5 : integer := 1440 ; constant TCNT6 : integer := 1696 ; constant TCNT7 : integer := 1952 ; constant TCNT8 : integer := 2000 ; -- clock divider signal iMCNT : integer range 0 to 19 ; signal iMCLK : std_logic ; signal iSCNT : integer range 0 to 499 ; signal iSCLK : std_logic ; -- state machine signal iSTATE : integer range 0 to 4 ; -- trigger signal iSTART_SFT : std_logic_vector(2 downto 0); signal iSTART_TRG : std_logic ; signal iSCLKA_SFT : std_logic_vector(1 downto 0); signal iSCLKA_TRG : std_logic ; signal iSCLKB_SFT : std_logic_vector(1 downto 0); signal iSCLKB_TRG : std_logic ; -- counter signal iCNT : integer range 0 to 2048 ; -- shift register signal iSFTREG : std_logic_vector(7 downto 0); -- data register signal iREG : std_logic_vector(7 downto 0); signal iSTART : std_logic ; signal iSCLKA : std_logic ; signal iSCLKB : std_logic ; signal iSDAT : std_logic ; -- show data signal iREGOUT : std_logic_vector(7 downto 0); begin -- output LOUT <= not iREGOUT ; -- monitor MCLK <= iMCLK ; MSTART <= iSTART ; MSCLKA <= iSCLKA ; MSCLKB <= iSCLKB ; MSDAT <= iSDAT ; -- input iSTART <= START ; iSCLKA <= SCLKA ; iSCLKB <= SCLKB ; iSDAT <= SDAT ; -- clock divider (generate 500kHz) process (nRESET,CLOCK) begin if ( nRESET = '0' ) then iMCNT <= 0 ; elsif rising_edge(CLOCK) then if ( iMCNT = 19 ) then iMCNT <= 0 ; else iMCNT <= iMCNT + 1 ; end if ; end if ; end process ; iMCLK <= '1' when ( iMCNT = 0 ) else '0' ; -- trigger sampling with 500kHz process (nRESET,iMCLK) begin if ( nRESET = '0' ) then iSTART_SFT <= "000" ; iSCLKA_SFT <= "00" ; iSCLKB_SFT <= "00" ; elsif rising_edge(iMCLK) then iSTART_SFT <= iSTART_SFT(1 downto 0) & iSTART ; iSCLKA_SFT <= iSCLKA_SFT(0) & iSCLKA ; iSCLKB_SFT <= iSCLKB_SFT(0) & iSCLKB ; end if ; end process ; iSTART_TRG <= '1' when ( iSTART_SFT = "011" or iSTART_SFT = "001" ) else '0' ; iSCLKA_TRG <= '1' when ( iSCLKA_SFT = "01" ) else '0' ; iSCLKB_TRG <= '1' when ( iSCLKB_SFT = "01" ) else '0' ; -- state machine process (nRESET,iMCLK) begin if ( nRESET = '0' ) then iSTATE <= 0 ; iCNT <= 0 ; elsif rising_edge(iMCLK) then case iSTATE is -- wait trigger when 0 => if ( iSTART_TRG = '1' ) then iSTATE <= 1 ; -- clear counter iCNT <= 0 ; -- clear counter iREGOUT <= iREG ; else iSTATE <= 0 ; end if ; -- judge last count when 1 => if ( iCNT = 2048 ) then iSTATE <= 4 ; else iSTATE <= 2 ; end if ; -- get data when 2 => if ( iSCLKA_TRG = '1' ) then iSTATE <= 3 ; iCNT <= iCNT + 1 ; else iSTATE <= 2 ; end if ; -- get data when 3 => if ( iSCLKB_TRG = '1' ) then iSTATE <= 1 ; iCNT <= iCNT + 1 ; else iSTATE <= 3 ; end if ; -- return first state when 4 => iSTATE <= 0 ; -- default when others => iSTATE <= 0 ; end case ; end if ; end process ; -- get data process (nRESET,iMCLK) begin if ( nRESET = '0' ) then iSFTREG <= (others => '0') ; iREG <= (others => '0') ; elsif rising_edge(iMCLK) then case iCNT is when TCNT0 => iSFTREG <= iSFTREG(6 downto 0) & iSDAT ; when TCNT1 => iSFTREG <= iSFTREG(6 downto 0) & iSDAT ; when TCNT2 => iSFTREG <= iSFTREG(6 downto 0) & iSDAT ; when TCNT3 => iSFTREG <= iSFTREG(6 downto 0) & iSDAT ; when TCNT4 => iSFTREG <= iSFTREG(6 downto 0) & iSDAT ; when TCNT5 => iSFTREG <= iSFTREG(6 downto 0) & iSDAT ; when TCNT6 => iSFTREG <= iSFTREG(6 downto 0) & iSDAT ; when TCNT7 => iSFTREG <= iSFTREG(6 downto 0) & iSDAT ; when TCNT8 => iREG <= iSFTREG ; when others => NULL ; end case ; end if ; end process ; end Behavioral;  秋月電子で販売されていたLatticeのCPLDでテスト。  ピンアサインは、以下。 BLOCK RESETPATHS; BLOCK ASYNCPATHS; # I/O BANK 0 VCCIO 3.3 V; BANK 1 VCCIO 3.3 V; BANK 2 VCCIO 3.3 V; BANK 3 VCCIO 3.3 V; # select 3.3V interface IOBUF ALLPORTS IO_TYPE=LVCMOS33 ; # system LOCATE COMP "nRESET" SITE "70" ; LOCATE COMP "CLOCK" SITE "56" ; # 10MHz # LED output LOCATE COMP "MSDAT" SITE "71" ; # BCS monitor LOCATE COMP "LOUT[0]" SITE "97" ; LOCATE COMP "LOUT[1]" SITE "98" ; LOCATE COMP "LOUT[2]" SITE "99" ; LOCATE COMP "LOUT[3]" SITE "100" ; LOCATE COMP "LOUT[4]" SITE "104" ; LOCATE COMP "LOUT[5]" SITE "105" ; LOCATE COMP "LOUT[6]" SITE "106" ; LOCATE COMP "LOUT[7]" SITE "107" ; # CLOCK monitor LOCATE COMP "MSCLKB" SITE "60" ; # SCLKB LOCATE COMP "MSCLKA" SITE "58" ; # SCLKA # CCD LOCATE COMP "SDAT" SITE "139" ; # data LOCATE COMP "START" SITE "140" ; # START trigger LOCATE COMP "SCLKB" SITE "141" ; # shift clock 100kHz LOCATE COMP "SCLKA" SITE "142" ; # shift clock 100kHz # monitor outputs LOCATE COMP "MCLK" SITE "61" ; LOCATE COMP "MSTART" SITE "65" ;  START、CLKA、CLKBは、5V近くまでスイングする  ため、3.3V動作CPLDには、電圧を抵抗による分圧  で与えました。  大会が終わり、復路の飛行機の中で考えると  3.3Vにプルアップして対応すればよいと思い  つきました。
目次

inserted by FC2 system