Amazonで買った NEO-6M モジュールでGNSS受信した
1. はじめに
Amazonで購入できる少しあやしげなGNSS(GPS)モジュールを昔仕入れて積みっぱなしだったので、うごかしてみることに。備忘録としてその操作方法等をざっくり書いておきます。とりあえず動かすにはPCと通信するためのFDTIも要る。(RXピンとTXピンをそれぞれクロスさせることに注意)
2. u-centerをダウンロードする
・u-blox社が提供しているu-centerというソフトをダウンロードします。
・ググると設定方法がでてきました。この資料を参考にさせていただく...。
・u-centerがうごいた。NEO-6M&GPSアンテナでGNSS電波を受信している。COMポートを接続してしばらくすると、このように、アメリカ国旗のGPS受信中のグラフィックへと移行した。
・"View" -> "PacketConsol" で出力データのログが見れる。
※上部メニューバーの "View" -> "Message View" -> [UBX] -> [CFG] -> [PRT] や [GNSS] や [RATE] や [CFG]で主要な設定ができる。
・[PRT] はポート設定。ボーレートは9600bpsとした。"Protocol in"と"Protocol out"の両方にUBX
が含まれる設定にして、USB接続での通信が滞らないようにする。
・[GNSS]は受信衛星の選択。今回扱うモジュール(NEO-6M)は、アメリカのGPSは受信できる。日本のQZSSは無理で、中国のBeiDouやロシアのGLONASSや欧州のGallileo...はどうだったか...? たぶんこっちも無理だろう。
・[RATE]は電波受信のサンプリング・レート。"Measurement Period"を500msに設定して、つまり2Hz受信とした。
・[CFG]は不揮発性メモリへの各種設定条件の書き込み(保存)。毎回忘れずにやる必要あり。上部メニューバーの "Reciever" -> "Action" -> "Save Config"でも同様の保存ができる模様です。(両方やっておくと損がない)
3. RTKLIBでRTK測位
東京海洋大学の先生が開発しているRTKLIBというGNSS界隈では超有名なGNSS受信・測位ソフトを使わせていただきます。使い方はいろいろ調べると出てきますが、ニッチな分野だからか、やってる人じたいの人口が少なく、情報量は多くはないです。ですが、やっぱりこの分野は面白いので、まあ少しでも何かの足しになれば......と備忘録を残しておく。
RTKLIBの中には色々なグラフィックで受信電波を可視化できるパッケージというのかそれが入っていて、観測したい様子に合わせてパッケージを選べばいい。下の図だと、Kinetic測位(RTK基準局:Base Station と移動局:Rover 、そして地球の周りを今の今にも周回している複数の衛星点の合計X点の相関関係によってRoverの現在位置を同定する測位方法、みたいに理解してます)を試みているときの受信強度をしめすグラフ(WindowsPC画面)の様子。
Fixは中々しない...。RTKLIBの方の設定でもいろいろチューニングが必要だったり、天候条件などにも左右されたりするが、それがまた楽しい。Fixするとほとんど快感です。
(Fixとは何か?⇒ RTKLIBのパワーでGNSSモジュールが拾った複数のGNSS電波の関係を計算して、Roverの現在位置が精密同定されることを言います。多分。)
詳細はやってみればわかります。楽ししい!!!
4. NEO-6MにRAW DATAを出力させるための秘儀コード??
NEO-6Mはとにかく安いし、はじめてRTKやGNSSを試したい時には入門に最適のモジュールだと思うのですが(値が張るNEO-M8Pなどもいずれ買う、というか買った)、通常はRAW DATAを吐かない。しかし、海外?に解析した人がいるらしく、英語文献を漁れば情報がでてくる様です...。( https://wiki.openstreetmap.org/wiki/U-blox_raw_format )NEO-6MにRAW DATAを出力させて.....R/T/Kなんてことも可能性がゼロなわけじゃない......のだろうか。
"View" -> "Packet Console" で出力確認。他の色々なデータ群もデフォルトだと出力されたので、[CFG] -> [MSG] の箇所でMessage
の欄を適宜切り換え、USB
の項目を目的のデータ以外チェックを外して "Send" してみた。
やり方をここに明記してしまってもいけないと思うので、
ヒントを... (ブラウザの開発者モードから開いてヒントを見て下さい)
(個人利用の遊びの範疇で。NEO-M8PやM8Tを大人しく買いましょう。 )
・
・
・
今回は以上です!!
5. 参考資料
・以下、専門雑誌。
※今も在庫があるかは不明ですが、出版社のサイトの通販だと定価で買えたはずです。
あの格安FPGAボードで 7セグメントLED をドライブする~Part.3
1. はじめに
今回は、前回Part.2に引き続いて、ストップウォッチ完成に向けた制作を進めました。具体的には、7セグメントLEDの桁数を2ケタに増設し、それぞれのケタを交互に高速点滅させることによって、人の目の残像効果を利用し、連続的に点灯しているかのように見せるダイナミック・ドライブというテクニックを使い実装しました。
2. 実験回路
NPN型汎用トランジスタ(2SC1815)のベースに微弱電流(0.3mA程度?)を流し、それを増幅して、なおかつコレクタ-エミッタ間を5ms毎に高速?スイッチングすることで、1ケタ目と2ケタ目の7セグメントLEDへの出力を高速に切り換え、ダイナミック点灯するようにした。なお、OUT(PINxx)はFPGAボードの各種ピンに接続しており、OUTPUTに設定している。
※OUTPUTピンの電流量はQuartus上のピンアサインの設定のところで変えられる。そのため、電流制限抵抗は挟んでいない。挟むのがベターと思うけれど。適宜、7セグLEDを光らせるのに適した設定値にしてください。
http://akizukidenshi.com/catalog/g/gI-04268/akizukidenshi.com
3. VHDLコード(全文)
このコードで動作確認した。野暮ったい書き方のところがあるかもしれないが、動くことを第一優先に。
CNT60.vhd
library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; entity CNT60 is port( CLK : in std_logic; SEG7LED1 : out std_logic_vector ( 7 downto 0 ); TR1, TR2 : out std_logic ); end CNT60; architecture RTL of CNT60 is signal SIX: std_logic_vector ( 2 downto 0 ); signal DEC: std_logic_vector ( 3 downto 0 ); signal CRY, T1S : std_logic; signal CNT: integer range 0 to 24999999*2; --1000ms分確保 signal CNT_SW: integer range 0 to 249999; -- 5ms分確保 signal TR1_SW, TR2_SW: std_logic; begin process(CRY) begin if(CRY'event and CRY = '1') then if( SIX = "101") then SIX <= "000"; else SIX <= SIX + 1; end if; end if; end process; process(T1S) begin if( T1S'event and T1S = '1') then if( DEC = "1001" ) then DEC <= "0000"; CRY <= '1'; else DEC <= DEC + 1; CRY <= '0'; end if; end if; end process; process( CLK ) begin if( CLK'event and CLK = '1') then if( CNT = 24999999*2 ) then T1S <= '1'; CNT <= 0; else CNT <= CNT + 1; T1S <= '0'; end if; end if; end process; process( CLK ) begin if( CLK'event and CLK = '1') then if( CNT_SW < 250000/2 ) then TR1_SW <= '1'; TR2_SW <= '0'; CNT_SW <= CNT_SW + 1; elsif( 250000/2 <= CNT_SW and CNT_SW < 250000) then TR1_SW <= '0'; TR2_SW <= '1'; CNT_SW <= CNT_SW + 1; else TR1_SW <= '0'; TR2_SW <= '0'; CNT_SW <= 0; end if; end if; end process; process(DEC, SIX, TR1_SW, TR2_SW) begin if(TR1_SW = '1' and TR2_SW = '0') then TR1 <= '0'; TR2 <= '1'; case SIX is when "000" => SEG7LED1 <= not "00000011"; --0 when "001" => SEG7LED1 <= not "10011111"; --1 when "010" => SEG7LED1 <= not "00100101" ; --2 when "011" => SEG7LED1 <= not "00001101" ; --3 when "100" => SEG7LED1 <= not "10011001"; --4 when others => SEG7LED1 <= not "01001001"; --5 end case; elsif(TR1_SW = '0' and TR2_SW = '1') then TR1 <= '1'; TR2 <= '0'; case DEC is when "0000" => SEG7LED1 <= not "00000011"; --0 when "0001" => SEG7LED1 <= not "10011111"; --1 when "0010" => SEG7LED1 <= not "00100101"; --2 when "0011" => SEG7LED1 <= not "00001101"; --3 when "0100" => SEG7LED1 <= not "10011001"; --4 when "0101" => SEG7LED1 <= not "01001001"; --5 when "0110" => SEG7LED1 <= not "01000001"; --6 when "0111" => SEG7LED1 <= not "00011011"; --7 when "1000" => SEG7LED1 <= not "00000001"; --8 when others => SEG7LED1 <= not "00001001"; --9 end case; else TR1 <= '0'; TR2 <= '0'; end if; end process; end RTL;
4. 実験結果
こんな感じで、60秒まで計れるストップウォッチぽくなってきました。次回はいよいよ完成するか...??
5. 参考文献
あの格安FPGAボードで 7セグメントLED をドライブする~Part.2
1. はじめに
あの格安FPGAボードを今回はタクトスイッチによる入力ではなく、FPGAボードにあらかじめ搭載の水晶発振器(50MHz)のクロック入力を用いて、自動で数を数えてくれる基本的なアップカウンタを設計してみます。ストップウォッチになる一歩前段階の制作です。Part.1とは違い、今回はブロック図を繋ぐやり方ではなく、全部VHDLで記述します。
(※ちなみに、Amazonの購入先リンクを貼っておきますが、AliExpress
で検索すれば同一商品がもう少し安くみつかります)
2. 実装する機能について
・7セグメントLED の表示がFPGAボード搭載の水晶発振器(50MHz)のクロック入力のたびに(1秒毎に)、カウントアップする。
・クロック入力されるに応じて、0~Fまでの16進数を数える。(今回も、とりあえず7セグLEDは1個だけの使用です)
3. VHDLコード(全文)
UpCounter.vhd
library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; entity UpCounter is port( CLK : in std_logic; SEG7LED : out std_logic_vector(7 downto 0) ); end UpCounter; architecture RTL of UpCounter is signal CNT : integer range 0 to 24999999; --500ms分確保 signal T500: std_logic; signal BIN4 : std_logic_vector(3 downto 0); signal DP : std_logic; begin --タイマー(500msごと) process( CLK ) begin if( CLK'event and CLK = '1') then if( CNT = 24999999 ) then T500 <= '1'; CNT <= 0; else CNT <= CNT + 1; T500 <= '0'; end if; end if; end process; --フリップ・フロップ process( T500 ) begin if( T500'event and T500='1') then DP <= not DP; end if; end process; --カウンタ process(DP) begin if(DP'event and DP = '1') then if( BIN4 = "1111" ) then BIN4 <= "0000"; else BIN4 <= BIN4 + 1; end if; end if; end process; --デコーダ process(BIN4) begin case BIN4 is when "0000" => SEG7LED <= "11111100"; --0 when "0001" => SEG7LED <= "01100000"; --1 when "0010" => SEG7LED <= "11011010"; --2 when "0011" => SEG7LED <= "11110010"; --3 when "0100" => SEG7LED <= "01100110"; --4 when "0101" => SEG7LED <= "10110110"; --5 when "0110" => SEG7LED <= "10111110"; --6 when "0111" => SEG7LED <= "11100100"; --7 when "1000" => SEG7LED <= "11111110"; --8 when "1001" => SEG7LED <= "11110110"; --9 when "1010" => SEG7LED <= "11101110"; --A when "1011" => SEG7LED <= "00111110"; --b when "1100" => SEG7LED <= "10011100"; --C when "1101" => SEG7LED <= "01111010"; --d when "1110" => SEG7LED <= "10011110"; --E when others => SEG7LED <= "10001110"; --F end case; end process; end RTL;
4. 実験結果
こんな感じで動作確認ができた。
あの格安FPGAボードで 7セグメントLED をドライブする~Part.1
1. はじめに
あの格安FPGAボードで、定番の7セグメントLED(とりあえず今回は1ケタ)をドライブする基本的な記事です。QuartusⅡ自体の操作に慣れるという意味でも、再現できるように、できるかぎり具体的な手順を追って書いていきます。
VHDLで記述した回路をブロック図(Block Diagram)として登録し、それらブロック図をグラフィカルにつなぎ合わせて、目的とする「機能」をもつ回路を設計するやり方を、自分への備忘録も兼ねて書きます。
2. 実装する機能について
・7セグメントLED がタクトスイッチを1回押すたびごとに、カウントアップする。 ・そうして、0~9までの数を数えられる。(今回、とりあえず7セグLEDは1個だけの使用です) ・タクトスイッチを押したときのチャタリング防止機能をつける。
(※チャタリングとは、スイッチを押すときに機械接点がバウンドして小刻みなON/OFFを繰り返す現象。入力状態が不安定になるので、防止対策をとるのが一般的)
3. 完成回路図
先に、QuartusⅡで簡単に設計した回路の完成図を図示します。最終的にはこんな感じで、VHDLのコードを書く、あるいは各種ブロック(シンボル)を組み合わせながら開発をして、目的とする「機能」を実装していきます。
なお、この全体回路図の中に含まれているブロックのそれぞれは単一の機能をもつモジュールのようなもので、これらを組み合わせて(組み合わせ論理回路)、うまい具合に目的の「機能」を実装します。この図では、外からの見た目はブロックのように見えるものの、中身はVHDLコードで記述されており、あくまでも人が開発する上で理解しやすいグラフィカルな表示になっているだけです(たしか...)。
この図から、2入力(CLK, SW)、8出力(7セグLED + 小数点LED1つ)の回路をこれから組み上げていくイメージをなんとなく先に持っておくと理解しやすいかと思います。
4. 実験回路の作製
FPGAボード、7セグメントLED、タクトスイッチの実体配線は図のような感じになりました。ジャンパワイヤでごちゃごちゃしてますが、ブレッドボード上での実験ということでいつも通りです。7セグLEDのピン配置については、こちらの記事を参考に。
スイッチ側のプルアップ抵抗は入れましたが、7セグLEDへの電流制限抵抗は今回自分は入れませんでした...( ゚Д゚)。
Quartus上で設定するピンアサインと実体配線が一致するように注意します。Quartus上でのピンアサイン図を載せておきますので、それを参考に実験回路を作製するといいかと。
5. VHDLコード全文とBlock Diagram (~.bdf)上での回路設計
BINCNT4.vhd
library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; entity BINCNT4 is port( DIN : in std_logic; SEG7LED : out std_logic_vector(7 downto 0) ); end BINCNT4; architecture RTL of BINCNT4 is signal BIN4 : std_logic_vector(3 downto 0); begin --カウンタ process(DIN) begin if(DIN'event and DIN = '1') then if( BIN4 = "1001" ) then BIN4 <= "0000"; else BIN4 <= BIN4 + 1; end if; end if; end process; process(BIN4) begin case BIN4 is when "0000" => SEG7LED <= "00000011"; --0 when "0001" => SEG7LED <= "10011111"; --1 when "0010" => SEG7LED <= "00100101"; --2 when "0011" => SEG7LED <= "00001101"; --3 when "0100" => SEG7LED <= "10011001"; --4 when "0101" => SEG7LED <= "01001001"; --5 when "0110" => SEG7LED <= "01000001"; --6 when "0111" => SEG7LED <= "00011011"; --7 when "1000" => SEG7LED <= "00000001"; --8 when "1001" => SEG7LED <= "00001001"; --9 when "1010" => SEG7LED <= "00010001"; --A when "1011" => SEG7LED <= "11000001"; --b when "1100" => SEG7LED <= "01101011"; --C when "1101" => SEG7LED <= "10000101"; --d when "1110" => SEG7LED <= "01100001"; --E when others => SEG7LED <= "01110001"; --F end case; end process; end RTL;
CHATTER.vhd
library ieee; use ieee.std_logic_1164.all; entity CHATTER is port( SSW: out std_logic; CLK, SW: in std_logic ); end CHATTER; architecture RTL of CHATTER is signal CHATT : std_logic_vector( 3 downto 0 ); signal CNT1MS: integer range 0 to 4999; begin process (CLK) begin if ( CLK'event and CLK = '1') then if(CNT1MS = 4999) then CNT1MS <= 0; CHATT <= CHATT (2 downto 0) & SW; else CNT1MS <= CNT1MS + 1; end if; end if; end process; SSW <= CHATT (3) or CHATT (2) or CHATT (1) or CHATT (0); end RTL;
・この2つのVHDLファイルが記述出来たら、それぞれをブロック図(シンボル)として登録してみます。VHDLファイルのタブで選んだ状態で、上部メニューバーの "File" -> "Create/Update" -> "Create Symbol Files for Current File" を選択すると、シンボル登録の完了です。
・シンボルとして登録が完了したら、Block Diagram上で図のようなアイコンを選択し、"Symbol"ウィンドウを開きます。すると、"Project"の中にいま生成したCHATTER
やBITCNT4
が回路図中に置くシンボルとして選択できるようになっています!!
・ここまででVHDLコードで記述したものをシンボルとしてブロック図のように組み合わせ、回路を設計することができるようになりました。下図のように、シンボルを置いたり各種配線をする(配線に名前を付けて配線すると確実です)などして設計が完了した回路図を、例えばtop.bdf
のファイル名で保存します。(※詳しい操作方法などはこちらのPDFを参考に)
top.bdf
・SEG7LED[7..0]
の出力バス(束)をNOTゲートで一度すべて反転させている理由は、使用した7セグLEDがカソードコモンである仕様に対応させるためです。(7セグLEDがアクティブLOWの想定でVHDLを書いていたのですが、後々確認したら7セグLEDがアクティブHIGHだったので...)
※設定上の注意点として...
・プロジェクト名を右クリックして表示されるメニューの "Settings.." -> "General"の"Top-level entity" で top.bdf
と設定する。
・"Settings.." -> "Files"の"File Name"欄にtop.bdf
, CHATTER.vhd
, BITCNT4.vhd
がすべて設定されているようにする。
6. 実験結果
タクトスイッチを1回押すごとに(しっかりとチャタリングが防止されたうえで)、7セグLEDに表示された数字が0~9のあいだでカウントアップされた!!
7. まとめ
あの格安FPGAボードと7セグメントLEDを1つ用いて、チャタリング防止しながらタクトスイッチを1回押すたびに0~9のあいだで確実に1カウントアップする基本的な回路の設計演習を行なった。次は同じハードウェアで桁数を増やしてとりあえずストップウォッチでしょうか...。
8. 参考図書
Jetson NanoではじめてのCUDAプログラミング + ImageJで可視化アニメーション
1. はじめに
本記事では、とても入手しやすいJetson Nano(NVIDIA社)を使って、はじめてのGPGPU(General-Purpose computing on Graphics Processing Units)に取り組みます。GPUは本来、画像処理・グラフィック向けの演算装置ですが、そのGPUに、それ以外の汎用的な演算処理(例えば数値計算など)を実行させる応用的技術をGPGPUと言います。 ここでは特に、CUDA(Compute Unified Device Architecture:クーダ)を導入して、GPGPUの入門的な演習を行います。CUDAはWikipediaによると、
NVIDIAが開発・提供している、GPU向けの汎用並列コンピューティングプラットフォーム(並列コンピューティングアーキテクチャ)およびプログラミングモデルである。
ざっくりとした言い方になおすなら、GPGPUを実施するためにNVIDIA社が頑張って開発してくれてなおかつ無料配布してくれているとても有難い開発プラットフォーム ということになるかと思います。 難しいことは実際に動かしながら覚えていくこととして、GPGPU & CUDAプログラミングに早速挑戦します。
2. Jetson NanoへのCUDAの導入
普通にJetson Nanoのセットアップが終了していれば、元々CUDAが入ってます。私はJetsonイメージをmicroSDに焼いて普通の手順にてセットアップ完了させましたが、OKでした。ちなみに2020年5月時点で、CUDAのバージョンは10.2でした。というわけで、今回の入門演習をとりあえず実施するだけで他に特別な操作をしない場合、特に何も手順はいらずにCUDAがすぐに使えそうです。
3. CUDAで拡散方程式をGPGPU
早速入門演習として、実際にCUDAを使って、CPUではなくGPUで数値計算を実行(GPGPU)します。参考図書としては、こちらを使いました。が、2009年が初版と少し前の書籍なので、その間にCUDAのバージョンが上がり、そっくりそのままサンプル・プログラムを実行しただけでは上手くいきませんでした。少し、現在のバージョンに合ったプログラムおよび環境設定に一部修正する必要があります。その変更点や修正箇所を本記事ではメインに書いていきますので、手元にこの書籍があると、照らし合わせながら考えられるので、いずれにせよ理解が進みやすいと思います。
なお、紹介した書籍の中に、サンプル・プログラムをダウンロードできるサイト情報が明記されていますので、サンプル・プログラムをダウンロードしたい方は書籍で詳しい情報を確認してください。
書籍のp.169~記載の「差分法による偏微分方程式のGPU計算」を演習として取り上げます。この問題では、拡散方程式を「水の入ったコップの中にインクを落としたとき」の拡散現象を想定してGPUで数値シミュレーションしています。データの可視化に際して、bmp出力画像を生成するのはCPUの方が得意なので、それはCPU側で対応し、役割を分担しています。
※実際に、数値シミュレーションが完了するまでの修正箇所も含めた手順を、以下の①~⑤に示します。
① ダウンロードしたzipフォルダを好きな場所に解凍する。仮に、そのときのフォルダ名をここではprob_jetson
とする。
② 現在ではCUDAのバージョンが上がったのにともなって、昔のバージョンで入っていたヘッダファイルcutil.h
が無くなったようなので、ココから復活させる(少し野暮ったい方法ですが)。rawデータを全文コピペして適当な好きなエディタを使い、cutil.h
の名前でひとまずどこでもいいので保存する。
③ 保存したヘッダファイルcutil.h
を、/usr/local/cuda-10.2/include
のディレクトリ(つまり保存先ディレクトリ)に sudo cp (保存元) (保存先)
コマンド等でコピーする。
④とりあえずの対策として、main.cu
のコードの中身を以下のように一部コメントアウトする(指定箇所をコメントアウトすると、GPU使用時の稼働時間のログが表示されなくなるが、一番肝心な出力のbmpファイルには影響しないと思われるので、とりあえずコメントアウトでエラー回避)。
//40 ~ 44 行目付近 /* unsigned int timer; cutCreateTimer(&timer); cutResetTimer(timer); cutStartTimer(timer); */ //55 行目付近 // printf("TIME = %9.3e\n",time); //60 ~ 65 行目付近 /* cutStopTimer(timer); float elapsed_time = cutGetTimerValue(timer)*1.0e-03; printf("Elapsed Time= %9.3e [sec]\n",elapsed_time); printf("Performance= %7.2f [MFlops]\n",flops/elapsed_time*1.0e-06); */
③ ~/prob_jetson$ make
で、フォルダ内にある Makefile をmakeする。すると、diffusion2d.o
, etc.o
, libbmp.o
, main.o
の合計4つのオブジェクトファイル(~~~.o)ができる。しかし、最終的にはエラーが発生し、make: ***[a.out] Error 1
とターミナルに表示される。つまり、Makefile 内で指定されているa.out
の記述が、リンクエラーが恐らくの原因で出力されない。Makefileを精密に書けるほど詳しくないので、応急処置で以下のシェルスクリプトを作成し対応する。何回も打つのは億劫なのと忘れるのでシェルに。
mycommand.sh
nvcc diffusion2d.o etc.o main.o libbmp.o -L/usr/local/cuda-10.2 -o run
④ 手順③で作成済みのmycommand.sh
に下記コマンドで権限を変更し実行すると...
~/prob_jetson$ chmod 755 mycommand.sh ~/prob_jetson$ ./mycommand.sh
この段階で、フォルダ内(prob_jetson
)に最終実行のためのファイルrun
が生成されるはず。
⑤ あとは、このファイルrun
を走らせるだけ。
~/prob_jetson$ ./run
フォルダ内に、水中のインクの拡散の様子をシミュレーションしたbmpファイルが40枚ほど時系列に沿って次々出力される!!
4. Jetson NanoのUbuntuに画像処理ソフトImageJを入れる
次は、Jetson NanoのUbuntuに、ImageJという生物画像解析の分野で有名なソフトをインストールし、それを使って先ほど出力したbmp画像群をひとつのアニメーションにサクッと変換してみます。インクが水中でじわじわと広がってゆく拡散の様子がよりわかりやすく観察できそうです。参考図書では別の可視化ソフトが紹介されていますが、今回は特に理由はないのですが、ImageJを使ってみます。ImageJダウンロードサイトの Linux から適切なものをダウンロードして下さい。 ダウンロードしたら解凍します。ImageJを置くディレクトリはどこでも良かったはずです。これも、毎回コマンドを忘れてしまうとあれなのでImageJ実行のためのシェルスクリプトを一応用意しておきます。 start.sh
java -jar ij.jar
続いて、権限の変更です。
~/ImageJ$ chmod 755 start.sh
その後、ImageJを起動します。
~/ImageJ$ ./start.sh
ImageJを起動したら、メニューバーの"File" -> "Import" -> "Image Sequence..."を選択して、No.0の画像を選びます。
すると、次のような"Sequence Option"ウィンドウが開くので、図のように設定し、"OK"を押して決定します。先ほど連番で40枚ほど出力されたbmp形式の画像ファイルが一気にひとまとまりでImageJに読み込まれます。
"File" -> "Save As" -> "Gif..." 等のファイル形式の指定で画像群がひとまとまりになったアニメーションに変換されました!!
アップロードする用に、今回は.gif形式に変換しましたが、.aviなどのその他形式への保存にももちろん対応しています。アニメーションにしたGifがこちらです。
5. まとめ
Jetson Nanoにインストール済みのCUDAを使って、拡散方程式の数値計算を演習に、入門編としてはじめてのGPGPUを実施しました。bmp形式で出力された画像群を、ImageJを導入してアニメーションに変換して可視化しました。今後は、より高度なCUDAプログラミングを勉強しつつやっていけたらと思ってます。
6. 参考図書
Jetson NanoでTuring不安定性?を数値計算してグラフィック描画する
1. はじめに
今回は、最近注目? のJetson Nanoを用いて、非線形性をもつ偏微分方程式として知られているGray-Scott Modelを、2次元空間において時間発展させる数値計算(数値シミュレーション)を行います。 Jetson Nano自体は15,000円くらいもあればACアダプタ等一式を秋月電子などですぐに入手できます。購入しやすい価格帯なのにもかかわらず、GPUが搭載されているので、ちょこっと計算して、その数値計算結果を少ない資源で手軽にいい感じに描画したい場合などの開発勉強用途に、もしかしたら良いのかもしれません。
2. 描画環境の構築(openFrameworksの導入)
・Jetson NanoのUbuntu環境のセットアップなどは、調べれば色々見つかります。
・ここでは、C++ベースの豊富な描画環境が比較的カンタンに扱えるといった理由で、Ubuntuセットアップ済みのJetson NanoにopenFrameworks(oF)を導入してみます。oFのJetson Nanoへの導入は、こちらの記事を参照してください。英語でずらっと書かれているので、わかり難い部分もあるかもしれませんが、基本的にこの通りでoFの導入はばっちりできます。
・Jetson Nano上でoFの導入が完了後、動作確認のため、oFに元々入っているサンプル(例として、3DPrimitivesExamples)を実行するとこんな感じで描画されます。なお、下記コマンドで make & プログラム実行です。
~/of_v20xxxxx/examples/3d/3DPrimitivesExample$ make && make run
【補足】最初、openFrameworks内のインクルードファイル?の多くをコンパイル&ビルドするのでちょっと時間がかかります。なので、~/of_v20xxxxx/examples/3d/3DPrimitivesExample$ make && make run -j3
のようにして実行すると処理が速くなります。
・なお、UbuntuにおけるC/C++の開発環境の作り方は、この記事が分かりやすいです。(※今回のoFを導入する前述の手順を踏んだ時点で必要な諸々がインストール済みとなり、もしかしたら、$sudo apt-get install build-essential
のコマンドは要らないかもしれませんが、とりあえずひと通り実行しました)
・あとは、vim, nanoエディタ等々でコードを直接編集したり書いたりすればOK、なはずです。
3. Turing不安定性(拡散不安定性)について
「Turing不安定性」「拡散不安定性」「反応拡散方程式」などのキーワードで検索するといろいろ面白そうな文献が出てきます。一応ここでも、北海道大学の文献1を引用して、さわりを説明をしておこうと思います。 ところで、Turing不安定性(拡散不安定性)とは...
生物は発生の過程で、1 個の卵(細胞)が分裂を繰り返して分化し、性質の異なる細胞集団群へと成長する。Turing はこのような空間一様状態から安定な空間非一様状態へと移行する現象に対して、1952 年に拡散不安定性という概念を提唱した。彼は物質の時間変化を反応 (局所的な物質の生成・消滅) と拡散(近隣領域との平均化) の和で記述し、拡散項がなければ定数解を持つ系が、拡散が加わることによって空間一様解が不安定化し、安定な空間非一様解が出現する場合があることを示した。この物質 (状態) の時間変化を反応と拡散の和で記述する枠組みは、反応拡散モデルと呼ばれる。
自然界には多種多様なパターン(模様)が存在する。身近なものには、貝殻や動物の模様などがあるが、これらの特徴のひとつは自己組織的にパターンが形成されることである。後天的に動物の体表パターンの一部を欠損させた場合にも、周りの状況に応じてパターンが修復されることが実験的にも確認されている。近年、これらのパターン形成が上述の反応拡散モデルによってよく再現されることがわかってきた。中でも Turing モデルと呼ばれるモデルでは、パラメータによって斑点や縞柄などのパターンが安定な状態として出現することが知られている。
次は、文献2からこの Gray-Scott Modelを具体的に以下に引用します。なお、ここで反応拡散現象がz方向に一様であると仮定すると、ラプラシアン△はx, yに関する2次元の2階微分となり、
以上が今回扱う Gray-Scott Model です。非線形項を含んだ方程式で記述されている事がわかります。写真の魚の模様のようなパターンが徐々に自然と生成されていく様子を、この後、コンピュータ(Jetson Nano)上でシミュレートします。その前に、コンピュータで計算を行えるようにするために、連続であるこのモデル式を「離散化」します。
4. Gray-Scott Modelの離散化
・「Gray Scott Model」でググると上位に出てきたこちらのサイトを参考にすると良いです。離散化した2階微分の項が漸化式として書いてあります。(各種パラメータを変化させたときの振る舞いをWeb上で確認できるサイトでもあり、とても助かります)
・差分式の誘導の仕方は書いてないですが、他にもグーグルで出てくるので、離散化手法の詳細などは調べてみて下さい。
・最終的にプログラムとして記述する際の数式が書いてあるので、これを参考にし、これを今回はoFのコードに落としこみます。
5. oFにおける数値計算&描画コード【全文】
・oFでは、主に3つのファイルが必要となります。①main.cpp ②ofApp.h ③ofApp.cpp の以上3つです。
・~/of_v20xxxxx/apps/myapps/emptyExample
のディレクトリに空のプロジェクトフォルダ(つまりemptyExample
)があるはずです。それをコピーしてどこか別の場所にバックアップとして保存しておきます。
・基本的に、oFをエラーなく動かすには、プロジェクトの階層構造の決まり?を守る必要があるらしく、その階層構造を崩さないようにするため、このemptyExample
の中のsrc
フォルダ内にある①main.cpp ②ofApp.h ③ofApp.cpp の3ファイルを丸ごと入れ替える、あるいは直接編集して開発するのが定番のようです。(プロジェクトファイル一式を自動生成してくれるProjectGenerator
というツールもあるようですがここでは割愛します)
・よくは分かってないのですが、oFのofVboというクラスメソッドを使うと、簡単にグラフィクスカード(≒GPU?)で描画できるようです。詳しくは公式リファレンスを参照ください。※またこちらのスライド資料も見ながら実装しました。
・以下に、3つのファイルのコード全文を記載します。空間はx, y方向ともに等間隔で刻むようにしました。今回の設定はWIDTH=200, HEIGHT=200とし、200x200の正方形領域を計算します。一応、これでひと通り動きました。
main.cpp
#include "ofMain.h" #include "ofApp.h" int main(){ ofSetupOpenGL(300,300,OF_WINDOW); ofRunApp(new ofApp()); }
ofApp.h
#pragma once #include "ofMain.h" class ofApp : public ofBaseApp { public: void setup(); void update(); void draw(); void keyPressed(int key); void keyReleased(int key); void mouseMoved(int x, int y); void mouseDragged(int x, int y, int button); void mousePressed(int x, int y, int button); void mouseReleased(int x, int y, int button); void mouseEntered(int x, int y); void mouseExited(int x, int y); void windowResized(int w, int h); void dragEvent(ofDragInfo dragInfo); void gotMessage(ofMessage msg); void Init(); int msec; // WIDTH x HEIGHT の正方形領域の指定 static const int WIDTH = 200; static const int HEIGHT = 200; static const int NUM_PARTICLES = WIDTH * HEIGHT; // インスタンス生成 ofEasyCam cam; ofVbo myBuff; // 3次元ベクトルで頂点情報を格納する ofVec3f myVerts[NUM_PARTICLES]; // 頂点の色情報を格納する ofFloatColor myColor[NUM_PARTICLES]; float de = 0.10/2; //空間ステップ float dt = 0.050*2; //時間ステップ //各種パラメータ float Du = 0.00050; float Dv = 0.0040; float F = 0.09; float k = 0.06; float Lapl_u; float Lapl_v; float Diff_u; float Diff_v; float t = 0; float u[WIDTH + 1][HEIGHT + 1]; float v[WIDTH + 1][HEIGHT + 1]; };
ofApp.cpp
#include "ofApp.h" //-------------------------------------------------------------- void ofApp::setup() { ofSetFrameRate(50); cam.setDistance(180); // 頂点情報を初期化 for (int i = 0; i < WIDTH; i++) { for (int j = 0; j < HEIGHT; j++) { myVerts[i * WIDTH + j].set(i - WIDTH / 2, j - HEIGHT / 2, 0); myColor[i * WIDTH + j].set(0.0, ofRandom(0.0, 1.0), 0.0, 1.0); } } //初期値揺らぎの設定 for (int j = 0; j < HEIGHT + 1; j++) { for (int i = 0; i < WIDTH + 1; i++) { u[i][j] = ofRandom(0.0, 1.0); v[i][j] = ofRandom(0.0, 1.0); } } //境界条件(ノイマン条件)の設定 for (int j = 0; j < HEIGHT + 1; j++) { u[0][j] = u[1][j]; u[WIDTH][j] = u[WIDTH - 1][j]; v[0][j] = v[1][j]; v[WIDTH][j] = v[WIDTH - 1][j]; } //バッファに位置と色の情報を設定 myBuff.setVertexData(myVerts, NUM_PARTICLES, GL_DYNAMIC_DRAW); myBuff.setColorData(myColor, NUM_PARTICLES, GL_DYNAMIC_DRAW); } //-------------------------------------------------------------- void ofApp::update() { for (int i = 1; i < WIDTH; i++) { for (int j = 1; j < HEIGHT; j++) { //拡散項 Lapl_u = ( (u[i - 1][j] - 2 * u[i][j] + u[i + 1][j]) + (u[i][j - 1] - 2 * u[i][j] + u[i][j + 1]) ) / (de*de); Lapl_v = ( (v[i - 1][j] - 2 * v[i][j] + v[i + 1][j]) + (v[i][j - 1] - 2 * v[i][j] + v[i][j + 1]) ) / (de*de); Diff_u = Du * Lapl_u; Diff_v = Dv * Lapl_v; float fu = std::pow(u[i][j], 2)*v[i][j] - (F + k) * u[i][j]; float fv = -std::pow(u[i][j], 2)*v[i][j] + F * (1.0 - v[i][j]); u[i][j] = u[i][j] + dt * ((fu)+Diff_u); v[i][j] = v[i][j] + dt * ((fv)+Diff_v); float val = ofMap(u[i][j], 0.0, 1.0, 100 / 255, 1.0); myColor[i * WIDTH + j].set( val, 0.5647, 0.5647, 1.0); } } //全点における色情報の更新 myBuff.updateColorData(myColor,NUM_PARTICLES); t = t + dt; } //-------------------------------------------------------------- void ofApp::draw() { // カメラ開始 cam.begin(); // 頂点の位置をドットで表示 glPointSize(3.0); myBuff.draw(GL_POINTS, 0, NUM_PARTICLES); // カメラ停止 cam.end(); // FPS,Time の表示 string info; info += " FPS = " + ofToString(ofGetFrameRate(), 2) + "\n" + "Time = " + ofToString(t, 2) + "\n" ; ofDrawBitmapString(info, 30, 30); ofDrawBitmapString(info, 30, 30); } void ofApp::Init() { //初期値揺らぎの設定 for (int j = 0; j < HEIGHT + 1; j++) { for (int i = 0; i < WIDTH + 1; i++) { u[i][j] = ofRandom(0.0, 1.0); v[i][j] = ofRandom(0.0, 1.0); } } //境界条件(ノイマン条件)の設定 for (int j = 0; j < HEIGHT + 1; j++) { u[0][j] = u[1][j]; u[WIDTH][j] = u[WIDTH - 1][j]; v[0][j] = v[1][j]; v[WIDTH][j] = v[WIDTH - 1][j]; } t = 0; } //-------------------------------------------------------------- void ofApp::keyPressed(int key) { if (key == 'r') { Init(); } } //-------------------------------------------------------------- void ofApp::keyReleased(int key) { } //-------------------------------------------------------------- void ofApp::mouseMoved(int x, int y) { } //-------------------------------------------------------------- void ofApp::mouseDragged(int x, int y, int button) { } //-------------------------------------------------------------- void ofApp::mousePressed(int x, int y, int button) { } //-------------------------------------------------------------- void ofApp::mouseReleased(int x, int y, int button) { } //-------------------------------------------------------------- void ofApp::mouseEntered(int x, int y) { } //-------------------------------------------------------------- void ofApp::mouseExited(int x, int y) { } //-------------------------------------------------------------- void ofApp::windowResized(int w, int h) { } //-------------------------------------------------------------- void ofApp::gotMessage(ofMessage msg) { } //-------------------------------------------------------------- void ofApp::dragEvent(ofDragInfo dragInfo) { }
6. 数値実験結果
・ランダムな微小摂動を初期条件(初期値)として与えて、その初期状態から計算を開始すると、系が時間発展するにつれて、徐々に縞模様や斑点状のパターンが形成されてゆくことがわかります。
・今回は例として、WIDTH x HEIGHT = 200 x 200 の小さめの空間領域において計算しました。このくらいの計算領域であれば、Jetson Nanoの描画能力でも、50近くのFPSが常に出ていることがわかります。
・各種パラメータ(Du, Dv, F, k)やタイムステップ dt を変えてみたり、もっと空間領域を大きくするなどして、数値シミュレーションしながら遊んでみて下さい。
・プログラム実行中、キーボード入力 "r" で初期リセットがかかります。やってみてください。
7. まとめ
Jetson Nanoに数値計算&描画環境としてC++ベースのopenFrameworksを導入し、その中でGray-Scott Modelを例にとって、Turing不安定性(拡散不安定性)の2次元数値シミュレーションを試行しました。比較的お手軽に、非線形性をもつ偏微分方程式の数値的解析のイントロダクションが行えました。
8.参考図書
格安FPGA評価ボードで格安LCD【1602A】を制御する
1. はじめに
前回の記事で紹介した格安中華製FPGA評価ボードを使って、今度は電子工作でおなじみのこのLCD(Liquid Crystal Display)を制御してみます。型番は【1602A】というらしいです。いつもならArduinoなどの開発環境で専用ライブラリを導入するだけでカンタンに取り扱えたこのLCDを、少しややこしいですが、トレーニングのためFPGAベースで駆動させてみるのが今回の目的です。(LCDはAmazonで安く買えます。Aliexpressならもっと安いですが届くのに時間がかかります)
2. LCD【1602A】の仕様
とりあえず、LCDの大まかな仕様を知りたい......。というわけで、データシートを探すと当然のようにありました!適宜ダウンロードしてください。細かい英文字が目にチカチカしますが、必要な箇所だけ拾い読む感じでゆっくりといきます。 LCDの信号線(ピン)は全部で16あります。ダウンロード先のデータシートから転載のピンアサイン図を以下に示します。
見てのとおり、No.7~14の8個のピン(DB0~DB7)は、8bit分のデータを受け取るためのデータバスになっています。つまり、表示したい文字(キャラクタ)の8bit ASCIIコードを、制御側(今回ではFPGAボード)が、制御対象であるLCD側に信号として送ることで文字の表示を制御できるといった仕組みです。 LCDにはピンヘッダを半田付けしています。それをメス-メス ジャンパワイヤでFPGAボードのピンヘッダと結線して、今回の実験の回路を構成します。
3. FPGAのピンアサイン・回路構成
電源周りのVss, Vdd, Vo, BLA, BLK を除くLCDの全てのピン(制御用信号線)を、それぞれ以下のようにVHDLで記述し定義しました。 ・ RS ---> "lcd_rs" ・ R/W ---> "lcd_rw" ・ E ---> "lcd_e" ・DB0~DB7 ---> "data[0]~data[7]"
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity lcdtest is port ( clk : in std_logic; --clock i/p lcd_rw : out std_logic; --read & write control lcd_e : out std_logic; --enable control lcd_rs : out std_logic; --data or command control data : out std_logic_vector(7 downto 0) ); --data line end lcdtest;
FPGAのピンアサインは図の赤枠に示すとおりです(これ通りでなくても適切に設定すれば動きますが、一応例として載せておきます)。なお、FPGAボードにあらかじめ載っている水晶?振動子(50MHz)をクロック入力として用いるので、今回使用するFPGAボード回路図の仕様に従って、VHDL内で宣言した "clk" には、INPUTとして既知の"PIN_17"を割り当てています。
以下が回路図です。こちらのサイトが参考になりました。一部抜粋して、ここに載せます。 設定したピンアサインと実際の配線が一致するようにします。この回路図に従い、LCDバックライトの調整のための可変抵抗を別途ブレッドボード上で構成しました。 このLCD【1602A】は+5V駆動なので、FPGAボードのVcc=+3.3Vとはレベルが異なる点に注意してください(最初、FPGAボードのVccから引っ張ってきた3.3Vでも動くだろうと高をくくってたら1時間くらいハマりました)。別途+5Vの電源(直列した乾電池など)で対応するのが手っ取り早くていいかと思います。
4. LCDに"Hello! "と表示するテストVHDLコード(全文)
lcdtest.vhd
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity lcdtest is port ( clk : in std_logic; --clock i/p lcd_rw : out std_logic; --read & write control lcd_e : out std_logic; --enable control lcd_rs : out std_logic; --data or command control data : out std_logic_vector(7 downto 0)); --data line end lcdtest; architecture Behavioral of lcdtest is constant N: integer :=5+7; type arr is array (1 to N) of std_logic_vector(7 downto 0); constant datas : arr := (x"38",x"0c",x"04",x"01",x"C0", x"48",x"65",x"6C",x"6C",x"6F",x"21",x"20"); begin lcd_rw <= '0'; process(clk) variable i : integer := 0; variable j : integer := 1; begin if ( clk'event and clk = '1' ) then if i <= 1000000 then i := i + 1; lcd_e <= '1'; data <= datas(j)(7 downto 0); elsif 1000000 < i and i < 2000000 then i := i + 1; lcd_e <= '0'; elsif i = 2000000 then j := j + 1; i := 0; end if; if j <= 5 then lcd_rs <= '0'; elsif j > 5 then lcd_rs <= '1'; end if; if j = 5+7 then j := 5; end if; end if; end process; end Behavioral;
x"48",x"65",x"6C",x"6C",x"6F",x"21",x"20"の16進表記の並びで、 "H", "e", "l", "l", "o", "!", " " の計7文字を表しています。
5. 実験結果
文字列"Hello! "がFPGAによりあの格安LCDに表示されています!!完了です!
6. まとめ
格安FPGA評価ボードを用いて、格安LCDの導入を行なうことができました。 今後はより実践的な内容で勉強しながら記事を書いていきたいと思います。
はじめてのFPGAをAmazon購入の格安ボード【ALTERA Cyclonell EP2C5T144 搭載】でやる
1. 格安中華ボード(ALTERA FPGA Cyclonell EP2C5T144)をひとまず買う
Amazonで手軽に購入できるAltera搭載の中華製のFPGAボード(¥2,000)をひとまず購入します。
2. 書き込み装置 USB Blaster も一緒に買う
Amazonじゃなくてもよいのですが、Amazonがラクなので一緒に購入します。価格は¥1,000くらいです。
3. 開発環境(Altera社 QuartusⅡ)の導入
Altera社 (現在、Intelに買収済み) のFPGA開発環境 QuartusⅡ をここではWindowsPCに導入します。ググると上位に出てくる こちらのサイト が非常に丁寧でわかりやすかったです。このサイトどおりにひと通り行うと、Lチカまで完了することができます。
4. 不揮発性メモリ(フラッシュ・メモリ)への書き込み方法
・FPGAは(3.の手順通りに)普通に書き込んだ場合、開発ボードの電源を1回落とすと、設計した回路データが消えてしまいます。それは非常に面倒なので、電源を入れるたびごとに書き込みデータが消失しないよう、フラッシュ・メモリに FPGA 回路データを事前にプログラミングしておく必要があります。
・そうすると、電源投入時に自動でフラッシュ・メモリからFPGAのほうにコンフィグレーションが行われ、FPGA内部の回路データを常に保持することができます。
以下の手順①~⑧が、FPGA の回路データ(コンフィギュレーション・データ)のフラッシュ・メモリへの書き込み手順です。(※この手順では、このサイトもとても参考になりますので、それを今回用いる中華製評価ボード専用の設定に書き換える感じで、以下記事を書いていきます。) ①ウィンドウ上部のメニューバーから "File" -> "Convert Programming File" を選択。
②下図のようなウィンドウが開くので、"Programming file type" と "Configuration device" と "Mode" を設定する。("File name"の箇所はディレクトリもファイル名も任意でOK)
③同様のウィンドウの"Input file to convert" の箇所で、"Flash Loader" をクリックしてアクティブにし、"Add Device..."を選択後、デバイス名"CyclonⅡ" の"EP2C5"を選択する。
④同様のウィンドウの"Input file to convert" の箇所で、"SOF Data" をクリックしてアクティブにし、"Add File..."を選択後、自分のファイル(ファイル名.sof)を選択する。(例としてこの図では、ファイル名は"vhdl1.sof")
⑤そして、"Generate"ボタンをクリックして、専用のファイル(ファイル名.jic)を作成!
⑥メインウィンドウに戻って、上部メニューバーの "Tool" -> "Programmer" を選択し、ウィンドウを開く。
⑦下図のようなウィンドウで、左の"Add File..." から、先ほど手順⑤で作成したファイル(ファイル名.jic)を選択し読み込む。
⑧"Program/Configure"にレ点(チェック)を入れ、左上の"Start"で書き込み開始!! Progress=100%で緑色になれば、書き込み成功!!
ためしに一度電源を切って入れてみても、 回路情報が保持されています。 おつかれさまでした!(^^)!
5. 最後に
※回路図等ボード情報詳細はココで拾えます