Archives

February 2009 の記事

ミニノートを持ち歩いていて、普段ネット端末やメモ機として使っています。
この場合外部ディスプレイ出力を使うことがまずないのですが、でもごくまれに
必要になることがあります。

小型なノートPC で特殊なコネクタを採用している場合は少々やっかいです。
専用アダプタや専用ケーブル経由で接続しなければならず、
かさばるし、滅多に使わないのに専用品を購入しなければならないし、
必要になったときにすぐ手に入らないかもしれません。

また実際に必要になって購入を考えると結構高いものです。
MobileGear2 の時はモニタ用ケーブルだけで 4000円、利用回数一回きり。
LOOX U50 店頭モデルでは本体同梱、使ったのは 2~3回。
VAIO type P の場合別売りで VGP-DA10 4980円 (LANアダプタ兼用)。

そこで専用品ではなく USB 接続のモニタアダプタを使うことにしました。
これだと機種を変えても使い回せるし、普段はデスクトップ PC につないだりと
他の用途でも使えます。

IO DATA USB-RGB
IO DATA USB-RGB/D

最近 USB で接続できる液晶モニタも話題になっていますが同じ原理のようです。

ぼくらは「USB-RGB」を誤解していたかもしれない (1/4)

USB バスパワーで動作するので外部ディスプレイ出力アダプタとほぼ同じ感覚で使え
そうです。

実際に試したところ、現在の Windows7 beta 1 では正しく動作しませんでした。
一応ソフトウエアのインストールが可能で、絵は出るものの解像度やモード
切り替えなどが出来ません。

VAIO type P にクリーンインストールした Vista の方では正常に動作しました。
ただし VAIO type P の場合あらかじめ Aero を切っておいた方がよいです。
接続時にエラーになりました。
タスクトレイアイコンからのモード切り替えも可能で 640x480 も使えています。

小型プロジェクター ADTEC AD-MP15A との接続も確認。
800x600 でもきちんと表示できました。

純正アダプタによる直接出力と比べると描画速度が多少遅いはずですが思ったより
気になりません。打ち合わせなどで文書や資料を表示するには十分です。


関連エントリ
ADTEC AD-MP15A 小型プロジェクター



マウスの感度を示す単位はミッキーというらしい。
じゃあどうやってミッキーを測るんだろう、ということで簡単に試してみました。

wikipedia マウス

マウスの入力値の取得には DirectInput を使用します。
DirectInput の Buffer モードを使って移動量の変化を取り出します。そのままだと
DInput の取得値にも速度に応じて加速がついてしまうので設定が必要です。

・コントロールパネルのマウスから
  ポインタオプションのタブ→速度のグループで
  「ポインタの精度を高める」のチェックを外す

これで加速補正が無くなります。
「ポインタの速度」は DirectInput の値に影響しません。

マウス付属のドライバやユーティリティは使わず、Windows 標準のドライバのみ
使用しています。

こんな感じでマウスにガイドを付けます。
出来るだけ水平、または垂直に移動できるように。

d2dmouse

カウンタをリセットしてだいたい 1インチ分だけ移動して得られた値を調べます。
精度が高いため手で試してもかなり誤差が入り厳密な値はとれません。
以下数値はかなりいい加減です。

だいたい 800~1000 くらいの範囲の値を返します。
おそらくこれがマウスセンサーのカウント値です。
1インチ移動する間に何回センサーの On/Off をカウントできるのか、どれだけ細かく
動きを読み取れるのか表す単位として cpi (count per inch ) があります。
メーカーによっては dpi (dot per inch) と表現されているものもありますがおそらく
同じものです。

テストしたマウス
(1) BlueTrack
Microsoft Explorer Mini Mouse
1000dpi/8000fps

結果
・800~1025 (8~10.2ミッキー)


(2) Laser
SIGMA APO SLATWRF
800,1600cpi/6600fps

結果
・800cpi モードで 800~900 (8~9ミッキー)
・1600cpi モードで 950~1450 (9.5~14.5ミッキー)


◎速く動かした方が値が小さい

センサーの読み取りエラーが発生しやすくなるためと思われます。速く動かした場合の
耐性を表す目安が、マウススペックに記載されている fps となるようです。


◎精度を切り替え可能な場合

低い cpi モードの方がほぼ期待通り、安定した数値になっています。

高い cpi モードの場合、全体的に数値は増えますがスペック通りの上限には達しま
せんでした。材質の相性や速度の影響を受けやすくなっているためと思われます。
精度は上がるがよりノイズを含んでいる状態といった感じでしょうか。


◎相性

机やマウスパッドの材質とセンサーの相性でかなり変わります。
反応が悪いと半分くらいの値になることも。


カタログスペック通りと想定するなら、800cpi(dpi) のマウスは 8ミッキーでカウント
値からマウスの移動量がわかります。もし正確ならば、マウスを使って長さを測る
こともできるでしょう。

でも実際は机やマウスパッドとの相性と速度の影響が大きく、頻繁に読み取りエラーが
発生していることがわかります。平均ミッキーはこれらの条件によって変わります。
特に速度は重要なパラメータで、cpi だけでなくマウスの性能として fps も併記
されているのはそのためでしょう。

きちんとツール化して調べれば、最もエラーの少ないマウスパッドの組み合わせを
探し出すなど活用できそうです。


参考にさせていただいたページ
その37.Windows標準のポインタ(カーソル)速度の変遷
Windows XP でマウス ポインタの加速を調節する方法

// DirectInput
DWORD	elems= 32;
DIDEVICEOBJECTDATA	data[32];
HRESULT	hr= iDevice->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), data, &elems, 0 );
if( FAILED( hr ) ){
    return;
}
DWORD	ac= elems;
for( DWORD i= 0 ; i< ac ; i++ ){
    switch( data[i].dwOfs ){
    case DIMOFS_X:
	PositionX-= *(int*)(&data[i].dwData);
	break;
    case DIMOFS_Y:
	PositionY-= *(int*)(&data[i].dwData);
	break;
    }
}




せっかくなのでもう少し送受信 API 周りを解説してみます。
前回 列挙して各デバイスのパスがわかったので、あとは Open して実際の通信を行います。
問題は Bluetooth stack によって使用可能な API が異なっていることです。

// デバイスの Open
HANDLE	hdev= CreateFile(
				path,
				GENERIC_READ|GENERIC_WRITE,
				FILE_SHARE_READ|FILE_SHARE_WRITE,
				NULL,
				OPEN_EXISTING,
				FILE_FLAG_OVERLAPPED,
				NULL
			);

if( !HidD_GetPreparsedData( hdev, &Ppd ) ){
	Close();
	return	FALSE;
}

HIDP_CAPS	caps;
if( !HidP_GetCaps( Ppd, &caps ) ){
	Close();
	return	FALSE;
}

// 必要なバッファサイズを調べておく。これを元にキューを作る。
InputByteLength= caps.InputReportByteLength;
OutputByteLength= caps.OutputReportByteLength;


● API テスト

コマンド送信に使う API を選択します。

WindowsVista 標準の bluetooth Stack では HidD_SetOutputReport() を使います。
ところが Toshiba bluetooth Stack / Bluesoleil ではこの命令が使えませんでした。

このあたり、各 stack による違いは下記のエントリでまとめています。
Toshiba bluetooth stack と Bluesoleil 対応

// テストコマンドを送信、直後の ReadFile() で Timeout 判定
if( !HidD_SetOutputReport(
		hDev,
		command,
		cmdlength
			) ){
	return	FALSE;
}

まず最初に HidD_SetOutputReport() を使って何らかのリターンがあるコマンドを
送信してみます。

この API が直接エラーを返す場合は WriteFile() の選択で決定です。ただしエラー
を返してくれるのは Toshiba Stack の場合だけ。Bluesoleil v5 ではエラー無しで
送信も出来ません。

判定の手順

(1) HidD_SetOutputReport() でリターンのあるコマンドを送信してみる。

(2) HidD_SetOutputReport() がエラーを返す場合は WriteFile() を選択で決定。
  Toshiba Bluetooth stack の場合。

(3) エラーが返らない場合は直後に ReadFile() でデバイスの返答を待ちます。

(4) 結果が返ってきたら HidD_SetOutputReport() を選択。Windows stack。

(5) Timeout 判定。一定時間待っても結果が来なければ Bluesoleil。
  送信は WriteFile() を選択する。


●データ送信

HidD_SetOutputReport() には送信するデータを直接渡すことが可能です。
たとえば 4byte なら 4byte のみで構いません。
WriteFile() を使う場合は、必ず OutputReportByteLength 分のバッファを作って
から転送を行います。

char*	bufp= Queue.WriteAlloc();
assert( length <= OutputByteLength );

if( !bufp ){
	// error
	return;
}
memcpy( bufp, command, length );

memset( &WriteOverlap, 0, sizeof(OVERLAPPED) );
WriteOverlap.hEvent= handle;
if( !WriteFileEx(
			hDev,
			bufp,
			OutputByteLength,
			&WriteOverlap,
			_WriteFileCB ) ){
	int	err= GetLastError();
	// error
}

このあたりも、成功するまで何度も試して調べた部分です。
Windows7 など Bluetooth のサポートも強化されるので、おそらく今後は
Windows stack の方が標準になっていくものと考えられます。
そのうちきっと HidD_SetOutputReport() だけ済む時代が来るでしょう。


関連エントリ
複数の HID から情報を読み出すには
Toshiba bluetooth stack と Bluesoleil 対応



以前公開したプログラムに関する質問のメールがほぼ同時に 2通ありました。
どちらも同じ内容でしたので、少々昔のプログラムを引っ張り出してみます。
「接続した複数の HID から情報を読み出すにはどうしたらいいのか」
制作時に参考にしたのはサンプル「\WinDDK\6000\src\hid\hclient\pnp.c」です。

HMODULE hModule= LoadLibrary( "hid.dll" );
HMODULE sModule= LoadLibrary( "setupapi.dll" );
GetProcAddress( hModule, "HidD_GetAttributes" ) )
GetProcAddress( hModule, "HidP_GetCaps" ) )
GetProcAddress( hModule, "HidD_FreePreparsedData" ) )
GetProcAddress( hModule, "HidD_GetPreparsedData" ) )
GetProcAddress( hModule, "HidD_SetOutputReport" ) )
GetProcAddress( hModule, "HidD_GetHidGuid" ) )
GetProcAddress( sModule, "SetupDiGetClassDevsA" ) )
GetProcAddress( sModule, "SetupDiEnumDeviceInterfaces" ) )
GetProcAddress( sModule, "SetupDiGetDeviceInterfaceDetailA" ) )
GetProcAddress( sModule, "SetupDiDestroyDeviceInfoList" ) )

以後列挙して目的のデバイスを探しています。

GUID	hidGuid;
HidD_GetHidGuid( &hidGuid );

HDEVINFO	hdevinfo;
hdevinfo= SetupDiGetClassDevs(
		&hidGuid,
		NULL,
		NULL,
		DIGCF_PRESENT
		|DIGCF_DEVICEINTERFACE
		|DIGCF_ALLCLASSES
	);


SP_DEVICE_INTERFACE_DETAIL_DATA*	funcClassData= NULL;

int	retCode= TRUE;
for( int i= 0 ; i< DEVICELIST_LIMIT ; i++ ){
	SP_DEVICE_INTERFACE_DATA	deviceInfoData;
	memset( &deviceInfoData, 0, sizeof(SP_DEVICE_INTERFACE_DATA) );
	deviceInfoData.cbSize= sizeof( SP_DEVICE_INTERFACE_DATA );
	if( SetupDiEnumDeviceInterfaces(
			hdevinfo,
			0,
			&hidGuid,
			i,
			&deviceInfoData
			) ){
		ULONG	reqLength= 0;
		SetupDiGetDeviceInterfaceDetail(
				hdevinfo,
				&deviceInfoData,
				NULL,
				0,
				&reqLength,
				NULL );

		funcClassData=
			reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA*>(
					dAlloc( reqLength ) );
		if( sizeof(void*) == 4 ){
			funcClassData->cbSize= 5;
		}else{
			funcClassData->cbSize= 
				sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
		}
		if( !funcClassData ){
			retCode= FALSE;
			break;
		}
		if( !SetupDiGetDeviceInterfaceDetail(
				hdevinfo,
				&deviceInfoData,
				funcClassData,
				reqLength,
				&reqLength,
				NULL ) ){
			retCode= FALSE;
			break;
		}
		if( _OpenTestHidDevice( funcClassData->DevicePath,
					VENDOR_ID, PRODUCT_ID ) ){
			// 目的のデバイスを保存しておく
			CheckDeviceOpen<DeviceType>( funcClassData->DevicePath );
		}
		if( funcClassData ){
			dFree( funcClassData );
			funcClassData= NULL;
		}
	}else{
		if( GetLastError() == ERROR_NO_MORE_ITEMS ){
			break;
		}
	}
}

SetupDiDestroyDeviceInfoList( hdevinfo );
if( funcClassData ){
	dFree( funcClassData );
	funcClassData= NULL;
}

VENDOR_ID, PRODUCT_ID はデバイスマネージャーのプロパティから
詳細→ハードウエア ID で調べることが出来ます。
このあたりのコードは力業です。たしか作成時 1つ 1つ試しながらだったので、
あまりきれいじゃないと思いつつ書いていた覚えがあります。

int _OpenTestHidDevice( const char* path, int vid, int pid )
{
	HANDLE	hdev= CreateFile(
					path,
					GENERIC_READ|GENERIC_WRITE,
					FILE_SHARE_READ|FILE_SHARE_WRITE,
					NULL,
					OPEN_EXISTING,
					0,
					NULL
				);
	int	found= FALSE;
	if( hdev == INVALID_HANDLE_VALUE ){
		return	FALSE;
	}

	HIDD_ATTRIBUTES	attr;
	if( HidD_GetAttributes( hdev, &attr ) ){
		if( attr.VendorID == vid && attr.ProductID == pid ){
			found= TRUE;
		}
	}
	CloseHandle( hdev );
	return	found;
}

検索して条件に合うデバイスをリストに保持し、それぞれ個別に通信を行っています。
CheckDeviceOpen() は内部でリストに追加しているだけです。
見つかった最初のデバイスだけ見ているか、それとも全部列挙しているかの違い
だけだと思います。

実際のデータ送受信はこのあとですが、Bluetooth Stack の違いを吸収するために
最初に API のテストや通信タイムアウトの判定などを行っています。
ここでの注意点は、複数のデバイスがあっても API の判定は最初の一度だけで
いいということ。

最初にリリースしたバージョンでは新たなデバイスが接続されるたびに API テストが
走ってしまい、すでにつながってるデバイスの通信も一時中断されてしまう問題が
ありました。このあたりの処理をきれいに組み込めずに少々苦労した覚えがあります。

また定期的に接続されているデバイスを確認して、動的な接続&切断に備えています。
すでにオープンしたデバイスかどうかの区別が必要となりますが、結局保持している
パスの文字列比較というもっとも安易な方法で済ませました。