Introduction
I felt I was missing an example with the Hardware Abstraction Framework write up because I didn't cover off a Serial device. The existing examples all use a USB connection, but it is just as easy to use Serial, given the right application running on the attached instrument. Here then is a further example interacting with an Arduino Uno; this post describes an interaction with LINX, but it's just as easy to use the VISA Write that the HAF is based on.
Note: this is not part of my final blog which remains the 7th in the series.
Table of Contents
Getting Started
Plug in the Arduino Uno and note the COM port. Launch NI-MAX, identify the Arduino (from the COM port - on my PC, the COM port was actually named Arduino so was easy to spot) and give it an alias, I chose ArduinoUnoR3.
Arduino Application
This simple sketch should be loaded onto the Arduino via the Arduino IDE:
#define RST "*RST" #define CLS "*CLS" #define OPC "*OPC" #define ESR "*ESR?" #define IDN "*IDN?" #define IDENTITY "Arduino,Uno R3,0123456789,1.1.0\n" #define COMPLETE "1\n" String cmd; String out; char terminator = '\n'; void setup() { // Start a serial channel at the correct Baud rate defined in the Hardware Abstraction Framework for Arduino Serial device Serial.begin(115200); } void loop() { // loop looking for commands. This example only deals with a small subset of commands, as used by the framework, plus *IDN? // Most are ignored; *ESR? is a request to ask the device if all previous commands have finished - if so, return 1. The framework // sends this command if the actual command must "wait for completion" so here, we return 1. if (Serial.available() > 0) { cmd = Serial.readStringUntil(terminator); if (cmd == RST) { } if (cmd == CLS) { } if (cmd == OPC) { } if (cmd == ESR) { Serial.print(COMPLETE); } if (cmd == IDN) { Serial.print(IDENTITY); } } }
To give a quick run through:
- The commands it recognises are defined at the start - these are core SCPI commands that I want to read although in this code most of them do nothing.
- I've defined an identity string to return if asked
- A serial port is opened at a fast speed 115200 baud. This should match the speed defined for the instrument in the HAF.
- The code in loop() then waits on the serial port, reads data from it and if it matches a command processes it. The current code only responds to the commands *ESR and *IDN? but you could see how extensible it could be. A more robust firmware for handling commands could be written of course.
That's it, nothing complicated on the Arduino side. I chose these simple SCPI commands but there's no reason why it couldn't respond to custom commands if you define them in HAF as well.
Create the Instrument
In the Framework, a Driver (Instrument) is required to represent the Arduino. Actually, the Framework already has this so really nothing must be done for this but if you want to follow this example with a different Serial device then this step is necessary. The Arduino Driver can be used for a pattern:
Note that only three methods are required. Read Serial Port Attributes is an override of the parent method and I use it to up the baud rate from the default 9600 to 115200 and to give a wait time of 2000ms to allow the Arduino to reset (from a default of 0ms.) This method, then, only needs overriding if you need to change the Serial attributes. Note that the Driver does NOT need to specify the COM port as the Framework picks that up automatically.
Running
To interact with the Arduino then, I just ran the Instrument Identification example - I didn't change anything in the Framework at all. This example just queries the selected device and asks for its identity and has been seen in the main blog with the PI Pico.
There's no commentary on this. I ran it, selected the Arduino in the drop down box (actually, it was pre-selected by the Framework as the first in the list of devices) and clicked Get ID. You'll notice a slight pause: when a serial channel is opened to an Arduino it resets so it's necessary to wait a short period of time for that to happen.
How Does it Work?
I've gone through this in some detail in post number 7 in the series (link above) and there's nothing more to add really. The HAF is sending commands to the device, so there must be a firmware implementation running on it that can respond to those commands. There is a need for a Driver, subclassed from SerialDriver, that represents the attached device and this necessitates two method overrides to be created, "Get Instrument Alias()" and "Build Driver Settings Mappings()" but that is all.
Summary
If I had to choose between using the HAF or using LINX, which would be better? I think they serve different purposes, and I'd just add that I did not set out to create a LINX rival:
- HAF expects a Firmware to be in place, it doesn't provide one, but it can handle custom commands that are identified to it. LINX can download a basic, working firmware to the device; that firmware can be extended with custom commands and used if the custom commands are identified to it.
- LINX is just another tool in the LabVIEW set, so creating a wider ranging application involving multiple instruments requires work to be done on the LabVIEW side. HAF already has that foundation requiring a more simple setup and run application to be created.
LINX is not integrated into the HAF so it is definitely a case of choose one or the other. Both require a level of work in terms of creating a non-basic firmware that runs on the device, although the HAF has no foundation for this unlike LINX. Both have the communications mechanisms built-in. The HAF does allow multiple instruments to be co-ordinated in an Interacting Application with a mix-and-match of USB and Serial as needed; this is additional work to pull LINX into an application which does the same thing.