lynxeyedの電音鍵盤

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

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で動作確認できている

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

f:id:Lynx-EyED:20140221225951p:plain

プラグインの導入

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にコードを追記しました。
なおディレクトリ階層は前回のプロジェクトの続きに作っているので以下の様に見えています。
f:id:Lynx-EyED:20140221231543p:plain

//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を探します。
f:id:Lynx-EyED:20140221233022p:plain

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で止めます。
f:id:Lynx-EyED:20140221234758p:plain

PLYを押して再生します。音源の再生が終わるまで、リモコンや側面ボリュームボタンを押すとそれに応じてボリュームスライダが変化し、音量レベルを数値で表示します。
f:id:Lynx-EyED:20140221235632p:plain

おしまい。

*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(モズペンタ)と名付けています。意味は不明です。


f:id:Lynx-EyED:20140210113604j:plain

もろもろを実装
f:id:Lynx-EyED:20140212130304j:plain

USBコネクタの近くにある56ピンのSSOPパッケージはFX2LPです。USB-Blasterのフリをする悪い奴です。(ぉ

ixo.de USB JTAG podのコードを、この基板用にピンアサインを変更してFX2LPにプログラムしています。
ixo.de USB JTAG pod

認識成功
f:id:Lynx-EyED:20140215131512p:plain

Lチカ
f:id:Lynx-EyED:20140212173815j:plain

PhoneGap 3.3.0でiOS開発する

PhoneGapとは

HTML5CSSJavaScriptのみでさまざまなスマートフォンプラットフォーム、例えばAndroidiOSWindows Phone7/8、BlackBerryに対応できるフレームワークです。

なんでこんなもん使おうと思ったのか

html5で本来ネイティブアプリケーションでないと操作が難しかった、ファイル、カメラ、GPS、近接センサ、電池残量検出といった機能を実装できる様になってきました。

もし、ブラウザで完結できるようなアプリであれば、わざわざXcodeをつかう開発しなくていいので、ブログやプレゼンでjsのコードだけ貼付けておけば手軽に試してもらえるかな、と思った訳です。

具体的にはマイクで音声信号を広い、デコードした内容に応じて結果をスピーカから音声で返すモデムの実装です。(一時期流行ったAudio Hi-Jackです)

でも、現実はそんなに甘くなく

ブラウザ経由でマイクからの音声を取得するのにgetUserMedia() APIを使いますが、これは2014年1月現在iOS Safariでは対応していないわけです。

というわけで、(Xcodeで開発しなきゃならないのは仕方ないですが)マイクの取得=ネイティブ or それに準ずる環境、それ以外=HTML5 + JSで実行する事になります。このような理由からPhoneGapを使いました。

導入まで

導入方法が書いてある書籍やブログに当たったんですが、3.3.0ではかなり変わっちゃったみたいで少し苦労しました。
以下はそのメモ。
まずPhoneGap本家サイト:
PhoneGap | Home


Xcodeの導入、iOSデバイスのプロビジョニング関連は完了しているものとする。またnode.jsは導入済みのものとする

本体のインストール

$  sudo npm install -g phonegap

作りたいアプリケーションをCompany Identifierとともに指定します。自分の場合はVoiceCoderと言う名前で、Identifierはcom.lynxeyed.voicecoderとしました。

$ phonegap create voicecoder com.lynxeyed.voicecoder "VoiceCoder"

ディレクトリに移動

$ cd voicecoder/

使用するプラグインを導入します。今回はMedia API、Media Capture APIを使うのでこちらを導入しました

$ phonegap plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-media.git

$ phonegap plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture.git

iOS用にビルドします。

$ phonegap build ios

すると、XCode用プロジェクトが、[アプリケーション名]/platform/ios/ 配下に作成されます。
f:id:Lynx-EyED:20140211230404p:plain

とりあえず、このプロジェクトファイルをダブルクリックして、Xcodeを起動します。
実行します。
f:id:Lynx-EyED:20140211230624p:plain

実機を指定している場合はiOSデバイス本体、そうでない場合はシミュレータが立ち上がって
こんな感じになれば動作確認は終了です
f:id:Lynx-EyED:20140211230810p:plain

とりあえずマイクから録音できるか確認する

Audio Hi-Jackネタではマイクが使えないといけないので、ここだけ確認します。
使うのはPhoneGapのMedia APIです。

以下のサイトのコードをほぼそのまま使っています。が、3.3.0ではiOS、AndroidでMedia APIの呼び出すメソッドの差異は無い模様なので修正しました。
ASCII.jp:JavaScriptで作れるiPhone用ボイスレコーダー (1/5)|古籏一浩のJavaScriptラボ
画面はこんな感じ。
f:id:Lynx-EyED:20140215120044p:plain

以下を/www配下のindex.htmlに記述します。iOS以外でも動作するはずですがwebkit系のブラウザ以外ではスタイルが崩れるはずなので各自修正を。

<!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;
               
               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(){
                rec.play();
                document.getElementById("stat").innerHTML = "play...";
            }
            function playStop(){
                rec.stop();
                document.getElementById("stat").innerHTML = "stop playing";
            }
            </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>
    </body>
</html>

エナジーハーベストPLDボードRODSZEPTA進捗状況(その2)

デバイステストが終了

RODSZEPTAにはエナジーハーベスト用電源管理ICにLTC3330を試験的に採用しています。
LTC3588-1と基本的構造は同じですが、バックブーストDCDCとLDOの2電源を内蔵しているところが異なります。また取得できる電流が少し減っているようです。
詳しくは LTC3330 - 環境発電(エナジーハーベスト)バッテリ寿命延長回路を内蔵したナノパワー昇降圧DC/DCコンバータ - Linear Technology
この電源ICの内蔵LDOからMAX Vのコア電源1.8V、その他IO電源をDCDCから取得しようと計画していました。

下の写真は加速劣化試験後のRODSZEPTA
f:id:Lynx-EyED:20140208153626j:plain
また振動発電を主なオルタネイティブ電圧源としてしているので、耐圧が低めの電気二重層コンデンサでも発電支援をしてくれるスーパーキャパシタバランサの特性も測定していました。

結果はほぼデータシート通りでしたので特筆する所は無いのですが、2電源欲しい場合、LTC3588-1に小電力タイプLDOを併用したときと比べてエネルギー損失にあまり差がないにもかかわらず、コストが倍近くかかることや、実装面積比の優位性が無いことなどがあり今回、LTC3330は一旦採用を見送りLTC3588-1を使ってみようと思います。
なお、一次電池によるバックアップはLTC3588-1でも可能です。

というわけで

LTC3588-1を試すのに便利なモジュールを買いました
秋月の環境発電モジュール
圧電環境発電(エナジーハーベスト)電源IC LTC3588−1: 半導体 秋月電子通商 電子部品 ネット通販
f:id:Lynx-EyED:20140208214248j:plain

これに、MAXVのコア電源1.8VはマイクロチップのMCP1801
http://ww1.microchip.com/downloads/en/DeviceDoc/22051c.pdf
を使おうと思います。