lynxeyedの電音鍵盤

MBDとFPGAと車載で使うデバイスの備忘録

Quartus II 14.0/14.1で初期値ありのUFMをインスタンシエートする

以前(アルテラCPLDで150LE未満のワンチップCPUを作ったお話 - Lynx-EyEDの電音鍵盤 新館)のときはQuartusII 13.1ベースで記事を書いていたので、14系列でかなりの変更があったと聞き、どう変わったか確認したときのメモです。

MegaWizardが消えたっぽい

MAX10 FPGAのUFMがAvalon-MMのモジュールとして使用できるようになっており(Altera On-Chip Flash)、その流れを受けてかMAX II/VのUFMもQsys経由でプロトコルや初期値のセットアップができるようになっています。
といってもMAXII/VのUFMはAvalon-MMのモジュールにならないので、後述するエクスポートするだけになります。

Qsysを今まで使っているなら特に問題ないと思いますが、Quartus IIでMAXII/Vのプロジェクトをつくり、Qsysを起動します(IP Catalogから起動してもOK)

UFMをSPIスレーブ、16bit,read onlyで初期化するので
画面左のIP Catalogからツリーをたどり、
Library -> Basic Functions -> On Chip Memory -> Altera USer Flash Memory for SPI Interface Protocol
を選択し、[add]ボタン
f:id:Lynx-EyED:20150103115111p:plain
プロトコルの詳細設定画面が出るので[General]タブで
Access mode : read only
Configuration mode : Extended mode (16bit address and data)
Port settings: Use 'osc' output port
にチェック
f:id:Lynx-EyED:20150103114123p:plain
次に[Initialization and Simulation]タブで
Memory Content Initialization: Initialize from hex or mif file
にチェックし、所望の初期値ファイル(ここではプロジェクトのルートにあらかじめ用意したUFM_Module.hex)を指定する。(後述:ここで問題が発生する)
して[Finish]する。
f:id:Lynx-EyED:20150103112801p:plain

QsysのSystem Contentsタブに戻り、
Avalonバス上につながるコネクションは今回ひとつもないので、すべてダブルクリックしてExportしておく。
f:id:Lynx-EyED:20150103112816p:plain
ここからはQsysのいつもの使い方なので駆け足で。
メニュー -> Generate -> Generate HDL -> Generate
Finish(Qsysを任意の名前で保存するのを忘れずに。)

Quartus II
Project -> Add/Remove Files in Project
で先ほどのQsysファイルをインポートする

インスタンシエート。自分はこんな感じでやりました

 qsys_ufm u0 (
	  .ufm_spi_0_ncs_ncs (ufm_ssel), // ufm_spi_0_ncs.ncs
	  .ufm_spi_0_osc_osc (int_osc), // ufm_spi_0_osc.osc
	  .ufm_spi_0_sck_sck (ufm_sclk), // ufm_spi_0_sck.sck
	  .ufm_spi_0_si_si   (ufm_mosi),   //  ufm_spi_0_si.si
	  .ufm_spi_0_so_so   (ufm_miso)    //  ufm_spi_0_so.so
 );

hexの中身が反映されない

これでコンパイルするとUFMに指定した初期値が反映されておりませんでした。
警告127003というのが目に付いたので見てみますと
f:id:Lynx-EyED:20150103112840p:plain

127003: Critical Warning (127003): Can't find Memory Initialization File or Hexadecimal (Intel-Format) File C:C:/altera/14.1/projects/ufm_module/altera14.1projectsufm_moduleUFM_Module.hex -- setting all initial values to 0

確かに初期値が読み込めてないっていうか、ファイル名バグってないですかこれ?System::Strings::Split('\')でも使って再連結するときに忘れたのかな(ぇ

対処:
プロジェクトフォルダからの相対パスでファイル名を指定する
自分の場合はプロジェクトのルートフォルダにUFM_Module.hexという名前で保存してあるので、
./UFM_Module.hex
f:id:Lynx-EyED:20150103113825p:plain
と記述し、先ほどと同じようにGenerate HDL -> Finish
コンパイルすれば、先ほどの警告はなくなり、初期値が反映される。

ちなみに

C:\altera\14.1\my_project_folder\my_project\my_ufm_data.hex という階層にhex/mifがある場合
C:\\altera\\14.1\\my_project_folder\\my_project\\my_ufm_data.hex
って書き直してやると動く模様。
でも、今後修正されたときにどんな挙動するかわかんなくなるし、やめたがいいかも。

JavaScriptでStewgate U経由でTwitterにポストする

半年近くブログ書いてない...

よく、OAuth非対応なArduinoやmbedや組み込みLinux機器から投稿する例は見るのですが、プレーンなhtml+JSでStewgate使うにはどうしたらいいのかなと悩んで、解決した後すぐ忘れてしまうのでメモ書き。

StewGate U: Throw your stuff into the stewittering pot.

ツイッタアカウントでログインすると、トークンというものがもらえます。

使い方
以下のURLにPOSTデータを送ると、StewGate UからTwitterアカウントに ポストできます:
        http://stewgate-u.appspot.com/api/post/

        POSTデータ:
        _t = (ここに取得したトークン)

        msg = "あなたの送信したいメッセージ"
        * UTF-8、URLエンコード済みにしてください。
        * メッセージに "%(name)s" を含めると、トークンの名前に置換されます。
        

        応答:
        OK (ポスト成功)
        エラーメッセージ (ポスト失敗)

と、あるので、(以下に続く)

XMLHttpRequestを使った例

というわけで、トークンをもらったら以下のコードを書きます。投稿後のレスポンスを取得したかったのでXMLHttpRequestを使いました。

var fd = new FormData(); 
	
fd.append("_t", "(取得したトークン)");
fd.append("msg","(ポストしたいメッセージ)");
	
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://stewgate-u.appspot.com/api/post/");

xhr.onload=function(){
	(レスポンスxhr.responseを表示するなりなんなり)
};
xhr.send(fd);

ちなみにTwitterは同じメッセージを連投できないので、メッセージに投稿時間や乱数を付け加えるのがいいと思います。

コピペ用全文

tweetした時間を投稿します。
スマートフォンなどからも使い易いようにスタイルシートを適用しています。

<html>
<head>
   <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no;" />
        <meta http-equiv="Content-type" content="text/html; charset=utf-8" target="_self">
            <style>
                .btn {
                    font-family: Arial, Helvetica, sans-serif;
                    font-size: 28px;
                    color: #fff;
                    padding: 12px 18px;
                    
                    background: #8893da;
                    background: -webkit-gradient(
                        linear, left top, left bottom,
                        from(#8893da),
                        to(#344c77)
                    );
                    
                    border-radius: 10px;
                    
                    -webkit-box-shadow:
                        12px 12px 12px rgba(000, 000, 000, 0.3),
                        inset 0px 0px 0px rgba(255, 255, 255, 0);
                    
                    text-shadow:
                    10px 10px 10px rgba(000, 000, 000, 1),
                    
                }
            </style>

<script type="text/javascript" charset="utf-8">

function postForm(){
	var fd = new FormData(); 
	
	var date_str = new Date();
	fd.append("_t", "(取得したトークン)");
	fd.append("msg","ただいまの時刻は"+date_str.getHours()+"時"+date_str.getMinutes() +"分"+date_str.getSeconds()+"秒です。");
	
	var xhr = new XMLHttpRequest();
	xhr.open("POST", "http://stewgate-u.appspot.com/api/post/");

	xhr.onload=function(){
		document.getElementById("stat").innerHTML = xhr.response; // レスポンスの表示
	};
	xhr.send(fd);
}

</script>
</head>
<body>
<div>
 <button class="btn" onclick="javascript:postForm()">POST_XHR</button>
</div>
<div id="stat"></div>
</body>
</html>

UTF-8エンコードで保存して、ブラウザから投稿テストします。POST_XHRのボタンを押すとTwitterにポストします。
うまくいけば、投稿ボタン真下にOKと出るはず。
f:id:Lynx-EyED:20141205221226p:plain
なにか時間経過で変化する事象をツイートする必要があれば、setInterval()などを使って上記関数を繰り返してもいいかもしれません。

アルテラCPLDで150LE未満のワンチップCPUを作ったお話

150LE未満のMISC CPU

スタック1段のみ、キャリーフラグなし、6命令のMISC CPUを作りました。
(ほんとは5命令だったんだけど、このネタに使うのに1命令追加した)

以下が命令です。内部的には最初のコマンド以外、殆ど同じ動きをします。

  • GO_IF_NON_ZERO_ELS_SUB スタックがゼロなら引数xxで示したアドレスへ、否ならスタック-xxをスタックに格納。マシン語0x0F,xx
  • GET_STREAM_AND_STACK xx bitだけ取得したストリームの下位8bitをスタックする。マシン語0x81,xx
  • PIPE_EXTERNAL_TO_FIFO 外部のデータをxxビットだけFIFOにセットする。マシン語0x82,xx
  • GET_STREAM_AND_SUBST スタックの値-取得したストリームxxを計算しスタックを上書き。マシン語0x83,xx
  • PIPE_SEEK_BITS (通常は命令としては使わない。プログラムを読み込むのにストリームの頭出しシフトをxx bit分するだけ。但し、Nop命令の代わりにはなるかもしれない)。マシン語0x84,xx
  • GET_STREAM_PUSH_OUT (これがネタ用に追加した命令)GPIOに引数xxを反映させる。これで思う存分Lチカができるってワケ。マシン語0x85,xx


f:id:Lynx-EyED:20140711222045j:plain
命令から推測がつくかもしれませんが、シリアルデータを右から左に流したり、条件によって複数あるデータ出力先を切り替えたりする「少し気の利いたFIFO」的なものを作ってました。
しかし条件が増え、デバイスによっては複雑な初期化シーケンスが必要なものもあると、回路規模が増大します。
AlteraのMAXII/MAXVはデバイスの規模にかかわらず1KByteのユーザーフラッシュ(UFM)があるので、初期化手順をここに記述してしまい、回路規模を抑えようというのが経緯です。

冒頭で示した図が、今回作ったCPUのブロック図です。UFMのデータを操作する手段はいくつか提供されていますが、どの手段でもユーザーロジックを消費します。
今回は規模の一番小さいSPI Slaveリードオンリメモリ(16bitアドレスモード = Extended Mode)としてインスタンスしています。回路規模はたったの40LEです。このCPUはこのUFMからコマンドとデータをシリアルリードする設計のため命令体系も回路も独特な構成になっています。
(UFMの取り扱いについてはこちらを参照http://www.altera.com/literature/hb/max2/max2_mii51010.pdf SPIの場合は9-24項以降参照)
メモリ側からはこのCPUがSPI Masterとして見えているので、AlteraでなくてもUFMを持っているLatticeのCPLDや、SPI EEPROMを接続したFPGAなら少しの変更で実装可能なはずです。

使い方

1.コマンドとプログラムを格納するUFMをMegaWizardでインスタンスします。インスタンス方法詳細はhttp://www.altera.com/literature/hb/max2/max2_mii51010.pdf を参照してください。
今回はufm_coreと言う名前でRead Only,Extended modeを選択しました。
f:id:Lynx-EyED:20140711224204p:plain
2. CPUメインのverilogコードがプロジェクトトップに来るようにします。
なお、コード中の8bitカウンタCountrは今回のブログ記事のためにクロックを分周するのに使っています。必要ない場合は取り除きます。
(コードはブログ最後に記述しました)

3.Lチカ
マシン語でLチカプログラムを書きUFMに保存します。今回の設定ではリードオンリーで実装されているのでQuartus II上でUFMに流し込むバイナリを作ります。アドレスは16bitモードであることに注意してください。

まず、フローを考えます。

(i) スタックに255を格納する
(ii) GPIOに0xFFを出力する(LED全点灯)
(iii) スタックから1減算する
(iv) スタックはゼロ?ゼロでなかったら引数に書かれている(ここでは(iii)の命令が書かれている番地) に戻る。ゼロだったら スタック - 引数の値をスタックに格納(つまり、引数をマイナス値にしたものが格納される)し次の命令へ
(v) スタックに255を格納する
(vi) GPIOに0x00を出力する(LED全消灯)
(vii) スタックから1減算する
(viii) スタックはゼロ?ゼロでなかったら引数に書かれている(ここでは(vii)の命令が書かれている番地) に戻る。ゼロだったら スタック - 引数の値をスタックに格納(つまり、引数をマイナス値にしたものが格納される)し次の命令へ
(ix) スタックはゼロ?ゼロでなかったら引数に書かれている(ここではゼロ番地) に戻る。ゼロだったら→(ここでスタック=ゼロはあり得ない)

これで、8bitの出力全てにLEDが付いていたら、一定周期ごとに全点灯、前消灯を繰り返す事が出来ます。
上記をマシン語にします。16進数表記、ビッグエンディアンです。
(i) 8110 00FF
(ii) 8510 00FF
(iii) 8310 0001
(iv) 0F04
(v) 8110 00FF
(vi) 8510 0000
(vii) 8310 0001
(viii) 0F0B
(ix) 0F00

とまぁこんな感じで記述していきます。LED全点灯/消灯だとつまらないので、もう少し点灯パターンを増やしたのが下記の画面です。
f:id:Lynx-EyED:20140711231347p:plain

これを実際に動作させてみました。MAX MMKボード上のLEDがちょうど8個あるので使ってみました。もちろんMAX Vでも動作します。(5M160から動作可能)

Minimal CPU on MAX II CPLD - YouTube

追記:せっかくLEDが8個もあるから、POVで「ウンコ」とか表示した方がおもしろかったかな。。。

Verilogコード全文

module ufm_module_top(
	input	clk,
	input reset,
	input key,
	output[7:0] LED
);

wire int_osc;

reg  ufm_ssel;
wire ufm_sclk;
wire ufm_mosi;
wire ufm_miso;
reg  ufm_miso_reg;

wire fifo_sys_clk;
reg  wait_clock;		// STOP SCLK flag

reg [7:0] ufm_com_select;	// command selector 
									// select= 8'b1xxx_xxxx -> command, select=8'b0xxx_xxxx -> data
reg [7:0] ufm_bit_count;	// clock counter
reg [7:0] user_stack_data;	// reg for stack data
reg [7:0] user_reg_data;	// general purpose reg
reg[23:0] ufm_fifo;			// 24bit UFM FIFO


parameter GO_IF_NON_ZERO_ELS_SUB	= 7'h0F;
parameter GET_STREAM_AND_STACK	= 7'h01;
parameter PIPE_EXTERNAL_TO_FIFO	= 7'h02;
parameter GET_STREAM_AND_SUBST	= 7'h03;
parameter PIPE_SEEK_BITS			= 7'h04;
parameter GET_STREAM_PUSH_OUT		= 7'h05;


// "countr" can be remove if you wish.
reg [6:0] countr;
always@(posedge int_osc)begin
	countr <= countr + 1;
end

// fifo_sys_clk: UFM's clock, upto 9.7-MHz
assign fifo_sys_clk = countr[6];//int_osc;

always@(posedge ufm_sclk, negedge reset)begin
	if(~reset)begin
		ufm_miso_reg <= 1'b0;
	end else if(~ufm_ssel)begin 
		ufm_miso_reg <= ufm_miso;
	end else begin
		ufm_miso_reg <= 1'b0;
	end
end


assign ufm_sclk = (wait_clock)? ufm_sclk: fifo_sys_clk;
always@(negedge fifo_sys_clk, negedge reset)begin
	if(~reset)begin
		wait_clock <= 1'b0;
		ufm_ssel <= 1'b1;
		ufm_bit_count <= 8'b0;
		user_stack_data <= 8'b0;
		ufm_com_select <= {1'b0, GO_IF_NON_ZERO_ELS_SUB}; 
		ufm_fifo <= 24'b0;//TODO: insert UFM Read command	
	end else if(ufm_bit_count == 0)begin
	// Command mode or Data	mode
	case(ufm_com_select[7])
		1'b1:	
		case(ufm_com_select[6:0])	// latch the commands
		
			GET_STREAM_AND_STACK:
			begin			
				wait_clock <= 1'b1;						// Stop SCLK
				ufm_ssel <= 1'b0;
				user_stack_data <= ufm_fifo[15:8];
				ufm_com_select	<= {1'b1,PIPE_SEEK_BITS};
				ufm_bit_count <= 8'd16;//8'd24;
			end
			
			GET_STREAM_PUSH_OUT:
			begin
				wait_clock <= 1'b1;						// Stop SCLK
				ufm_ssel <= 1'b0;
				user_reg_data <= ufm_fifo[15:8];
				ufm_com_select	<= {1'b1,PIPE_SEEK_BITS};
				ufm_bit_count <= 8'd16;//8'd24;
			end
			GET_STREAM_AND_SUBST:
			begin			
				wait_clock <= 1'b1;						// Stop SCLK
				ufm_ssel <= 1'b0;
				user_stack_data <= user_stack_data - ufm_fifo[15:8];
				ufm_com_select	<= {1'b1,PIPE_SEEK_BITS};
				ufm_bit_count <= 8'd16;//8'd24;
			end 
			
			default:
			begin
				wait_clock <= 1'b1;						// Stop SCLK
				ufm_ssel <= (ufm_fifo[23]==0 && user_stack_data != 8'h00)? 1'b1 : 1'b0;
				user_stack_data <= user_stack_data;
				ufm_com_select	<=  ufm_fifo[23:16]; // if (ufm_fifo[23]==1 && bit_count == 0) this system freezes. (this can be used this for test)
				ufm_bit_count	<=  (ufm_fifo[23] == 1'b0)?8'b0 : ufm_fifo[15:8];
			end		
		endcase
		
		1'b0:
		begin // UFM command data
			// send command to UFM
			if(ufm_ssel == 1'b1)begin // if(user_stack_data == 0)jump to address else skip
				wait_clock <= 1'b0;
				ufm_ssel <= 1'b0;
				ufm_bit_count <= 8'd48;//8'd24;
				user_stack_data <= user_stack_data;
				ufm_com_select	<= {1'b1,GO_IF_NON_ZERO_ELS_SUB}; // change Command mode
				ufm_fifo <= {16'h0300,ufm_fifo[15:8]};//TODO: insert UFM Read command + address
			end else begin
				wait_clock <= 1'b1;						// Stop SCLK
				ufm_ssel <= 1'b0;
				ufm_bit_count <= 8'd16;//8'd24;
				user_stack_data <= user_stack_data - ufm_fifo[15:8];
				ufm_com_select	<= {1'b1,PIPE_SEEK_BITS};
				ufm_fifo <= ufm_fifo;
			end
		end
	endcase
	end else begin
		wait_clock <= 1'b0;
		ufm_ssel <=1'b0;
		ufm_fifo <= {ufm_fifo[22:0], ufm_miso_reg};
		ufm_bit_count <= ufm_bit_count - 8'b1;
	end
end 

assign LED = /*(~key)? ~user_stack_data :*/ ~user_reg_data;//~ufm_fifo[23:16];

assign ufm_mosi = (ufm_com_select[6:0]==GO_IF_NON_ZERO_ELS_SUB)? ufm_fifo[23] : 1'b0;
// assign other_pin =(ufm_com_select == GET_STREAM_AND_STACK)? ufm_fifo[8]: other_pin;


ufm_core u0(
	.ncs	(ufm_ssel),
	.sck	(ufm_sclk),
	.si	(ufm_mosi),
	.osc	(int_osc),
	.so	(ufm_miso)
	);
/*
	input	  	ncs;
	input	  	sck;
	input	  	si;
	output	osc;
	output	so;
*/


endmodule

◆参考
めくるめくギリギリCPUの世界 - .mjtの日記復帰計画
http://www.altera.co.jp/literature/hb/max2/max2_mii51010_j.pdf
http://www.altera.co.jp/literature/hb/max-v/mv51007_j.pdf

SigmaDSP ADAU1781のトライアルボード

SigmaDSP評価

前回(コーディング不要のSigmaDSPを試食する - Lynx-EyEDの電音鍵盤 新館)ADAU1701/ADAU1401Aで実験したとき、動作中は人肌くらいの温度まで出ており、擬似差動入力に同相信号を入力したとき、実測で0.1~0.5mVppほどの規則性の無いノイズが確認されていました。
万能基板上の実験だったのでアテにはなりませんが、省電力、低ノイズを謳ってるADAU1781をターゲットに再チャレンジするべく基板を起こしてみました。*1

f:id:Lynx-EyED:20140628162212p:plain
ホストCPUはPSoC4です。コンフィグ、I2Sマスタとして動作します。
600mil幅、4層基板です。

f:id:Lynx-EyED:20140627100452j:plain
でとりあえず、完成。
ADAU1781はAD/DAがそれぞれ2chのみですが、外部デジタル入力(PDM,TDM)に最大8chまで対応でき、クロック入力周波数が固定されていなかったりと、便利です。今回はPSoC4内部で24MHz信号をD-FFを通し12MHzをつくりADAU1781に与えています。
PSoC4のUDBが少ないのと、SCBの制約から、SigmaDSPのコンフィグはSPIを利用しています。

これからの方針

しばらくこのボードのアナログ特性の評価をしますが、PSoC4の動作速度の都合上、I2Sの通信速度はあまり早くありません。96kspsのデータをすばやく取得するのにMAXVの利用を考えています。Avalon-STインターフェースにぶら下げられるI2Sスレーブとしての利用、UFMからのSigmaDSPコンフィグレーションなどができると便利そうです。

*1:ほんとは評価基板購入を考えていましたが、意外に高い

GLFW3で等高面に応じた色の3次元グラフを作る

前回の続き。複数のセンサーの出力結果と相関を時間軸でみるのには3次元グラフが便利かなということでいろいろググったらこんなサイトに

隠線処理を用いた三次元グラフの作成:CodeZine

OpenGL + GLFWで表示してみる

Z軸の等高面で色が変化するようになっていますが、あまり真面目に作っていません。上記のコードのようにswitch~caseで行うか、HSVRGB変換を扱う範囲の最大最小値で正規化してやるほうがよいと思います。詳細はget_color関数をご覧ください

グラフのメモリがついてないのはまた後でということで。
式はfunc_t関数内の式を計算して表示しています。

#define GLFW_INCLUDE_GLU
#include <math.h>
#include <stdio.h>
#include <GLFW/glfw3.h>
#include <Windows.h>
#include <locale.h>

#pragma comment(lib, "GLFW3.lib")
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")

#define MIN -5.0 // Z軸の最小
#define MAX 5.0  // z軸の最大
#define DX 0.8   // ポリゴンの大きさ。あまり小さいと動作が緩慢に、大きすぎるといびつな形になる
#define DY 0.8 // 同上
#define MATH_PI 3.141592653589793238662643383279

static double eye_z = -70.0;
int ROTATE_INDICATE; // 1: rotate , 0: do not rotate
float a2rad(double angle)
{
	return (float)(angle / 180 * MATH_PI);
}

double r2deg(float radian)
{
	return (double)(radian * 180 / MATH_PI);
}

double func_t(double x, double y){
	return  4.8 * cos(sqrt(x * x + y * y) / 1.0);
	
}


void get_color(GLubyte *color, double z)
{
	color[0] = (GLubyte)fmod(((z - MIN) * 255 / (MAX - MIN) + 250) , 256);
	color[1] = (GLubyte)fmod(((z - MIN) * 255 / (MAX - MIN) + 50) , 256);
	color[2] = (GLubyte)fmod(((z - MIN) * 255 / (MAX - MIN) + 100) , 256);
}

void reshape(int width, int height)
{

	static GLfloat lightPosition[4] = { 0.25f, 1.0f, 0.25f, 0.0f };
	static GLfloat lightDiffuse[3] = { 1.0f, 1.0f, 1.0f };
	static GLfloat lightAmbient[3] = { 0.25f, 0.25f, 0.5f };
	static GLfloat lightSpecular[3] = { 1.0f, 1.0f, 1.0f };



	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);

	glShadeModel(GL_SMOOTH);
	glViewport(0, 0, width, height);
	

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0, (double)width / (double)height, 0.1, 100.0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	gluLookAt(0.5, 1.5, eye_z, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0);

	glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse);
	glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient);
	glLightfv(GL_LIGHT0, GL_SPECULAR, lightSpecular);

}

void draw_polygon(double x0, double y0, double z0,
	double x1, double y1, double z1,
	double x2, double y2, double z2,
	double x3, double y3, double z3)
{
	GLubyte color[3];
	glBegin(GL_POLYGON);
	get_color(color, z0);
	glColor3ub(color[0], color[1], color[2]);
	glVertex3d(x0, y0, z0);
	get_color(color, z1);
	glColor3ub(color[0], color[1], color[2]);
	glVertex3d(x1, y1, z1);
	get_color(color, z2);
	glColor3ub(color[0], color[1], color[2]);
	glVertex3d(x2, y2, z2);
	get_color(color, z3);
	glColor3ub(color[0], color[1], color[2]);
	glVertex3d(x3, y3, z3);
	glEnd();
}

void edit_xyz_axis(void){

	glBegin(GL_LINES);

	glColor3f(1.0f, 0.0f, 0.0f);
	glVertex3f(50.0f, 0.0f, 0.0f);
	glVertex3f(0.0f, 0.0f, 0.0f);

	glColor3f(0.0f, 1.0f, 0.0f);
	glVertex3f(0.0f, 50.0f, 0.0f);
	glVertex3f(0.0f, 0.0f, 0.0f);

	glColor3f(0.0f, 0.0f, 1.0f);
	glVertex3f(0.0f, 0.0f, 25.0f);
	glVertex3f(0.0f, 0.0f, 0.0f);

	glEnd();

}


void display_polygon(){


	static GLfloat diffuse[3] = { 1.0f, 1.0f, 1.0f };
	static GLfloat ambient[3] = { 0.25f, 0.25f, 0.25f };
	static GLfloat specular[3] = { 1.0f, 1.0f, 1.0f };
	static GLfloat shininess[1] = { 32.0f };

	static float angle = 0.f;

	glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse);
	glMaterialfv(GL_FRONT, GL_AMBIENT, ambient);
	glMaterialfv(GL_FRONT, GL_SPECULAR, specular);
	glMaterialfv(GL_FRONT, GL_SHININESS, shininess);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_DEPTH_TEST);
	glClearColor(1.0f, 1.0f, 1.0f, 1.0f);

	if (ROTATE_INDICATE == 1){
		ROTATE_INDICATE = 0;
		angle += 2.5f;
		if (angle > 360.0f) {
			angle -= 360.0f;
		}
	}
	if (ROTATE_INDICATE == -1){
		ROTATE_INDICATE = 0;
		angle -= 2.5f;
		if (angle < 0.0f) {
			angle += 360.0f;
		}
	}

	glRotatef(angle, 1.0f, 1.0f, 0.0f);
	glPushMatrix();


	double x, y;
	for (y = -25; y <= 25; y += DY)
	for (x = -25; x <= 25; x += DX)
	{
		draw_polygon(x, y, func_t(x, y),
			x + DX, y, func_t(x + DX, y),
			x + DX, y + DY, func_t(x + DX, y + DY),
			x, y + DY, func_t(x, y + DY));
	}

	edit_xyz_axis();
	glPopMatrix();

}

void idle()
{

	Sleep(10);
}

void error_callback(int eror, const char* description)
{
	fputs(description, stderr);
}

static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods){
	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
		glfwSetWindowShouldClose(window, GL_TRUE);
	else if (key == GLFW_KEY_PAGE_DOWN && action == GLFW_PRESS)
		eye_z = eye_z - 2.5;
	else if (key == GLFW_KEY_PAGE_UP && action == GLFW_PRESS)
		eye_z = eye_z + 2.5;
	else if (key == GLFW_KEY_DOWN && action == GLFW_REPEAT | GLFW_PRESS)
		ROTATE_INDICATE = -1;
	else if (key == GLFW_KEY_UP && action == GLFW_REPEAT | GLFW_PRESS)
		ROTATE_INDICATE = +1;
}

//static void mouse_callback(GLFWwindow* window, )
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
	switch (uMsg) {

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nShowCmd)
{

	int width, height;
	// コールバックの設定
	glfwSetErrorCallback(error_callback);

	// GLFWを初期化
	if (!glfwInit())
		exit(EXIT_FAILURE);

	GLFWwindow* window = glfwCreateWindow(400, 400,"3D-graph display test", NULL, NULL);
	if (!window)
	{
		glfwTerminate();
		exit(EXIT_FAILURE);
	}

	glfwMakeContextCurrent(window);
	glfwSetKeyCallback(window, key_callback);


	// メインループ 
	while (!glfwWindowShouldClose(window))
	{
		glfwGetFramebufferSize(window, &width, &height);
		reshape(width, height);
		idle();

		display_polygon();
		glfwSwapBuffers(window);

		// イベントのポーリング
		glfwPollEvents();
	}

	// Windowの削除
	glfwDestroyWindow(window);
	// GLFWの終了処理
	glfwTerminate();
	exit(EXIT_SUCCESS);
}


結果

PageUp / PageDownで拡大縮小
カーソルキー上下で回転します
f:id:Lynx-EyED:20140622002257p:plain

結論

GLFWはGLUT同様ウィンドハンドラ周りの心配しなくても面倒見の良いライブラリだけど.Net環境でダイアログベースで開発できなくてつらい。なのでGLFW使ったお勉強はここまでにしようと思います。


◆参考
c++ - Using GLFW to render and WinAPI to handle messages - Stack Overflow

GLFW3 + VS2013を使った立方体の描画と回転

OpenGL + GLFW3 + VS2013で立方体を回転させる

ちょっと計測関連でOpenGLを触らないといけなくなったのでメモがわりに。
学生時代、GLUT+GLSLで水蒸気爆発シミュとかやった記憶がありましたが、今回もGLUTに頼ろうとしたものの、Mac OS X(Marvericks)/Linux/Windows 7 or 8.1/iOS*1 で同じような動作をしてほしかったこともあり、となると…OpenGL 3以上が必要な感じになり、どうしたものかと。

ライブラリ導入

  • GLFW3

GLFW - An OpenGL library
GLFW3では、先ほどの条件を満たし、かつ複数のウィンドウをハンドルしたりスレッドを扱う事が出来るので今回の用途に最適と思われました。そのかわりGLUTで便利なあのtorusとかteapotをひょこっと出す、みたいな芸当はできませんけれども。
というわけでさっそく、Windows 8.1 32bitのタブレット(Dell Venue 8 Pro)に導入してみました。

Visual Studio Express
GLFW3.02を導入したのですが、コンパイル済みdllを使うとなるとVS2012以降が必要になるようなので、VS Express 2013をインストール(要Windows liveアカウント)

  • GLFW3

GLFW - Download
Source Packageと32-bit Windows binariesの両方をダウンロードします。
● 32-bit Windows binaries
[lib-msvc120]フォルダの中の
glfw3.dll -> C:¥Windows¥System32¥
glfw3.lib, glfw3dll.lib -> C:¥Program Files¥Windows Kits¥8.1¥Lib¥winv6.3¥um¥x86
にコピー
● Source Package
[include]フォルダの
[GLFW] -> C:¥Program Files¥Windows Kits¥8.1¥Include¥um¥
にフォルダごとコピー

立方体を作って回転させる

GLUTでの作例はたくさんあるのでそれをGLFWのドキュメントを参考に、GLFWで作り直す方法でやってみます。
GLUTによる「手抜き」OpenGL入門
GLUTとOpenGLで立方体を回転させて表示するプログラム | GLUTでOpenGL 3Dプログラミング


VS2013 VC++WIn32プロジェクトを新規作成
上記ソースコードをGLFW3向けに書き換え

#define GLFW_INCLUDE_GLU
#include <stdio.h>
#include <GLFW/glfw3.h>
#include <Windows.h>

#pragma comment(lib, "GLFW3.lib")
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")


void reshape(int width, int height)
{

	static GLfloat lightPosition[4] = { 0.25f, 1.0f, 0.25f, 0.0f };
	static GLfloat lightDiffuse[3] = { 1.0f, 1.0f, 1.0f };
	static GLfloat lightAmbient[3] = { 0.25f, 0.25f, 0.25f };
	static GLfloat lightSpecular[3] = { 1.0f, 1.0f, 1.0f };



	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);

	glShadeModel(GL_SMOOTH);
	glViewport(0, 0, width, height);



	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0, (double)width / (double)height, 0.1, 100.0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	gluLookAt(0.5, 1.5, 2.5, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

	glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse);
	glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient);
	glLightfv(GL_LIGHT0, GL_SPECULAR, lightSpecular);

}





static GLfloat vertices[][3] =
{
	{ -0.5f, -0.5f, 0.5f },
	{ 0.5f, -0.5f, 0.5f },
	{ 0.5f, 0.5f, 0.5f },
	{ -0.5f, 0.5f, 0.5f },
	{ 0.5f, -0.5f, -0.5f },
	{ -0.5f, -0.5f, -0.5f },
	{ -0.5f, 0.5f, -0.5f },
	{ 0.5f, 0.5f, -0.5f }
};

static GLfloat normals[][3] =
{
	{ 0.0f, 0.0f, 1.0f },
	{ 0.0f, 0.0f, -1.0f },
	{ 1.0f, 0.0f, 0.0f },
	{ -1.0f, 0.0f, 0.0f },
	{ 0.0f, 1.0f, 0.0f },
	{ 0.0f, -1.0f, 0.0f }
};


void display(void)
{

	static GLfloat diffuse[3] = { 1.0f, 0.0f, 0.0f };
	static GLfloat ambient[3] = { 0.25f, 0.25f, 0.25f };
	static GLfloat specular[3] = { 1.0f, 1.0f, 1.0f };
	static GLfloat shininess[1] = { 32.0f };

	static float angle = 0.0f;

	glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse);
	glMaterialfv(GL_FRONT, GL_AMBIENT, ambient);
	glMaterialfv(GL_FRONT, GL_SPECULAR, specular);
	glMaterialfv(GL_FRONT, GL_SHININESS, shininess);

	glEnable(GL_DEPTH_TEST);

	glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	angle += 2.5f;
	if (angle > 360.0f) {
		angle -= 360.0f;
	}

	glPushMatrix();
	glRotatef(angle, 0.0f, 1.0f, 0.0f);

	// 前
	glBegin(GL_QUADS);
		glNormal3fv(normals[0]);
		glVertex3fv(vertices[0]);
		glVertex3fv(vertices[1]);
		glVertex3fv(vertices[2]);
		glVertex3fv(vertices[3]);
	glEnd();

	// 後
	glBegin(GL_QUADS);
		glNormal3fv(normals[1]);
		glVertex3fv(vertices[4]);
		glVertex3fv(vertices[5]);
		glVertex3fv(vertices[6]);
		glVertex3fv(vertices[7]);
	glEnd();

	// 右
	glBegin(GL_QUADS);
		glNormal3fv(normals[2]);
		glVertex3fv(vertices[1]);
		glVertex3fv(vertices[4]);
		glVertex3fv(vertices[7]);
		glVertex3fv(vertices[2]);
	glEnd();

	// 左
	glBegin(GL_QUADS);
		glNormal3fv(normals[3]);
		glVertex3fv(vertices[5]);
		glVertex3fv(vertices[0]);
		glVertex3fv(vertices[3]);
		glVertex3fv(vertices[6]);
	glEnd();

	// 上
	glBegin(GL_QUADS);
		glNormal3fv(normals[4]);
		glVertex3fv(vertices[3]);
		glVertex3fv(vertices[2]);
		glVertex3fv(vertices[7]);
		glVertex3fv(vertices[6]);
	glEnd();

	// 下
	glBegin(GL_QUADS);
		glNormal3fv(normals[5]);
		glVertex3fv(vertices[1]);
		glVertex3fv(vertices[0]);
		glVertex3fv(vertices[5]);
		glVertex3fv(vertices[4]);
	glEnd();

	glPopMatrix();
	Sleep(10);

}
void idle()
{

	Sleep(10);
}

void error_callback(int eror, const char* description)
{
	fputs(description, stderr);
}

static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods){
	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
		glfwSetWindowShouldClose(window, GL_TRUE);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nShowCmd)
{
	int width, height;
	// コールバックの設定
	glfwSetErrorCallback(error_callback);

	// GLFWを初期化
	if (!glfwInit())
		exit(EXIT_FAILURE);
	

	GLFWwindow* window = glfwCreateWindow(400, 400, "GL Sample", NULL, NULL);
	if (!window)
	{
		glfwTerminate();
		exit(EXIT_FAILURE);
	}

	glfwMakeContextCurrent(window);
	glfwSetKeyCallback(window, key_callback);


	// メインループ 
	while (!glfwWindowShouldClose(window))
	{
		glfwGetFramebufferSize(window, &width, &height);
		reshape(width, height);
		idle();
		
		display();
		glfwSwapBuffers(window);

		// イベントのポーリング
		glfwPollEvents();
	}

	// Windowの削除
	glfwDestroyWindow(window);
	// GLFWの終了処理
	glfwTerminate();
	exit(EXIT_SUCCESS);
}

実行すると、こんな感じに立方体が回る。
f:id:Lynx-EyED:20140611224226p:plain
◆参考
OpenGLで物質の質感を定義するメモ(glMaterialfv)/ 日々、思ウコト(遠藤理平)


*1:iOSOpenGL ES

コーディング不要のSigmaDSPを試食する

Analog DevicesのドメインスペシフィックDSPの試食

去年末に偶然発見したSigmaDSP
SigmaDSP プロセッサ | DSP&MCU | アナログ・デバイセズ

SigmaDSPにはいくつか世代や種類があるようですが、今回目を付けたのがADAU1701。(同一性能&パッケージで温度のみ車載グレードはADAU1401A)

24bitのAD/DAが入ってるだけで凄いのにDSP入りでコーディングが不要。ついでにポテンショメータなどの用途向けに8bitの補助的ADCもついてる。

SigmaDSP自身はプログラム格納フラッシュを持たずI2C/SPI経由でパラメータやブートプログラムをDSPに流し込む仕組みで、外部マイコンやPC等から書き込みが可能。
一部のデバイスは外部I2C EEPROMからブートすることも可能。最新のADAU1452はSPI EEPROMも使えるみたいですね。


擬似差動入力や、低消費電力、低ノイズ向けデバイスもありますが、すべての機能が搭載されているわけではないのでアプリケーションに応じて選択するのが良いと思います。まず手始めに一通りの機能がそろってるADAU1701/1401Aをつかってその後、アプリケーションに応じてデバイスを選択すると良いと思います。たとえば低ノイズが要求される場面でADAU1781をAD/DA+デジタルフィルタ代わりに使ってADAU1445をI2SでつなげてGPIO拡張や追加の演算をさせると、低ノイズで高速な制御系が要求される場面で役立つのではないかなと思います。


ただ、外部にSDカードをつなげたりUARTでprintfしたりといった「汎用マイコン」としての機能はまったくないので、結局外部にコプロセッサとしてのI2C/SPI通信が可能なマイコンが必要となるかも。*1

GUI開発環境から外部マイコン用にヘッダファイルも生成してくれるので、それをつかってArduinoとかmbedからSigmaDSPをコンフィグするのもいいかもしれない。

また、SigmaDSPのAD変換結果をリードしたり、DA変換器にデータを流し込んだりするのにI2SかTDMインターフェースを持ってるマイコンがあると便利なはず。

最近流行りのドメインスペシフィック開発(特定用途向け限定開発)の流れを汲んでいて、プロパティドリブンとなっている。
たとえば、1kHzの正弦波を出したければ、Toneのアイコンを配置して1000と入力するだけで完成。(Toneのプロパティは周波数)

開発環境をそろえる

  • 開発はSigmaStudio。現在はXPとVistaと7のみ対応しているらしい*2。ダウンロードはフリー。
  • デバッガはUSBi。USBiの実態はUSB to I2Cマスタのドングルなので、SigmaStudioが生成したhexをSigmaDSPに流し込める自前のI2C環境があるなら、必須ではない。

でもUSBiがあると、GUIで制御パラメータを変更したときにリアルタイムに結果を確認できるといった恩恵を享受できるので、あるに越したことはないです。(Digikeyで1万円とちょっと)
海外だとArduinoとVSでSigmaDSP書き込み環境を構築している作例も見ることができる。このソフトはダウンロードできるので興味のあるArduino使いの方はDLして試してみてもいいと思います。(紹介ビデオのリンクはこちらA Universal Through-Hole Audio DSP Board Based on the ADAU1701 [130232-I] | Elektor Labs)
なお、USBiはこんな外観。小さい。
f:id:Lynx-EyED:20140412144618j:plain
2x5ピンのフラットケーブルもついてくる。なおこの基板のメインはCypress FX2LP (CY7C68053)で回路図は公開されている

  • ターゲット

開発ボードもあるんですが、部品が山盛り載ってて自分の試したいアプリケーションで使うには大改造が必要な感じだったので最初はバラックで組むことにしました。うまくいったらPSoC4+ADAU1701基板起こしますかね。
ADAU1701をサンプルでADIからゲット。

  • データシートなど資料

ADIのチップは頭から足の先まで末恐ろしい挙動をする悪魔のデバイスなのでマニュアルにのっているデータだけではなく各方面の資料を調べて総合的に判断した方が良さげです。
ADAU1701で調べたのは以下の資料

試食用ハードを用意

大体使い方がわかってきたところで、バラック組みをしてみました。ほぼADAU1701データシートのp.49『SELF-BOOT MODE』の通りに組み上げましたが、公式評価ボードを参考にし、DSPのWPピンを10kでプルアップ、I2C EEPROMのWPをGNDに落としています。(こうしないとEEPROMからブートしない)
f:id:Lynx-EyED:20140408145418j:plain
ぐちゃっ

ソフト

とりあえず、DSPで正弦波を作ってDACから出力しようと思います。
2014年4月8日現在、SigmaStudio 3.9が最新版です。Beta 3.10.3もありますが、自分の環境だとUSBiが時々見えなくなる現象が起きたので使用を控えました。

(メニュー)File -> New Project

を選択します。Tree ToolBoxからUSBi, ADAU1701, E2Promを選び、ひとつずつ隣のHardware Configurationタブにドラッグし、下図のように結線します。
f:id:Lynx-EyED:20140408213651p:plain
次にHardware ConfigurationタブからSchematicタブに移動します。

ここでも同様にTree ToolBoxから機能パーツを選択します。
(IC 1)ADAU1701のツリーから以下のパーツを探して、Schematicタブにドラッグしてきます。

  • Basic DSP -> Logic -> Invert -> Signal Invert
  • Basic DSP -> Arithmetic Operations -> Signal Add
  • Sources -> DC -> DC Input Entry
  • Sources -> Oscillators -> Sine tone

あと、DACを2つ使うので、

  • IO -> Output -> Output (を2つドラッグしてくる)

加えて信号分岐用にTree ToolBoxの上層のSchematic Design -> T Connection をドラッグしてくる。
下図のようになるはず。
f:id:Lynx-EyED:20140408220100p:plain

これを以下の図のように配線する。
f:id:Lynx-EyED:20140408220350p:plain
Tone1には100(Hz)、DCには0、Output1,2はそれぞれDAC0,DAC1をプルダウンから選択する。inv1にチェックを入れるのを忘れずに。

これで完成したので、リンクとコンパイルを行う。メニュー下のアイコン一覧から、Link Projectアイコンをクリックする。
f:id:Lynx-EyED:20140408220936p:plain
ポップアップウィンドウでエラーがないことを確認したら、このウィンドウを閉じる。万が一エラーが出たら未結線のコネクションがないかもう一度確認する。

次に隣のアイコンのLink Compile Connectをクリックする。
f:id:Lynx-EyED:20140408221252p:plain


もしこのときUSBiが接続されていない旨のエラーが出た場合(下図のエラー)
f:id:Lynx-EyED:20140412150020p:plain
USBiを使ってない場合はそのままOKを押して次へ。
もし使っている場合はUSBiの接続や、ドライバがこけていないか確認する。


f:id:Lynx-EyED:20140408221701p:plain
(USBiユーザのみ)次にそのまた隣のアイコン、Link Compile Downloadをクリックする。こうすると、ボードとの接続が開始され、Schematicタブでプロパティ変更を行うと瞬時に結果が反映される。DAC0,DAC1はADAU1701の46,45番ピンにアサインされているので、オシロのプローブを当たると下図のようになるはず。DAC1はSignal Invertを挿入しているのでDAC0の信号を反転した波形が出力されているはず。
f:id:Lynx-EyED:20140408222008p:plain

なお、下図のようにバイアスを与えてみる(DCのプロパティに1を代入)と
f:id:Lynx-EyED:20140408222444p:plain
半波整流回路を通した後のような波形が得られる。
f:id:Lynx-EyED:20140408222526p:plain

※ EEPROMに書き込みたい場合は以上の手順を踏んだ後、Hardware Configurationタブに戻り、ADAU1701のアイコン上で右クリック→ Write Latest Compilation to E2PROMを選択する。これで、SigmaDSPが起動後、SELFBOOT端子がHighになっていればI2C EEPROMからパラメータをロードしてDSPを起動することができる。
f:id:Lynx-EyED:20140412150956p:plain

結論

すごいこれ。ほかにも多彩なフィルタやヒルベルト変換、サンプルホールドなど、必要と思われる以上の機能が詰まっていて、遊び甲斐がありそうです。うひょ。

おしまい。


*1:ADAU1452以外のSelf-Boot機能を持っているSigmaDSPならI2Cを一貫して使うほうが便利。SPIを選択するとDSPはSPIスレーブとしてのみ動作するのでSelf-Bootできない

*2:公式にはサポートされてないみたいだけど、自分の環境ではWindows8.1 32bitで動作確認できている