画像認識におけるフーリエ変換とフーリエ記述子(その1)
はじめに
一連のFPGA関連ネタとして、画像認識を扱おうと思います。
ここでは周波数スペクトルを推定する離散フーリエ変換からはじまり、フーリエ記述子、ウェーブレット変換、ニューラルネットワークに至るまで取り扱うつもりです。
画像の特徴を抽出する
入力画像から特定の形状を抽出して、基準画像と比較することは画像認証ではよく行う方法です。そのためには、入力画像データから図形を抽出し、数式化する必要があります。
例えば一辺の長さが2の正方形を画像の中から抽出し数式化しなければならないとします。もし図1の様に、画像の縦横に対して図形が平行なら、数式化は難しくありません。
図1:一辺が2の正方形
この正方形は以下の式で表せます。
しかし、図2の様に正方形に傾きがある場合、数式化は難しいかもしれません。不可能ではないですが、その数式から正方形であるかどうか判別するのに時間がかかるはずです。
図2:傾きのある正方形
このように傾きがある場合でも、画像中に正方形が存在する事を数式として表現できるでしょうか。
一つの答えは、図3のように正方形上を移動する点Pを用意する事です。
図3:正方形上を移動する点P
言葉で表現すると以下の様になります。
- 距離2だけ進み、開始時のベクトル方向より正の方向(反時計回り)に90度回転
- 距離2だけ進み、開始時のベクトル方向より正の方向(反時計回り)に180度回転
- 距離2だけ進み、開始時のベクトル方向より正の方向(反時計回り)に270度回転
- 距離2だけ進み、開始時のベクトル方向より正の方向(反時計回り)に360度回転
このように表現すると正方形の傾きは関係なくなります。
グラフ化してみましょう。点Pが移動する距離をs、開始時のベクトルと現時点でのベクトルのなす角度をθとします。先ほどdegreeで角度を表現しましたが、便宜上radian表示に直しました。
図4:点Pの軌跡をグラフ化
少し違和感のあるグラフになりました。点Pはそもそも正方形上を移動しているので2x4=8だけ進むと周期的に元の場所に戻って来ているはずです。
そこで、点Pの軌跡を「正規化」します。*1
先ほどの様に言葉で表現するとこうなります。
- 距離2だけ進み、正の方向(反時計回り)に90度回転
- (1)を4回繰り返す
グラフで表現しましょう。「正規化」しているので少し複雑になります。
θの正規化偏角関数をθN(s)、
正方形の全長をL、
開始時のベクトル方向をθ(0)、
現在の角度とθ(0)とのなす角をθ(s)
とすると、

となり、グラフ化すると図5の様になります。

図5:正方形に対する正規化偏角関数
図5から、正方形を傾きに依存せず周期的な関数で表せる事がわかります。*2
このように、図形の持つ角度(または周波数成分)から、どのような形状をしているかを知る(推定する)ことが可能であり、情報圧縮や特徴抽出に利用されます。
取得したデータにどの周波数成分がどの程度含まれているかを知る上でフーリエ変換は有効と言えます。
フーリエ変換
周期T[s]の周期関数の基本角周波数ω[rad/s]は
…(1)
となります。
フーリエ級数における基本的な概念は、
「全ての周期信号はバイアス成分とω[rad/s]およびその整数倍の角周波数をもつ三角関数の和として表現できる」
というものです。
ある時間変化する周期信号 s(t)に対して、
…(2)
が成り立ちます(実フーリエ級数展開)。
ただし三角関数の直交性から
…(3)
となります。
式(2)を複素指数関数で表現すると、以下の様になります。
…(4)
式(4)は、分解して考えると
…(5)
式(5)と式(2)の係数をオイラーの公式を考慮しつつ対応させて考えると、以下の様になります。
…(6)
式(3)でも示しましたが、フーリエ級数展開は周期関数に対して有効な手がかりです。しかし非周期信号や孤立波を対象にする場合はこの方法は有効ではありません。どうすれば良いでしょうか?
非周期信号を周期無限大の周期関数とみなせばよいのです。*3
具体的には積分を±無限大の範囲で行う事になります。
しかし数学的にはその手法をとる事は可能ですが、コンピュータでは離散化された信号が対象であり、有限回で計算を終える必要があります。そのために
時間Tのあいだにサンプリングした全データNが周期的に繰り返されるものとみなす
という工夫をして、この問題を解決*しようと*しています。(なぜ「解決しています」では無いのかは後述) これが離散フーリエ変換の考え方になります。
離散フーリエ変換(DFT)
前述のフーリエ級数展開では、信号s(t)は連続時間tの間に変化する信号であったので時間軸に対して積分を行っていました。ここではN[個]のサンプリングデータに対して演算を行います。従って複素フーリエ係数を求める際には時間tではなく、i=0番目から(N-1)番目のサンプリングデータに対して積分(正確に言うと総和)を求めます。
アナログ入力信号: s(t)
サンプリング周期: τ[s/回]
総サンプリングデータ: N[回]またはN[個]
サンプリング時間: T[s]
とする。s(t)を標本化したデータのうちi番目のものs[i]は、

…(7)
時間t[s]は標本化されたiτ[s]に置き換えられます。
また、
…(8)
…(9)
前述した通りN[個]のサンプリングデータに対して演算を行うため、式(5)で求めた複素フーリエ係数は離散化したデータにおいて、
 …(10)
式(10)が離散フーリエ変換と呼ばれるものになります。また、式(4)を直接離散化すると、 逆離散フーリエ変換(IDFT)が得られ、

…(11)
となります。しかしT=Nτはサンプリング時間であり、信号の周期と必ずしも一致しません。例えば図6のようにサンプリング周期Tと実際の波形の周期T’が異なっている場合を考えましょう。
図6:サンプリング周期と実際の周期が異なる場合
この波形に対してDFTを行い、その後IDFTを行うと、図7の様に不連続な波形となってしまい、スペクトルには多くの高次成分が現れてしまいます。
図7:不連続な波形として再現されてしまい、多くの高次成分が現れる
この様な現象を抑えるためハミングや、ハニング関数が用いられます。
ちょっと長いので位相回転因子やFFTの話題、ハニング窓関数のお話はまた次回に。
◆参考文献
酒井幸市 (2003);ディジタル画像処理入門 OpenDesignBooks, pp.151-156. CQ出版社
酒井幸市 (2007);改訂版 ディジタル画像処理の基礎と応用 ディジタル信号処理シリーズ, pp.119-125. CQ出版社
後藤尚久 (2001);なっとくする電気数学, pp.99-119. 講談社
佐野理 (1992);キーポイント 微分方程式, pp.108-138. 岩波書店
昌達慶仁 (2010);圧縮処理プログラミング, pp.318-319. ソフトバンククリエイティブ
Avalon-MM Master Bridgeを探す
USB付きで簡単に使えるBridge
Qsysを使ってAvalonバスにモジュールをぶら下げて使えるシステムを開発中です。
Alteraのデバイスの以下のシリーズで検討しています。
- Cyclone IV E / GX
- Cyclone V E
- MAX V CPLD
Nios IIは規模が500LE未満と軽量なソフトマクロなのでこれをAvalon-MM マスタとしてインプリメントするのが正解かもしれませんが、以下の理由からこれを見送りました。
・CPUとロジックの各部を物理的に分けておきたい(問題発生時の切り分け、USBによる外部との通信)
・CPLDの場合はNios IIは通常インプリメントできない
・システム動作中に適宜、リコンフィグレーションする用途を考えているので、CPUは外部に欲しい
そこで外部にUSBデバイスorOTG対応マイコンの採用を考えました。Avalonバスを選択してもバスマスタが別個に選択できるのは便利な点です。マスタ候補は以下の通りです。
- Vinculum II
- LPC11U37
- PIC32MX250F128/220F032
ここから、安価で、部品点数が減らせて、開発が容易でそこそこ演算能力がある(配列を持たせ、その積和を求める事があるので)デバイスを探しました。
- LPC11U37はNXPUSBLibなど豊富なライブラリに支えられており、とても使いやすい環境を提供されているのですが、BOMが増大する傾向にあったので今回は採用を取りやめました。残念。
- PIC32MX250Fは(ESDプロテクションを考慮しないなら)そのままUSBバスに直結が可能な32bit MIPS M4KコアPICです。ライブラリが豊富でUSB OTGにも対応しているので今回採用しました。高速ADコンバータも魅力です。
PIC32MXでUSB
PIC32MXでLチカ - Lynx-EyEDの電音鍵盤 新館で触れていたので、MPLABXで開発しました。USB-CDCで動作確認をします
ハードは以前作ったものに、USB mini-Bを追加工したもの。
おもて↓
うら↓
必要なソフト
- MPLAB X v1.8以降
- Microchip Libraries for Applications
基本的にこのライブラリにはいっている"Device - CDC - Serial Emulator"で良いのですが、PIC32MX220F/250Fベースのものがないのと、パイロットランプ用LEDやプッシュスイッチなどは不要、またifdefで宣言されているPIC16/18/24のコードは不要ですので、ばっさり削除しました。
githubにそのときのコードを上げてあります
lynxeyed-atsu/USB_CDC_on_PIC32MX220 · GitHub
ファイルツリー構成は以下の図の様にしてください。
メイン動作はUSB_CDC.cのProcessIO()関数が行っています。
この関数の後半部を
if(USBUSARTIsTxTrfReady()) { numBytesRead = getsUSBUSART(USB_Out_Buffer,64); putUSBUSART("unko",4); } CDCTxService();
とかきかえて、PIC32MX220F/250Fに書き込み。
Macでは
$ screen /dev/tty.usbmodem621
のようにオープンすると、怒濤のunkoが出迎えてくれる*1
うん。もういい。
ちゃんと認識されてる事も確認。
というわけで、サクッと動くことがわかったので、コレから作るデバイスのブロック図を作ってみました。以下の図はCyclone Vのものですが、他のデバイスもおおよそ同じ構成となります。
設計途中の基板の3Dビュー(下図はMAX V(5M2210)+PIC32MX)
PIC32MXはボード裏側に配置しました。
◆参考
no crystal usb
PIC32MX220F032B クリスタル無し、内蔵FRCでUSB|marumonのページ
裏付けの無い開発手法とFPGA石器時代
それは10年前と変わっていないのか
以前の記事( 魔法のようなSoCはない - Lynx-EyEDの電音鍵盤 新館)に付け加えたい事があったのでちょっと書いてみました。
最近FPGAの図書がまた増えつつあるCQ関連ムックですが、
例えばコレとか FPGAスタータ・キットで初体験!オリジナル・マイコン作り
Qsysをつかってモジュールをマウスでバスにぶら下げる…はずが、なぜかHDLを1から書いて車輪の再発明していたりとか、ゼロから作る○○の様な記事が多い。
正直これらの記事を見てて、
FPGAってこんなに開発難しいんですか。該当するインターフェース乗ったマイコンの方が遥かに良いですね☆
という感想持たれてもしかたないのでは。
現在では各FPGAベンダで各モジュールをブロックの様にくみ上げて、最後にトップモジュールにインスタンシエートする機構を用意しています。ここにソフトマクロCPUがほぼ必然的に入ってしまうベンダもありますがw(コレについては結論参照)
使用可能ロジックギリギリで設計してしまったなら*1いざ知らず、なぜこのような機構を利用しないのでしょうか。
この根底にあるのは、紛れも無く
HDL書かない手法とか負けだと思うんだ
という圧力でしょう。
どこからの圧力?自分自身の内面からです。
残念ながら今まで培って来た(それも10数年以上)努力を否定されている様に思えるのでしょう。
この意見に対する反論は大まかに言って以下の通りです
- 1から組めば理解できるんですっ!!!
- ベンダ提供のお気楽ツール使うと一つのFPGAベンダに依存してしまうことになり、他ベンダFPGAへの移植が困難
- どうせその手のツールはつかったところで動かない。だっていままでそうだったし
それに対する答えは要約すると以下の通りです
- 理解する前にハードルが高すぎて死ぬ。それに検証どうするんですか?
- え、その前にLUTオンリーなドノーマルFPGAってあるんですか?
- 今あなたは管理職なんですね。お疲れさまです
(1) 1から組めば理解できる
この台詞、免罪符の如く良く聞きます。
たしかに個人の勉強にはなるでしょう。
例えばHDLで記述した条件分岐がどのような順序でセレクタを推論するのか分かっていた方が良いに決まっています。すこし事情が異なりますがC言語で開発するとしても、そのMCUのアセンブラを多少なりとも理解してた方が対処しやすいのと同じです。
もし、ベンダーが作成した機能モジュールのHDLが理解できないから自作する、という理由であるとすればもう少しHDLの勉強に時間を割くべきです。
加えて、作ったあとどうするのでしょうか?
FPGAで単一モジュールだけを動作させる事はまず無いでしょう。
例えばDRAMアクセスコントローラを入れただけでは、何の役にも立ちません。
- このモジュールからデータを読み込む機能ブロック
- 書き込む機能ブロック
- この両者がぶつからない様に監視するインスタンス
が必要です。
つまるところ、ある程度汎用性のあるバスの規格も考えて自作しなくてはなりません。
さらに帯域を増やすためにこれらを並列化するにはどうしたら良いでしょうか?
自作モジュールの検証は?
自作バスの検証は?
この反論(1)に類似した内容で、「QsysやXPSと言ったツールだって中身を作る人がいるんだ」というものがあります。
→じゃ、そのモジュールの検証結果と妥当性示してくださいね。使えませんから。加えて、もしオリジナルバスで作った場合、他の開発者にそれを学習させて使わせる訳ですが、仕様書って書きました?コードカバレッジどうなってます?
いずれにせよ、莫大なリソースをつぎ込める大企業ならともかく、これら全ての要件を満たすまえに力尽きるでしょう。
(2) ベンダ提供のお気楽ツール使うと一つのFPGAベンダに依存してしまうことになり、他ベンダFPGAへの移植が困難
なるほど、言いたい事は分かります。時代はグローバル()ですよね。
でもそれって、「C言語で記述すればどんなアーキテクチャのCPUにも簡単に移植できますよね」理論と一緒かも。
ベンダ個性の無いFPGAって無いと思われます…。
例えば、積和演算器はたいていFPGA内部にありますけど、実装がベンダーごとに違いますよね(ex.前置加算付き積和演算器)
大抵、ハードマクロなどベンダごとの差別化要素があり、依存しない訳が無いです。
いま、Cortex-AクラスのハードCPUがオンチップでFPGA搭載される方向に進んでいます。この流れでベンダごとにチップはおろか開発手法から差別化が加速するものと思われます。
すなわち、よほど、単純なロジックでない限りベンダに依存しない開発は不可能です
(3) どうせその手のツールはつかったところで動かない。だっていままでそうだったし
10年前の話をステレオタイプに触れ込むのは、どうなんでしょうね。
いずれにせよFPGAは順序や論理回路を放り込む箱に過ぎず、箱の組み立て方に囚われるのは賢明とは言えません。
Xの功罪
では、HDL推奨派に十字架を背負わせて魔女裁判でもすれば良いのでしょうか。
決してそうでは無いでしょう。
例えばザイリンクス。
開発者は検証されたバスに準拠したモジュールを使う方が容易で合理的である事に気が付いてはいるものの、そのバスを使うとなると必然的にソフトマクロCPUのおまけ付きになってしまうのです。バスを選択するとCPUとそのアーキテクチャも必然的に選択してしまっている訳です。
C/C++開発環境と、HDL開発環境を行き来しなくてはなりません。
もちろんCPUを切り離して、オリジナルバスマスタを用意できなくはないですが、新たに有償ツールを使うはめになったり、車輪の再発明が容易ならぬ事態になる事も多くここで敗戦処理と軍事裁判がですね(ry
また、機能モジュールをブロックの様に組み合わせても、そのモジュールのHDLソースが隠蔽されているケースも多く、開発者に不安を与える一因となっています。
こうなると、HDLのみの開発に頼るのも理解できなくはないです。
AlteraいいよAltera
AlteraのQsysでは抽象化を一部犠牲にし、バスとCPUとCPUアーキテクチャの関係を完全に分離しています。
NiosIIがないとしてもUSB Blaster経由でJTAGから、あるいはSPIを使って外部からバス配下のモジュールにアクセスできます。
つまりソフトマクロCPUを載せる前からデバッグ出来るので、問題の切り分けが容易です。このモジュール自身も複数のアダプタで構成されており、自分の好むインターフェースに挿げ替えも可能です。
Qsysを使った開発手法では、マウスで欲しい機能を選択し組み立てて行くだけですが、必要な場合はHDLコードを見たり自分でAvalonバスインターフェースを持ったモジュールを作成することもできます。
もちろん検証モジュールも用意されています。
ソフトマクロCPUで抽象化を図ったベンダとは異なり、モジュール間の接続を完全に意識させ、デバッグ環境を充実させています。JTAGからバスマスタにブリッジ出来るのはとても便利です。
最近CQから出版されたDE0本は何故か、Alteraツールで開発していながら、Qsysをあまり活用できていない様に思います。コレについてはしかるべき方法で取り上げたいと思います。
*1:そのような設計をした時点でアウトだけど
LPC810でArduino API互換環境eXodusinoを使うまとめ
8ピンDIP LPC810でArduinoライクな開発をする
Cortex-M0/M0+向けArduino API互換環境eXodusinoはLPC81xシリーズに対応開始しました。
ここでは、8ピン DIPバッケージLPC810で使う方法を取り上げます。
- LPCXpresso IDEのダウンロード
- スイッチマトリクスツールのダウンロード
- eXodusinoのダウンロード
- スイッチマトリクスツールでペリフェラルが使うIOピンを指定する
- eXodusinoプロジェクトにコピー
- マイコンへの書き込み
- eXodusinoのLPC810M021F用プログラムのビルド方法
基本的な使い方は以前に書いたまとめとほぼ一緒です。
Arduino API互換環境eXodusinoをLPC1114 DIPで使うまとめ - Lynx-EyEDの電音鍵盤 新館
今回はスイッチマトリクスツール関連が新たに加わっています。
■必要なもの
- PC
- LPCXpresso IDE ver5.0.12以上
- LPC810M021F(DIPのLPC810マイコン)
- LPC-Link か USB-UART(プログラム書き込みに使う)
(1) LPCXpresso IDEの用意
インストール方法はCQのページから
LPCXpresso IDEのインストール
LPCXpresso IDEダウンロードは(要ログイン)
Home Page | LPCXpresso, powered by Code Red Technologies
ここではLPCXpressoの詳細設定を取り扱うことは致しません。ダウンロード後、無事起動できる事を確認してください。
(2) スイッチマトリクスツールのダウンロード
LPC81xはSPIやUARTやI2CなどのIOピンを電源以外のほぼ全てのピンから自由に割り当てる事が出来ます。
たとえばSPI0は以下のように指定します
/* SPI0_MOSI */ // PIO0_14 -> MOSI (LSB) /* SPI0_MISO */ // PIO0_6 -> MISO /* SPI0_SSEL */ // not in use LPC_SWM->PINASSIGN4 = 0xffff060eUL;
でも、このように手作業で書くのは面倒ですし、GUIツールに任せた方がよいので以下のツールの力を借ります。(ダウンロードにログインは不要)
NXP Switch Matrix Tool for LPC800 | www.LPCware.com
(3) eXodusinoのダウンロード
eXodusino[エクソダシノ]とは簡単に言うとArduinoの記述をCortex-M0/M0+に理解させるための手段です。
現状でLPC81xで動作しているのは以下の機能です
- Serial
- SPI (一部未実装)
- 割り込みを除くGPIOの機能
- delay
- millis
ここからダウンロードできます。
lynxeyed-atsu/eXodusino · GitHub
(図:この部分の「ZIP」とかいてあるボタンをクリック)
ダウンロード後、解凍すると「lynxeyed-atsu-eXodusino-(数字)」という謎フォルダ名になってしまうので、「exodusino」(鍵かっこ含まず)という名前に変更する。
これをLPCXpresso IDEの作業用ディレクトリに移動など、作業しやすい場所に移動してください。
LPCXpresso IDEを起動します
QuickStartウィンドウの[Import Existing Projects]を選択し、先ほど移動したexodusinoフォルダを指定してください。
インポートが完了するとProject Explorerウィンドウに下図のような構成でeXodusinoプロジェクトが展開されます。
ここでLPCXpresso IDEの必要最小限の設定はおしまいです。
(4) スイッチマトリクスツールでIOピンを指定する
ダウンロードして来たスイッチマトリクスツールのjarファイルを起動します。Mac OSXの場合はダブルクリックで起動するはずですが、起動しない場合は、ターミナル.appで
java -jar Switch\ Matrix\ Tool\ -\ LPC8xx\ ver1\ 20121210.jar
のように起動します。
今回は8pinのLPC810M021FN8を選択します。
ペリフェラルが使うIOを指定します。今回はUARTとLED点滅用のGPIOピンを使おうと思います。
UARTはTXとRXの2ピンが必要です。どのピンにも指定可能ですが、今回は
PIO0_4 -> UART0_TXD PIO0_0 -> UART0_RXD
に指定します。USART0をクリックします
次にPIO0_4をクリックし、ポップアップから、U0_TXDを選択します。
同様にPIO0_0をクリックし、ポップアップから、U0_RXDを選択します。
これで設定は完了です。ウィンドウ下部のswm.cをクリックし、SwitchMatrix_Init()関数の中身を貰ってきます。右クリックではコピーメニューが出ませんので、ショートカットキー[Ctrl + C]でコピーできます。
(5) eXodusinoプロジェクトにコピー
/eXodusino/src/core/device_dependent/lpc800/swm.cpp にSwitchMatrix_Init()関数の中身をペーストします。
(6)マイコンへの書き込み
ここではリンクを紹介するにとどめます。NXPのfacebookからですが、
- LPCXpressoからの書き込み方法
- flash magicを使う、シリアルからの書き込み方法
が紹介されています。
「LPC810 (= DIP8_ARMマイコン) + LPCXpresso」で,Lチカ (LED点滅)プログラムを動かしてみる | Facebook
LPC810 (= DIP8_ARMマイコン) のフラッシュへの書き込みをシリアルインターフェースで行なう | Facebook
(7)eXodusinoのLPC810M021F用プログラムのビルド方法
今回のビルド条件は以下のようなものです
デバイス:LPC810F021F
LPCXpressoから直接書き込み
マイコンの内蔵クロックで動作
LPCXpresso IDEのProject Explorer → eXodusinoプロジェクトトップフォルダ右クリック → Propertiesを選択
[C/C++ Build]の項目を選択 → Manage Configurations... → [LPC810M_F021F_DIP8]を選択 → [Set Active]
[OK] → [Apply] → [OK]
これで完了です。*1
Arduinoのスケッチに相当するファイルが[eXodusinoプロジェクトトップ] → [src] → [user_application.cpp]です。
USB-UARTのRX,TXをLPC810のPIO0_4,PIO0_0にそれぞれ接続するとPCからシリアルコンソールで確認する事が出来ます。また、PIO0_1にLEDを接続すると、1秒周期で点滅します。
#include <libmary.h> void setup(void) { pinMode(P0_1, OUTPUT); Serial.begin(9600); Serial.println("Start.."); } void loop(void) { digitalWrite(P0_1,HIGH); delay(500); digitalWrite(P0_1,LOW); delay(500); Serial.println(millis() / 1000, DEC); }
一旦、メニューのProject → Cleanを選択し、その後、Project → Build Allします。
エラーが出現しなければマイコンへ書き込みをします。
[eXodusinoプロジェクトフォルダ]/LPC810M_021F_DIP8/eXodusino.axf
が書き込むバイナリです。
■配線図
書き込み後、以下の回路でテストしました。
3.3V電源で駆動してください。
シリアルから9600baudで
1秒ごとに経過時間が表示され、LEDが1秒間隔で点滅します。
実験風景
番外編:LPC812も使ってみる
いつものフラクタルを描いてみました。今回はLPCXpresso LPC812とMAPLEボードを使っています。一部、OLEDの配線の関係でジャンパーを飛ばしました。
詳細はまた、別記事にて。
*1:DIP8パッケージを選択した場合、eXodusinoはInternal RCで動作するようコンフィグレーションします
eXodusinoのフレームワーク
対応デバイスの増加と機能追加に対応する
現在、eXodusinoのgithubで公開しているプロジェクトでは、
- LPC1114L系列*1
- LPC1115/301
に対応しています。
[ lynxeyed-atsu/eXodusino · GitHub]
現在、国内とUSAのコミッタを合わせた5人で、複数のNXP Cortex-Mxに対応するような努力が払われており、最終の調整を進めています。
NXPのマイコンはローエンドからミッドレンジまでペリフェラルIPが統一されているので、少しの変更でかなりのデバイスをカバーできるはずですが、例えば、SPIのコアはLPC8xxとLPC11xxで異なります。また、GPIOやSystick以外のタイマーはそれぞれのデバイスに応じて実装されているので、これらの差分もeXodusinoは吸収しなければならない事になります。つまりAPIの抽象度を上げて、それよりも抽象度の低いフレームワークをコールし、このフレームワークがデバイス依存の低レベルのコードを読み出します。
もっとも、某R社のように同じIPと見せかけてデバイス毎に全く違うドライバを書かなきゃいけない、ミスを誘発するような仕組みにはなっておらず、差分もわずかなので、少しの調整で済むと思われます。*2
いままで、Arduino APIにデバイスドライバをべた書きしてしまっているコードも多く、汎用性に乏しかった事もあり、この機会に書き直す事にしました。
幸い、NXPがそこそこ汎用性のあるデバイスドライバを用意してくれているので、コレの上にeXodusinoを構築する事にしました。↓こんな感じ。いままで、APIにべた書きしていた部分を完全に分離するのが目標です。
Max II CPLDでQsysをつかってみた
QsysのProject SettingsメニューにCPLDもある
前回(JTAG to Avalon Master Bridgeを使ったバスコントロール - Lynx-EyEDの電音鍵盤 新館)でやってみた内容とほぼ同じ事を、CPLDでやってみました。
使用ボードはMax II Micro Kit (Terasic MMK)です。
具体的にはQsysで
- JTAG to Avalon-MM bridge
- PIO
のモジュールをMax II CPLD(EPM2210)向けにGenerateして、トップモジュールからインスタンスするだけです。
トップモジュールはmaxII_avalon_trialという名前にして、生成されたモジュールをインスタンシエート。
module maxII_avalon_trial( output reg [7:0] LED = 8'h0, input [3:0] button, input clk ); avalon_module u0 ( .clk_clk (clk), // clk.clk .pio_0_external_connection_export (LED) // pio_0_external_connection.export ); endmodule
論理合成結果は
1600LE近くを消費するという事態に
JTAG to Avalon MM Bridgeが64byteのFIFOを持つため、CPLDはLEを消費してしまうのが原因。(長船氏)
ちなみに、SPI slave to Avalon MM Bridgeに入れ替えると、消費ロジックは約330LEまで減ります。
動作確認
PIOの出力はLEDに接続されるようにしました。PIOのアドレスは今回は0x00000000にしてあります。
前回のDE0 nanoと変わらないので詳細は書きません。
0xAAをライトした時
0x55をライトした時
※ 出力をLレベルにするとLEDが点灯します
JTAG to Avalon Master Bridgeを使ったバスコントロール
バスアーキテクチャの学習というハードル
2013年も1月が終わろうとしていますが、今年初記事です。(笑
74シリーズで論理設計した経験があり、HDLをある程度習得しているなら単体モジュールをFPGA内に作り込むのはさほど困難な事ではないと思われます。
問題となるのは、このような単体モジュール群が複数あり、それぞれがデータストリームやレジスタを共有する必要が出た場合。(必ずと言っていいほどこの状況になりますが。。)
個々のモジュールを協調または排他動作させるアービタが必要になります。
FPGAベンダもこれらの問題を解決するツールを用意していますが、ソフトマクロCPUという形を取り、バスマスタを隠蔽しています。
このような形を取ると、各モジュールをすべてソフトウェアで制御(場合によってはRTOSも載せる)する必要があります。ソフトウェア開発者には受け入れやすくなる一方、構成によってはバスアービタがノイマンボトルネックの影響をもろに被る可能性があります。加えて、大抵の場合ソフトマクロCPUは同規模のゲート数をもつハードCPUより消費電力が高く、最高速度はそれに劣ります。
ではなぜこのような構成を取るのでしょうか。
主な理由は、バスマスタのみをツール側で用意したとしても、開発者はまずそのバスアーキテクチャの全容を理解し、デバッグ手法、カバレッジの妥当性を1から学習調査しなければならなくなり、ハードルが高くなるからです。
この問題に解決手法があるのか無いのかずっと考えていたのですが…
AlteraのQsysが提供するAvalonバスの場合、バスマスタがNios IIである必要は全くありません。
現状ではSystem Consoleが使えるJTAG to Avalon Master Bridge, SPI slave to Avalon Master Coreなどがあります。よって、SPIからバスを制御できるのです。例えばArduinoやmbedのSPIをバスマスタとして繋げてもOKです。これによりソフトマクロCPUのインプリメントの回避や、バスアーキテクチャの学習といったハードルがぐっと下がる事になります。
コンソールもQsys側で用意されているので、独自仕様シェルを作らなくても大丈夫です。
AvalonバスはQsys(またはSOPC Builder)で提供している内部バスインタフェースで、
- 単一方向ストリームデータを扱うのに適している Avalon-ST
- メモリマッピングされた空間上でリードライトするのに適した Avalon-MM
があります。詳細はここでは省略致します。
使ってみる
今回はAvalon-MMバスインタフェース上に、Qsysで提供されている
をマッピングしました。
SDRAMのアドレス: 0x0000_0000 ~ 0x01ff_ffff に配置
PIOのアドレス: 0x0200_0000 ~ 0x0200_000f に配置
ALTPLLのアドレス: 0x0400_0000 ~ 0x0400_000f に配置
ハードウェアはDE0 nanoです。これにあわせてモジュールの設定を行います。
・SDRAMコントローラの設定
AlteraのユーザーズガイドでもSDRAMにPLLでクロックを与える際、Avalon-MMシステムクロックとSDRAMとのクロック位相に関してはボードごとに試行錯誤が必要である旨が書かれています
(ユーザーズガイド p.32)
今回はDE0 nanoサンプルプロジェクトに従い、SDRAMに供給するクロックの位相を-60°としています。
PresetsをCustom
データ幅:16bit
Row:13bit
Column:9bit
のみ変更
・PIOの設定
DirectionでOutputにチェック
・ALTPLL
Speed GradeはFPGAに依存し6、入力クロック50MHz、c0は2倍、c1(SDRAM用クロック)も2倍で、かつ位相は-60°
詳細は、下に示すスクリーンキャプチャをご覧下さい。
このキットで用意されているDE0_NANO_GOLDEN_TOPをひな形としてQsysの生成したモジュール群をインスタンシエートしました。
DE0_nano.vのStructural codingと書かれている部分にQsysが示したインスタンシエート例HDL Exampleをもとに以下を追記します。
//======================================================= // Structural coding //======================================================= qsys_test u0 ( .clk_clk (CLOCK_50), // clk.clk .sdram_0_wire_addr (DRAM_ADDR), // sdram_0_wire.addr .sdram_0_wire_ba (DRAM_BA), // .ba .sdram_0_wire_cas_n (DRAM_CAS_N), // .cas_n .sdram_0_wire_cke (DRAM_CKE), // .cke .sdram_0_wire_cs_n (DRAM_CS_N), // .cs_n .sdram_0_wire_dq (DRAM_DQ), // .dq .sdram_0_wire_dqm (DRAM_DQM), // .dqm .sdram_0_wire_ras_n (DRAM_RAS_N), // .ras_n .sdram_0_wire_we_n (DRAM_WE_N) , // .we_n .pio_0_external_connection_export (LED) , // pio_0_external_connection.export .altpll_0_areset_conduit_export (1'b0), // altpll_0_areset_conduit.export .altpll_0_locked_conduit_export (), // altpll_0_locked_conduit.export .altpll_0_phasedone_conduit_export (), // altpll_0_phasedone_conduit.export .altpll_0_c1_clk (DRAM_CLK) // altpll_0_c1.clk );
コンパイルを行い、生成されたsofファイルをFPGAにダウンロードします。
Programmerを終了させ、Qsysに戻りSystem Consoleを立ち上げます。
マスターにhogeというインスタンス名を与え、サービスを起動します
% set hoge [lindex [get_service_paths master] 0] % open_service master $hoge
ためしにPIO(LED)にデータをライトします。アドレスは0x02000000ですので、
% master_write_32 $hoge 0x02000000 0xAA
LEDは下のように点灯します
% master_write_32 $hoge 0x02000000 0x55
今度は下のようになります。
SDRAMのR/Wを確認します。アドレス0x00000000に0xFFAAをライトし、その後リードします
% master_write_32 $hoge 0x00000000 0xFFAA % master_read_32 $hoge 0x00000000 1
無事、読み込めました。
■参考
システム・コンソールを使用した H/W デバッグ環境構築例
Qsysを使ってみた - Sim's blog
[FPGA][DE0]QsysのJtagデバッガでLチカしてみた - GeekleBoard