I'm building a GUI for the electronic load we made here on element14. The development will be in a few incremental steps. This is the second iteration:
|
Retrieve Instrument Status: Multiple SCPI Commands Combined
The screen refreshes the GUI controls based on the instrument status.
- LCD display displays mode, set current, measured current and voltage, input on/off status.
- "Load Active" checkbox reflects the current status of the input (you can also change the status with it.
- Errors button shows the number of errors captured by the instrument. If there are unretrieved errors, it shows the count and turns red.
There are a number of SCPI commands required to get that info.
To avoid many back-and forths, and to minimise the time that the instrument is locked for other SCPI commands, all queries are combined in a composite SCPI statement.
INP?;FUNC?;CURR?;:MEAS:CURR:DC?;:MEAS:VOLT:DC?;:SYST:ERR:COUNT?
//Handle timer event. @Override public void actionPerformed(ActionEvent e) { // get info - as much as we can together String s; s = ("INP?;FUNC?;CURR?;:MEAS:CURR:DC?;:MEAS:VOLT:DC?;:SYST:ERR:COUNT?\n"); synchronized (instrument) { instrument.write(s); s = instrument.read(); } // parse StringTokenizer tokenizer = new StringTokenizer(s, ";"); // input if (tokenizer.hasMoreTokens()) {
Each command is separated by a semicolon, as specified by the SCPI standard. The eLoad understands this and can handle it.
Then I use a tokeniser to split the reply in its components.
The result string also separates the data by semicolon. A tokeniser is a convenient way to break these up into individual values.
All values are then gathered in a dedicated object to capture latest status
public class RefreshData { public boolean bActive; public String sMode; public String sSetValue; public String sI; public String sV; public String sW; public String sR; public String sErrCount; }
// input if (tokenizer.hasMoreTokens()) { refreshData.bActive = tokenizer.nextToken().startsWith("1"); } // mode if (tokenizer.hasMoreTokens()) { refreshData.sMode = tokenizer.nextToken().replace("\"", ""); } // set value if (tokenizer.hasMoreTokens()) { refreshData.sSetValue = tokenizer.nextToken().replace("\n", ""); } // measured current if (tokenizer.hasMoreTokens()) { refreshData.sI = tokenizer.nextToken().replace("\n", ""); } // measured voltage if (tokenizer.hasMoreTokens()) { refreshData.sV = tokenizer.nextToken().replace("\n", ""); } // set error count if (tokenizer.hasMoreTokens()) { refreshData.sErrCount = tokenizer.nextToken().replace("\n", ""); }
Then the GUI components are updated.
// update LCD display jTextAreaLCD.setText("mode: " + (String) (refreshData.sMode.startsWith("CURRent") ? "I" : "*ERROR*") + " " + refreshData.sSetValue + "\nI: " + refreshData.sI + "\nV: " + refreshData.sV + "\nInput: O" + (String) (refreshData.bActive ? "n " : "ff")); // update Input Active control jCheckBoxInputActive.setSelected(refreshData.bActive); // update error count jButtonSCPIErrorClear.setText("Errors: " + refreshData.sErrCount); if (refreshData.sErrCount.startsWith("0")) { jButtonSCPIErrorClear.setBackground(Color.green); } else { jButtonSCPIErrorClear.setBackground(Color.red); }
Timed Status Refresh
I use a Swing timer to retrieve the values and update the controls. If a timed action updates graphic portions, it's preferred to use the Swing timer.
It works well with the Swing threading model.
The timer uses the main application frame as its parent. For that, the frame's declaration needs to be modified to flag that it implement's the interface that the timer uses to wake it up.
public class MainFrame extends javax.swing.JFrame implements ActionListener { // .... /** * Creates new form MainFrame */ public MainFrame() { initComponents(); initTimer(); } private void initTimer() { timer = new Timer(100, this); timer.setInitialDelay(900); timer.start(); }
When the timer kicks in, it'll call our frame's actionPerformed method (the one shown in the previous section)
Switch Load Input On and Off
There's a checkbox that you can use to enable and disable the load.
In the first section you saw that it shows the current state of the device.
But it's an active control. By checking and unchecking it, you control the instrument's output.
You'll also see this reflected in the LCD display (after the next timer event).
private void jCheckBoxInputActiveActionPerformed(java.awt.event.ActionEvent evt) { String s; s = "INP " + (jCheckBoxInputActive.isSelected() ? "ON\n" : "OFF\n"); synchronized (instrument) { instrument.write(s); } }
Error Handling
Each refresh, the timer checks if there are errors logged on the instrument.
If not, it colours the Error button green and labels it with "Errors: 0".
If there are errors, the button colours red and the number of errors are shown.
You can click the red button to retrieve the error messages into the log.
Upon that action, the button turns green again and shows "Errors: 0".
That's because the action of retrieving an error removes it from the instrument.
We retrieve all errors, so the backlog is clean.
Clicking that green button clears the error log on screen.
How can you get errors when using the GUI:
- enter an invalid value for the constant current (a character, an under/overflow)
- enter a wrong or unsupported SCPI command in the SCPI tab (the log shows different messages for wrong vs. unsupported)
TAB for Constant Current Controls and SCPI Command Prompt
To avoid clutter, and to present a simple interface by default, I used a TAB control where you can switch between Constant Current and SCPI control.
You can mix and match the two ways of controlling. The instrument doesn't know whether you used a button or typed a command .
Neither does the interface, once you did the action.
So there are no side effects (except when you use the debug level :DEVELOP: SCPI commands to directly control the DACs).
Minor improvement: when you press enter in an edit box, it performs the same task as the Send button next to it.
It avoids that you have to switch between keyboard and mouse.
I've used this application extensively today and it does the job. That was the objective set at the start.
NetBeans project is attached. The code is also on GitHub: https://github.com/jancumps/eLoadGui.
You need to use the latest firmware for the eload, available on the project start page.
There's also a distro attached, ready to run with dependencies included. It has a batch file for Linux and Windows.
You need to adjust the COM port to the one the instrument is using. For Linux, make the batch file executable.
Top Comments