Introduction
Controlling test and measurement equipment programmatically is vital in modern labs and industrial automation. With the arrival of .NET 9, developers now benefit from improved performance, streamlined async programming, and better support for hardware integration. In this post, we’ll build robust drivers and control logic for signal generators, oscilloscopes, and PAN/TILT camera units using recommended frameworks and clean C# 12 code.
Whether you’re working in Visual Studio 2022 or VS Code, all examples will run smoothly and be cross-platform where possible.
🔧 Recommended Libraries and Technologies
Purpose | Library/Tool | Notes |
---|---|---|
USB/GPIB/Serial I/O | National Instruments VISA / Keysight VISA | Common for signal generators and scopes |
SCPI over TCP/IP | Raw sockets, VISA.NET, or LXI | Many modern devices support this |
Serial Communication | System.IO.Ports | For PAN/TILT units or legacy devices |
Instrument abstraction | IVI Foundation drivers | Vendor-neutral instrument APIs |
Logging and diagnostics | Serilog , ILogger , System.Diagnostics | For debugging and maintainability |
🧪 Controlling a Signal Generator via SCPI over TCP
1. Establishing a TCP/IP Connection
Most signal generators (Rigol, Keysight, Siglent, etc.) support SCPI over TCP.
using System.Net.Sockets;
using System.Text;
var client = new TcpClient("192.168.1.100", 5025); // IP and port of signal generator
using var stream = client.GetStream();
// SCPI: Set frequency to 1 MHz and output ON
string[] commands = {
"*IDN?",
"FREQ 1MHZ",
"VOLT 1.0",
"OUTP ON"
};
foreach (var cmd in commands)
{
var bytes = Encoding.ASCII.GetBytes(cmd + "\n");
stream.Write(bytes, 0, bytes.Length);
stream.Flush();
if (cmd.Contains("?"))
{
byte[] buffer = new byte[256];
int bytesRead = stream.Read(buffer, 0, buffer.Length);
Console.WriteLine($"Response: {Encoding.ASCII.GetString(buffer, 0, bytesRead)}");
}
}
📦 Notes:
- Many vendors use port
5025
. - SCPI commands are standard for instrument control.
📉 Reading Oscilloscope Data
Oscilloscopes often support both VISA and LXI/TCP/IP. For this example, let’s use System.IO.Ports
for a serial interface model.
using System.IO.Ports;
var port = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);
port.Open();
// Request waveform data
port.WriteLine(":WAV:DATA?");
string response = port.ReadLine();
Console.WriteLine($"Waveform Data: {response}");
port.Close();
For USB/LAN oscilloscopes, using Keysight VISA with their .NET driver is a better fit. You’ll use:
using Ivi.Visa;
using NationalInstruments.VisaNS;
var rmSession = new ResourceManager();
var instrument = rmSession.Open("TCPIP0::192.168.1.105::inst0::INSTR") as MessageBasedSession;
instrument.Write("*IDN?");
string id = instrument.ReadString();
Console.WriteLine($"Connected to: {id}");
instrument.Write(":WAV:DATA?");
string waveform = instrument.ReadString();
Console.WriteLine(waveform);
🎥 Controlling PAN/TILT Units (e.g., Pelco-D Protocol over Serial)
Many PTZ units use RS-485/RS-232 with binary commands.
Binary Command Example (PAN RIGHT)
byte[] panRightCommand = new byte[] {
0xFF, // Start
0x01, // Camera address
0x00, // Command 1
0x02, // Command 2: Pan right
0x20, // Speed
0x00, // Speed
0x23 // Checksum
};
using var serialPort = new SerialPort("COM4", 2400, Parity.None, 8, StopBits.One);
serialPort.Open();
serialPort.Write(panRightCommand, 0, panRightCommand.Length);
serialPort.Close();
Calculating the Checksum
byte CalcChecksum(params byte[] bytes)
{
int sum = 0;
for (int i = 1; i < bytes.Length - 1; i++)
sum += bytes[i];
return (byte)(sum % 256);
}
You can wrap this in a PanTiltController
class for better structure.
🔄 Creating a Unified Device Abstraction
Using interface-based design for scalability:
public interface IInstrument
{
Task ConnectAsync();
Task<string> QueryAsync(string command);
Task SendAsync(string command);
void Disconnect();
}
You could then implement SignalGenerator
, Oscilloscope
, and PanTiltUnit
classes.
Example:
public class TcpSignalGenerator : IInstrument
{
private TcpClient _client;
private NetworkStream _stream;
public async Task ConnectAsync()
{
_client = new TcpClient("192.168.1.100", 5025);
_stream = _client.GetStream();
}
public async Task<string> QueryAsync(string command)
{
var data = Encoding.ASCII.GetBytes(command + "\n");
await _stream.WriteAsync(data, 0, data.Length);
byte[] buffer = new byte[1024];
int bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length);
return Encoding.ASCII.GetString(buffer, 0, bytesRead);
}
public async Task SendAsync(string command)
{
var data = Encoding.ASCII.GetBytes(command + "\n");
await _stream.WriteAsync(data, 0, data.Length);
}
public void Disconnect()
{
_stream?.Close();
_client?.Close();
}
}
✅ Running in Visual Studio or VS Code
Visual Studio 2022
- Target
.NET 9
(install preview SDK if needed). - Add
System.IO.Ports
NuGet package if working with serial ports. - Enable
unsafe code
if low-level manipulation is required.
VS Code
- Install C# Dev Kit extension.
- Create a new project:
dotnet new console -n InstrumentControl
- Install packages:
dotnet add package System.IO.Ports dotnet add package Serilog
- Run:
dotnet run
📚 Summary
.NET 9 offers an excellent foundation for building hardware control applications thanks to its performance, async capabilities, and maturity of I/O libraries. Whether you’re automating a test bench, controlling lab instruments, or operating surveillance PTZ units, combining SCPI, serial communication, and modern design patterns lets you build powerful, maintainable applications.
Key Takeaways:
- Use SCPI over TCP for modern instruments.
- Abstract your devices using interfaces.
- Rely on open standards like VISA and LXI where possible.
- Choose Visual Studio 2022 or VS Code depending on your environment.