INtime上でADA16-32/2(PCI)Fからアナログデータを取得するアプリ

# はじめに
[こちら](https://qiita.com/mine820/items/7db8b0827939baf53f0f)の記事で、INtimeアプリケーションの作成方法を書きましたので、今回はその続きとして、具体的にI/Oボードからデータを取得するところを書きたいと思います。

対象となるI/OボードはPCI入出力ボード「[ADA16-32/2(PCI)F](https://www.contec.com/jp/products-services/daq-control/pc-helper/pci-card/ada16-322(pci)f/)」になります。
アナログ32ch入力／2ch出力、デジタル8ch入出力が可能です。

基本的には、I/Oボードの説明書やテクニカルリファレンスを参考にする必要があるのですが、様々な理由で、結構開発は苦労します。

# 開発
今回の開発は、テンプレートとしてポーリングスレッドを使用します。
（割り込み処理でもいいのですが、ここではそこまでのことはしないので）

追加する処理としては、以下の2つの機能を作成します。

- 初期化処理
    - ボードの初期化
    - 初期設定
- 逐次処理
    - アナログデータの取得

## 初期化処理
プログラムが汚くならないように、初期化処理は別関数にしておきます。
呼び出しはこんな感じです。

```Poll1.c
	// 本スレッドが生存していることを登録
	gInit.htPoll1	= GetRtThreadHandles(THIS_THREAD);

	// スレッドカタログ、ただし失敗は無視
	Catalog(NULL_RTHANDLE, gInit.htPoll1, "TPoll1");

	// PCIボードの初期化 ★★★ここを追加★★★
	InitPCIBoard();

	// 外から止めないと終わらない
	while (!gInit.bShutdown)
	{
```

以下、InitPCIBoard()内の説明をしていきます。

### ベースアドレスの取得
今回はPCIのI/Oボードを使用していますので、ボードとやり取りを行う場合、PCIデバイスに対してレジスタの値を書き換えることになります。
[参考]http://www.mnc.co.jp/english/INtime/faq07-2_kanren/PCIconfigurationregister.htm

それぞれのI/Oボードごとに、ベースアドレスからのマップが決まっていますので、そこに値を設定したり、そこから値を取得したりします。

そのために、まず、ベースアドレスを取得します。

```c
	// 設定されたパラメータを用いて、デバイスを検索します
	dev.wVendorId		= 0x1221;
	dev.wDeviceId		= 0xC103;
	dev.wDeviceIndex	= 0; // Instance;
	if (!PciFindDevice(&dev))
		Fail("PCI device %04x/%04x(%u) not found\n", dev.wVendorId, dev.wDeviceId, dev.wDeviceIndex);
 
 	if (dev.bUnusable)
		Fail("PCI device %04x/%04x(%u) not assigned to this node", dev.wVendorId, dev.wDeviceId, dev.wDeviceIndex);
 
	// 電源状態をD0状態(作業状態)に移行させ、メモリ及びI/Oへのアクセスを可能にします
	PciEnableDevice(&dev);

	// 必要に応じて、バス マスタ機能を設定します
	PciSetMaster(&dev);

	// ベースアドレスの取得
	port = (WORD)dev.dwBaseAddr[0] & 0xfffc;
```

ベンダーID／デバイスIDが各I/Oボードごとに異なりますので注意してください。
また、ベースアドレスにはI/O空間とメモリ空間の2種類がありますので、気を付けてください。

### 初期化
ベースアドレスがわかったら、いよいよボードの初期設定を行います。

### リセット
まず、ボードの設定をリセットします。

```c
	// リセット
	outword( port + 0x80, 0x00000000 );	// イベントコントローラ
	outword( port + 0x08, 0x00000000 );	// アナログ入力
	outword( port + 0x18, 0x00000000 );	// アナログ出力
	outword( port + 0x28, 0x00000000 );	// デジタル入力
	outword( port + 0x38, 0x00000000 );	// デジタル出力
	outword( port + 0x48, 0x00000000 );	// カウンタ
	outword( port + 0x58, 0x00000000 );	// メモリ
```

ポートに値を出力する関数は「outpd()」「outpw()」などがありますが、INtime APIではそれぞれ「outword()」「outhword()」を使用します。

### イベントコントローラ　パルス入出力
次に、イベントコントローラのパルス入出力の設定を行います。
これは、各機能の入出力信号を結び付ける操作になります。

```c
	// イベントコントローラ　パルス入出力設定
	outword( port + 0x80, 0x00000003 );
	outhword( port + 0x84, 0x0000 );	// アナログ入力 格納許可トリガ
	outhword( port + 0x86, 0x0180 );	// 汎用コマンド1
	outword( port + 0x80, 0x00000003 );
	outhword( port + 0x84, 0x0001 );	// アナログ入力 切り替えトリガ
	outhword( port + 0x86, 0x0000 );	// No Select
	outword( port + 0x80, 0x00000003 );
	outhword( port + 0x84, 0x0002 );	// アナログ入力 格納不許可トリガ
	outhword( port + 0x86, 0x0181 );	// 汎用コマンド2
	outword( port + 0x80, 0x00000003 );
	outhword( port + 0x84, 0x0004 );	// アナログ入力 サンプリングクロック
	outhword( port + 0x86, 0x0004 );	// 内部標準タイマ
```

アナログ入力の場合、以下のように処理を行います。
（簡単に説明しています）

1. 内部ゲートをオープン
2. イベントコントローラからの格納を許可
3. データ入力
4. イベントコントローラからの格納を不許可
5. 内部ゲートをクローズ

ここでは、ソフトウェアから格納許可／格納不許可を行うようにしています。
特に、事前入力（ビフォートリガサンプリング）終了時にビフォートリガサンプリング回数終了フラグが立ち、それにより格納不許可トリガが発火するのが通常なのですが、ここでは無限サンプリングを可能とするために汎用コマンド2と接続して、ビフォートリガサンプリング回数終了フラグが立っても格納不許可トリガが発火しないようにしています。

読み込みデータ数が決まっている場合は、ビフォートリガサンプリング回数終了と接続します。

```c
	outhword( port + 0x84, 0x0002 );	// アナログ入力 格納不許可トリガ
	outhword( port + 0x86, 0x0011 );	// ビフォートリガサンプリング回数終了
```


### アナログ入力設定
次にアナログ入力の設定を行います。

```c
	// アナログ入力設定
	outword( port + 0x08, 0x00000003 );	// 内部クロック（ここで設定した間隔で取り込まれる）
	outword( port + 0x0C, interval*1000*1000/25-1 );	// 外部指定（msec -> usec）
	outword( port + 0x08, 0x00000004 );	// チャンネルスキャンクロック（チャンネルの切り替え間隔）
	outword( port + 0x0C, 0x0000004F );	// 2usec
	outword( port + 0x08, 0x00000005 );	// 格納チャンネル数（-1して設定）
	outword( port + 0x0C, ch-1 );		// 外部指定
	outword( port + 0x08, 0x00000006 );	// チャンネルシーケンス（chと順番の組み合わせ）
	for (i=0; i < ch; i++){
		outhword( port + 0x0C, i );	// i番目
		outhword( port + 0x0E, i );	// iチャンネル
	}
	outword( port + 0x08, 0x00000007 );	// 入力方式
	outword( port + 0x0C, 0x00000000 );	// シングルエンド入力
	outword( port + 0x08, 0x00000008 );	// レンジ（ADA16-32/2(PCI)Fだけの設定）
	outword( port + 0x0C, 0x00000001 );	// 0～+10V
```

ここで「interval」は取得したいタイミング間隔（msec）としています。
この間隔で、ボードからいったんバッファにアナログ値が保存されます。
この後逐次処理で取得するときか、ため込んだバッファから取得することになります。

※ここで設定する間隔は、ポーリングスレッド内にある無限ループのRtSleep()で指定している間隔とは関係ありません
　（こっちはため込んだバッファから取得する間隔）

また「ch」は使用するアナログ入力のチャンネル数になります。
ここで指定したチャンネル数だけ、いったんバッファに保存されます。
例えば2ch使用する場合は、1,2,1,2,・・・とバッファの保存されていきます。
当然取得するときも、1,2,1,2,・・・と取得されます。

さらに、レンジはアナログ入力に対して1つしか設定できませんので注意してください。
（チャンネルごとにレンジが違うのは不可）

なお、無限サンプリングではなく、指定回数データを取得する場合は、ビフォートリガサンプリング回数等を指定します。

```c
	outword( port + 0x08, 0x00000009 );	// ビフォートリガサンプリング回数（最初に取り込んでおくデータ数）
	outword( port + 0x0C, 0x000003E7 );	// 1000回
	outword( port + 0x08, 0x0000000A );	// アフタートリガサンプリング回数（その後に取り込んでおくデータ数）
	outword( port + 0x0C, 0x00000000 );	// 1回
	outword( port + 0x08, 0x0000000B );	// リピート回数（上記の繰り返し回数）
	outword( port + 0x0C, 0x00000000 );	// 0回
```

なお、テクニカルリファレンスでは、リピート回数の初期値が「0（無限）」となっていますが間違いです。

余談ですが、ビフォートリガサンプリングからアフタートリガサンプリングに切り替わるタイミングで切り替えトリガが発生します。


### 値の調整

ボード固有の性格があるため、その調整を行います。

```c
	// 調整値の取得
	outword( port + 0x08, 0x00000021 );	// 調整用データ確認
	outhword( port + 0x0C, 0x0202 );		// 0～+10V、調整箇所A
	do{	// EPROMBusyの間、待ち
		ai_sts = inword( port + 0x04 ) & 0x00000200;
	}while( ai_sts != 0x00000000 );
	eepromdata = inhword( port + 0x0E );	// 設定データ読み込み
	digpot_offset = eepromdata & 0x00FF;		// オフセット値
	digpot_gain =( eepromdata >> 8) & 0x00FF;	// ゲイン値（A）
	outword( port + 0x08, 0x00000021 );	// 調整用データ確認
	outhword( port + 0x0C, 0x0212 );		// 0～+10V、調整箇所B（ユニポーラのため）
	do{	// EPROMBusyの間、待ち
		ai_sts = inword( port + 0x04 ) & 0x00000200;
	}while( ai_sts != 0x00000000 );
	eepromdata = inhword( port + 0x0E );	// 設定データ読み込み
	digpot_gain2 =( eepromdata >> 8) & 0x00FF;	// ゲイン値（B）

	// 調整値の設定
	outword( port + 0x08, 0x00000020 );		// キャリブレーション条件設定
	outhword( port + 0x0C, 0x0002 );			// オフセット、0～+10V、調整箇所A
	outhword( port + 0x0E, digpot_offset );	// オフセット値
	do{	// Calibration Busyの間、待ち
		ai_sts = inword( port + 0x04 ) & 0x00000100;
	}while( ai_sts != 0x00000000 );
	outword( port + 0x08, 0x00000020 );		// キャリブレーション条件設定
	outhword( port + 0x0C, 0x0003 );			// ゲイン、0～+10V、調整箇所A
	outhword( port + 0x0E, digpot_gain );		// ゲイン値（A）
	do{	// Calibration Busyの間、待ち
		ai_sts = inword( port + 0x04 ) & 0x00000100;
	}while( ai_sts != 0x00000000 );
	outword( port + 0x08, 0x00000020 );		// キャリブレーション条件設定
	outhword( port + 0x0C, 0x0013 );			// ゲイン、0～+10V、調整箇所B（ユニポーラのため）
	outhword( port + 0x0E, digpot_gain2 );	// ゲイン値（B）
	do{	// Calibration Busyの間、待ち
		ai_sts = inword( port + 0x04 ) & 0x00000100;
	}while( ai_sts != 0x00000000 );
```

基本的には、オフセットとゲイン（ユニポーラレンジの場合は2種類）を調整します。

### メモリ設定

最後にメモリの設定を行います。

```c
	// メモリ設定
	outword( port + 0x58, 0x00000001 ); // アナログ入力転送設定
	outword( port + 0x5C, 0x00000001 ); // I/O転送
	outword( port + 0x58, 0x00000002 ); // アナログ出力転送設定
	outword( port + 0x5C, 0x00000001 ); // I/O転送
```

I/O転送しかないくせに、デフォルト値は未設定なので、いちいち設定する必要があります。
（アナログ出力しない場合も、必ず設定する必要があります）

## 逐次処理

いよいよアナログデータを受け取る処理になります。

ただ、受け取る前に「内部ゲートのオープン」と「アナログ入力格納許可」を行う必要があります。

```c
	// アナログ入力サンプリング開始
	outword( port + 0x08, 0x00000001 );	// アナログ入力 内部ゲートオープン
	do{	// スキャンロックの間、待ち
		ai_sts = inword( port + 0x04 ) & 0x00000010;
	}while( ai_sts == 0x00000010 );
	outword( port + 0x80, 0x00000005 );	// AI 格納許可（汎用コマンド1を使用）
	do{	//  アナログ入力ビフォートリガサンプリング回数終了になるまで、待ち
		ai_flg = inword( port + 0xA4 ) & 0x00000002;
	}while( ai_flg != 0x00000002 );
```

今回は無限サンプリングのため、ビフォートリガサンプリング回数が指定した回数終了するまで待ちます。
ビフォートリガサンプリング回数を指定した場合は、別の待ち方をします。

```c
	do{	// アナログ入力動作終了フラグが立つまで、待ち
		ai_flg = inword( port + 0xA4 ) & 0x80000000;
	}while( ai_flg != 0x80000000 );
```

こちらは、アナログ入力動作が終了したかをチェックしています。

その後、無限ループ内でアナログデータを取得します。

```c
		// アナログ値の入力
		for (i=0; i < ch; i++){
			valData[i] = inhword( port + 0x00);
		}
```

# まとめ
事前必ず設定しなくてはいけないもの、やりたいことを実現するために設定するもの、動作の途中で設定／発火されるフラグ／トリガ／イベントなど、いろいろ気にしなくてはいけないのが面倒です。

なお、INtimeを使用せずWindowsからI/Oボードにアクセスする場合は、もっと便利な[ライブラリ](https://www.contec.com/jp/products-services/daq-control/pc-helper/daq-software/api-aio(wdm)/support/)があるようです。

# おまけ
苦労したおかげで、いろいろ知見が溜まりましたので、同様にお困りに方がいらっしゃいましたらお気軽にご相談ください。
（できればお仕事として．．．（笑））