FC2カウンター FPGAの部屋 2009年11月
fc2ブログ

FPGAやCPLDの話題やFPGA用のツールの話題などです。 マニアックです。 日記も書きます。

FPGAの部屋

FPGAの部屋の有用と思われるコンテンツのまとめサイトを作りました。Xilinx ISEの初心者の方には、FPGAリテラシーおよびチュートリアルのページをお勧めいたします。

ウッドデッキ用のテーブルを作った

ウッドデッキ用のテーブルをあまった2X4材で作った。サイズは1500X740X670。高さの計算を間違って低くなってしまった。せっかくルート2を計算してX型の脚の長さを出したのに、天板を引いた脚の高さが間違っていた。。。失敗。脚の高さを630mm、脚の長さを890mm にしてしまった。これでは天板の厚さ39mm を足しても、670mm 程度になってしまう。720mm くらいの高さにしたかったのだが、天板の厚さを完全に勘違いしてしまった。まあこのくらいに高さだとまだ、許容範囲だろう?
その代わりに、椅子の高さを低くしようと思っている。また、椅子は、1つずつ高さを変えようと思っている。なぜならば、私は座高が高いので、いつも食卓に座ると猫背になってしまう。いろんな高さのいすがあっても良いんじゃないだろうか?
テーブルはうちの奥さんがX型の脚が良いとのことで作ることにした。切り欠きを入れて脚同士をはめ込まなかったので、脚の高さを揃えるのが難しかった。やはり嵌め込んでから切った方が脚がそろうのかな?切り欠きを入れないで仮止めして脚の高さをそろえようとしたが、同じ高さにするのが大変だった。次回に製作する場合は嵌め合わせにしたい。
table_091129.jpg

  1. 2009年11月29日 16:58 |
  2. 木工
  3. | トラックバック:0
  4. | コメント:0

YUV-RGB変換3(画像ボードやタイミングの再検討)

YUV-RGB変換2(画像ボードやタイミングの検討)”でタイミングを検討したが、どうやら、KBCR-M04VG(内蔵CMOS撮像素子、OV7725)は前のKBCR-M03VG(内蔵CMOS撮像素子、OV7640)と違っているようだ。リセットは逆で0でリセットのようだし、デフォルトではYUYVとデータが出てくるようだ。KBCR-M04VGの全体のフレームのタイミングを下に示す。データシートのタイミングチャートを基に作成した。
YUV_RGB_conv_2_091128.png
  図1 OV7725のフレーム・タイミングチャート

クロックはいい加減に書いてあるので、ご了承いただきたい。tp = 2 tPCLK となっている。これはYU, YVで一画素を表しているからだ。実際にはHSYNCはないはずなのだが、データシートに書いてあったので、付けくわえておいた。今度のボードは50MHzの水晶発振器が付いているので25MHzピクセルクロックのVGAとなる。Spartan3A Starter Kitのユーザーズマニュアルに書いてあるタイミングを下に示す。(表 6-2 を引用)
YUV_RGB_conv_3_091128.png

図1と表6-2を比べるとタイミングがだいぶ違う。今まではVGAはCMOSカメラのフレームの1/2としていたが、今回はCMOSカメラ、VGAともそれぞれのタイミングで入力、出力してもらうことにした。OV7725はVGAを60フレーム/secで出力することもできるようだが、とりあえず30フレーム/secとする。つまり、少なくとも1/15sec カメラを止めていれば、思い通りのVGA出力が出てくるだろう。

次に全体のタイミングチャートを示す。
YUV_RGB_conv_4_091128.png
 図3 YUV-RGB変換回路タイミングチャート

前のタイミグンチャートと同じようだけど、もう1つSRAMが増えているので、メモリのデータ入出力が2つある。MD1とMD2だ。MD1は以前同様に16ビット幅に2つのYを保存する。MD2は同じく16ビット幅の上位バイトにUを下位バイトにVを保存する。一度に1バイトをWriteするので、1クロック間では上位バイトか下位バイトだけをWriteする。例えばMD2のタイミングチャートの上に書いてある、”CAMERA U0, XX”はCMOSカメラからのUの0番目をSRAMの上位バイトだけに書き込み、下位バイトは書きこまないことを示す。”XX, CAMERA V0”はVの0番目を下位バイトだけに書き込み、上位バイトには書きこまないことを示す。
SRAMのRead/WriteがR/Wで、アドレスがMADDRになっている。アドレスは2つのSRAM共通とする。SRAM_WRは実際にSRAMに書き込むタイミングを示し、UBはUpper Byteの書き込み、LBはLower Byteの書き込みを示す。
CAM_Yはカメラから入力したデータのフォーマットを示す。上に書いてあるのがYUVの出力されるフォーマットである。UYVY...と出力される。Y_FFはそのCAM_Yをラッチした出力でYを保存しておくためのFFだ。
このような仕様でSRAMにCMOSカメラのYUVデータを格納しようと思っている。ちなみにY、UVとも最初はCMOSカメラからのデータの書き込みが間に合わないので、変なデータをディスプレイに表示してしまうが、1フレーム終われば正常な値を出力するので問題はない。
YUV422なのでYに対してUVは半分のデータ量しかないため、ディスプレイに表示するためにSRAMからUVはラッチされて2ピクセルの間、使う必要がある。その辺はタイミングチャートに表していない。
DISPLAY_DATAはVGA用のデータをSRAMからリードするタイミングで、これはCMOSのタイミングとは全く別にする。以前はmaster_syncでVGAのアドレスも同期していたが、これはやめることにする。VGAは521ライン、CMOSカメラは510ラインなので、VGAのフレーム2回はCMOSカメラのフレーム1回に入らなくなった。

  1. 2009年11月28日 20:10 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:4

電子工作コンテスト2009

電子工作コンテスト2009ノミネート作品が発表されました。いずれ劣らぬ作品たちで動画を見ると楽しめます。教えていただいた、いえながさんも3作品がノミネートされています。おめでとうございます。
特に印象に残ったのが、Timpy Rev8.0とコイルガン戦車。
Timpy Rev8.0は有機ELディスプレイ付きで、漢字表示可能。耳かけスピーカーに組み込んだMP3プレーヤー。Micro SDカードが入る。USBでWindowsにもマウントできるし、売り物みたい。完成度がすごい。売っていたら欲しいかも?
コイルガン戦車はここまでやるか?という感じ。戦車を改造して、コイルガンを載せている。パチンコの玉を磁力で打ち出す。それがはんぱない。コイルを4重に搭載して、ストロボ用の電解コンを何個も搭載し、コーラの缶を打ち抜く威力がある。一見の価値ありだと思う。
世の中には、いろんなことやっている人がいるもんだ。。。おもしろそう。。。
  1. 2009年11月27日 05:04 |
  2. その他のFPGAの話題
  3. | トラックバック:0
  4. | コメント:2

YUV-RGB変換2(画像ボードやタイミングの検討)

YUV-RGB変換1(方式の検討)”の続き
方式は決まったので、画像ボードとタイミングの検討をしよう。
画像ボードは”DWM誌の2007年8月号と2007年10月号の基板でカメラの画像を表示”のボードではSRAMの帯域が足りない。10nsの非同期SRAMを10nsサイクルで使いたくないので、SRAMだったら倍のデータバス幅が欲しい。そこで、同じESP企画の画像ベースボード(Spartan3E XC3S1200+4M高速SRAM)【CQBB-IMG】とデジタルCMOSカメラ【KBCR-M04VG】の組み合わせで行くことにした。
CQBB-IMGボードは回路図を見ると、16ビットのアクセスタイム10ns のSRAMが2つ搭載されている。(SDRAMやDDR SDRAMが搭載されていると楽なんだけど)これで25MHzで動作させてもメモリ帯域が足りるはず。デジタルCMOSカメラ【KBCR-M04VG】はNDAの影響か?データシートがネットにない。カタログがこの辺にある。一応画像ボードと一緒にもう少し詳しいデータシートが添付されているのだが、まだなぞの部分が多い。このCMOSカメラは前のと違ってVGAで60フレーム出力することができるが、とりあえず以前同様VGAを30フレームで出力させることにする。
OV7725のデータシートを見てみると、GRB4:2:2で出力することができるようだ。SCCBバスで設定してこれで出すのもありだろうか?でも満足なマニュアルがなくて、どんなフォーマットで出てくるかが書いていない。手探り状態なので、やはり、とりあえずYUV-RGB変換してみることにする。
検討したタイミングチャートを下図に示す。
YUV_RGB_conv_1_091126.png

前のタイミグンチャートと同じようだけど、もう1つSRAMが増えているので、メモリのデータ入出力が2つある。MD1とMD2だ。MD1は以前同様に16ビット幅に2つのYを保存する。MD2は同じく16ビット幅の上位バイトにUを下位バイトにVを保存する。一度に1バイトをWriteするので、1クロック間では上位バイトか下位バイトだけをWriteする。例えばMD2のタイミングチャートの上に書いてある、”CAMERA U0, XX”はCMOSカメラからのUの0番目をSRAMの上位バイトだけに書き込み、下位バイトは書きこまないことを示す。”XX, CAMERA V0”はVの0番目を下位バイトだけに書き込み、上位バイトには書きこまないことを示す。
SRAMのRead/WriteがR/Wで、アドレスがMADDRになっている。アドレスは2つのSRAM共通とする。SRAM_WRは実際にSRAMに書き込むタイミングを示し、UBはUpper Byteの書き込み、LBはLower Byteの書き込みを示す。
CAM_Yはカメラから入力したデータのフォーマットを示す。上に書いてあるのがYUVの出力されるフォーマットである。UYVY...と出力される。UV_FFはそのCAM_Yをラッチした出力でUVを保存しておくためのFFだ。
このような仕様でSRAMにCMOSカメラのYUVデータを格納しようと思っている。ちなみにY、UVとも最初はCMOSカメラからのデータの書き込みが間に合わないので、変なデータをディスプレイに表示してしまうが、1フレーム終われば正常な値を出力するので問題はない。
YUV422なのでYに対してUVは半分のデータ量しかないため、ディスプレイに表示するためにSRAMからUVはラッチされて2ピクセルの間、使う必要がある。その辺はタイミングチャートに表していない。

(2009/11/27 追記)
KBCR-M04VGは前のKBCR-M03VGと違っているようだ。リセットは逆で0でリセットのようだし、デフォルトではYUYVとデータが出てくるようだ。もう一度、タイミングを書きなおす。
  1. 2009年11月25日 05:53 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0

MTM04に行ってきた

今日はMake Tokyo Meeting 04に行ってきました。
MTM04_1_091122.jpg

東工大のキャンパスは樹木も程よく配置されて良い感じだった。
MTM04_2_091122.jpg

はじめに西9号館に行ったが、11時で始まったばかりで準備ができていないようなので体育館に行った。
はじめに見たのは、猫に行ってはいけないところを教えるための放電装置だそうだ。音や放電の光で猫が来ないようにするとのこと。約4000Vの放電だそうだ。
MTM04_3_091122.jpg

poleさんもいらしていて、いろいろ見せてもらった。最初は約700nAで動作するマイコン。電力を測定する基板もあって、測定値を表示していた。
MTM04_4_091122.jpg

次に3軸の加速度センサが搭載されているFreescaleのマイコン基板でをボールに入れて加速度をいろいろなフォーマットで見せていただいた。ボールをバウンドさせた時より、受け取った時に加速度が大きいとのこと。考えてみればそうか。。。
MTM04_5_091122.jpg

次は、3DプリンタCupCake CNCを見た。とてもよさそうなのだが、お値段が157,500円とお高い。買えない。やはりABS樹脂を溶かして、おいて行く方式だが、サポート材はないので、中空の物は作れないとのこと。USで買うと$750のようなので、もう少しお安くならないものか?(USで買えば良いて?送料が高いのかな?はたしてinternational shippingはいくらなんだろう?)
MTM04_6_091122.jpg

後はメイドロボットになるはずの予定の基板。いろいろセンサを接続してもプロセッサが認識できる。センサをたくさんつなぐときなどにとても便利。
MTM04_7_091122.jpg

なひたふさんのところのSpartan-6ボードを使った光センサー兼LED表示もあった。
MTM04_8_091122.jpg

パラメトリックスピーカーもあった。初めてまともに聞いたが、本当に指向性が強い。少し離れると聞こえない。でも音質はいまいちのようだ。ラジオの音が聞きとれる程度か?
MTM04_9_091122.jpg

変わったところではビスマスの結晶と魚やカエルを薬品処理して肉を透明化して、骨に着色した標本があった。両方とも販売していた。下の写真は標本。
MTM04_10_091122.jpg

LED Tileもあった。実際に見ても面白い動き。。。ただ、ユニットをつなぐコネクタが頼りない感じがした。
MTM04_11_091122.jpg

Sun SPOTの無線ユニットで作ったハンドベル。このワイングラスを振るとそれぞれの高さのハンドベルの音が鳴る。なかなか良い音だった。ぜひ演奏してほしかった。
MTM04_12_091122.jpg

自転車の車輪にLEDを付けて、転送制御して書いてある。ネギも振っていた。
MTM04_13_091122.jpg

いえながさんのフリスクケースに入れたMP3プレーヤー、MicroSDカードが入るそうだ。スピーカーもフリスクケースに入っていた。凄い高密度実装。
MTM04_14_091122.jpg

最後にもう1つ見つけたFPGAの応用。赤外LEDとフォトダイオードで検出するタッチパネルを使った楽器。検出用演算モジュールにマルツのSpartan3Eボードを使用していた。
MTM04_15_091122.jpg

MTM04で買ってきたもの。1,200円のボーカロイドCDとインフロー(P版)のお猿さんコースター、200円
MTM04_16_091122.jpg

インフローで買ったからか?P版のパンフレット入れに生基板が入っていた。これってPIC18?どこに回路図や資料があるんだろう?
MTM04_17_091122.jpg

大体2時過ぎころ会場を出て帰宅した。いろいろ楽しかったが、芸術系の展示が多くなっている感じがする。これはこれで楽しいのだが、電子工作を見に行っているつもりの私からは路線が外れ始まっている気がする。

そういえばテスラコイルがどこにあるかわからなかった。残念。。。bmblogさんのMTM04の記事で見せていただいた。
  1. 2009年11月22日 20:45 |
  2. その他のFPGAの話題
  3. | トラックバック:0
  4. | コメント:6

テレビ台を設置

昨日、この前から作ってきたテレビ台をリビングに設置した。細かいところは不満があるけれど、我ながら満足いく出来に仕上がった。床の色とも合っている。
作ってきた過程は、”板と板のはぎ”、”今日の日記 (2009/10/04)”、”テレビ台、人間ドック”、”引き出し製作1”、”引き出し製作2”。
TVdai_091122.jpg
  1. 2009年11月22日 17:02 |
  2. 木工
  3. | トラックバック:0
  4. | コメント:2

YUV-RGB変換1(方式の検討)

さて、Sobelフィルタがうまくいっていないが、たぶん飽和演算の丸め方の問題だと思っている。ラプラシアン・フィルタもうまく行ったので、今度はYUV-RGB変換してカラーにしてみようと思う。
DWMの2007年8月号の”VGA ディジタルCMOS カメラ・モジュールからの入力回路を作ろう”でもYUV-RGB変換のやり方が書いてあるが、乗算器を9個使ってしまうので、精度は落ちても乗算器を使わないでやってみようと思う。
YUVフォーマット及び YUV<->RGB変換”ページの”8bitフルスケールYUVと8bitフルスケールRGBの相互変換”を参考にしてみた。それによるとU/Vが0から255の値を取る場合のRGB信号を求める式は下のようになる。

R = 1.000Y + 1.402(V-128)
G = 1.000Y - 0.344(U-128) -0.714(V-128)
B = 1.000Y + 1.772(U-128)


上記の式を1とする。

ここで、Twitterでs_osafuneさんに変換の公式を教えていただいた。それによるとYUV=8:S8:S8, RGB=8:8:8とすると、下のような式になるとのことだった。(s_osafuneさん、昨日の時点でここまでブログは式1まで書きかけでした)

R = (Y<<8 + 359*V)>>8
G = (Y<<8 - 183*V - 88*U)>>8
B = (Y<<8 + 454*U)>>8


上記の式を2とする。これを16ビットまでで飽和演算をするそうだ。RGBよりもYUVの方が空間が広いとのことだった。これは良く考えてみれば、通常の値を取り扱うRGBよりもUVが差分値のYUVの方が同じ8ビット長ずつとすれば、表せる値が大きいのがわかる。
あれ、良く見ると式2のR式のVの係数359を256で割ると1.4023...、G式の183を256で割ると0.7148...、Uの88を256で割ると0.34375、B式の454を256で割ると1.7734...となり、1式と2式は(U-128),(V-128)を除いて、ほとんど等価であるということがいえると思う。それでは式2のUをU-128、VをV-128に置き換えた下の3式でYUV-RGB変換をしてみようと思う。(s_osafuneさん、ありがとうございました。式1を変換する手間が省けました)

R = (Y<<8 + 359*(V-128))>>8
G = (Y<<8 - 183*(V-128) - 88*(U-128))>>8
B = (Y<<8 + 454*(U-128))>>8


ここで定数項を括りだして、最終的な式に展開する前に1.402などの10進数の小数を2進数の小数に変換する方法を紹介しておこうと思う。(”小数表現”などを参照した)
2進数の小数の0.1は2^(-1) = 10進数の0.5
2進数の小数の0.01は2^(-2) = 10進数の0.25
よって、2進数の0.11を10進数に直すと0.5 + 0.25 = 0.75となる。
逆に10進数の0.75を2進数に直すのは2^(-1)と2^(-2)を引くと0になるので0.11となる。
参考にしたサイトに紹介されていたのは10進数の小数に2を掛けて行って桁上がりしたところの2進数のビットを1にする方法だ。
この方法で0.402を8桁の2進数の小数に直してみよう。
まずは1.402を2進数に変換してみよう。小数部分だけ取り出した0.402に2を掛ける。

0.402 X 2 = 0.804 なので2進数の小数の最初の桁は0
0.804 X 2 = 1.608 なので2進数の小数のこの桁は 1、次は桁上がりの1は除く。
0.608 X 2 = 1.216 なので2進数の小数のこの桁は 1、次は桁上がりの1は除く。
0.216 X 2 = 0.432 なので2進数の小数のこの桁は 0
0.432 X 2 = 0.864 なので2進数の小数のこの桁は 0
0.864 X 2 = 1.728 なので2進数の小数のこの桁は 1、次は桁上がりの1は除く。
0.728 X 2 = 1.456 なので2進数の小数のこの桁は 1、次は桁上がりの1は除く。
0.456 X 2 = 0.912 なので2進数の小数のこの桁は 0


ここまでで、0.402を2進数の小数に直すと0.01100110になった。最後に0.912残っているので四捨五入すると、0.01100111となる。これに1を加えると1.01100111となる。これを8ビット右シフトすると1_0110_0111となった。これを10進数に変換すると359となる。これで計算することができた。

さて、式3を展開して、定数項を出すと下の式4が導ける。

R = (Y<<8 + 359*V - 45,952)>>8
G = (Y<<8 - 183*V - 88*U + 34,688)>>8
B = (Y<<8 + 454*V - 58,112)>>8


これを16進数に直すと下の式5となる。(2進数では書くのが面倒なため)

R = (Y<<8 + X"167"*V - X"B380")>>8
G = (Y<<8 - X"58"*U - X"B7"*V + X"8780")>>8
B = (Y<<8 + X"1C6"*U - X"E300")>>8


これからはこの式を元にYUVをRGBに変換することにする。演算のビット数は2+1+16 = 19ビットとする。

(2009/11/23 追記)
takepon256さんから式を展開しないで、U-128、V-128の演算をしたほうが良いとのご指摘を受けたので、上の式3で演算してみることにした。

(200/12/02 追記)
式3を16進数に変換した。

R = (Y<<8 + X"167"*(V-128))>>8
G = (Y<<8 - X"B7"*(V-128) - X"58"*(U-128))>>8
B = (Y<<8 + X"1C6"*(U-128))>>8


さらに2進数に変換した。

R = (Y<<8 + "1_0110_0111"*(V-128))>>8
G = (Y<<8 - "1011_0111"*(V-128) - "0101_1000"*(U-128))>>8
B = (Y<<8 + "1_1100_0110"*(U-128))>>8


やはり演算のビット数は19ビットとする。

(2009/12/03 追記)
符号付きの掛け算のやり方を調べたが面倒だということがわかった。参照元は4ビットの符号付き掛け算器ウィキペディアの乗算器
やはり符号なしの掛け算ができる式5を使った方が演算器も少なくて済むという結論に達した。(U,Vはもともと符号なしの値)
それで式5を変形する。

R = (Y<<8 + "1_0110_0111"*V - X"B380")>>8
G = (Y<<8 - "1011_0111"*V - "0101_1000"*U + X"8780")>>8
B = (Y<<8 + "1_1100_0110"*U - X"E300")>>8


これで最終的にやってみることにした。

2010/08/27:追記
Rの式が間違っていたので修正しました
  1. 2009年11月22日 06:19 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:4

ウッドデッキ完成

今まで、”ウッドデッキ作り1”、”ウッドデッキ作り2”で作ってきたウッドデッキが遂に完成した。
今日は奥さんが手伝ってくれたので、はかどったが、例によって喧嘩をしながら作ることになった。午前中に本体は完成したので、午後から塗装をした。奥さんの希望でキシラデコールの白を塗装。やっぱり、凄いハデだ。。。舞台みたい。かなり目立つことだろう。。。女の人は白いのが好きなのか???とりあえずまあまあの出来だった。今度は材料があまったので、上に置くテーブルやいすを作成予定。
wood_deck_4_091121.jpg

wood_deck_5_091121.jpg
  1. 2009年11月21日 20:23 |
  2. 木工
  3. | トラックバック:0
  4. | コメント:0

ET2009に行ってきた

昨日、ET2009に行ってきた。

最初にAlteraブースへ、リコンフィギュレーションのデモを見せていただいた。でも、この機能はXilinxではすでに実装されて久しい気がした。次に10Gbps以上で伝送時のアイパターンを見せてもらった。50cmの伝送路だけど、10Gbps以上でアイが開いているのはすごい。FPGAでは初めてではないのかな?思えばVirtex2proXが出てきた時は10Gbpsだという触れ込みだったが、いつの間にか6.3Gbps になっていた。ここではマイ箸をいただいた。
次にXilinxブースへVirtex6でのPCIe Gen2 X8 でのDMA転送を見せていただいた。Virtex6はファンが付いているので良くわからない。PCIe Gen2 X8 ともなるとローカルバスがきつくなるはず。ローカルバスは64bit, 250MHzでタイミング的にきつめとのこと。。。そうなんでしょうね?
Spartan-6 FPGA SP605 Evaluation Kitが置いたあったので、じ~と見てきた。かなりDC-DCコンバータモジュールが多い。
もう少し、他のところを見て、後は、あまり見ないでセミナへ。
”デモでわかる!ソフトコア CPU を使ったFPGAのネットワーク・ソリューション”を受講。大体、SOPC BuilderやNios2の紹介だった。大体知っていることだった。
昼食は懐かしい方と外に食べに行った。いろいろお話が聞けて楽しかった。
”ModelSim を使いこなして FPGA 開発効率を上げる”は聞かないで飛ばしてしまった。
この時間を利用して、カメラモジュールメーカーのシキノハイテックに行って、カメラモジュールのデータシートをお願いしたら、やはりNDAで難しいとのことだった。NDAにかからないデータシートをお願いした。その後、ESP企画に行ったが、技術の担当者がいないとのことで、詳しい話は聞けなかった。

”ここまで進化した!低システムコスト化を実現するザイリンクス最新 FPGA、Virtex-6 & Spartan-6”を聞いた。低消費電力の話が多かった。やはり、この辺にフォーカスしているんだろうか?水平ブロックごとにクロックゲーティングができるそうだ。後で試してみたい。

”FPGAを活用した高速画像処理とデータ転送”は今、私も画像関係をやっているので、興味深く聞いた。5X5のラプラシアン・フィルタを使っているそうだ。DSPブロックでFIRフィルタを使っているとのこと。そうかそういう方法もあったな。いろいろIPがあるらしい。いろいろ試してみたくなった。

”FPGAの部分再構成の活用開発事例”は、今まで謎だった部分再構成が聞けて、とてもよかった。これだけでもET2009に来た価値がある。一番、興味深かったのはICAPというモジュールがあって、そこで、部分再構成を制御して、FPGA内のブロックRAMのデータを使って、部分再構成ができるということだった。これだと、再構成ブロックにもよるだろうが、かなり素早く、部分再構成をすることもできそう。部分再構成をするには、今はFEにお願いしないとだめなそうだが、来春のISE12.1からは標準で使えるようになるらしい。

最後にいつも恒例のクイーンズ・タウンのクリスマスツリー。今年は上に大きなハートマークが付いていた。
ET2009_2_091120.jpg
  1. 2009年11月20日 05:57 |
  2. その他のFPGAの話題
  3. | トラックバック:0
  4. | コメント:2

”CMOSカメラから画像を入力してディスプレイへ出力”のSRAMのデータ出力イネーブル用FFをIOBへ入れる

CMOSカメラから画像を入力してディスプレイへ出力15(できた!!!)”でCMOSカメラからの画像をディスプレイに出力することができた。そこから画像のいろいろなエッジ検出を試みてきた。今までは、動作周波数が低いので、あまりIOBにFFを入れるとか考慮していなかったので、やってみようと思った。
一番問題なのは、SRAMへのデータをWriteしてから、すぐにリードなので、FPGAの出力用ドライバーをすぐにOFFする必要があることだ。”CMOSカメラから画像を入力してディスプレイへ出力16(VHDLソースを公開)”を元にすると、SRAM_Controller.vhd から出力されたmem_data_oe が、最上位のCamDispCntrl_SRAM.vhd のIOBUF のT 入力に反転されて入力され、SRAMへのデータ出力イネーブルとなっている。その部分のソースを下に示す。

    n_mem_data_oe <= not mem_data_oe;
    
    MEM_DATA_GEN : for i in 15 downto 0 generate
        IOBUF_inst : IOBUF port map(
            O => input_mem_data(i),
            IO => mem_data(i),
            I => mem_data_out(i),
            T => n_mem_data_oe
        );
    end generate MEM_DATA_GEN;


mem_data_oe はどうやって出力しているかというと、SRAM_Controller.vhd の中にあるステートマシンの出力だ。その部分のソースを下に示す。

    -- n_mem_we, mem_data_oe を生成する。48MHzのステートマシンで出力
    process(clk48) begin
        if clk48'event and clk48='1' then
            if reset='1' then
                cs_we <= idle_we;
            else
                cs_we <= ns_we;
            end if;
        end if;
    end process;
    
    process(cs_we, r_w, cam_href_3d) begin
        case cs_we is
            when idle_we =>
                n_mem_we_node <= '1';
                mem_data_oe <= '0';
                if r_w='0' and cam_href_3d='1' then -- hrefの間だけSRAMにWriteする
                    ns_we <= we_active;
                else
                    ns_we <= idle_we;
                end if;
            when we_active => -- r_w が1時48MHzクロック1クロック間だけ n_mem_we を0にする
                n_mem_we_node <= '0';
                mem_data_oe <= '1';
                ns_we <= we_holdoff;
            when we_holdoff => -- この時はまだr_wが0
                n_mem_we_node <= '1';
                mem_data_oe <= '0';
                ns_we <= idle_we;
        end case;
    end process;
    n_mem_we <= n_mem_we_node;


次にFPGA Editor でSRAMのデータ入出力パッドを見てみよう。下にその図を示す。
iob_ff_1_091119.jpg

やはり出力用バッファのアウトプットイネーブルで、IOBのFFは使用されていない。これを使用するようにVHDLソースを書き換えましょうというのが、今回の趣旨だ。
さて、mem_data_oe を1つのFFということで、別に定義してみよう。ステートマシンの方にはnext_mem_data_oe という、mem_data_oe の1クロック前の状態を表すsignal を定義することにする。SRAMへのデータ出力イネーブルmem_data_oe はSRAMのデータ出力パッド1個に対して1個必要なため、16個宣言した。さらに、IOB制約をVHDLコード上に書いた。下にSRAM_Controller.vhdの一部を示す。

signal mem_data_node : std_logic_vector(15 downto 0);
signal next_mem_data_oe : std_logic;

attribute iob : string;
attribute iob of n_mem_data_oe : signal is "TRUE";
attribute keep : string;
attribute keep of n_mem_data_oe : signal is "TRUE";
attribute keep of next_mem_data_oe : signal is "TRUE";
begin

.....

    -- n_mem_we, mem_data_oe を生成する。48MHzのステートマシンで出力
    process(clk48) begin
        if clk48'event and clk48='1' then
            if reset='1' then
                cs_we <= idle_we;
            else
                cs_we <= ns_we;
            end if;
        end if;
    end process;
    
    process(cs_we, r_w, cam_href_3d) begin
        case cs_we is
            when idle_we =>
                n_mem_we_node <= '1';
                if r_w='0' and cam_href_3d='1' then -- hrefの間だけSRAMにWriteする
                    ns_we <= we_active;
                    next_mem_data_oe <= '1';
                else
                    ns_we <= idle_we;
                    next_mem_data_oe <= '0';
                end if;
            when we_active => -- r_w が1時48MHzクロック1クロック間だけ n_mem_we を0にする
                n_mem_we_node <= '0';
                ns_we <= we_holdoff;
                next_mem_data_oe <= '0';
            when we_holdoff => -- この時はまだr_wが0
                n_mem_we_node <= '1';
                ns_we <= idle_we;
                next_mem_data_oe <= '0';
            when others =>
                n_mem_we_node <= '1';
                ns_we <= idle_we;
                next_mem_data_oe <= '0';
        end case;
    end process;
    n_mem_we <= n_mem_we_node;
    
    -- mem_data_oeの出力
    process(clk48) begin
        if clk48'event and clk48='1' then
            if reset='1' then
                n_mem_data_oe <= (others => '1');
            else
                for i in 15 downto 0 loop
                    n_mem_data_oe(i) <= not next_mem_data_oe;
                end loop;
            end if;
        end if;
    end process;


最上位のCamDispCntrl_SRAM.vhd のIOBUF にn_mem_data_oe を1本ずつ入れた。

    MEM_DATA_GEN : for i in 15 downto 0 generate
        IOBUF_inst : IOBUF port map(
            O => input_mem_data(i),
            IO => mem_data(i),
            I => mem_data_out(i),
            T => n_mem_data_oe(i)
        );
    end generate MEM_DATA_GEN;


これをインプリメントしたところ無事にmem_data のIOBに出力イネーブル用のFFが入った。mem_data<0> の様子を下図に示す。
iob_ff_2_091119.png

これでSRAMへの出力データのドライブオン、ドライブオフ時間が最短になることと思う。

(2009/11/20: 追記)
keep制約をかけないとIOBにn_mem_data_oe が入らないときがあるので追加しました。
  1. 2009年11月19日 21:25 |
  2. FPGAチップ内の配線方法
  3. | トラックバック:0
  4. | コメント:0

ET2009に11/19(木)に行きます

FPGAカンファレンス東京は、急に重要な仕事が入ってしまっていけなくなってしまい残念だった。
ET2009には、行こうと思っている。行くのは木曜日だ。FPGAの無料セミナーも申し込んだし、出張の申請もした。午後はFPGAの無料セミナーを受講して、主に午前中に展示を見ようと思っている。XilinxやAlteraブース、その他楽しみなブースがありそうなので、楽しみだ。ここは見ておいた方が良いというところがあったら教えてください。よろしくお願いします。それでは、会場でお会いしましょう。
  1. 2009年11月17日 12:26 |
  2. その他のFPGAの話題
  3. | トラックバック:0
  4. | コメント:6

画像のエッジ検出9(汎用の3x3演算)

ラプラシアン・フィルタはできたので、SobelフィルタやPrewittフィルタをやってみることにした。その際に、constant 値を変更することで、いろいろなフィルタになるように出来ないかということで、汎用の3x3演算回路を作ってみることにした。
まずは、constant宣言部分を貼る。

-- 設定できる係数は-16 to 15
-- ラプラシアン・フィルタ
constant Xnm1Ynm1VaL1 : integer := -1;
constant XnYnm1VaL1 : integer := -1;
constant Xnp1Ynm1VaL1 : integer := -1;
constant Xnm1YnVaL1 : integer := -1;
constant XnYnVaL1 : integer := 8;
constant Xnp1YnVaL1 : integer := -1;
constant Xnm1Ynp1VaL1 : integer := -1;
constant XnYnp1VaL1 : integer := -1;
constant Xnp1Ynp1VaL1 : integer := -1;

constant Xnm1Ynm1VaL2 : integer := 0;
constant XnYnm1VaL2 : integer := 0;
constant Xnp1Ynm1VaL2 : integer := 0;
constant Xnm1YnVaL2 : integer := 0;
constant XnYnVaL2 : integer := 0;
constant Xnp1YnVaL2 : integer := 0;
constant Xnm1Ynp1VaL2 : integer := 0;
constant XnYnp1VaL2 : integer := 0;
constant Xnp1Ynp1VaL2 : integer := 0;

-- Sobelフィルタ,X方向, Y方向
-- constant Xnm1Ynm1VaL1 : integer := -1;
-- constant XnYnm1VaL1 : integer := 0;
-- constant Xnp1Ynm1VaL1 : integer := 1;
-- constant Xnm1YnVaL1 : integer := -2;
-- constant XnYnVaL1 : integer := 0;
-- constant Xnp1YnVaL1 : integer := 2;
-- constant Xnm1Ynp1VaL1 : integer := -1;
-- constant XnYnp1VaL1 : integer := 0;
-- constant Xnp1Ynp1VaL1 : integer := 1;

-- constant Xnm1Ynm1VaL2 : integer := -1;
-- constant XnYnm1VaL2 : integer := -2;
-- constant Xnp1Ynm1VaL2 : integer := -1;
-- constant Xnm1YnVaL2 : integer := 0;
-- constant XnYnVaL2 : integer := 0;
-- constant Xnp1YnVaL2 : integer := 0;
-- constant Xnm1Ynp1VaL2 : integer := 1;
-- constant XnYnp1VaL2 : integer := 2;
-- constant Xnp1Ynp1VaL2 : integer := 1;

-- Prewittフィルタ, X方向, Y方向
-- constant Xnm1Ynm1VaL1 : integer := -1;
-- constant XnYnm1VaL1 : integer := 0;
-- constant Xnp1Ynm1VaL1 : integer := 1;
-- constant Xnm1YnVaL1 : integer := -1;
-- constant XnYnVaL1 : integer := 0;
-- constant Xnp1YnVaL1 : integer := 1;
-- constant Xnm1Ynp1VaL1 : integer := -1;
-- constant XnYnp1VaL1 : integer := 0;
-- constant Xnp1Ynp1VaL1 : integer := 1;

-- constant Xnm1Ynm1VaL2 : integer := -1;
-- constant XnYnm1VaL2 : integer := -1;
-- constant Xnp1Ynm1VaL2 : integer := -1;
-- constant Xnm1YnVaL2 : integer := 0;
-- constant XnYnVaL2 : integer := 0;
-- constant Xnp1YnVaL2 : integer := 0;
-- constant Xnm1Ynp1VaL2 : integer := 1;
-- constant XnYnp1VaL2 : integer := 1;
-- constant Xnp1Ynp1VaL2 : integer := 1;


それぞれの部分をコメントから解除することで、フィルタを切り替える。
演算部分を下に示す。

    op_int_vaL1 <= Xnm1Ynm1VaL1*CONV_INTEGER(Xnm1Ynm1) + XnYnm1VaL1*CONV_INTEGER(XnYnm1) + Xnp1Ynm1VaL1*CONV_INTEGER(Xnp1Ynm1)
        + Xnm1YnVaL1*CONV_INTEGER(Xnm1Yn) + XnYnVaL1*CONV_INTEGER(XnYn) + Xnp1YnVaL1*CONV_INTEGER(Xnp1Yn)
        + Xnm1Ynp1VaL1*CONV_INTEGER(Xnm1Ynp1) + XnYnp1VaL1*CONV_INTEGER(XnYnp1) + Xnp1Ynp1VaL1*CONV_INTEGER(Xnp1Ynp1);
        
    op_int_vaL2 <= Xnm1Ynm1VaL2*CONV_INTEGER(Xnm1Ynm1) + XnYnm1VaL2*CONV_INTEGER(XnYnm1) + Xnp1Ynm1VaL2*CONV_INTEGER(Xnp1Ynm1)
        + Xnm1YnVaL2*CONV_INTEGER(Xnm1Yn) + XnYnVaL2*CONV_INTEGER(XnYn) + Xnp1YnVaL2*CONV_INTEGER(Xnp1Yn)
        + Xnm1Ynp1VaL2*CONV_INTEGER(Xnm1Ynp1) + XnYnp1VaL2*CONV_INTEGER(XnYnp1) + Xnp1Ynp1VaL2*CONV_INTEGER(Xnp1Ynp1);
    
    op_vaL1 <= CONV_STD_LOGIC_VECTOR(op_int_vaL1, op_vaL1'length);
    op_vaL2 <= CONV_STD_LOGIC_VECTOR(op_int_vaL2, op_vaL2'length);

    process(op_vaL1) begin
        if op_vaL1(op_vaL1'left)='1' then -- 負の値
            abs_op_cal_vaL1 <= not(op_vaL1 -1);
        else -- 正の数
            abs_op_cal_vaL1 <= op_vaL1;
        end if;
    end process;
    process(op_vaL2) begin
        if op_vaL2(op_vaL2'left)='1' then -- 負の値
            abs_op_cal_vaL2 <= not(op_vaL2 -1);
        else -- 正の数
            abs_op_cal_vaL2 <= op_vaL2;
        end if;
    end process;
    abs_op_cal_val <= abs_op_cal_vaL1 + abs_op_cal_vaL2;

    ydata_out <= x"00" when h_valid_flag='0' or v_valid_flag='0' else
        x"FF" when (abs_op_cal_val(12) or abs_op_cal_val(11) or abs_op_cal_val(10) or abs_op_cal_val(9) or abs_op_cal_val(8))='1'
        else abs_op_cal_val(7 downto 0); -- 飽和演算


やはり、一旦integer に直して演算し、またstd_logic_vector に戻すことにした。op_int_vaL1とop_int_vaL2 はinteger だが範囲を-4096 to 4095 にしてある。
これでインプリメントしてやってみた。
その結果、ラプラシアン・フィルタは以前のように正常にエッジが検出できた。
edge_detect_18_091117.jpg

SobelフィルタとPrewittフィルタはエッジを検出しすぎて?ディスプレイの画面が真っ白になってしまった。値は合っていると思うのだが?どこがおかしいのか?
edge_detect_19_091117.jpg

  1. 2009年11月17日 05:06 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0

ウッドデッキ作り2

ウッドデッキ作り1”の続き。
今日は平らなところの板を張った。2X4X10F結構重いし、足が疲れた。2X4X10Fに4mmのシナべニアを間に入れて、コーススレッドで留めて行った。その後に、シナべニアを抜くと、4mmの隙間があいて、2X4の板が並ぶ。
下が今日まで完成下部分の写真。2X4X10Fは長いので、60cm程切ってある。
wood_deck_2_091115.jpg

wood_deck_3_091115.jpg
  1. 2009年11月15日 19:39 |
  2. 木工
  3. | トラックバック:0
  4. | コメント:0

Spartan3Eの乗算器

このところ、乗算をしているので、私としては初めてSpartan3Eの乗算器をつかったはず。。。ということで確かめてみた。
まずは、”画像のエッジ検出5(2X2の画素の演算でやってみた)”の2X2演算器。下の演算をしているので、乗算器を使っているはず。

diff_cal_val <= "011"*(cam_ydata_2d) - ("000"&bram_data_1d) - ("000"&bram_data) - ("000"&y_data_1d);


Project NavigatorのDevice Utilization Summary を見ると、Number of MULT18X18SIOsが1になっていて、1個の乗算器を使用している。
MULT18X18_1_091114.png

つぎにFPGA Editor で見てみよう。
MULT18X18_2_091114.png

確かに乗算器を使っている。当たり前か?

次に、”画像のエッジ検出7(3X3演算のシミュレーション)”の3X3演算を見てみよう。演算式を下に示す。

Laplacian_cal_val <= "01000"*(XnYn) - ("00000"&Xnm1Ynm1) - ("00000"&XnYnm1) - ("00000"&Xnp1Ynm1) - ("00000"&Xnm1Yn) - ("00000"&Xnp1Yn) - ("00000"&Xnm1Ynp1) - ("00000"&XnYnp1) - ("00000"&Xnp1Ynp1) ;
-- マトリクスの絶対値


Project NavigatorのDevice Utilization Summary を見ると、Number of MULT18X18SIOsがない。
MULT18X18_3_091114.png

乗算器を使用していない。。。これは被乗数が8で3ビット左シフトすれば良いからか?
Project Navigator のProcesses ウインドウのSynthesize のView RTL Schematic で見てみたが、やはり乗算器は生成されていなかった。XSTは2のn乗の掛け算の時には乗算器を使わないのではないだろうか?
  1. 2009年11月14日 19:36 |
  2. FPGAチップ内の配線方法
  3. | トラックバック:0
  4. | コメント:0

画像のエッジ検出8(3X3の演算(ラプラシアン・フィルタ))

画像のエッジ検出7(3X3演算のシミュレーション)”でシミュレーションしてみたので、今度はインプリメントしたビットファイルを実機でしてみた。
まずは、”画像のエッジ検出5(2X2の画素の演算でやってみた)”の結果を先に示す。バグを修正したら、それらしいエッジが出てきた。(色が付いているの映り込みです。カメラで撮影しているので、ベースが黒いためどうしても、周りの物が映りこんでしまいます)
edge_detect_14_091113.jpg

それらしい。。。なかなか雰囲気が出ている。これもよかったが、ラプラシアン・フィルタの結果を下に示す。
edge_detect_16_091113.jpg

ちょっとノイズが乗っている気がするが、エッジは良く出ている気がする。次に私の顔のラプラシアン・フィルタの出力画面を下に示す。
edge_detect_17_091113.jpg

結構楽しい。。。いろいろやってみたくなる。最後に実験基板とUSBダウンロードケーブル。
edge_detect_15_091113.jpg

そうだ。だれか、このラプラシアン・フィルタの全ソース欲しいですか?まあ、ほとんどソースはブログに貼ってあるし、公開しているようなものですけどね。。。
  1. 2009年11月13日 19:27 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:2

画像のエッジ検出7(3X3演算のシミュレーション)

3X3演算のVHDLコードは昨日できたんだけど、もう朝食の時間になってしまったので、シミュレーションができなかった。今日はシミュレーションの結果を書いておこうと思う。
このシミュレーションの最中にBRAMによるダブルバッファリングのアドレスのコードにバグが見つかった。1水平ラインについて切り替えるはずが、1フレームで切り替えとなっていた。これは、画像のエッジ検出4(実機でテスト)”と画像のエッジ検出5(2X2の画素の演算でやってみた)”に影響する。後で、記事を書き換えるつもりなので、よろしくお願いします。しかし、”画像のエッジ検出4(実機でテスト)”は正常にエッジが検出出来ていたように見えたのだが、なんでだろう?(追記:これは修正されていました)

ともかく今回の、3X3演算のVHDLコードの主要な部分を下に示す。

    Xnp1Ynm1    <= bram_data2;
    Xnp1Yn        <= bram_data;
    Xnp1Ynp1    <= cam_ydata_2d;
    -- 1クロック前のBRAMの出力と現在の値の1クロック前の値を保持
    process(clk) begin
        if clk'event and clk='1' then
            if reset='1' then
                Xnm1Ynm1    <= (others => '0');
                XnYnm1        <= (others => '0');
                Xnm1Yn        <= (others => '0');
                XnYn        <= (others => '0');
                Xnm1Ynp1    <= (others => '0');
                XnYnp1        <= (others => '0');
            else
                if r_w='1' then
                    Xnm1Ynm1    <= XnYnm1;
                    XnYnm1        <= Xnp1Ynm1;
                    Xnm1Yn        <= XnYn;
                    XnYn        <= Xnp1Yn;
                    Xnm1Ynp1    <= XnYnp1;
                    XnYnp1        <= Xnp1Ynp1;
                end if;
            end if;
        end if;
    end process;
    
    Laplacian_cal_val <= "01000"*(XnYn) - ("00000"&Xnm1Ynm1) - ("00000"&XnYnm1) - ("00000"&Xnp1Ynm1) - ("00000"&Xnm1Yn) - ("00000"&Xnp1Yn) - ("00000"&Xnm1Ynp1) - ("00000"&XnYnp1) - ("00000"&Xnp1Ynp1) ;
    -- マトリクスの絶対値
    process(Laplacian_cal_val) begin
        if Laplacian_cal_val(12)='1' then -- 負の値
            abs_Laplacian_cal_val <= not(Laplacian_cal_val-1);
        else -- 正の数
            abs_Laplacian_cal_val <= Laplacian_cal_val;
        end if;
    end process;

    ydata_out <= x"00" when h_valid_flag='0' or v_valid_flag='0' else
        x"FF" when (abs_Laplacian_cal_val(12) or abs_Laplacian_cal_val(11) or abs_Laplacian_cal_val(10) or abs_Laplacian_cal_val(9) or abs_Laplacian_cal_val(8))='1'
        else abs_Laplacian_cal_val(7 downto 0); -- 飽和演算


いずれは、汎用的な3X3演算器を目指すのだが、とりあえずはベタで書いてある。BRAMは2つインスタンシエーションしてあり、bram_data が下図のBRAM1のReadデータ出力、bram_data2 が下図のBRAM2のReadデータ出力に相当する。Xnm1Ynm1 はXn-1Yn-1に相当し、Xnp1Ynp1 はXn+1Yn+1 に相当する。
edge_detect_12_091113.png

h_valid_flag は1水平ラインで2つの輝度(Y)データが来たら1となる。それまではydata_out出力は0になる。v_valid_flag は2水平ラインまで0で、3水平ラインから1になる。それまでは、ydata_out出力は0になる。
下にCamera_Controller モジュールのシミュレーション波形を示す。
edge_detect_13_091113.png

上の図で、ピンクの四角はcam_ydata_2d のうちの輝度データの位置を示す。そのうちのカーソルに位置の演算(水色の四角)を検算してみることにする。

Xn-1Yn-1 = 01
XnYn-1  = 02
Xn+1Y-1 = 04
Xn-1Yn  = 02
XnYn    = 03
Xn+1Yn  = 04
Xn-1Yn+1 = 82
XnYn+1  = 04
Xn+1Yn+1 = 08


なので、
-------------
| -1| -1| -1|
-------------
| -1| 8| -1|
-------------
| -1| -1| -1|
-------------

は、03*8 - 01 - 02 - 04 - 02 - 04 - 82 - 04 - 08 = 1F7D (laplacian_cal_val)
絶対値 (abs_laplacian_cal_val)は0083 で検算結果と合う。
これで実機で確かめてみようと思うが、忙しいのでどうなるか?
  1. 2009年11月13日 05:36 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0

画像のエッジ検出6(3X3での方式の検討)

3X3でエッジを検出する場合の方式の検討をする。
画像のエッジ検出4(実機でテスト)”で書いた方式は”ディジタル信号処理講座”によると”ラプラシアンによるエッジ検出”と呼ばれている。
後はSobelフィルタ、Robertsフィルタなどがあるようだ。これらは、X方向とY方向のフィルタがあって、その絶対値同士の和を取り飽和演算する方法とXの2乗とYの2乗のルートを取る方法があるようだ。FPGAなどのハードウェア実装されているのが多いのが前者で、ソフトウェア実装は後者になっているようだ。
とりあえず、ラプラシアンとSobelフィルタ、Robertsフィルタ両方に対応するために、|フィルタ1|+|フィルタ2|の飽和演算として、ラプラシアンの場合はフィルタ2を0にすることにする。Sobelフィルタ、Robertsフィルタの場合は|フィルタX|+|フィルタY|の飽和演算とすることにする。(参考文献、”画像処理IP とFPGA 実装法の研究開発”)
下に3X3演算をする場合のBRAMやFFの構成を下に示す。
edge_detect_11_091111.png

Xn-1Yn-1... Xn+1Yn+1 の9つの輝度データから、3X3の演算を2つ行い、絶対値を取って加算する。最初の2行の輝度データXn-1Yn-1, XnYn-1, Xn+1Yn-1, Xn-1Yn, XnYn, Xn+1Yn に輝度データが入るのはVSYNCがアクティブになってから3つ目のHREFの時なので、最初の2水平ラインはブランクとする。横方向もXn-1, Xn までがないと演算できないので、最初の2ドットはブランクとする。

ラプラシアンはいろいろな係数があるみたいだが、とりあえず前回の下の演算子でやってみる。
-------------
| -1| -1| -1|
-------------
| -1| 8| -1|
-------------
| -1| -1| -1|
-------------


Sobel フィルタの演算子を下に示す。
------------      -------------
| -1| 0| 1|     | -1| -2| -1|
------------     -------------
| -2| 0| 2|     | 0| 0| 0|
------------     -------------
| -1| 0| 1|     | 1| 2| 1|
------------     -------------

X方向           Y方向

(参考URL: ”1次微分(差分)によるエッジ検出”、”2次微分(差分)によるエッジ検出”、”基本的な画像処理手法について”)
  1. 2009年11月11日 05:03 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0

画像のエッジ検出5(2X2の画素の演算でやってみた)

画像のエッジ検出4(実機でテスト)”の最後に書いた2X2の画素の演算でやってみた。
-------
| -1| -1|
-------
| -1| 3|
-------
上の演算でやってみるために、VHDLコードを書きなおした。演算部分を下に示す。

    -- 1クロック前のBRAMの出力と現在の値の1クロック前の値を保持
    process(clk) begin
        if clk'event and clk='1' then
            if reset='1' then
                bram_data_1d <= (others => '0');
                y_data_1d <= (others => '0');
            else
                if r_w='1' then
                    y_data_1d <= cam_ydata_2d;
                    bram_data_1d <= bram_data;
                end if;
            end if;
        end if;
    end process;
    
    diff_cal_val <= "011"*(cam_ydata_2d) - ("000"&bram_data_1d) - ("000"&bram_data) - ("000"&y_data_1d);
    -- マトリクスの絶対値
    process(diff_cal_val) begin
        if diff_cal_val(10)='1' then -- 負の値
            abs_diff_cal_val <= not(diff_cal_val-1);
        else -- 正の数
            abs_diff_cal_val <= diff_cal_val;
        end if;
    end process;
    ydata_out <= x"FF" when (abs_diff_cal_val(10) or abs_diff_cal_val(9) or abs_diff_cal_val(8))='1' else abs_diff_cal_val(7 downto 0);    


これでシミュレーションで確かめてみたが、演算はあっているようだ。
ちなみに最初は飽和演算を入れていなかったのだが、”Sobel Core Module Verilog Code”を参考にさせていただいた。
インプリメントしてディスプレイ出力を見てみた。下の写真。
edge_detect_10_091109.jpg

うまくいかない。こうなったら3X3の画素の演算をインプリメントしてみようと思う。

(2009/11/13 追記)
BRAMによるダブルバッファリングのアドレスのコードにバグが見つかりました。バグを修正後、正常にエッジが表示されるようになりました。結果を下図に示します。
edge_detect_14_091113.jpg
  1. 2009年11月09日 21:41 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:4

ウッドデッキ作り1

昨日はウッドデッキを作り始めた。
グランステージで防腐処理済み2X4X8F、70本を注文したのだが、届いたのは防腐処理済み2X4X10Fだった。同じ値段でこれでも良いか?という話だったので承諾。
昨日の午前中、グランステージで軽トラックを借りて家まで持ってきた。
午後から、家で余っているレンガを使って、土台作り、低いところには土を追加した。まあ大体レベルがあっていればOKとした。その上に横に2X4を並べて、縦に2X4を張っていく予定だ。
台形の枠は大体出来たので、4mm の隙間を空けて2X4を張って行って、後で長い部分はカットする。
wood_deck_1_091109.jpg
  1. 2009年11月09日 06:18 |
  2. 木工
  3. | トラックバック:0
  4. | コメント:0

Quartus2 Ver9.1を使ってみた

Quartus2 Ver.9.1からNios2/eコアが無償化されたということを聞いたので、確かめてみた。つまり、なんちゃらかんちゃらtime_limited.sof ではなく、ちゃんとスタンドアロンで動く.sof ができるはず。。。
Quartus2 Ver.9.1をインストールしたので、以前の7セグメントLEDのダイナミック点灯AvalonMMスレーブを作った時点のVer.9.1のプロジェクト(”Avalon-MMスレーブペリフェラル4(Nios2 IDEで7セグLEDをテスト”)をコピーして、Quartus2 Ver.9.1からプロジェクトを起動してみたところ、問題なく変換出来て、Quartus2が立ち上がった。
さて、さっそくSOPC Builder を立ち上げる。問題なく変換して立ち上がった。CPUを見てみると、Nios2/s だった。そのままにしてGenerate。Quartus2に戻ってコンパイルした。
Quartus2_v91_1_091108.png

コンパイルが通って、Programmerを立ち上げるとnios2_sdram_led_time_limited.sof がプログラムできるようになる。ファイルの日付を見ると今日の日付になっているし、以前と同様に限定されたSOFファイルは出来るようだ。

次に、SOPC Builder を立ち上げ、CPUをNios2/e に変更した。
Quartus2_v91_2_091108.png

Generate して、Quartus2 でコンパイルし、プロジェクトのフォルダを見ると、nios2_sdram_led.sof が出来ていました。
Quartus2_v91_3_091108.png

ちゃんと、nios2_sdram_led.sof が出来て、機能を確認できたけど、JTAG-UART でやり取りなので、今までとあまり変わらないはず。とりあえず、基板を取りだすのが面倒なので、今日はこの辺で終了。
  1. 2009年11月08日 06:25 |
  2. QuartusⅡ
  3. | トラックバック:0
  4. | コメント:0

休止状態から復帰した後でネットにつながらなくなる

私はいつも継続してHDLを書いたり、ブログを書いたりするために通常はパソコンを休止状態にして終了している。
最近、何らかのアップデートをしてからか?休止状態から復帰した後でネットにつながらなくなることが頻発した。再起動をすれば問題ないのだが、それでは休止状態にした意味がない。
早速ネットで検索。そうすると東芝の”スリープまたは休止状態から復帰した後に、ネットワークに入れない場合(有線LAN)<Windows Vista(R)>”を発見。これに従って、ネットワークアダプタ(有線LAN)の省電力機能を無効にしたところ問題は今のところ解決している。
でも、今までなったことはなく、最近なったので、何らかのアップデートか何かが関係していると思うのだが???
  1. 2009年11月07日 19:32 |
  2. パソコン関連
  3. | トラックバック:0
  4. | コメント:0

FPGAの部屋のまとめサイトにAltera Tools Tutorialを追加

FPGAの部屋のまとめサイトAltera Tools Tutorialを追加した。
今までカテゴリ別に分かれていたので、自分でも見にくかったが、Altera Tools Tutorialでは時系列順に並べ替えた。これで少しは見やすくなったと思う。
  1. 2009年11月07日 19:25 |
  2. その他のFPGAの話題
  3. | トラックバック:0
  4. | コメント:0

画像のエッジ検出4(実機でテスト)

画像のエッジ検出3(シミュレーション)”でシミュレーションが終了したので、実機で試してみた。
やってみたが、最初はかなり真っ白、どこかおかしい。トラブルシュート開始。
その結果、BRAMのアドレスのダブルバッファ用アドレス発生がおかしいことを発見して修正した。その結果、結構エッジが見えるようになった。
差分のスレッショルドは16にし、元の画像を輝度を1/2にして加えてある。
edge_detect_9_091106.jpg

結構エッジは見えてきたようだ。結構見ていると面白いがエッジの検出具合がいまいちだ。顔とかの輪郭は出るが、やはりコントラスト差がないところは厳しい。

現在のエッジ検出方式は縦横斜めに差分を取ってスレッショルドの値以上ならば大きな値を輝度データとしてセットしている。(下図参照)これは、私がこうやったらエッジが検出できるんじゃないかな?と思った方法で一般的な方法ではない。
edge_detect_1_091028.png

しかし、今日、教授にお聞きしたところ、普通は3X3などのマトリクスを使い、それぞれの位置で係数をかけて中心の値を求めるとエッジの検出ができるとのこと。
検索してみると、”ディジタル信号処理講座”やkoujinz blogの”エッジ検出・エッジ強調・ぼかし”記事を読むと3X3のオペレータで各要素からの差分を取るようだ。(下に示すようなオペレータ)各要素をORしてスレッショルド決めて1、0判定をすることはしていない。
----------
| -1| -1| -1|
----------
| -1|  8| -1|
----------
| -1| -1| -1|
----------
差分、つまり微分するということのようだが、いろいろな係数があるみたい。これも試してみたい。
未来の自分の為にもう少し説明しておくと、画素の1部分を切り取った3X3の領域を考える。
---------------------------
|Xn-1Yn-1| XnYn-1| Xn+1Yn-1|
---------------------------
| Xn-1Yn |  XnYn | Xn+1Yn |
---------------------------
| Xn-1Yn+1| XnYn+1 | Xn+1Yn+1|
----------------------------
上のオペレータを適用した場合の差分の式を下に示す。
XnYnのところの差分 = -(Xn-1Yn-1) -(XnYn-1) -(Xn+1Yn-1)
              -(Xn-1Yn) +8(XnYn) -(Xn+1Yn)
              -(Xn-1Yn+1) -(XnYn+1) -(Xn+1Yn+1)

画像のグレースケール/ネガティブ/エッジ検出”を参考にした。この3X3のオペレータはとても便利で係数を変えればぼかしなども係数を変えることで対応することができる。
良く考えれば、私のやっていた方式も2X2の画素の演算と考えることができるので、下のような微分オペレータを作ることができそう。
-------
| -1| -1|
-------
| -1| 3|
-------
これで、どうなるか一回やってみようと思う。
(追加)”コンボリューションを用いた画像の平滑化、鮮鋭化とエッジ検出”もわかりやすかった。
  1. 2009年11月07日 04:45 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0

ブログがおかしかった

今日、午後6時ころからブログの表示がおかしくなった。やっと、午後10時30分頃復旧したが、とても心配だった。私の補助記憶といっても良いくらいに育ったので、これがなくなったらどうしよう???
一応、月1でバックアップは取っているけど、壊れたら復旧するまでには大変だと思う。なんかお手軽に自動バックアップしてくれないかな?
ちなみに、ブログの記事数は今のところ 1,261記事、テキストの容量は4.45MB、画像ファイル数は4230、171.28MB となった。
グーグルで検索すると、自分のブログが出てきて、そうか、あのころ、同じようなことやっていたな。というのが良くある。これも公開しているメリットだろうか?
  1. 2009年11月05日 21:53 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:2

ISE11.3でプロジェクトをフォルダごとコピーした時のWorking directory

ISE11.3でのプロジェクトの絶対パス問題?”で家からフォルダごとコピーしてきたプロジェクトをインプリメントすると、テンポラリファイルやbitファイルが家と同じフォルダに作られるという問題に遭遇した。
この原因が分かった。”33511 - 11.2 ISE - 「ERROR: no Simulation Engine found in the Transform Input that matches testbench_isim_beh.exe」というエラー メッセージが表示される”によると”ISE 11.2 で、Project Navigator にプロジェクトの作業ディレクトリとしてメインのプロジェクト ディレクトリ以外のディレクトリを指定できる機能が追加されました。”ということだ。この作業用ディレクトリを見てみた。
Project Navigator のDesign Edit Properties アイコンをクリックする。
working_directory_1_091105.png

Design Properties ダイアログが開く。
working_directory_2_091105.png

Location はコピー先のフォルダになっているが、Working directory はコピー元の情報のままだ。これでは作業用ディレクトリが異なってしまう。それに、Working directory のところはハイドされていて書き換えができない。それで、フォルダごとコピーした時にはこの問題をどうしようと思ったが、Copy Project というのがあった。これでやってみる。
File メニューからCopy Project を選択すると、Copy Projectダイアログが出てくる。
working_directory_3_091105.png

ここで、新規フォルダを指定して、プロジェクトをコピーする。その時に作業用ディレクトリも指定することができる。上のダイアログの設定だと、プロジェクトが立ち上がるので、そのままインプリすれば、指定したフォルダにテンポラリファイルやbitファイルが生成される。
なお、このヘルプによると”Working directory はプロジェクトを作成するときでないと変更できない”と読めるように書いてあった。
  1. 2009年11月05日 17:42 |
  2. Xilinx ISEについて
  3. | トラックバック:0
  4. | コメント:0

画像のエッジ検出3(シミュレーション)

画像のエッジ検出2(方式の検討”の続き。

最初に注意。この記事では水平ラインの最初を0ラインと記述しています。その他の記事では最初の水平ラインを1ラインと記述しているものがあるのでご注意ください。


一応、縦横斜めエッジ検出のVHDLソースが出来上がったので、シミュレーションをしてみた。
その前に、ちょっと方式の説明をする。縦と斜めのエッジを検出するためにBRAMに1ライン分の輝度データを保存している。BRAMのWriteアドレスとReadアドレスは、偶数ラインと奇数ラインでアドレスを分けている。最上位ビットを0にしたり、1にしたりしている。例えば0ライン目(最初のライン)はWriteアドレス(bram_wr_addr) が000、Readアドレス(bram_rd_addr) が400から始まる。(BRAMは2ポートSRAMです) 次の1ライン目はWriteアドレスが400、Readアドレス が000となる。こうやって競合しないようにBRAMを使用する。
h_valid_flagは横方向に比較できることを示すフラグ信号で、ラインの最初だけ0でその後は1となる信号だ。v_valid_flagは縦、斜め方向に比較できることを示すフラグ信号で、つまり0ライン(最初の水平ライン)だけ0で、それ以外のラインは1となる。
以上を踏まえてCamera_Controller.vhd の全ソースを貼ろうと思ったが、400行近くあるのでやめた。比較部分だけを貼ることにする。

    -- 1クロック前のBRAMの出力と現在の値の1クロック前の値を保持
    process(clk) begin
        if clk'event and clk='1' then
            if reset='1' then
                bram_data_1d <= (others => '0');
                y_data_1d <= (others => '0');
            else
                if r_w='1' then
                    y_data_1d <= cam_ydata_2d;
                    bram_data_1d <= bram_data;
                end if;
            end if;
        end if;
    end process;
    
    -- 縦横斜めピクセルの引き算の絶対値
    process(cam_ydata_2d, bram_data) begin -- 縦
        if unsigned(cam_ydata_2d) >= unsigned(bram_data) then
            vertical_comp_val <= unsigned(cam_ydata_2d) - unsigned(bram_data);
        else
            vertical_comp_val <= unsigned(bram_data) - unsigned(cam_ydata_2d);
        end if;
    end process;
    process(cam_ydata_2d, y_data_1d) begin -- 横
        if unsigned(cam_ydata_2d) >= unsigned(y_data_1d) then
            horizontal_comp_val <= unsigned(cam_ydata_2d) - unsigned(y_data_1d);
        else
            horizontal_comp_val <= unsigned(y_data_1d) - unsigned(cam_ydata_2d);
        end if;
    end process;
    process(cam_ydata_2d, bram_data_1d) begin
        if unsigned(cam_ydata_2d) >= unsigned(bram_data_1d) then
            skew_comp_val <= unsigned(cam_ydata_2d) - unsigned(bram_data_1d);
        else
            skew_comp_val <= unsigned(bram_data_1d) - unsigned(cam_ydata_2d);
        end if;
    end process;

    -- 比較器 (縦横斜めのYデータを比較してエッジを検出する)
    process(h_valid_flag, v_valid_flag, vertical_comp_val, horizontal_comp_val, skew_comp_val) begin
        if h_valid_flag='0' and v_valid_flag='0' then -- hとvのvalid_flag が0の時は比較しない
            ydata_out <= '0' & cam_ydata_2d(7 downto 1);
        elsif h_valid_flag='1' and v_valid_flag='0' then -- 最初の水平ラインの時は水平方向だけ比較する
            if horizontal_comp_val>=Edge_Threshold_Def then
                ydata_out <= CONV_STD_LOGIC_VECTOR(Edge_Value_Def, 8) or ('0' & cam_ydata_2d(7 downto 1));
            else
                ydata_out <= '0' & cam_ydata_2d(7 downto 1);
            end if;
        elsif h_valid_flag='1' and v_valid_flag='1' then -- 比較
            if vertical_comp_val>=Edge_Threshold_Def or horizontal_comp_val>=Edge_Threshold_Def or skew_comp_val>=Edge_Threshold_Def then -- 縦横斜めどれかがスレッショルドを超えていたらEdge_Value_Defをセット
                ydata_out <= CONV_STD_LOGIC_VECTOR(Edge_Value_Def, 8) or ('0' & cam_ydata_2d(7 downto 1));
            else
                ydata_out <= '0' & cam_ydata_2d(7 downto 1);
            end if;
        else
            ydata_out <= '0' & cam_ydata_2d(7 downto 1);
        end if;
    end process;


ModelSimでのシミュレーションの0ライン目を下に示す。
edge_detect_7_091105.png

上の図でr_w が1の時が輝度データで、cam_ydata_2dのr_w が1の時が輝度データである。ydata_outはエッジ検出値を含んだ輝度データで、これが、Camera_Controller.vhd のデータ出力である。bram_wr_addr、bram_rd_addr はそれぞれBRAMのWriteアドレス、Readアドレスだ。下の、vertical_comp_val は縦の差分の絶対値、horizontal_comp_val は横の差分の絶対値、skew_comp_val は斜めの差分の絶対値を示す。ここではh_valid_flag が輝度データ2個目から1になるが、v_valid_flagは0のままとなる。
次の1水平ライン目のシミュレーションを下に示す。
edge_detect_8_091105.png

bram_wr_addr、bram_rd_addr は初期値が400、000となり、0ライン目とは逆になっている。h_valid_flag が輝度データ2個目から1になり、v_valid_flagも1になっているのがわかる。

これでシミュレーションは大体大丈夫だと思うので、インプリして実機でテストすることにする。
  1. 2009年11月05日 05:54 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0

サイクリング、ウッドデッキ、映画

今日の午前中に縦横斜めのエッジ検出のVHDLコード書いていたら飽きてきたので、サイクリングに行ってきた。この辺は田舎なので農道が縦横無尽につながっていて、そこを自転車で走ると人にも車にもめったに会わない。(ちなみに道の周りの芝は畑です。芝を栽培して表面をはぎ取って売ります。ゴルフ場とかに使われるんでしょうね?)
cycling_1_091103.jpg

雲がぽかぽか浮かんでいて、とても良い天気で気持ちが良い。とってもペダルが軽いと思ったら、追い風だった。これは、調子に乗って漕いでいると帰りが大変。5, 6Kmくらい行って引き返してきた。帰りは筑波山がきれいに見えるが、向かい風でペダルが重い。
cycling_2_091103.jpg

午後2時ころ、ウッドデッキの材料を買いに、奥さんと一緒にグランステージへ、防腐処理 2X4X8Fを70本、買いに行った。

ウッドデッキパーツ表

防腐処理 2X4X8F 55本 床板用 計 (4,850(970*5)mm / 93mm = 52.15本)55(52.15)本*520円 = 27,560円
防腐処理 2X4X8F 10本 床桟用 5,200円

計 防腐処理 2X4X8F 65本 32,760円

斜めの分
防腐処理 2X4X8F 5本 床板用 (970mm / 93mm = 10.43本)半分なので5本でOK

総計 防腐処理 2X4X8F 70本 36,400円


材木を持ってこようと思ったが、取り寄せということになった。今日の予定が空いたので、急遽、映画を見に行こうということになり、イーアスへ。
”風が強く吹いている”を見てきた。箱根駅伝を見るのが好きなので、良かった。設定が安易な気もするが、美しい走り(と私は思える)を見るのは気持ちが良い。
  1. 2009年11月03日 18:59 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0

画像のエッジ検出(横方向のみ)2(実機で試してみた)

画像のエッジ検出(横方向のみ)1”で作ったコードをインプリメントして実機で確かめてみた。
最初に、Edge_Threshold_Def(エッジ検出のスレッショルド)が64でやってみたが、真っ暗で隣のピクセルと64値が離れているところはないようだった。
次に、Edge_Threshold_Def = 32 とした。こうすると少しはエッジが見えてきた。詳しくは下図参照。
edge_detect_3_091103.jpg

後の私の顔とかが映っていますが、エッジが少し見えてきた。まだ足りなそうなので、Edge_Threshold_Def = 16 としたところ下のようになった。下図参照。
edge_detect_4_091103.jpg

だいぶエッジが見えてきた。これだけだとなにを撮っているかがわからないので、白黒画像で表示した写真を下に示す(若干位置が違うけど、こんな雰囲気)。
edge_detect_5_091103.jpg

やはりエッジの表示だけだとさびしいし、画像のどの部分のエッジを取っているかが良くわからない。そこで、CMOSカメラのY成分と重ね合わせることにした。今は、Edge_Value_Def = 128なので、ちょうどCMOSカメラのY成分(cam_ydata_2d) を1ビット右シフトしてORすれば、ちょうど良いのではないかと思った。早速、Camera_Controller.vhd のエッジを検出する部分を下のように書き換えた。

    -- 1つ前のYデータと現在のYデータを比較してエッジを検出する
    process(h_valid_flag, alu_out, cam_ydata_2d) begin
        if h_valid_flag='0' then -- 1つ前のYデータがない
            ydata_out <= '0' & cam_ydata_2d(7 downto 1);
        elsif h_valid_flag='1' then
            if alu_out >= Edge_Threshold_Def then
                ydata_out <= CONV_STD_LOGIC_VECTOR(Edge_Value_Def, 8) or ('0' & cam_ydata_2d(7 downto 1));
             else
                ydata_out <= '0' & cam_ydata_2d(7 downto 1);
            end if;
        else
            ydata_out <= '0' & cam_ydata_2d(7 downto 1);
        end if;
    end process;


こうしてディスプレイに表示された画面を下図に示す。
edge_detect_6_091103.jpg

これを見ると画像のどの位置のエッジを検出しているかが良くわかる。雑誌のエッジ、コンフィグケーブルのUSBケーブルのエッジを検出しているのがわかる。雑誌を見ると、横方向のエッジは検出しているが縦方向のエッジは検出してないことがわかる。これは、横方向のエッジのみ検出する方式を使用しているので当然だ。
今度は縦横斜めのエッジを検出して、横だけ検出のものと比較してみたい。
  1. 2009年11月03日 04:36 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:2

CMOSカメラから画像を入力してディスプレイへ出力のVHDLソース修正

CMOSカメラから画像を入力してディスプレイへ出力16(VHDLソースを公開)”で公開したVHDLソースだが、エッジ検出のVHDLソースを作りながら、前のVHDLソースをシミュレーションしていたら、波形が出なくなってしまった。
考えてみると、実機の方でデバックしながらリセットの極性を逆にしたのだった。CMOSカメラのモデルOV7640_Model.vhd のリセットを逆にした。さらに良く見るとOV7640_Model.vhd の波形にもバグがあった。HREF が640PCLKの間しか1になっていない。これは1280PCLKのはず。VHDLソースを見るとバグがあったので修正した。ついでにリセットの名前がn_cam_reset だったので、cam_reset に修正。そうすると、CamDispCntrl_SRAM_tb.vhd、CamDispCntrl_SRAM.vhd も修正となってしまった。
これでシミュレーションは正常になったが、インプリメントも確かめなくては!ということでISE11.3を立ち上げて、インプリメントしてみたが、なぜかマップでエラー。どうやらCMOSカメラのSIO_Dに出力するcam_sio_d が入力専用ピンだと言われているみたいだ。
UCFを見るとピンのアサインはP26 となっていて確かに間違っている。これはP3が正解。これを修正すると問題なくインプリメントが通った。しかし、今まではなんでインプリメントが通っていたのだろうか?なぞ?
これで大丈夫なはずということで、ダウンロードファイルを作り直して再度アップした。ファイル名はCamDispCntrl_SRAMp091101.ZIPとした。

今まで、ダウンロードされた方は、”CMOSカメラから画像を入力してディスプレイへ出力16(VHDLソースを公開)”からもう一度ダウンロードをお願いいたします。

(2009/11/02 : 追記)
またまた、バグありのVGA_Display_Controller.vhd を入れてしまいました。もう一度ダウンロード、お願いします。今度は大丈夫だと思われます。
  1. 2009年11月02日 19:09 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0

画像のエッジ検出(横方向のみ)1

画像のエッジ検出2(方式の検討)”で縦横斜めのエッジを検出する予定だったのですが、思ったより手間取るようなので、shirouさんにコメントで教えていただいた横方向のエッジ検出だけを行ってみることにした。これならば、今まで書いてきたVHDLコードをコピーするだけで簡単に書くことができる。後で縦横斜めエッジ検出と性能を比べることもできる。
書きなおしたのはCamera_Controller.vhdとトップのCamDispCntrl_SRAM.vhd、それに、シミュレーションの時にY(輝度)データの差分がうまく取れるように、OV7640_Model.vhd も改造した。
とりあえずは、Camera_Controller.vhd から下に貼る。

-- CAMERA_CONTROLLER
-- カメラデータを書き込むためのSRAMのアドレスを出力
-- 将来的にはI2Cコントローラを付けて、CMOSカメラの設定を変更することも考えている

library IEEE;
use IEEE.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;

entity Camera_Controller is
    port(
        clk : in std_logic;
        reset : in std_logic;        
        cam_href_2d : in std_logic;    -- CMOSカメラからのHREFのラッチ出力
        cam_href_3d : in std_logic;    -- CMOSカメラからのHREFのラッチ出力(3クロック遅延)
        r_w : in std_logic; -- Read / Write
        master_sync : in std_logic; -- 同期信号
        cam_ydata_2d : in std_logic_vector(7 downto 0); -- CMOSカメラからの輝度データ
        ydata_out : out std_logic_vector(7 downto 0); -- 比較した値
        mem_addr : out std_logic_vector(17 downto 0) -- CMOSカメラのデータを書き込むSRAMのアドレス
    );
end Camera_Controller;

architecture RTL of Camera_Controller is
constant Edge_Threshold_Def : integer := 64;
constant Edge_Value_Def : integer := 128;

signal mem_a : unsigned(17 downto 0);
signal sub_count : std_logic_vector(1 downto 0);
type COMPARE_H_STATE is (no_data_h, comparing_h);
signal cs_chs : COMPARE_H_STATE;
signal h_valid_flag : std_logic;
signal alu_out : unsigned(7 downto 0);
signal ydata_1d : std_logic_vector(7 downto 0);

begin
    -- 4クロックにアドレスが1つ進むので、4つ数えるカウンタを用意する。
    process(clk) begin
        if clk'event and clk='1' then
            if reset='1' or master_sync='1' then
                sub_count <= (others => '0');
            else
                if cam_href_2d='1' then 
                    sub_count <= sub_count + 1;
                end if;
            end if;
        end if;
    end process;
    
    -- SRAMのアドレス生成、4クロックに一回+1する
    process(clk) begin
        if clk'event and clk='1' then
            if reset='1' or master_sync='1' then
                mem_a <= (others => '0');
                -- mem_a <= "000000000100000000";
            else
                if cam_href_2d='1' and sub_count="11" then
                    mem_a <= mem_a + 1;
                end if;
            end if;
        end if;
    end process;
    mem_addr <= std_logic_vector(mem_a);
    
    -- 水平方向の2つのデータが比較可能ということを表すステートマシン、cam_href_2dが1になった最初のデータは比較するデータがないので比較できない
    process(clk) begin
        if clk'event and clk='1' then
            if reset='1' then
                cs_chs <= no_data_h;
                h_valid_flag <= '0';
            else
                case cs_chs is
                    when no_data_h =>
                        if cam_href_3d='1' then -- 最初の1つは排除
                            cs_chs <= comparing_h;
                            h_valid_flag <= '1';
                        else
                            cs_chs <= no_data_h;
                        end if;
                    when comparing_h =>
                        if cam_href_2d='0' then
                            cs_chs <= no_data_h;
                            h_valid_flag <= '0';
                        else
                            cs_chs <= comparing_h;
                        end if;
                end case;
            end if;
        end if;
    end process;

    -- 1つ前のYデータを覚える
    process(clk) begin
        if clk'event and clk='1' then
            if reset='1' then
                ydata_1d <= (others => '0');
            elsif r_w='1' then
                ydata_1d <= cam_ydata_2d;
            end if;
        end if;
    end process;
    
    -- cam_ydata_2d - ydata_1dの絶対値を取る
    process(cam_ydata_2d, ydata_1d) begin
        if unsigned(cam_ydata_2d) >= unsigned(ydata_1d) then
            alu_out <= unsigned(cam_ydata_2d) - unsigned(ydata_1d);
        else
            alu_out <= unsigned(ydata_1d) - unsigned(cam_ydata_2d);
        end if;
    end process;
    
    -- 1つ前のYデータと現在のYデータを比較してエッジを検出する
    process(h_valid_flag, alu_out) begin
        if h_valid_flag='0' then -- 1つ前のYデータがないので、値は0
            ydata_out <= (others => '0');
        elsif h_valid_flag='1' then
            if alu_out >= Edge_Threshold_Def then
                ydata_out <= CONV_STD_LOGIC_VECTOR(Edge_Value_Def, 8);
             else
                ydata_out <= (others => '0');
            end if;
        else
            ydata_out <= (others => '0');
        end if;
    end process;
    
end RTL;


constant文でEdge_Threshold_Def(エッジ検出のスレッショルド)を決定している。前のデータとの差がEdge_Threshold_Defよりも大きければ、Edge_Value_Defを出力する。それ以外の場合は0を出力データとする。
次は、CamDispCntrl_SRAM.vhd。

--CamDisplay Contoroller with SRAM(トップモジュール)
-- トップモジュールから全部作ることにする。
-- CMOSカメラから出てくるビデオ出力はUYVYとする
-- VSYNCは正論理、HREFも正論理
-- clk(48MHz)はそのまま使用して、PCLKをDCMで受けて使用する。
-- IOBにマップされるロジックはこのトップモジュールにインスタンシエーションする。
-- synchronizerでは_1dのデータを使用し、その他のモジュールでは_2dのデータを使用する
-- 全体のタイミングは_2dを基準とする

library IEEE;
use IEEE.std_logic_1164.all;
use ieee.numeric_std.all;

-- pragma translate_off
library UNISIM;
use UNISIM.VComponents.all;
-- pragma translate_on

entity CamDispCntrl_SRAM is
    port (
        clk    : in std_logic;            -- 水晶発振器からの48MHzクロック
        cam_vsync : in std_logic;    -- CMOSカメラからのVSYNC
        cam_href : in std_logic;    -- CMOSカメラからのHREF
        cam_pclk : in std_logic;        -- CMOSカメラからのpclk
        cam_ydata : in std_logic_vector(7 downto 0); -- CMOSカメラからのYデータ
        cam_sio_c : out std_logic;    -- CMOSカメラのI2Cクロック
        -- cam_sio_d : inout std_logic; -- CMOSカメラのI2Cデータ
        cam_sio_d : out std_logic; -- CMOSカメラのI2Cデータ
        cam_clk    : out std_logic;     -- CMOSカメラのクロック
        cam_reset    : out std_logic;     -- CMOSカメラのリセット(Hでリセット)
        dac_red    : out std_logic_vector(7 downto 0);    -- DACへのRED出力
        dac_green : out std_logic_vector(7 downto 0); -- DACへのGREEN出力
        dac_blue : out std_logic_vector(7 downto 0); -- DACへのBLUE出力
        n_dac_blank : out std_logic; -- DACへのアクティブローBLANK
        dac_clk : out std_logic; -- DACクロック(24MHz)
        vga_vsync : out std_logic; -- VGAのVSYNC
        vga_hsync : out std_logic; -- VGAのHSYNC
        mem_data : inout std_logic_vector(15 downto 0);    -- SRAMのmemory data
        n_mem_we : out std_logic; -- SRAMのmemory write enable
        n_mem_rd : out std_logic; -- SRAMのmemory read enable
        n_mem_cs0 : out std_logic; -- SRAMのchip select 0
        n_mem_cs1 : out std_logic; -- SRAMのchip select 1
        n_mem_cs2 : out std_logic; -- SRAMのchip select 2
        n_mem_cs3 : out std_logic; -- SRAMのchip select 3
        n_mem_lowerB : out std_logic; -- SRAMのmemory lower byte enable
        n_mem_upperB : out std_logic; -- SRAMのmemory upper byte enable
        mem_addr : out std_logic_vector(17 downto 0) -- SRAMのmemory address
    );
end CamDispCntrl_SRAM;

architecture RTL of CamDispCntrl_SRAM is
component DCM_module_24MHz
    port(
        clk48MHz_in : in std_logic;
        clk48_out : out std_logic;
        cam_clk_out : out std_logic;
        pclk_in : in std_logic;
        mclk_out : out std_logic;
        cam_clk_locked : out std_logic;
        mclk_locked : out std_logic
    );
end component;
component ODDR2
 generic(

      DDR_ALIGNMENT : string := "NONE";
      INIT          : bit    := '0';
      SRTYPE        : string := "SYNC"
      );

  port(
      Q           : out std_ulogic;

      C0          : in  std_ulogic;
      C1          : in  std_ulogic;
      CE          : in  std_ulogic := 'H';
      D0          : in  std_ulogic;
      D1          : in  std_ulogic;
      R           : in  std_ulogic := 'L';
      S           : in  std_ulogic := 'L'
    );
end component;
component VGA_Display_Controller
    port(
        clk : in std_logic;
        reset : in std_logic;
        master_sync : in std_logic; -- 表示タイミングの同期信号
        pixel_y_data : in std_logic_vector(15 downto 0); -- Yのデータ、下のdata_enableが1の時に有効
        data_enable : in std_logic;
        h_count_out : out unsigned(9 downto 0); -- 水平カウンタのカウント出力
        v_count_out : out unsigned(9 downto 0); -- 垂直カウンタのカウント出力
        red_out : out std_logic_vector(7 downto 0); -- VGA出力
        green_out : out std_logic_vector(7 downto 0); -- VGA出力
        blue_out : out std_logic_vector(7 downto 0); -- VGA出力
        blank_out : out std_logic; -- BLANK出力
        h_syncx_out : out std_logic; -- 水平同期出力
        v_syncx_out : out std_logic; -- 垂直同期出力
        mem_addr : out std_logic_vector(18 downto 0) -- SRAMのアドレス、バイト単位
    );
end component;
component synchronizer
    port(
        clk : in std_logic;
        reset : in std_logic;
        cam_vsync_1d : in std_logic;
        cam_href_1d : in std_logic;
        master_sync : out std_logic;
        r_w : out std_logic
    );
end component;
component SRAM_Controller
    port(
        clk : in std_logic;
        clk48 : in std_logic;
        reset : in std_logic;
        r_w : in std_logic; -- Read or Write
        cam_href_3d : in std_logic; -- cam_href の3クロック遅れ、各モジュールで2クロック遅れの信号を使っているので、それよりも1クロック遅れている信号をweのイネーブルとして使用する。これはUYVYとデータが来るので、サンプルするのに1クロック遅れるから。
        master_sync : in std_logic;
        cam_mem_addr : in std_logic_vector(17 downto 0); -- CMOSカメラの書き込み用のアドレス
        cam_ydata_in : in std_logic_vector(7 downto 0); -- CMOSカメラのデータ
        vga_mem_addr : in std_logic_vector(18 downto 1); -- VGAコントローラー用読み出し用アドレス
        
        mem_data_out : out std_logic_vector(15 downto 0); -- SRAMのデータ出力。CMOSカメラのデータを2つ集めたもの。
        mem_data_oe : out std_logic; -- SRAMのmemory data出力のOutput Enable
        
        n_mem_we : out std_logic; -- SRAMのWE
        n_mem_cs0 : out std_logic; -- SRAMのchip select 0
        n_mem_cs1 : out std_logic; -- SRAMのchip select 1
        n_mem_cs2 : out std_logic; -- SRAMのchip select 2
        n_mem_cs3 : out std_logic; -- SRAMのchip select 3
        n_mem_lowerB : out std_logic; -- SRAMのmemory lower byte enable
        n_mem_upperB : out std_logic; -- SRAMのmemory upper byte enable
        mem_addr : out std_logic_vector(17 downto 0) -- SRAMのmemory address
    );
end component;
component Camera_Controller
    port(
        clk : in std_logic;
        reset : in std_logic;        
        cam_href_2d : in std_logic;    -- CMOSカメラからのHREFのラッチ出力
        cam_href_3d : in std_logic;    -- CMOSカメラからのHREFのラッチ出力(3クロック遅延)
        r_w : in std_logic; -- Read / Write
        master_sync : in std_logic; -- 同期信号
        cam_ydata_2d : in std_logic_vector(7 downto 0); -- CMOSカメラからの輝度データ
        ydata_out : out std_logic_vector(7 downto 0); -- 比較した値
        mem_addr : out std_logic_vector(17 downto 0) -- CMOSカメラのデータを書き込むSRAMのアドレス
    );
end component;
component IOBUF
    port(
        O : out std_logic;
        IO : inout std_logic;
        I : in std_logic;
        T : in std_logic
    );
end component;

signal mclk : std_logic;
signal reset : std_logic;
signal mclk_locked : std_logic;
signal master_sync : std_logic;
signal r_w : std_logic;
signal cam_href_1d, cam_vsync_1d : std_logic;
signal cam_href_3d : std_logic;
signal cam_href_2d, cam_vsync_2d : std_logic;
signal cam_ydata_1d, cam_ydata_2d : std_logic_vector(7 downto 0);
signal w_r : std_logic;
signal vga_blank : std_logic;
signal vga_mem_addr : std_logic_vector(17 downto 0);
signal vga_mem_addr_19 : std_logic_vector(18 downto 0);
signal cam_mem_addr : std_logic_vector(17 downto 0);
signal clk48 : std_logic;
signal mem_data_out : std_logic_vector(15 downto 0); -- SRAMのデータ出力。CMOSカメラのデータを2つ集めたもの。
signal mem_data_oe : std_logic; -- SRAMのmemory data出力のOutput Enable
signal n_mem_data_oe : std_logic;
signal vga_ydata : std_logic_vector(15 downto 0);
signal input_mem_data : std_logic_vector(15 downto 0);
signal cam_clk_node, cam_clk_locked : std_logic;
signal n_cam_clk_node, cam_clk_reset : std_logic;
signal n_mclk : std_logic;
signal ydata_YUV : std_logic_vector(7 downto 0);

begin
    cam_sio_c <= '1';
    cam_sio_d <= 'Z';
    cam_reset <= '0'; -- OV7640のRESETはアクティブハイ
    
    DCM_module_24MHz_inst : DCM_module_24MHz port map(
        clk48MHz_in => clk,
        clk48_out => clk48,
        cam_clk_out => cam_clk_node,
        pclk_in => cam_pclk,
        mclk_out => mclk,
        cam_clk_locked => cam_clk_locked,
        mclk_locked => mclk_locked
    );
    reset <= not mclk_locked;
    
    -- CMOSカメラ用クロックの生成
    n_cam_clk_node <= not cam_clk_node;
    cam_clk_reset <= not cam_clk_locked;
    ODDR2_for_cam_clk : ODDR2 generic map(
        SRTYPE => "ASYNC"
    ) port map(
        Q => cam_clk,
        C0 => cam_clk_node,
        C1 => n_cam_clk_node,
        CE => '1',
        D0 => '1',
        D1 => '0',
        R => cam_clk_reset,
        S => '0'
    );
    
    -- DAC用クロックの生成
    n_mclk <= not mclk;
    ODDR2_for_dac_clk : ODDR2 generic map(
        SRTYPE => "ASYNC"
    ) port map(
        Q => dac_clk,
        C0 => mclk,
        C1 => n_mclk,
        CE => '1',
        D0 => '1',
        D1 => '0',
        R => reset,
        S => '0'
    );
    
    -- CMOSカメラからの入力は一度IOBのFFを通す
    process(mclk) begin
        if mclk'event and mclk='1' then
            if reset='1' then
                cam_vsync_1d <= '0';
                cam_href_1d <= '0';
                cam_ydata_1d <= (others => '0');
                cam_vsync_2d <= '0';
                cam_href_2d <= '0';
                cam_ydata_2d <= (others => '0');
                cam_href_3d <= '0';
            else
                cam_vsync_1d <= cam_vsync;
                cam_href_1d <= cam_href;
                cam_ydata_1d <= cam_ydata;
                cam_vsync_2d <= cam_vsync_1d;
                cam_href_2d <= cam_href_1d;
                cam_ydata_2d <= cam_ydata_1d;
                cam_href_3d <= cam_href_2d;
            end if;
        end if;
    end process;
    
    -- CMOSカメラの書き込みアドレスの生成
    
    synchronizer_inst : synchronizer port map(
        clk => mclk,
        reset => reset,
        cam_vsync_1d => cam_vsync_1d,
        cam_href_1d => cam_href_1d,
        master_sync => master_sync,
        r_w => r_w
    );
    
    -- mem_dataの入力FF
    process(mclk) begin
        if mclk'event and mclk='1' then
            if reset='1' then
                vga_ydata <= (others => '0');
            else
                vga_ydata <= input_mem_data;
            end if;
        end if;
    end process;
    w_r <= not r_w;
    
    VGA_Display_Controller_inst : VGA_Display_Controller port map(
        clk => mclk,
        reset => reset,
        master_sync => master_sync,
        pixel_y_data => vga_ydata,
        data_enable => w_r,
        h_count_out => open,
        v_count_out => open,
        red_out => dac_red,
        green_out => dac_green,
        blue_out => dac_blue,
        blank_out => vga_blank,
        h_syncx_out => vga_hsync,
        v_syncx_out => vga_vsync,
        mem_addr => vga_mem_addr_19
    );
    n_dac_blank <= not vga_blank;
    
    Camera_Controller_inst : Camera_Controller port map(
        clk => mclk,
        reset => reset,
        cam_href_2d => cam_href_2d,
        cam_href_3d => cam_href_3d,
        r_w => r_w,
        master_sync => master_sync,
        cam_ydata_2d => cam_ydata_2d,
        ydata_out => ydata_YUV,
        mem_addr => cam_mem_addr
    );
    
    vga_mem_addr <= vga_mem_addr_19(18 downto 1);
    SRAM_Controller_inst : SRAM_Controller port map(
        clk => mclk,
        clk48 => clk48,
        reset => reset,
        r_w => r_w,
        cam_href_3d => cam_href_3d,
        master_sync => master_sync,
        cam_mem_addr => cam_mem_addr,
        cam_ydata_in => ydata_YUV,
        vga_mem_addr => vga_mem_addr,
        mem_data_out => mem_data_out,
        mem_data_oe => mem_data_oe,
        n_mem_we => n_mem_we,
        n_mem_cs0 => n_mem_cs0,
        n_mem_cs1 => n_mem_cs1,
        n_mem_cs2 => n_mem_cs2,
        n_mem_cs3 => n_mem_cs3,
        n_mem_lowerB => n_mem_lowerB,
        n_mem_upperB => n_mem_upperB,
        mem_addr => mem_addr
    );
    n_mem_data_oe <= not mem_data_oe;
    
    MEM_DATA_GEN : for i in 15 downto 0 generate
        IOBUF_inst : IOBUF port map(
            O => input_mem_data(i),
            IO => mem_data(i),
            I => mem_data_out(i),
            T => n_mem_data_oe
        );
    end generate MEM_DATA_GEN;
    n_mem_rd <= not r_w;
end RTL;


次はOV7640_Model.vhd だが、これは変更点のYデータを出力するprocessだけを示す。

    -- ydata の出力
    process(reset, pclock) begin -- Yデータ
        if reset='1' then
            y_data <= "00000001";
        elsif pclock'event and pclock='1' then
            if h_count<H_ACTIVE_VIDEO and h_count(0)='1' then -- 水平の表示区間で2クロックに1回シフト
                if y_data=128 then
                    y_data <= "00000001";
                else
                    y_data <= y_data(6 downto 0) & '0';
                end if;
            elsif h_count=0 and v_count=0 then -- 1フレーム描画の最初にリセット
                y_data <= "00000001";
            end if;
        end if;
    end process;


これでシミュレーションをしてみた。その結果を下の図に示す。
edge_detect_2_091102.png

cam_ydata_2d信号の四角で囲ったデータがY(輝度)データだ。その値は左シフトしているので、16進数で0x40, 0x80, 0x01 と変化している。それぞれ、Edge_Threshold_Defの64と同じか超えているので、Camera_Controller.vhdはydata_out出力に128、16進数で0x80を出力している。その様子が矢印で書かれている。つまりinput_mem_dataに80と出力されている。
これでシミュレーションはうまく行ったようなので、実機で確かめてみることにする。

#今朝はおしまい。今から録画してあったアブダビF1を見ます。今年の最終戦です。ジェンソン・バトン、チャンピオンおめでとう。

(修正)Camera_Controller.vhdが間違っていたので修正しました。
  1. 2009年11月02日 05:41 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0
»