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 なのか)を、こちらで付けて送る必要があります。
区切りを切り替えられる端末アプリを作った
この経験から、送信と受信の区切りをそれぞれ画面で選べる、小さな端末アプリを作りました。同じ機器で区切りだけを変えながら試せれば、次に同じことが起きても早く切り分けられます。
- 起動時に、PCにつながっているVISAアドレスを一覧で表示する
- 受信の区切りは「Auto」「CR」「LF」「CRLF」「None」から選べる(既定はAuto)
- 送信の区切りも同じように選べる(既定はCR)
- 「送信」「受信」「送って受け取る(Query)」の3つのボタン
- ログには、区切り文字を
<CR><LF>のように見える形で表示する
受信の区切りの設定
受信側は、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 だったと思います。
もし同じように応答が返らない場合は、次の順で試すと切り分けが早いはずです。
- Tera Termなど、区切りを気にしないツールで送受信できるか確かめる
- 受信できたら、応答の末尾に付いている区切り文字(
\r・\n・\r\n)を確かめる - VISA側の受信の区切りをその文字に合わせる(
TerminationCharacterEnabled = true、TerminationCharacterを指定) - 送信側も、機器がコマンドの終わりとして待っている区切りを付ける
なお、区切りは機器の設定で変えられることが多いので、機器側の通信設定もあわせて確認しておくと安心です。
まとめ
- VISAの受信は、END・終端文字・バッファ・タイムアウトのどれかで終わる
- USB・GPIB、VXI-11・HiSLIPの測定器は、ENDがあるので終端文字なしでも読み取れることが多い
- LANのソケット接続(
::SOCKET)やシリアルの機器にはENDがないので、終端文字を機器の送る区切りと合わせる - ツールで結果が違うときは、機器より通信設定を疑う。Tera Termとの比較は切り分けに役立つ
- 送信側の改行は自分で付ける
- 区切り文字をログに見える形で出すと、原因の切り分けが早くなる