アルテラ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
命令から推測がつくかもしれませんが、シリアルデータを右から左に流したり、条件によって複数あるデータ出力先を切り替えたりする「少し気の利いた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を選択しました。
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全点灯/消灯だとつまらないので、もう少し点灯パターンを増やしたのが下記の画面です。
これを実際に動作させてみました。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
ホストCPUはPSoC4です。コンフィグ、I2Sマスタとして動作します。
600mil幅、4層基板です。
でとりあえず、完成。
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次元グラフが便利かなということでいろいろググったらこんなサイトに
OpenGL + GLFWで表示してみる
Z軸の等高面で色が変化するようになっていますが、あまり真面目に作っていません。上記のコードのようにswitch~caseで行うか、HSV→RGB変換を扱う範囲の最大最小値で正規化してやるほうがよいと思います。詳細は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で拡大縮小
カーソルキー上下で回転します
結論
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 2013 for Windows Desktopの導入
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); }
実行すると、こんな感じに立方体が回る。
◆参考
OpenGLで物質の質感を定義するメモ(glMaterialfv)/ 日々、思ウコト(遠藤理平)
コーディング不要の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はこんな外観。小さい。
2x5ピンのフラットケーブルもついてくる。なおこの基板のメインはCypress FX2LP (CY7C68053)で回路図は公開されている。
- ターゲット
開発ボードもあるんですが、部品が山盛り載ってて自分の試したいアプリケーションで使うには大改造が必要な感じだったので最初はバラックで組むことにしました。うまくいったらPSoC4+ADAU1701基板起こしますかね。
ADAU1701をサンプルでADIからゲット。
- データシートなど資料
ADIのチップは頭から足の先まで末恐ろしい挙動をする悪魔のデバイスなのでマニュアルにのっているデータだけではなく各方面の資料を調べて総合的に判断した方が良さげです。
ADAU1701で調べたのは以下の資料
- データシート:http://www.analog.com/static/imported-files/data_sheets/ADAU1701.pdf
- 純正評価ボード:http://www.analog.com/static/imported-files/eval_boards/EVAL-ADAU1701MINIZ.pdf
- SigmaDSPのGPIOの使い方とか:http://www.analog.com/static/imported-files/application_notes/AN-951.pdf
- SigmaStudio Wiki:SigmaStudio and SigmaDSP Documentation [Analog Devices Wiki]
試食用ハードを用意
大体使い方がわかってきたところで、バラック組みをしてみました。ほぼADAU1701データシートのp.49『SELF-BOOT MODE』の通りに組み上げましたが、公式評価ボードを参考にし、DSPのWPピンを10kでプルアップ、I2C EEPROMのWPをGNDに落としています。(こうしないとEEPROMからブートしない)
ぐちゃっ
ソフト
とりあえず、DSPで正弦波を作ってDACから出力しようと思います。
2014年4月8日現在、SigmaStudio 3.9が最新版です。Beta 3.10.3もありますが、自分の環境だとUSBiが時々見えなくなる現象が起きたので使用を控えました。
(メニュー)File -> New Project
を選択します。Tree ToolBoxからUSBi, ADAU1701, E2Promを選び、ひとつずつ隣のHardware Configurationタブにドラッグし、下図のように結線します。
次に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 をドラッグしてくる。
下図のようになるはず。
これを以下の図のように配線する。
Tone1には100(Hz)、DCには0、Output1,2はそれぞれDAC0,DAC1をプルダウンから選択する。inv1にチェックを入れるのを忘れずに。
これで完成したので、リンクとコンパイルを行う。メニュー下のアイコン一覧から、Link Projectアイコンをクリックする。
ポップアップウィンドウでエラーがないことを確認したら、このウィンドウを閉じる。万が一エラーが出たら未結線のコネクションがないかもう一度確認する。
次に隣のアイコンのLink Compile Connectをクリックする。
もしこのときUSBiが接続されていない旨のエラーが出た場合(下図のエラー)
USBiを使ってない場合はそのままOKを押して次へ。
もし使っている場合はUSBiの接続や、ドライバがこけていないか確認する。
(USBiユーザのみ)次にそのまた隣のアイコン、Link Compile Downloadをクリックする。こうすると、ボードとの接続が開始され、Schematicタブでプロパティ変更を行うと瞬時に結果が反映される。DAC0,DAC1はADAU1701の46,45番ピンにアサインされているので、オシロのプローブを当たると下図のようになるはず。DAC1はSignal Invertを挿入しているのでDAC0の信号を反転した波形が出力されているはず。
なお、下図のようにバイアスを与えてみる(DCのプロパティに1を代入)と
半波整流回路を通した後のような波形が得られる。
※ EEPROMに書き込みたい場合は以上の手順を踏んだ後、Hardware Configurationタブに戻り、ADAU1701のアイコン上で右クリック→ Write Latest Compilation to E2PROMを選択する。これで、SigmaDSPが起動後、SELFBOOT端子がHighになっていればI2C EEPROMからパラメータをロードしてDSPを起動することができる。
PhoneGapからiOSデバイスのハードウェアボリュームを制御する
前回の続きになります。
有線でiOSデバイスに信号を送るのに手っ取り早い方法は、音声信号にデコードした情報をマイク入力で送るか、リモコンで情報を送る方法だと思います。
あとは送信された音声データや音量レベルをデコードするアプリを書けば良いのです。
HTML5にはmedia要素にvolumechangeイベント、またWeb Audio APIにもAudioGainNode.gain.valueが存在し、音量の調整・読み込みが出来るのですが、試したところ音源の音量を調整・検出するのみでハードウェアボリュームとは連動しませんでした(って、当たり前か…)
参考:Getting Started with Web Audio API - HTML5 Rocks
なのでPhoneGapでもHTML+JSのみでは無理でObjective-Cでネイティブプラグインコードを書いてやる必要があります。*1
プラグインの導入
iOSネイティブプラグインの作り方:PhoneGap API Documentation
読んでてクッソめんどくさいなとおもって諦めかけてたところこんなものを発見
devgeeks/VolumeSlider · GitHub
コードを見るとMPVolumeViewクラスがラッピングされている事が分かります。
このプラグインを自分のプロジェクトに導入します。ターミナル.appで自分のプロジェクトディレクトリに移動し、
phonegap local plugin add https://github.com/devgeeks/VolumeSlider.git
これで、プラグインを追加した際のxmlの設定など、煩雑な事を全部やってくれます。
あとは適宜必要なコードを追加するだけです。
ボリュームコントロールするコードの追記
VolumeSliderプラグインはスライダを初期化、UIに表示、非表示する機能のみなので、ハードウェアボリュームの値を読む、制御するObj-Cコードを追記します。
- 音量レベルのリード例
float value = [[MPMusicPlayerController applicationMusicPlayer] volume];
valueは0(最小)~1(最大音量)の範囲になります。
- 音量レベルの指定例
[MPMusicPlayerController applicationMusicPlayer].volume = 0.5f;
以上をふまえてプロジェクトの/Plugins の中にVolumeSlider.mにコードを追記しました。
なおディレクトリ階層は前回のプロジェクトの続きに作っているので以下の様に見えています。
//VolumeSlider.mに追記 - (void)getVolumeSlider:(CDVInvokedUrlCommand *)command { NSArray* arguments = [command arguments]; self.callbackId = command.callbackId; NSString *resultType = [arguments objectAtIndex:0]; float value = [[MPMusicPlayerController applicationMusicPlayer] volume]; // return value for example CDVPluginResult *result; if ( [resultType isEqualToString:@"level"] ) { result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:value]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } else { [MPMusicPlayerController applicationMusicPlayer].volume = 0.5f; result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Changed!"]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } }
//VolumeSlider.hに追記 - (void)getVolumeSlider:(CDVInvokedUrlCommand *)command;
次に上記ネイティブ関数をコールするexec()関数をJSで記述します。exec()関数は以下の文法になります
exec( resultHandler, errorHandler, native_class, native_function, [resultType]);
PhoneGapは上記の関数によってネイティブコードをコールします。
exec()関数は、resultHandler、errorHandler、呼び出すネイティブクラス(native_class)、ネイティブ関数への参照(native_function)、ネイティブコードに渡されるパラメーターの配列([resultType])をネイティブ関数へ引き渡します。
先ほど書いたObj-Cのコード(VolumeSlider.m / .h)はexec関数からコールされる関数です。今回のコードではJSからネイティブへ"level"という文字列とともにコールされた場合はresultHandlerにボリュームレベルを戻り値としてコールバックしています。
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:value]; // valueには音量レベルが代入されている [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
また、それ以外の文字列ならば、ボリュームレベルを0.5にして"Changed!"という文字列を戻り値としてerrorHandlerをコールバックしています。
[MPMusicPlayerController applicationMusicPlayer].volume = 0.5f; result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Changed!"]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
- JSの修正
/www配下のVolumeSlider.jsを探します。
VolumeSlider.jsに以下を追記します。
module.exports = { //中略 , // <-前の関数のあとにカンマを付けるのを忘れずに getVolumeSlider : function (success,fail,resultType) { return exec(success, fail, "VolumeSlider","getVolumeSlider",[resultType]); } };
次に、index.htmlのscriptにハンドラを記述します
</script> var volumeSlider = null; var volLevel = null; // 中略 volumeSlider = window.plugins.volumeSlider; // 中略 function getVolume() { volumeSlider.getVolumeSlider(VSLevelHandler, VSControlHandler, "level" ); return volLevel; } function resetVolume() { volumeSlider.getVolumeSlider(VSLevelHandler, VSControlHandler, "reset" ); } function VSLevelHandler (result) { volLevel = result; } function VSControlHandler (error) { document.getElementById("res").innerHTML = "result=" + error; } </script>
index.html全文
そして前回の記事で作ったプロジェクトに追記したindex.html全文です
<!DOCTYPE html> <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"> <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" src="phonegap.js"></script> <script type="text/javascript" charset="utf-8"> var rec = null; var src = null; var volumeSlider = null; var volLevel = null; var timerID = null; document.addEventListener("deviceready", init(), false); function init(){ src = "test.wav"; } function startRec(){ rec = new Media(src, // success callback function() { console.log("Audio Success"); }, // error callback function(err) { console.log("Audio Error: "+ err.code); }); // Record audio rec.startRecord(); document.getElementById("stat").innerHTML = "recording..."; } function stopRec(){ rec.stopRecord(); document.getElementById("stat").innerHTML = "stop recording"; } function playRec(){ volumeSlider = window.plugins.volumeSlider; volumeSlider.createVolumeSlider(10,350,300,30); volumeSlider.showVolumeSlider(); rec.play(); document.getElementById("stat").innerHTML = "play..."; resetVolume(); // 音量は0.5fにリセット timerID = setInterval(intervalGetVolume,300); } function intervalGetVolume(){ getVolume(); document.getElementById("res").innerHTML = "vol=" + volLevel; } function playStop(){ rec.stop(); clearInterval(timerID); document.getElementById("stat").innerHTML = "stop playing"; } function getVolume() { volumeSlider.getVolumeSlider(VSLevelHandler, VSControlHandler, "level" ); return volLevel; } function resetVolume() { volumeSlider.getVolumeSlider(VSLevelHandler, VSControlHandler, "reset" ); } function VSLevelHandler (result) { volLevel = result; } function VSControlHandler (error) { document.getElementById("res").innerHTML = "result=" + error; } </script> </head> <body> <h1>Using Media APIs</h1> <div> <button class="btn" onclick="startRec()">REC</button> <button class="btn" onclick="stopRec()">STOP REC</button> <button class="btn" onclick="playRec()">PLY</button> <button class="btn" onclick="playStop()">STOP PLAY</button> </div> <div id="stat"></div> <div id="res"></div> </body> </html>
- 使い方
立ち上がったら、とりあえずRECを押して数秒間録音します(10秒くらい)。そのあとSTOP RECで止めます。
PLYを押して再生します。音源の再生が終わるまで、リモコンや側面ボリュームボタンを押すとそれに応じてボリュームスライダが変化し、音量レベルを数値で表示します。
おしまい。
*1:ちなみにPhoneGapにはvolumedown/volumeupイベントがありますが、これはblackberryのみ対応です http://docs.phonegap.com/en/3.3.0/cordova_events_events.md.html#volumeupbutton
エナジーハーベストPLDボードRODSZEPTA進捗状況(その3) :通常電力版MOZPENTAを作った
ハード開発環境の分離
前回の結果をうけて、エナジーハーベスト電源側とCPLD側の開発を別々に進める事にしたわけですが、MAX V 1270LEの144ピンTQFPパッケージ(5M1270ZT144)が手に入ったため、このデバイスで基板を起こしました。
RODSZEPTA(ラセタ)と区別するのにMOZPENTA(モズペンタ)と名付けています。意味は不明です。
もろもろを実装
USBコネクタの近くにある56ピンのSSOPパッケージはFX2LPです。USB-Blasterのフリをする悪い奴です。(ぉ
ixo.de USB JTAG podのコードを、この基板用にピンアサインを変更してFX2LPにプログラムしています。
ixo.de USB JTAG pod
認識成功
Lチカ