Controlling GPIB instruments from Free Pascal with GPIBEE and Lazarus

Overview

This is the Pascal counterpart to the Code::Blocks tutorial. Same idea, different language: a native Windows program that opens an instrument through GPIBEE, sends SCPI commands and reads the answers back. This time it has a window rather than a console.

The example is a complete Lazarus project, ready to download. It gives you a small GUI where you type a VISA resource name and a command, press buttons for Open, Read, Write, Query and Close, and watch a timestamped log of exactly what happened. It is a useful thing to keep around even after you have written your own program, because it answers the question "is it the instrument or is it my code" in about ten seconds.

By the end you will have that program running against your own instrument, and you will know the handful of calls needed to write your own.

Why Lazarus

Lazarus is a free, open source IDE for Free Pascal, and it is deliberately close to Borland Delphi: the same Object Pascal language, the same form designer with components dragged onto a window, the same event handlers behind the buttons. If you learned Delphi at some point, you already know how to use it, and a great deal of Delphi code compiles under it unchanged.

It also cross-compiles. The same project builds on Windows, Linux and macOS, which is the main reason people reach for it over Delphi.

Delphi:this example is written in Object Pascal against the standard runtime, and nothing in it is Lazarus-specific beyond the form file, so it should build under Delphi with little or no change. That is an expectation rather than a promise: it has not been tried. If you do test it, we would be glad to hear how it went at support@gpibee.com.

Prerequisites

  • A GPIBEE, with an instrument on the GPIB bus.
  • Windows. The example targets Windows, though Free Pascal itself is cross-platform.
  • A VISA implementation installed on the PC. See below.
  • Lazarus. See further down if you do not have it.

Install a VISA

VISA is the standard API between your program and the instrument. Your code says "open this resource, write this string, read the answer", and VISA handles the transport underneath, whether that is LAN, USB or a GPIB card. GPIBEE appears on the network as a VXI-11 instrument, so any VISA can talk to it.

VISA is not part of Windows and does not come with Lazarus. You have to install one, and the example needs it only to run, not to compile.

Recommended:R&S VISA from Rohde & Schwarz. It is free, the download needs no customer account, and it installs a GUI tool that will find your instrument's resource name for you. Our Installing & Using a VISA tutorial walks through the install screen by screen, and then through the R&S VISA Tester.

NI-VISA and the Keysight IO Libraries Suite work just as well. If one is already installed, use it.

Install Lazarus

Skip this if you already have Lazarus. Otherwise the official download page is:

Pick the current release for 64-bit Windows and accept the defaults. The installer brings the Free Pascal compiler with it, so there is no separate toolchain to set up: install it, start it, and you can build.

Download the example

LazarusFreePascalGpibeeExample.zip
Complete Lazarus project in Object Pascal. Builds with no VISA SDK, headers or import library installed.
⤓ Download the project

Unpack it somewhere convenient. Inside:

FileWhat it is
XyGpibDemo.lpiThe Lazarus project file. This is the one you open.
XyGpibDemo.lprThe program source, which does nothing but start the form.
MainForm.pas / .lfmThe window and the code behind its buttons. This is where you will spend your time.
XyGPIB.pasThe TXyGpibInstrument class described on this page. The only unit your own code needs to use.
VisaInterface.pasThe VISA runtime loader underneath it. Used internally; you should not need to open it.
No VISA SDK needed to build:VisaInterface.pas declares the few VISA types it uses itself rather than needing the IVI Foundation headers, and the VISA DLL is loaded at run time instead of being linked. The project therefore compiles on a machine with no VISA installed at all. VISA becomes a requirement only when you press Open.

Open and run the project

Open XyGpibDemo.lpi in Lazarus. The .lpi is the project file: it ties the units together and carries the compiler settings. Opening MainForm.pas on its own gives you a lone source file with no project around it, and the build commands will have nothing to build.

Then start it with the green play button in the toolbar, which compiles the project and runs it in one step. F9 does the same thing from the keyboard.

The Lazarus toolbar with the green play button that builds and runs the project
The green play button. Build and run in one press.

The window comes up like this:

The XyGPIB Demo window just after starting, with the resource name and command pre-filled and the log showing VISA loaded from visa64.dll
Just started. The first log line is the good news: VISA was found and loaded.

That first log line, VISA loaded from: C:\WINDOWS\SYSTEM32\visa64.dll, is written at startup before you touch anything. If you see it, the VISA part of the chain is already proven and any later problem lies further along.

Only Open is enabled to begin with. Read, Write, Query and Close stay greyed out until a session exists, which saves you from the most common first mistake.

The resource name

The resource field is pre-filled with:

Default in the example
TCPIP::192.168.3.2::inst0,4::INSTR

Read from the left: reach the device over TCP/IP, at 192.168.3.2, and talk to GPIB address 4 behind it. The address here is a GPIBEE connected over USB, which presents itself on its default address 192.168.3.2. That is used purely because it is predictable and makes a good demo. Nothing about this is USB-specific: over Ethernet it works identically, you simply put in whatever address the adapter has on your network, which could be anything.

You do not have to work the string out by hand. The R&S VISA Tester searches the network, lists what it finds and hands you the exact name to paste. The VISA tutorial covers that, including the GPIB scan that shows which addresses are actually occupied.

Using the demo

Open

Press Open. The session is created and the other four buttons come to life:

The demo after pressing Open, with Open now greyed out, the other buttons enabled, and Open: OK in the log
After Open. The resource field locks so the session and the name on screen cannot drift apart.

Query

With *IDN? in the command field, press Query. That writes the command and reads the answer in one step:

The demo after pressing Query, with the log showing the IDN query and a response from a HEWLETT-PACKARD 34401A
The instrument answering for itself: manufacturer, model, serial number and firmware revision.

*IDN? is part of the IEEE 488.2 common command set, so almost any SCPI instrument understands it. That makes it the right first thing to send: if it comes back, the entire chain works, and everything after that is a matter of sending different commands.

Write and Read

Write sends the command field and returns immediately, which is what you want for commands that produce no answer, such as *RST or CONF:VOLT:DC. Read fetches a reply on its own. Query is simply the two in sequence, and is what you want for anything ending in a question mark.

Pressing Read when nothing is waiting will sit there until the timeout expires and then report a failure. That is normal and is worth doing once deliberately, so you recognise it later.

Close

Close releases the session and re-enables the resource field, so you can point the program at a different instrument without restarting it.

When it does not work

If the GPIBEE cannot be reached, or nothing answers at the GPIB address you asked for, Open fails and the log says so:

The demo showing a failed open, with ViOpen failed VISA status 0xBFFF0011 and Open: FAILED in the log
A failed open. VISA was fine; what came after it was not.

Two lines matter here. ViOpen failed: VISA status 0xBFFF0011 (Insufficient location information or the device or resource is not present in the system.) is VISA's own wording, decoded and routed into the log window. Below it, Open: FAILED is the demo's own message.

Note that the first line in that log is still the successful VISA load. VISA is working; the problem is past it.

Despite the wording, 0xBFFF0011 almost always means one of two ordinary things: the adapter is not reachable at that IP address, or it is reachable but nothing answers at that GPIB address. Check in that order. Open the GPIBEE web interface in a browser to confirm the address, then use its GPIB scan to see which addresses actually have an instrument on them. An instrument switched off, set to a different address, or with a GPIB connector not seated properly all produce this same error.

Termination and timeout

Termination is how both ends know a message has ended, and it is the single most common reason a first attempt hangs until it times out. The demo fixes both, along with the timeout, in one place near the top of MainForm.pas:

const
  { Fixed for this demo: LF write/read termination (typical for SCPI
    over VXI-11/GPIB-LAN instruments), 3 s I/O timeout. See XyGPIB.pas
    for the other TXyGpibTerm choices (xtCR, xtCRLF, xtEOI) if your
    instrument needs different termination. }
  IoTimeoutMs = 3000;
  WriteTerm   = xtLF;
  ReadTerm    = xtLF;

Change those and rebuild if your instrument wants something different. Write and read are set independently, which matters because plenty of instruments are not symmetrical about it.

Write termination is appended to every string you send. Most SCPI instruments expect a line feed and will wait silently if they do not get one.

Read termination is applied to the session itself, so the read returns the moment the terminator arrives instead of waiting out the full timeout. Read then strips the trailing CR, LF or CRLF from what it returns, so you never have to trim it yourself.

ValueOn writeOn read
xtLFappends #10stops at #10. The usual choice, and what the demo uses.
xtCRappends #13stops at #13. Some older instruments want this.
xtCRLFappends #13#10stops at #10, since VISA matches a single byte and CRLF always ends in LF.
xtEOIappends nothingno terminator byte. The read ends on the EOI signal alone, and nothing is stripped.

If you do not know what your instrument wants, start with xtLF for both. If writes clearly work but reads always time out, the read termination is the first thing to change.

The timeout

IoTimeoutMs is in milliseconds and applies to every read and write on the session. Three seconds suits most instruments. It is an upper bound rather than a delay: a read that gets its answer immediately returns immediately, and the timeout only decides how long the call waits before giving up.

Raise it if you trigger long measurements. A sweep that takes ten seconds to complete needs a timeout longer than the sweep, or the read gives up while the instrument is still working. The failed-open screenshot above shows the other side of this: those three seconds of execution time are exactly IoTimeoutMs elapsing.

Write, read and query

Three methods cover almost everything you will do with a SCPI instrument. All three return Boolean: True means it worked, and no VISA status code reaches your code.

Write: send a command

The termination configured at Open time is appended for you, so do not add it yourself.

FInstrument.Write('*RST');                  { reset the instrument  }
FInstrument.Write('CONF:VOLT:DC 10,0.001'); { 10 V range, 1 mV res. }

Read: fetch an answer

The answer comes back through an out parameter, so there is no buffer to allocate or size. The trailing CR/LF is already removed.

var
  Response: string;
begin
  if FInstrument.Read(Response) then
    Log('got: ' + Response);
end;

Read takes an optional maximum length, defaulting to 256 bytes. Raise it for long replies such as error lists or captured data:

FInstrument.Read(Response, 4096);

Query: write then read, in one call

This is the workhorse, and what the Query button does.

var
  Response: string;
begin
  if not FInstrument.Query('*IDN?', Response) then
    Log('query failed')
  else
    Log('instrument: ' + Response);
end;

Put together, a complete measurement is short:

var
  Response: string;
  Volts: Double;
begin
  FInstrument.Write('CONF:VOLT:DC');

  if FInstrument.Query('READ?', Response) then
  begin
    Volts := StrToFloat(Trim(Response));
    Log(Format('%.6f V', [Volts]));
  end;
end;
Decimal separators:StrToFloat follows the system locale, and an instrument always sends a dot. On a German or French Windows that combination raises an exception. Use StrToFloat(S, FS) with a TFormatSettings whose DecimalSeparator is '.' for anything you intend to keep.

When the text methods are the wrong tool

Screenshots, waveform dumps and anything else that is not text can contain any byte value. For those use WriteRaw and ReadRaw, which add nothing, strip nothing, and report exactly how many bytes arrived.

var
  Buffer: array[0..65535] of Byte;
  Got: SizeUInt;
begin
  FInstrument.Write('HCOP:DATA?');

  if FInstrument.ReadRaw(Buffer, SizeOf(Buffer), Got) then
    Log(Format('%d bytes', [Got]));
end;

XyGPIB reference

Everything the demo uses. TXyGpibInstrument is an ordinary class: create one, open it, use it, free it. Methods that can fail return Boolean.

MethodWhat it does
Startup (class methods, shared by every instance)
class function LoadVisa(out ALoadedPath: string): Boolean Loads the system VISA runtime, trying visa64.dll then visa32.dll. Call once before opening anything. Returns the path it loaded in ALoadedPath.
class function VisaLoadErrorDetail: string Why the previous LoadVisa failed, in readable text.
Lifetime
constructor Create Creates an instrument object. No session exists yet.
function Open(const AResourceName: string; AWriteTerm, AReadTerm: TXyGpibTerm; ATimeoutMs: LongWord): Boolean Opens the resource, sets the timeout and both termination modes.
function Close: Boolean Closes the session. Safe to call when not open; the session is released either way.
property IsOpen: Boolean Whether a session is currently open. The demo uses this to enable and disable its buttons.
Text transfers
function Write(const AText: string): Boolean Sends AText with the write termination appended.
function Read(out AText: string; AMaxLength: Integer = 256): Boolean Reads a reply and strips the trailing CR/LF. AMaxLength caps how much is read.
function Query(const ACommand: string; out AResponse: string; AMaxLength: Integer = 256): Boolean Write then read, in one call.
Binary transfers
function WriteRaw(const AData; ALength: SizeUInt): Boolean Sends exactly ALength bytes, unmodified.
function ReadRaw(var ABuffer; AMaxReadLength: SizeUInt; out AActualReadLength: SizeUInt): Boolean Reads up to AMaxReadLength bytes with no stripping. AActualReadLength receives the count, and is 0 on failure.
Diagnostics (class-wide)
class procedure SetDebug(AEnable: Boolean) When on, every VISA error is reported as it happens. Off by default; the demo switches it on.
class property OnLog: TXyGpibLogEvent Where those reports go. A console program would print to stderr; the demo points this at its log window, which is why VISA's own error text appears there.
class function GetLastVisaError: TViStatus The VISA status of the most recent call that failed. One remembered value, not a queue, and a later success does not clear it.
The log window is not magic:the lines beginning ViOpen failed: come from VISA itself, by way of SetDebug(True) and OnLog. Three lines in FormCreate wire that up. It is worth copying into your own program, because it turns a silent failure into a sentence.

What XyGPIB is, and is not

XyGPIB is example code, not a product. It is one unit that ships inside the project you just downloaded. There is no library to install, no version to track, no release notes and no support commitment behind it. It is yours to read, change, rename, cut down or delete outright.

It is meant to be taken and used. That is the point of it being here: copy XyGPIB.pas into your own project and you have GPIBEE talking to an instrument on the first afternoon rather than the third. Loading the VISA runtime, opening and closing sessions cleanly, getting the termination attributes right and turning status codes into something readable are all solved already, and those are exactly the parts that cost a day each when you start from nothing. Treat it as a running start for your own integration, not as a finished component to be admired from a distance.

The other half of its job is keeping a first program readable. VISA is a capable and fairly large API, and meeting it head-on means a resource manager session, an instrument session, attribute codes for timeout and termination, and a status code to decode after every call, all before you can ask an instrument its name.

Working with VISA directly is a perfectly good choice, and past the basics it is the better one. Everything XyGPIB does, VISA does; XyGPIB simply exposes fewer operations with an easier interface. When you outgrow it, use VisaInterface.pas from the project, or your own external declarations, and call VISA yourself. The two mix freely in the same program.

This is the same helper as in the Code::Blocks tutorial, translated to Object Pascal. The behaviour matches; the signatures follow Pascal conventions, so the C XyGPIB_Open(handle, ...) becomes a method on an object, and the C caller's buffer becomes an out parameter.

What is deliberately missing

XyGPIB covers opening a session and moving bytes, and stops there. Several GPIB primitives you may well need are simply not in it:

  • Device clear, to reset an instrument's interface and empty its buffers (viClear in VISA).
  • Triggering, the Group Execute Trigger that starts a measurement on one or several instruments at once (viAssertTrigger).
  • Reading the status byte by serial poll, the usual way to ask an instrument whether it is finished or has an error waiting (viReadSTB).
  • Service requests and events, so an instrument can interrupt you rather than being polled.
  • Locking, and the full VISA attribute set.

None of these are missing by oversight, and none are waiting on a future version. This is example code: adding them would make it a library, which is not what it is for. VISA offers all of them, and since XyGPIB is a thin layer over VISA you can call them directly alongside it in the same program.

Stuck adding one of these?If you need device clear, triggering, serial poll or anything else in that list and cannot get it working, write to support@gpibee.com. Tell us what you are trying to do and we will help you get there.

Troubleshooting

ProblemSolution
"Could not load a working VISA runtime" at startup No VISA installed, or a 32-bit build looking for a 64-bit DLL. Install R&S VISA, and check the project is building 64-bit.
Open fails with 0xBFFF0011 The GPIBEE is not reachable at that IP, or no instrument answers at that GPIB address. Confirm the address in a browser, then use the GPIB scan in the web interface.
Write works, Read always fails Read termination. The instrument is not sending the terminator you configured. Try xtEOI, or one of the other CR/LF variants, in MainForm.pas.
The answer has a stray character at the end You are using xtEOI, which strips nothing by design. Switch to xtCR, xtLF or xtCRLF, or trim it yourself.
Long measurements fail, short ones work The timeout is shorter than the measurement. Raise IoTimeoutMs past the longest operation you trigger.
Answers arrive one command late A previous read left data queued, usually after a truncated read. Raise AMaxLength, and make sure every query is read exactly once.
StrToFloat raises an exception on a reading A locale using the comma as decimal separator. Pass a TFormatSettings with DecimalSeparator := '.'.
Prefer C?The same example, the same helper and the same instrument exist in the Code::Blocks tutorial, as a console program.