最近、在宅ワークが増えたため出社して作業場で仕事する機会も減ってきました。
家で快適にプログラミングできるのは心地よいのですが、仕事柄オシロスコープやファンクションジェネレータを使うためやりにくい部分もあります。
そこで、今回はC#でNI-VISAというサポート製品を使ってファンクションジェネレータをUSB接続してリモート制御する方法を紹介していきます。
- ファンクションジェネレータなどの計測器をUSB接続してリモート制御したい
- NI-VISAのプログラミング例を知りたい
NI-VISAって何?
NI-VISAは「Virtual Instrument Software Architecture」の略で、ナショナルインスツルメンツ(National Instrument)が開発したAPIになります。
簡単にいうとNI-VISAを使えば、Ethernet、GPIB、シリアル、USBなどの計測器を制御するプログラミングが簡単にできるというサービスになります。
NI-VISAのダウンロードページからインストールすることでAPIを使用できるようになります。
NI-VISAのAPIを使ったC#コード
NI-VISAのインストール手順は省かせてもらって、早速プログラミングしていきたいと思います。
- エヌエフ回路設計ブロックのWF1948(USB接続でリモート制御)
USB機器の接続
USB接続機器と繋げるためにリソースを検索する作業をしていきます。
次のようなフォームを作成します。
ListBox
に接続可能なデバイスが表示されるので、対象のファンクションジェネレータを選んで接続するというものです。

サンプルコード
using NationalInstruments.Visa;
using System;
using System.Windows.Forms;
namespace FunctionGeneratorController
{
public partial class SelectResource : Form
{
public SelectResource()
{
InitializeComponent();
}
private void SelectResource_Load(object sender, EventArgs e)
{
// This example uses an instance of the NationalInstruments.Visa.ResourceManager class to find resources on the system.
// Alternatively, static methods provided by the Ivi.Visa.ResourceManager class may be used when an application
// requires additional VISA .NET implementations.
using (var rmSession = new ResourceManager())
{
var resources = rmSession.Find("(ASRL|GPIB|TCPIP|USB)?*");
foreach (string s in resources)
{
listBoxAvailableResources.Items.Add(s);
}
}
}
private void listBoxAvailableResources_DoubleClick(object sender, EventArgs e)
{
string selectedString = (string)listBoxAvailableResources.SelectedItem;
ResourceName = selectedString;
this.DialogResult = DialogResult.OK;
this.Close();
}
private void listBoxAvailableResources_SelectedValueChanged(object sender, EventArgs e)
{
string selectedString = (string)listBoxAvailableResources.SelectedItem;
ResourceName = selectedString;
}
public string ResourceName
{
get
{
return textBoxVisaResourceName.Text;
}
set
{
textBoxVisaResourceName.Text = value;
}
}
}
}
まず、NI-VISAのAPIを使うためにNationalInstruments.Visa
を呼び出しましょう。
using NationalInstruments.Visa;
次に、USB接続機器をオープンするための処理を加えます。
using (var rmSession = new ResourceManager())
{
var resources = rmSession.Find("(ASRL|GPIB|TCPIP|USB)?*");
foreach (string s in resources)
{
listBoxAvailableResources.Items.Add(s);
}
}
ファンクションジェネレータにASCIIデータを送って制御してみる
続いて、ファンクションジェネレータを制御するためのコマンドを実装していきます。
まず、使用コマンドを定義していきます。
サンプルコード
using System;
using System.Collections;
using System.Collections.Generic;
namespace FunctionGeneratorController
{
/// <summary>
/// 使用コマンドを定義
/// </summary>
public class SubSystemCommand : IEnumerable<string>
{
/// <summary>
/// SOURCE
/// </summary>
public string SOURCE { get; set; } = "SOURce";
/// <summary>
/// OUTPUT
/// </summary>
public string OUTPUT { get; set; } = "OUTPut";
/// <summary>
/// SYSTEM
/// </summary>
public string SYSTEM { get; set; } = "SYSTem";
/// <summary>
/// ROOTコマンド
/// </summary>
/// <returns></returns>
public IEnumerator<string> GetEnumerator()
{
yield return SOURCE;
yield return OUTPUT;
yield return SYSTEM;
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
/// <summary>
/// SOURCE
/// </summary>
public class Source : IEnumerable<string>
{
/// <summary>
/// VOLTAGE
/// </summary>
public string VOLTAGE { get; set; } = "VOLTage";
/// <summary>
/// FREQUENCY
/// </summary>
public string FREQUENCY { get; set; } = "FREQuency";
/// <summary>
/// PHASE
/// </summary>
public string PHASE { get; set; } = "PHASe";
/// <summary>
/// SOURCEコマンド
/// </summary>
/// <returns></returns>
public IEnumerator<string> GetEnumerator()
{
yield return VOLTAGE;
yield return FREQUENCY;
yield return PHASE;
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
public class Voltage : IEnumerable<string>
{
/// <summary>
/// LEVEL
/// </summary>
public string LEVEL { get; set; } = "LEVel";
/// <summary>
/// IMMEDIATE
/// </summary>
public string IMMEDIATE { get; set; } = "IMMediate";
/// <summary>
/// AMPLITUEDE
/// </summary>
public string AMPLITUEDE { get; set; } = "AMPLitude";
/// <summary>
/// OFFSET
/// </summary>
public string OFFSET { get; set; } = "OFFSET";
public IEnumerator<string> GetEnumerator()
{
yield return LEVEL;
yield return IMMEDIATE;
yield return AMPLITUEDE;
yield return OFFSET;
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
public class Frequency : IEnumerable<string>
{
/// <summary>
/// FIXED
/// </summary>
public string FIXED { get; set; } = "FIXed";
/// <summary>
/// CW
/// </summary>
public string CW { get; set; } = "CW";
public IEnumerator<string> GetEnumerator()
{
yield return FIXED;
yield return CW;
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
}
/// <summary>
/// OUTPUT
/// </summary>
public class Output : IEnumerable<string>
{
/// <summary>
/// STATE
/// </summary>
public string STATE { get; set; } = "STATe";
/// <summary>
/// OUTPUTコマンド
/// </summary>
/// <returns></returns>
public IEnumerator<string> GetEnumerator()
{
yield return STATE;
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
/// <summary>
/// FUNCTION
/// </summary>
public class Function : IEnumerable<string>
{
/// <summary>
/// ON
/// </summary>
public string ON { get; set; } = "ON";
/// <summary>
/// OFF
/// </summary>
public string OFF { get; set; } = "OFF";
public IEnumerator<string> GetEnumerator()
{
yield return ON;
yield return OFF;
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
}
}
次に、コマンド送信用のクラスを作成していきます。
サンプルコード
using NationalInstruments.Visa;
using System;
namespace FunctionGeneratorController
{
public class Control
{
SubSystemCommand sysCmd = new SubSystemCommand();
SubSystemCommand.Source source = new SubSystemCommand.Source();
SubSystemCommand.Output output = new SubSystemCommand.Output();
SubSystemCommand.Function function = new SubSystemCommand.Function();
SubSystemCommand.Source.Voltage voltage = new SubSystemCommand.Source.Voltage();
SubSystemCommand.Source.Frequency frequency = new SubSystemCommand.Source.Frequency();
/// <summary>
/// チャネル番号
/// </summary>
enum Channel
{
One = 0,
Two
}
/// <summary>
/// チャネル出力状態
/// </summary>
enum ChannelState
{
OFF = 0,
ON
}
/// <summary>
/// コマンドを送信する
/// </summary>
/// <param name="ch"></param>
/// <param name="cmds"></param>
/// <param name="func"></param>
public void SendCommand(MessageBasedSession mbSession, int ch, string[] cmds, string func)
{
var text = string.Empty;
foreach(var cmd in cmds)
{
text += ":";
text += cmd;
}
if (func != "?")
{
text += " " + func;
}
else
{
text += func;
}
mbSession.RawIO.Write(text);
}
/// <summary>
/// 指定チャネルの出力をONにする
/// </summary>
/// <param name="mbSession">セッション</param>
/// <param name="channel">対象チャネル</param>
public void FuncON(MessageBasedSession mbSession, int channel)
{
try
{
var cmds = new string[] { sysCmd.OUTPUT + channel.ToString(), output.STATE };
var func = function.ON;
SendCommand(mbSession, channel, cmds, func);
}
catch (Exception ex)
{
}
}
/// <summary>
/// 指定チャネルの出力をOFFにする
/// </summary>
/// <param name="mbSession">セッション</param>
/// <param name="channel">対象チャネル</param>
public void FuncOFF(MessageBasedSession mbSession, int channel)
{
try
{
var cmds = new string[] { sysCmd.OUTPUT + channel.ToString(), output.STATE };
var func = function.OFF;
SendCommand(mbSession, channel, cmds, func);
}
catch (Exception ex)
{
}
}
/// <summary>
/// 振幅を設定する
/// </summary>
/// <param name="mbSession"></param>
/// <param name="channel"></param>
/// <param name="value"></param>
/// <param name="unit"></param>
public void SetAmplitude(MessageBasedSession mbSession, int channel, float value, string unit)
{
try
{
var cmds = new string[] { sysCmd.SOURCE + channel.ToString(), source.VOLTAGE, voltage.LEVEL, voltage.IMMEDIATE, voltage.AMPLITUEDE };
var func = value.ToString() + unit;
SendCommand(mbSession, channel, cmds, func);
}
catch (Exception ex)
{
}
}
/// <summary>
/// 周波数を設定する
/// </summary>
/// <param name="mbSession"></param>
/// <param name="channel"></param>
/// <param name="value"></param>
/// <param name="unit"></param>
public void SetFrequency(MessageBasedSession mbSession, int channel, float value, string unit)
{
try
{
var cmds = new string[] { sysCmd.SOURCE + channel.ToString(), source.FREQUENCY, frequency.CW };
var func = value.ToString() + unit;
SendCommand(mbSession, channel, cmds, func);
}
catch (Exception ex)
{
}
}
/// <summary>
/// オフセットレベルを設定する
/// </summary>
/// <param name="mbSession"></param>
/// <param name="channel"></param>
/// <param name="value"></param>
public void SetDcOffset(MessageBasedSession mbSession, int channel, float value)
{
try
{
var cmds = new string[] { sysCmd.SOURCE + channel.ToString(), source.VOLTAGE, voltage.LEVEL, voltage.IMMEDIATE, voltage.OFFSET};
var func = value.ToString();
SendCommand(mbSession, channel, cmds, func);
}
catch (Exception ex)
{
}
}
/// <summary>
/// 指定チャネルのON/OFF状態をチェック
/// </summary>
/// <param name="mbSession"></param>
/// <param name="channel"></param>
/// <returns></returns>
public bool ReadFuncOnOff(MessageBasedSession mbSession, int channel)
{
try
{
var cmds = new string[] { sysCmd.OUTPUT + channel.ToString(), output.STATE };
var func = "?";
SendCommand(mbSession, channel, cmds, func);
var returnValue = RemoveCommonEscapeSequences(mbSession.RawIO.ReadString());
if (Convert.ToInt32(returnValue) == (int)ChannelState.OFF) return false;
return true;
}
catch (Exception ex)
{
return false;
}
}
/// <summary>
/// 周波数を読み込む
/// </summary>
/// <param name="mbSession"></param>
/// <param name="channel"></param>
/// <returns></returns>
public float ReadFrequency(MessageBasedSession mbSession, int channel)
{
try
{
var cmds = new string[] { sysCmd.SOURCE + channel.ToString(), source.FREQUENCY, frequency.CW };
var func = "?";
SendCommand(mbSession, channel, cmds, func);
var returnValue = RemoveCommonEscapeSequences(mbSession.RawIO.ReadString());
float outValue = 0;
float.TryParse(returnValue, out outValue);
return outValue;
}
catch (Exception ex)
{
return 0;
}
}
/// <summary>
/// 振幅値を読み込む
/// </summary>
/// <param name="mbSession"></param>
/// <param name="channel"></param>
/// <returns></returns>
public float ReadAmplitude(MessageBasedSession mbSession, int channel)
{
try
{
var cmds = new string[] { sysCmd.SOURCE + channel.ToString(), source.VOLTAGE, voltage.LEVEL, voltage.IMMEDIATE, voltage.AMPLITUEDE };
var func = "?";
SendCommand(mbSession, channel, cmds, func);
var returnValue = RemoveCommonEscapeSequences(mbSession.RawIO.ReadString());
float outValue = 0;
float.TryParse(returnValue, out outValue);
return outValue;
}
catch (Exception ex)
{
return 0;
}
}
/// <summary>
/// オフセット電圧を読み込む
/// </summary>
/// <param name="mbSession"></param>
/// <param name="channel"></param>
/// <returns></returns>
public float ReadOffset(MessageBasedSession mbSession, int channel)
{
try
{
var cmds = new string[] { sysCmd.SOURCE + channel.ToString(), source.VOLTAGE, voltage.LEVEL, voltage.IMMEDIATE, voltage.OFFSET };
var func = "?";
SendCommand(mbSession, channel, cmds, func);
var returnValue = RemoveCommonEscapeSequences(mbSession.RawIO.ReadString());
float outValue = 0;
float.TryParse(returnValue, out outValue);
return outValue;
}
catch (Exception ex)
{
return 0;
}
}
/// <summary>
/// 改行文字を削除
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
private string RemoveCommonEscapeSequences(string s)
{
return s.Replace("\n", string.Empty).Replace("\r", string.Empty);
}
}
}
この中のSendCommand(mbSession, channel, cmds, func)
関数を使うことで次のことができるようになります。
- 出力チャネルの選択
- 周波数設定
- 振幅設定
- オフセット電圧設定
使用コマンドを増やしたい場合はSubSystemCommand.cs
内のコマンドを定義することで拡張可能です。
コメント