マイクを多数並べたマイクロフォンアレイ用のデータ収集システムを作製しています。このシステムのハードウェアは,以下の3つの要素で構成されています。
- FPGA
- USBインタフェースFT232H
- PC
1ビット出力のMEMSマイクをFPGAの入力ピンに接続し,これをUSBインタフェースICのFT232Hを介してPCに転送しています。
- FPGA内部の回路はVHDLコードARRAY_MIC.vhdで記述した
このハードウェアはFT232Hの容量1kバイトのFIFOにバイト単位でデータを書き込む。 - PC側のC++コードはFT232Hからのデータ受信と保存を行う
動作確認のため指定したマイクロフォンの1ビットデータをモニタに表示する。バイト単位で受信したデータはそのままテキストデータとして保存する。 - 1ビットデータからマルチビットデータへの変換はMATLABコードで実行
保存されたデータからマルチビットの波形への変換はMATLABコードで実行している。
本稿では,C++コードについて説明します。
コードの概略の構成
C++コードは以下のような処理を実行しています。
VHDLコード全体は,概略,以下のような構成になっています。
ヘッダファイルの指定,
FT232Hを使う準備
パラメータの設定
データ読み込み
データの保存
ReadFromMEMS_Mic.cppの構成
includeするヘッダファイルを指定。
FTDI社から提供されているftd2xx.hを指定する点に注意。
マイクロフォンの個数や保存するデータの長さなどのパラメータを設定。
データを読み込む。
データをテキストファイルとして保存。動作確認用にモニタ画面に実際に読み込まれたバイト数を表示する。
それでは,ソースコードの上から順番に説明していきます。
ヘッダファイルの指定,デバイスを使う準備
ヘッダファイルの指定
#include <windows.h>
#include <stdio.h>
#include "ftd2xx.h" //ftd2xxドライバを使うためのヘッダファイル
3行目の#include “ftd2xx.h”で,コメントにもあるように,FT232Hのドライバを使うためにインクルードするヘッダファイルを指示します。(ftd2xxドライバのインストールについては,別の投稿でまとめる予定です。)
FT232Hを使う準備
8行目付近から40行目付近まででFT232Hを使う準備をしています。順番に説明していきます。
// FT232Hを使う準備 ///////////////////////////////////////////////
FT_HANDLE fthandle1; // FT232Hデバイスのハンドル変数
FT_STATUS status; // ドライバ関数実行時のステータス変数
// FT232H基板上のFT232HデバイスのIDを設定
// 対応するデバイスに応じてコメントを外す
//PVOID id = (PVOID)"FT63ZE1L"; // 田村のシステム
//PVOID id = (PVOID)"FT6MVP8N"; // 工藤君のシステム
PVOID id = (PVOID)"FT723GDQ"; // 田村のシステム 小林君用
- FT_HANDLE fthandle1; // FT232Hデバイスのハンドル変数
FT_STATUS status; // ドライバ関数実行時のステータス変数
変数型FT_HANDLEとFT_STATUSは,ft2xx.hの中で定義されている。
FT_HANDLE型のfthandle1は,FT232Hデバイスを指定するための“識別番号”のような変数。
FT_STATUS型のstatusは,ft2xxドライバが提供する関数を実行したときに,実行の失敗や成功などの“状態”を返すための変数。 - PVOID id = (PVOID)”FT723GDQ”; // 田村のシステム 小林君用
PVOID型は,個々のFT232Hチップに固有の識別子を表すための変数型である。この識別子はFT_ProgというプログラムでUSBバスに接続したチップから読み取ることができる。ここでは”FT723GDQ”という識別子を読み取っていたので,それをPVOID型に型変換してidという変数に代入している。
コメントアウトされている,
status = FT_Open(0, &fthandle1);
は,FT232Hチップ固有のIDを指定しないでデバイスをオープンする関数です。しかし,PCに複数のUSBインタフェースを接続して動作させる際に,エラーが発生することがあったので,チップのIDを指定してオープンする関数,
status = FT_OpenEx(id, FT_OPEN_BY_SERIAL_NUMBER,&fthandle1);
を使うようにしました。変数idによって表されるIDは長さ8の文字列です。FTDI社が提供するアプリケーションソフトFT_progを使って調べました。(おそらく,ft2xxライブラリの中にある関数でも調べることができるはずですが,ここではFT_progで調べた値を使っています。)
// デバイスをオープンしハンドルfthandle1を取得
// ハンドルというのは,プログラム内でのデバイスのアドレスみたいなもの
status = FT_OpenEx(id, FT_OPEN_BY_SERIAL_NUMBER, &fthandle1);
// デバイス固有のIDを使わない場合は次のコマンドでOK
//status = FT_Open(0, &fthandle1) ;// 変数fthandleにハンドルの値が入る
// ハンドル取得に失敗した場合は,プログラム終了
if (status != FT_OK)
{
printf("open device status not ok %d\n", status);
return 0;
}
- status = FT_OpenEx(id, FT_OPEN_BY_SERIAL_NUMBER, &fthandle1);
FT_OpenEx関数を呼び出してfthandle1というハンドル型変数の値を取得している。
id:先に取得したPVOID変数
FT_OPEN_BY_SERIAL_NUMBER:シリアルナンバーであるidを使ってオープンするという指示
&fthandle1:変数fthandle1にハンドルの値を書き込ませるため,&fthanfle1と“&”を付けて,fthandle1へのポインタを引数としている。
関数の戻り値のstatusには,関数呼び出しが正常に実行されたかどうかなどの情報が書き込まれる。 - if (status != FT_OK)
・・・
関数呼び出しが正常に実行された場合は,statusの値はFT_OKになっている。そうでない場合は,何かの原因でデバイスを認識できなかったということなので,プログラム実行を終了しする。なお,他のFT232H関連の関数呼び出しでは,FT_OKでない場合のプログラム終了のコードを省いている。コードの安全性の点では問題あり。
// readとwriteのタイムアウト時間をmsec単位で設定
status = FT_SetTimeouts(fthandle1, 10000, 500);
if (status != FT_OK)
printf("timeout device status not ok %d\n", status);
- status = FT_SetTimeouts(fthandle1, 10000, 500);
タイムアウトを設定している。なんらかの原因でFT232Hの動作が遅れた場合に,設定した時間が経過したら実行終了を待たずに関数から戻るようにする。タイムアウトの時間はms単位で指定する。この場合,受信と送信のタイムアウト時間をそれぞれ10000msと500msに設定している。この設定値が適切かどうかはまだ不明。
// デバイスのモードを設定(入力,Synchronous FIFOモード)
UCHAR MaskA = 0x00; // Set data bus to inputs
UCHAR modeA = 0x40; // Configure FT232H into 0x40 Sync FIFO Mode
status = FT_SetBitMode(fthandle1, MaskA, modeA);
if (status != FT_OK)
printf("mode A status not ok %d\n", status);
Sleep(500); // 500msだけスリープ
- UCHAR MaskA = 0x00; // Set data bus to inputs
FT232HのデータバスD7~D0は入力か出力かをビット毎に設定できる。そのためのマスクがUCHAR型(ftd2xx.hで定義されていて,実際は8ビット幅の符号なしキャラクタ)のMaskAという変数になる。このC++コードの目的はFT232Hのバスは8ビット全部を入力として使うので,MaskAは16進表記の00,つまり全てのビットを0にしている。 - UCHAR modeA = 0x40; // Configure FT232H into 0x40 Sync FIFO Mode
modeA変数も8ビット幅で,FT232Hの動作モードを設定するために使う。この値を16進の40に指定するとSynchronous FIFOモードに設定される。Synchronous FIFOモードは60MHzのクロックに同期してFIFOデータにデータを読み書きするモードで最大40MB/sの転送速度が実現できる。 - Sleep(500); // 500msだけスリープ
モード設定に時間がかかる可能性があるので,500msのスリープを挿入。
パラメータの設定
// パラメータの設定 ///・・・というコメント行から80行付近までは,データ収集に関する様々なパラメータの設定をしています。
// パラメータの設定 /////////////////////////////////////////////////////
const int N_CH = 8; // VHDLコードの「チャンネル数」
const int N_BYTE = 1; // VHDLコードの「チャンネル当たりのバイト数」
DWORD RxBytes;// 読み出しバイト数を格納する変数
DWORD TxBytes;// 書き込みバイト数を格納する変数
DWORD EventDword;// FT232Hのイベントを表現する変数
setvbuf(stdout, (char*)NULL, _IONBF, 0); //現在,未使用
int num; // 1回に読み込むデータの総バイト数
// 1回の読み込みの総バイト数をUSBパケットの最大に設定
num = 1024*64; // USBパケットの最大長
int loop_n = 4; // 繰り返し数,変更して総データ数を調節
int nTime = num * loop_n/N_CH/N_BYTE;// 総時間サンプル数
// 入力バッファ用配列を宣言
UCHAR* data_in = new UCHAR[num]; // 1回の転送データ用の配列
UCHAR* data_buf = new UCHAR[num*loop_n];// 全データ用の配列
//getchar();
// 受信および送信のFIFOバッファを空にする
status = FT_Purge(fthandle1, FT_PURGE_RX | FT_PURGE_TX);
if (status != FT_OK)
printf("status not ok %d\n", status);
// レイテンシタイマを設定 この値が適切かどうかは不明
status = FT_SetLatencyTimer(fthandle1, 2);
if (status != FT_OK)
printf("status not ok %d\n", status);
// USBパラメータのセット
// 転送サイズは64バイトの整数倍(64~64Kバイト)
status = FT_SetUSBParameters(fthandle1, 0x10000, 0x10000);
// 送受信とも最大値0x10000 = 2^16 = 65536(64K)に設定
if (status != FT_OK)
printf("status not ok %d\n", status);
UCHAR* data_out = new UCHAR[nTime]; //波形の出力配列を確保
int* bytes_read = new int[loop_n];// 読込バイト数の確認用
DWORD data_read, r_data_len ;
読めば理解できると思いますが,少しややこしい部分もあるので,以下に抜粋して解説します。
- const int N_CH = 8; // VHDLコードの「チャンネル数」
const int N_BYTE = 1; // VHDLコードの「チャンネル当たりのバイト数」
N_CHとN_BYTEはFPGAの回路を記述しているVHDLコードの変数に合わせる。 - int num; // 1回に読み込むデータの総バイト数
FT232H用の関数ではUSB転送の1回で読み込むデータが決まっている。numは1回に読み込むデータの総バイト数。 - num = 1024*64; // USBパケットの最大長
FT232H用のライブラリのドキュメントで読むと,1回のデータ転送のバイト数は転送サイズは64バイトの整数倍(64~64Kバイト)となっている。そこでnum は最大値1024*64に設定している。この設定が妥当かどうかは今後検討する必要がある。 - status = FT_SetUSBParameters(fthandle1, 0x10000, 0x10000);
FT_SetUSBParametersによりUSB転送のバイト数を設定する。受信・送信ともに16進の10000つまり1024*64に設定している。(この部分は,固定値ではなくnumを設定するとそれに応じて変更されるように修正すべき。) - int loop_n = 4; // 繰り返し数,変更して総データ数を調節
1回のデータ転送では検出できる音響信号の長さが不十分なので,データ転送をループで繰り返している。その繰り返し回数をloop_nという変数で指定する。この値を変更することで,記録できるデータの長さを調節する。
コメント