田舎人の書斎作ったり、試したり、書き留めたり。

田舎人、機器の返事を待つ。

VISAのLAN(ソケット)通信で応答が返らないとき:区切り文字とENDの仕組み(C#・IVI-VISA)

公開

試した環境

言語
C#(.NET Framework 4.8、WPF)
ライブラリ
IVI VISA.NET(Ivi.Visa 8.0)
VISA
Keysight IO Libraries Suite
機器
オムロンのバーコードリーダー、キーエンスの画像判別センサ IV4(どちらもLAN接続)

VISAは、測定器だけでなく、LANでつながるさまざまな機器をPCから操作するのにも便利な仕組みです。ところが、コマンドを送っても応答が返ってこない、というつまずきに長く悩まされました。

原因は、区切り文字(終端文字)の食い違いでした。この記事では、そのときの経緯と、VISAが「受信の終わり」をどう判断しているかを整理し、区切り文字を切り替えながら試せるように作った端末アプリを紹介します。

起きたこと

やりたかったのは、LANでつないだ機器をPCから操作することです。オムロンのバーコードリーダーに読み取りのトリガーをかけたり、キーエンスの画像判別センサ IV4 にトリガーを送って判定結果を受け取ったりする処理です。

ところが、コマンドを送っても応答が返ってきません。送信はできているようなのに、受信で止まってしまいます。

まず、Keysight IO Libraries Suiteに付属するInteractive IOで試しました。送信側の区切りを変えると、機器がコマンドに反応して動くところまでは確認できました。つまり、コマンドは届いています。ところが、肝心の結果を受け取ることができません。受信側の区切りを変える方法も見つからず、何が起きているのかは分かりませんでした。

そこで、同じ機器にTera Termで接続してみたところ、こちらでは送受信ができました。ツールによって結果が変わるなら、機器ではなく通信の設定に原因がある。区切り文字(デリミタ)が怪しい、と気づいたのはここからです。

Interactive IOでは受信の区切りを試せなかったので、自分のコードの側で受信の区切りを合わせて確かめました。

VISAは「受信の終わり」をどう判断するか

VISAの読み取りは、次のどれかが起きたところで終わります。

終わる条件 内容
END(終了通知) 機器が「ここでメッセージ終わり」と知らせる信号。GPIBならEOI、USB(USBTMC)や測定器向けのLAN(VXI-11・HiSLIP)ではプロトコルの中で通知される
終端文字 終端文字の判定を有効にしている場合、指定した1文字(既定は \n)を受け取った時点
バッファが一杯 指定した長さを読み切った時点
タイムアウト どれも起きないまま、決められた時間が過ぎた時点

USBやGPIB、VXI-11・HiSLIPに対応した測定器は、応答の最後でENDを送ってくれます。そのため、終端文字の判定を無効にしておいても、読み取りはきちんと終わります。

ところが、バーコードリーダーや画像判定機のような機器は、測定器向けのプロトコルではなく、素のTCPソケットで通信するものが多くあります。VISAではアドレスの末尾が ::SOCKET になる接続です。ソケットには「メッセージの終わり」を知らせる仕組みがありません。機器は応答の最後に \r などの区切り文字を付けて送ってくるだけなので、VISA側でその区切り文字を終端文字として指定しない限り、受信は終わらずタイムアウトまで待ち続けることになります。

シリアル(ASRL)も同じです。シリアルにもENDの信号がなく、VISAは既定で終端文字(既定値は \n)を受け取ったところを終わりとみなします。機器が \r だけで応答を締めくくると、\n を待ち続けてしまいます。

Tera Termで送受信できたのは、Tera Termが「区切り文字で受信を終える」という考え方をせず、届いた文字をそのまま画面に出すからです。

送信側も同じで、VISAの RawIO.Write は改行を自動で付けません。機器が「コマンドの終わり」として待っている文字(\r なのか \n なのか \r\n なのか)を、こちらで付けて送る必要があります。

区切りを切り替えられる端末アプリを作った

この経験から、送信と受信の区切りをそれぞれ画面で選べる、小さな端末アプリを作りました。同じ機器で区切りだけを変えながら試せれば、次に同じことが起きても早く切り分けられます。

受信の区切りの設定

受信側は、VISAの終端文字の判定を切り替えています。「Auto」と「None」は終端文字を使わず、機器からのENDに任せます。

public void ConfigureRx(VisaDelimiter delimiter)
{
    if (delimiter == VisaDelimiter.Auto || delimiter == VisaDelimiter.None)
    {
        // 終端文字を使わず、機器からの END で読み取りを終える
        _session.TerminationCharacterEnabled = false;
    }
    else
    {
        _session.TerminationCharacterEnabled = true;
        _session.TerminationCharacter = delimiter == VisaDelimiter.CR
            ? (byte)'\r'
            : (byte)'\n'; // LF と CRLF は '\n' で区切る
    }
}

終端文字として指定できるのは1文字だけです。そのため、CRLFの場合は最後の \n で区切り、受け取った文字列の末尾には \r\n が残ります。

名前は「Auto」としていますが、実際には区切りを自動で見分けているわけではなく、ENDに任せているだけです。ENDを送る測定器ならこれで動きますが、ソケット接続やシリアルの機器では、機器に合わせて区切りを選ぶ必要があります。

送信の区切りの付与

送信側は、選んだ区切りを文字列の末尾に付けてから送ります。

private string ApplyTxDelimiter(string s)
{
    switch (TxDelimiter)
    {
        case VisaDelimiter.CR:   return s + "\r";
        case VisaDelimiter.LF:   return s + "\n";
        case VisaDelimiter.CRLF: return s + "\r\n";
        default: return s;
    }
}

区切り文字をログで見えるようにする

区切り文字は画面に表示されないため、食い違っていても気づきにくいものです。そこでログでは、制御文字を置き換えて表示しています。

private static string VisualizeControlChars(string value)
{
    if (string.IsNullOrEmpty(value)) return value ?? string.Empty;

    return value
        .Replace("\r\n", "<CR><LF>")
        .Replace("\r", "<CR>")
        .Replace("\n", "<LF>");
}

[TX] *IDN?<CR> のように表示されるので、何を送り、何が返ってきたのかがひと目で分かります。

結果

コード側で受信の区切りをLF(\n)に合わせたところ、応答を受け取れるようになりました。手元に記録が残っていないため記憶を頼りに書いていますが、機器が応答の最後に付けていたのは \n だったと思います。

もし同じように応答が返らない場合は、次の順で試すと切り分けが早いはずです。

  1. Tera Termなど、区切りを気にしないツールで送受信できるか確かめる
  2. 受信できたら、応答の末尾に付いている区切り文字(\r・\n・\r\n)を確かめる
  3. VISA側の受信の区切りをその文字に合わせる(TerminationCharacterEnabled = true、TerminationCharacter を指定)
  4. 送信側も、機器がコマンドの終わりとして待っている区切りを付ける

なお、区切りは機器の設定で変えられることが多いので、機器側の通信設定もあわせて確認しておくと安心です。

まとめ