C#によるリモートアプリ開発

このページではWindowsの.NET Framework上で動作するCosmo-Z操作プログラムの開発方法について説明します。

このページの概要

C/C++によるリモートアプリ開発のページで紹介したリモートアクセス用DLL "libcsz.dll" はCの形式のDLLであるため、C#からは参照できません。そこで、DllImportを使って読み込み、C#の関数として呼び出せるようにラッパするクラス CosmoZWrapper を使います。。

例えば、csz_open関数は、CosmoZWrapperの中で

//! ドライバをオープンし、その他の雑多な初期化処理を行う。
[DllImport("libcsz.dll")]
protected static extern bool csz_open(INTERFACE_PORT ifport, String target);
・・・
public bool open(INTERFACE_PORT ifport, String target)
{
    if(csz_open(ifport, target))
    {
        return true;
    }
    return false;
}

として実装されています。

C#からCosmo-ZにアクセスするにはCosmoZWrapperクラスのインスタンスを作り、そのオブジェクトに対して操作します。

CosmoZWrapper csz = new CosmoZWrapper();
csz.open(INTERFACE_PORT.INTERFACE_TCPIP, "cosmoz");
 ・・・

 

このページでは単純なプログラムを作成して、波形のキャプチャができるようにします。トリガの設定やイベントキャプチャについては後のページで解説します。

 

C#プログラムのための準備

Cosmo-ZのC#ラッパ CosmozWrapperを開発するには、以下のDLLをダウンロードします。

解凍すると以下のようなファイルとフォルダが現れます。

この中で必要なライブラリは、CosmoZWrapper.dllとlibcsz.dllです。libcsz.dllがTCP/IPを通じたリモート接続を行い、CosmoZWrapper.dllがそれをC#の形式でラッパします。

Visual Studioのプロジェクトとしては、対象プラットフォームはx64にしてください。Any CPUだとDLLを呼び出せず実行時にエラーとなります。

 

最初のプログラム

以下のプログラムを作成し、Program.csとして保存します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CosmoZ
{
    internal class Program
    {
        static void Main(string[] args)
        {
            if(args.Length < 1)
            {
                Console.WriteLine("Usage:csztest.exe hostname");
                return;
            }
            CosmoZWrapper csz = new CosmoZWrapper();
            if(!csz.open(INTERFACE_PORT.INTERFACE_TCPIP, args[0]))
            {
                Console.WriteLine($"Can not connect to {args[0]}");
                return;
            }
            Console.WriteLine($"FPGA Version={csz.fpga_version():x8}");
            Console.WriteLine($"Currnet temperature={csz.xadc_tempe()}");
            csz.close();
            Console.WriteLine("Hit any key");
            Console.ReadKey();
        }
    }
}

 

関数の説明

CosmoZWrapperクラスのメソッドは、Cosmo-Z APIの関数の先頭のcsz_を取り去ったものとなります。ラッパのオブジェクト名をcszにすると、csz_openがcsz.openになるなど、APIとほぼ同じ名前でアクセスできるようになるのでわかりやすくなります。

  • open()・・・Cosmo-Zに接続するための関数です。リモートアプリケーションを作る場合はINTERFACE_TCPIPを指定します。第二引数にはホスト名またはIPアドレスを入れます。
  • fpga_version()・・・FPGAに埋め込まれたバージョン番号を読みだします。バージョン番号は16進数8桁で、最初の6桁はFPGAが作られた年月日になっていて、最後の2桁はその日の中の改版番号です。
  • xadc_tempe()・・・FPGA内の内蔵ADCで測ったFPGAの温度を返します。

ビルド

Visual Studioの統合環境からビルドを行ってください

実行結果

csztest.exeというファイルが出来上がるので実行します。引数にはIPアドレスまたはCosmo-Zのホスト名(cosmozまたはcszmini)を指定します。
FPGAに埋め込まれたバージョン番号と現在のFPGA温度が表示されます。

C:\Users\user\Desktop\csharp\CosmoZ-CSharp>csztest.exe cszmini
Connect to 192.168.2.6
FPGA Version=23070908
Currnet temperature=61.8900207519532
Hit any key

 

ADコンバータの値を読む

次にADコンバータの値を単純に読みだして表示するプログラムを作成します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CosmoZ
{
    internal class Program
    {
        static void Main(string[] args)
        {
            if(args.Length < 1)
            {
                Console.WriteLine("Usage:csztest.exe hostname");
                return;
            }
            CosmoZWrapper csz = new CosmoZWrapper();
            if(!csz.open(INTERFACE_PORT.INTERFACE_TCPIP, args[0]))
            {
                Console.WriteLine($"Can not connect to {args[0]}");
                return;
            }

            for (int ch = 0; ch < 4; ch++)
            {
                Console.Write($"CH{ch + 1}\t");
            }
            Console.WriteLine();
            for (int ch = 0; ch < 4; ch++)
            {
                Console.Write($"{csz.adc_val(ch + 1)}\t");
            }
            Console.WriteLine();
            csz.close();
            Console.WriteLine("Hit any key");
            Console.ReadKey();
        }
    }
}

 

このプログラムではcsz_adc_val(ch)という関数を使用しています。この関数は指定されたチャネルのADCの値を読むことができます。

実行結果は次のとおりです。

C:\Users\user\Desktop\csharp\CosmoZ-CSharp>csztest.exe cszmini
Connect to 192.168.2.6
CH1     CH2     CH3     CH4
8603    8715    4922    4638
Hit any key

 

ADC値の換算方法

csz_adc_valで返される値は生のADC値です。ADCが12bitの場合は0~4095、14bitの場合は0~16383、16bitの場合は0~65535となります。これを電圧に換算するには、

計測電圧 = (ADC値/分解能) * (最大電圧 - 最小電圧) + 最小電圧

とします。

また、標準のハードウェアでは最大電圧は通常は0.5、最小電圧は通常は-0.5です。電圧レンジを変更している場合はこの限りではありません。

ADC値と同時に電圧を表示するように改良したプログラムの例を示します。

            UInt32[] adcval = new UInt32[4];
            for (int ch = 0; ch < 4; ch++)
            {
                adcval[ch] = csz.adc_val(ch + 1);
            }
            for (int ch = 0; ch < 4; ch++)
            {
                Console.Write($"{adcval[ch]}\t");
            }
            Console.WriteLine(" [ADC]");
            for (int ch = 0; ch < 4; ch++)
            {
                Console.Write($"{adcval[ch] / 16384.0 - 0.5:f3}\t");
            }
            Console.WriteLine(" [V]");

実行結果は以下のようになります。

C:\Users\user\Desktop\csharp\CosmoZ-CSharp>csztest.exe cszmini
Connect to 192.168.2.6
8601    8713    5353    11669    [ADC]
0.025   0.032   -0.173  0.212    [V]
Hit any key

連続同時サンプリング

csz_adc_val()と、それをラップしたcsz.adc_val()は、関数を呼び出した時点でADCの値をサンプリングしているため、複数チャネルの同時サンプリングはできません。適当なタイミングでサンプリングされるので、サンプリングされた時刻もわかりません。

同時サンプリングを行うにはcsz.capture_execute関数を使用します。データの取り出しにはcsz.capture_datacopy関数を使用します。

以下にサンプルプログラムを示します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace CosmoZ
{
    internal class Program
    {
        static void Main(string[] args)
        {
            if(args.Length < 1)
            {
                Console.WriteLine("Usage:csztest.exe <hostname>");
                return;
            }
            CosmoZWrapper csz = new CosmoZWrapper();
            if(!csz.open(INTERFACE_PORT.INTERFACE_TCPIP, args[0]))
            {
                Console.WriteLine($"Can not connect to {args[0]}");
                return;
            }

            csz.adc_freq(80);
            Thread.Sleep(100); // ADC周波数を変えた場合は30ms以上のWaitが必要

            const int DATASIZE = 10;
            CAPTURE_INFO info;
            info = csz.capture_execute(0xff, DATASIZE, CAPTURE_TRIGGER.CAPTRIG_AUTO, 1);
            if (info.result != CAPTURE_RESULT.CAPRESULT_SUCCESS)
            {
                Console.WriteLine("キャプチャエラー");
                csz.close();
                return;
            }
            Console.WriteLine("time[ns]\tCH1\tCH2\tCH3\tCH4");
            UInt16[] data1 = new UInt16[DATASIZE];
            UInt16[] data2 = new UInt16[DATASIZE];
            UInt16[] data3 = new UInt16[DATASIZE];
            UInt16[] data4 = new UInt16[DATASIZE];
            csz.capture_datacopy(info, 1, data1); // CH1を読む
            csz.capture_datacopy(info, 2, data2); // CH2を読む
            csz.capture_datacopy(info, 3, data3); // CH3を読む
            csz.capture_datacopy(info, 4, data4); // CH4を読む
            for (int i = 0; i < DATASIZE; i++)
            {
                Console.WriteLine($"{i * 12.5:f1}\t{data1[i]}\t{data2[i]}\t{data3[i]}\t{data4[i]}");
            }
            csz.close();
            Console.WriteLine("Hit any key");
            Console.ReadKey();
        }
    }
}

</hostname>

 

ADC周波数の設定

csz.adc_freq(80)はADコンバータのサンプリング周波数を80MHzに設定します。単位はMHzで、設定可能な値は1,2,4,8,10,16,20,25,40,50,80,100,125です。

この関数を呼び出してサンプリング周波数を変更した後、ADCが正しいデータを送ってくるようになるまで100msほど待ってください。

キャプチャ関数の説明

csz.capture_execute()はキャプチャを実行する関数で、定義は

public CAPTURE_INFO capture_execute(UInt32 chmask, Int32 length, CAPTURE_TRIGGER type, Int32 maxseq)

となっています。

第1引数のchmaskは、どのチャネルをサンプリングするかをビットマスクで指定します。例えば0x15にした場合、二進数で00010101なので、CH1、CH3、CH5をキャプチャします。

第2引数のlengthはキャプチャしたい長さです。10,000,000くらいまでの値を指定することが可能ですが、キャプチャしたデータのサイズは (チャネル数×長さ×2) Byte になります。512MByteを超えないようにしてください。

第3引数のCAPTURE_TRIGGER typeには、CAPTURE_TRIGGER.CAPTRIG_AUTO か CAPTURE_TRIGGER.CAPTRIG_NORMAL を指定します。CAPTRIG_AUTOはトリガがなくてもキャプチャを行います。CAPTRIG_NORMALはトリガが入るまでキャプチャの開始を待ちます。

第4引数のmaxseqはシーケンシャルモードで使います。通常は0または1を指定します。(0も1もどちらも同じ意味で、シーケンシャルモードを使わないことを意味します。)

実行結果はCAPTURE_INFO型の構造体で返されます。CAPTURE_INFO構造体には以下に示すような情報が格納されています。

変数名 機能
CAPTURE_RESULT result

キャプチャ実行結果
CAPRESULT_SUCCESS キャプチャは成功した
CAPRESULT_ABORTED ユーザからの指示で中断された
CAPRESULT_ERROR データの転送が間に合わない.
CAPRESULT_ANOTHERRUN  他のキャプチャが動作中
CAPRESULT_TRIGGER_TIMEOUT トリガ待ち状態
CAPRESULT_CORRUPTED 取得したデータが壊れている

UInt32 chmask キャプチャしたチャネル
Int32 chcount キャプチャしたチャネル数
Int32 length キャプチャ長
Int32 iteration 繰り返し回数
Int32 maxseq 最大シーケンス長
CAPTURE_TRIGGER trig

キャプチャトリガの種類
CAPTRIG_AUTO   オシロのAUTOと同じ
CAPTRIG_NORMAL  オシロのNORMALと同じ
CAPTRIG_MESUNIT イベントモードで秒数で指定
CAPTRIG_EVENT イベントモードでイベント数で指定
CAPTRIG_LAST  最後に表示した波形を再取得

Int32 padding1 64bit境界に合わせるためのパディング
UInt64 start_time 開始時刻(Unix時刻)
UInt64 end_time 終了時刻(Unix時刻)
double sampling_rate サンプリングレート
Int32 adc_freq 実行開始時のADC周波数
Int32 adc_div 実行開始時のデシメーション比
Int32 resolution 分解能(bit単位)
UInt32 gps_time 実行開始時のGPS時刻
UInt32 gps_10ns 実行開始時のGPS時刻 10ns単位
UInt32 gps_maxcount 実行開始時のGPS最大カウント
UInt64 start_time48 実行開始時の48bitタイムスタンプ
UInt32 top_addr 格納される物理アドレスの先頭
UInt32 bot_addr 格納される物理アドレスの最後尾
UInt32 last_addr 格納された物理アドレスのlast
Int32 mesp 現在読み出し中のデータのポインタ
Int32 evcount 現在読み出し中のデータの数
Int32 prev_capaddr イベントキャプチャのデータをここまで読んだポインタ
UInt32 start_addr イベントキャプチャのデータが入っている先頭アドレス

RESULTは実行結果で、CAPTURE_RESULT.CAPRESULT_SUCCESSを返した場合はキャプチャは成功です。

この構造体を参照することで、キャプチャ時のADC分解能やサンプリングレート、開始時刻、データ長などを知ることができます。

実行結果

実行結果を示します。最初の列は時刻、2番目の列はCH1、3番目の列はCH2、4番目の列はCH3、5番目の列はCH4のデータとなります。得られたADCの値を電圧に変換するにはADCの分解能の情報が必要ですが、(1 << info.resulution)-1 で取得することができます。

C:\Users\user\Desktop\csharp\CosmoZ-CSharp>csztest.exe cszmini
Connect to 192.168.2.6
time[ns]        CH1     CH2     CH3     CH4
0.0     8600    8712    4797    7410
12.5    8605    8713    4803    7438
25.0    8602    8708    4810    7465
37.5    8600    8713    4816    7498
50.0    8602    8712    4828    7528
62.5    8599    8712    4837    7558
75.0    8601    8710    4841    7590
87.5    8605    8713    4853    7624
100.0   8600    8709    4859    7647
112.5   8600    8712    4865    7679
Hit any key

コマンドを実行する際に、

csztest.exe > result.txt

としてテキストファイルに出力して、それをWindowsのエクスプローラで開いてテキストエディタで開いてコピーペーストして、Excelに貼り付ければ波形をグラフ化できます。