DAQ - Digital multimeter data acquisition software

Keysight 34461A digital multimeter could measure up to 1000 samples/s and store 10,000 samples to internal memory. At maximum sample rate that's enough just for ten seconds of measurement. DAQ software extends 34461A DMM measurement time by fetching data from internal DMM buffer thus making space for new readings.

DAQ is written in C#, using .NET 4 and WPF (Windows Presentation Foundation) for user interface. DMM is controlled through the SCPI language (SCPI - Standard Commands for Programmable Instruments), so it should work on other DMM's beside 34461A (currently DAQ is tested on 34461A and 34465A). DAQ supports USB and Ethernet connections to DMM.

For establishing communication with DMM, information from David Tu's blog were used: 'Continuous Measurements with a 34461A Digital Multimeter'.

Prerequisites:

  • Windows 7 or above
  • Keysight IO Library Suite (should be installed prior to DAQ installation)
  • DMM firmware should be up to date. DAQ is tested for firmware version 2.11
  • Microsoft Visual Studio 2010 or above for modifying project

Below is a detailed description of installation and measurement procedure. You may start immediately by downloading DAQ installation and/or DAQ project containing complete Visual Studio project with source files. Current version is 1.0.3, please check revision history.


DAQ installation

Download installation file DAQsetup.zip, extract it to the folder of your choice and run setup.

DAQ installation

Installer will create desktop icon and Start menu shortcut. Starting either way DAQ will open the main window:

DAQ main window

Preparing and performing data acquisition

1. Setting communication options

Selecting Settings menu option opens dialog for specifying communication options and selecting AC main frequency for timing calculations:

DAQ currently supports connection through USB and Ethernet. Connection string for connected instrument is easily determined with Connection Expert, which comes with Keysight IO Library Suite. Sample USB connection string is shown below.

2. Connecting instrument

Assuming that DMM is properly connected by USB or Ethernet cable and settings are correct, pressing Connect button will establish connection with DMM and displays DMM ID right to the Connect button. 'Connected to DAQ' message will be displayed at the instrument panel.

3. Setting acquisition parameters

DAQ supports Auto (internal) and External trigger source. For Auto setting DMM takes single measurement on periodic intervals determined by Sample rate value.

For External option TTL level trigger signal should be connected to rear Ext Trg connector. DMM takes single measurement on selected active slope of external trigger signal. If frequency of external trigger signal is known it should be written into Sample rate field. That will ensure that output data time column contains accurate time, otherwise it will be set to zero. Frequency of external trigger signal should be between 1 Hz and 1000 Hz.

Sample rate should be between 1 and 1000 samples per second. Recording time could be set only for auto triggering, where DAQ will automatically stop acquisition when the time expires. For external trigger it is necessary to manually stop acquisition.

Select file button opens file manager dialog to select folder and data file.

4. Setting measurement type and range

For high speed sampling auto range DMM mode is not possible (because it takes time for DMM to adjust range), so it is necessary to select range according to highest expected measured value. Currently DAQ supports DC voltage, current and resistance measurement. If needed it is easy to implement other types of measurements that DMM support (frequency, temperature, ...)

From version 1.0.3 setting of measurement type and range are separated:

5. Starting data acquisition

Pressing Start button starts acquisition. Acquisition task is executed in separate thread, so application GUI stays responsive. Remaining time is displayed both at DAQ progress bar and at DMM panel. Pressing Stop button will abort acquisition. Already acquired data will be saved.

Stored data format

Data are saved to text file. Header (lines beginning with '*') contains information about measurements. Measurements are formatted in two Tab delimited columns representing time and measured value.

Recorded data
* Date and time:2.2.2016. 15:06:38
* Instrument:Agilent Technologies,34461A,MY53209449,A.01.10-02.25-01.10-00.35-01-01
* Sample rate: 1000 samples/s
* Recording time: 1 s
* Measurement range: VOLT - 1000
* -------- DAQ utility by Davor Antonic --------
*
* Data format: [time value] (Tab delimited)
0.001000 -194.976644
0.002000 -271.193274
0.003000 -311.453219
0.004000 -317.691102
0.005000 -315.381838
0.006000 -253.534825
0.007000 -180.038568
0.008000 -93.833547
0.009000 6.122927
0.010000 105.606809
0.011000 193.140149
. . .
0.993000 152.967992
0.994000 235.015273
0.995000 301.608158
0.996000 315.640466
0.997000 318.318834
0.998000 292.383062
0.999000 220.685963
1.000000 133.152638

Formatted data is easy to import into chosen application for further processing. Sample MATLAB code for importing and plotting data is provided below:

MATLAB functions
% Read data from file into two-column array
function [measurements]=readDAQ(fname)

fid=fopen(fname);

% skip header (beginning with '*') and read data
C = textscan(fid,'%f %f','CommentStyle','*');
measurements = [C{1} C{2}];
fclose(fid);


% Plot data
function plotDAQ(data)

h = figure('Name', 'DAQ', 'OuterPosition', [100,100,1000,600]);

plot(data(:,1),data(:,2),'-bx','LineWidth',1.5,'MarkerEdgeColor','r');
ha = gca;
set(ha, 'Box', 'on');
grid on
xlabel('t [s]');
ylabel('U [V]');

Following plot presents first 200 ms of acquired data. DMM was set to 1000V DC range and connected to main voltage (220V AC, 50 Hz)

Data plot

Known issues:

  1. DMM timing calculation is adjusted for 50Hz main. For 60Hz main TRIG:DEL should be adjusted accordingly (file 'InstrumentControl.cs', line 88) - FIXED
  2. DAQ one million samples limit is imposed by TRIG:COUNT limit. Setting number of samples to INF doesn't work (instrument doesn't record any data). - FIXED

Code for performing measurements

Function btnStart_Click is event handler for Start button. It performs setting validity check and starts acquisition thread.

Function btnStart_Click
/// <summary>
/// Check settings validity (sample rate, recording time, file) and start data acquisition
/// in separate thread
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnStart_Click(object sender, RoutedEventArgs e)
    {
            . . .  
    SystemSounds.Beep.Play();
    btnStart.Background = Brushes.PaleGreen;
 
    // Data acquisition thread
    DAQThread = new Thread(new ThreadStart(DAQmethod));
    // Thread updating GUI should be STA (Single Threaded Apartment)
    DAQThread.SetApartmentState(ApartmentState.STA);    
    DAQThread.IsBackground = true;
    DAQThread.Start();
    Settings.newFile = false;
    }

Function DAQmethod (executed in separate thread)
/// <summary>
/// Configure DMM for selected measurement, acquire and write data to file
/// (running in separate thread)
/// Revisions:
///     2016-02-24 D.A. added:
/// - external trigger support
/// - timing calculation for 50Hz / 60Hz AC power
/// </summary>
public void DAQmethod()
{
    Settings.Abort = false;     // not aborted
 
    try
    {
        Stopwatch stopWatch = new Stopwatch();
 
        Settings.FileHandle = new StreamWriter(Settings.FileName);
 
        // Header
        Settings.FileHandle.WriteLine("* Date and time:" + DateTime.Now);
        Settings.FileHandle.WriteLine("* Instrument:" + Settings.IDN);
        if (Settings.ExtTrigger)
        {
            if (Settings.SampleRate != 0)
                Settings.FileHandle.WriteLine("* External trigger - Sample rate: " + 
Settings
.SampleRate + " samples/s");             else                 Settings.FileHandle.WriteLine("* External trigger - 'time' should be determined" + 
" from trigger source"
);         }         else         {             Settings.FileHandle.WriteLine("* Sample rate: " + Settings.SampleRate + " samples/s");             Settings.FileHandle.WriteLine("* Recording time: " + Settings.RecTime + " s");         }         Settings.FileHandle.WriteLine("* Measurement range: " + Settings.MeasurementType + " - " +              Settings.MeasurementRange);         Settings.FileHandle.WriteLine("* -------- DAQ utility by Davor Antonic --------");         Settings.FileHandle.WriteLine("*");         Settings.FileHandle.WriteLine("* Data format: [time value] (Tab delimited)");         //Configure the DMM measurement state         // DC voltage/current range selection         Settings.Dmm.WriteString("CONF:" + Settings.MeasurementType + ":DC " + 
Settings
.MeasurementRange, true);         if (Settings.MeasurementType == "VOLT")         {             //10MOhm voltage input impedance setting , "ON" sets >10GOhm             Settings.Dmm.WriteString(Settings.MeasurementType + ":IMP:AUTO OFF"true);         }         // Autozero Off
Settings
.Dmm.WriteString("SENS:" + Settings.MeasurementType + ":DC:ZERO:AUTO OFF"true);         //Set integration time to .02 PLC (400 us at 50 Hz; 333 us at 60 Hz)         Settings.Dmm.WriteString(Settings.MeasurementType + ":NPLC .02"true);         CheckDMMError(Settings.Dmm);                                // Check the DMM for errors         // Trigger settings         int totNoSamples = 0;         if (Settings.ExtTrigger)         {             // Set trigger to external and set slope             if (Settings.UpTrigger) Settings.Dmm.WriteString("TRIG:SOUR EXT;SLOP POS"true);             else Settings.Dmm.WriteString("TRIG:SOUR EXT;SLOP NEG"true);             Settings.Dmm.WriteString("TRIG:DEL:AUTO OFF"true);    //Turn OFF Automatic trigger delay             Settings.Dmm.WriteString("TRIG:DEL 0"true);             Settings.Dmm.WriteString("TRIG:COUN INF"true);    //Set trigger count         }         else         {             totNoSamples = Settings.SampleRate * Settings.RecTime;             Settings.Dmm.WriteString("TRIG:SOUR IMM"true);        //Set trigger source to internal             Settings.Dmm.WriteString("TRIG:DEL:AUTO OFF"true);    //Turn OFF Automatic trigger delay         // Trigger delay: T = delay + Integration time + 0.51ms => Delay = T - 0.51ms - 0.02 / MainFreq             double delay = 1 / (double)Settings.SampleRate - 0.00051 - 0.02 / Settings.MainFreq;             string strDelay = delay.ToString(CultureInfo.InvariantCulture);             Settings.Dmm.WriteString("TRIG:DEL " + strDelay, true);             // Set trigger count to infinity - requires Firmware version 2.11 or above             Settings.Dmm.WriteString("TRIG:COUN INF"true);         }         CheckDMMError(Settings.Dmm);                            // Check the DMM for errors         // Allow up to 25 seconds for DMM to make readings         Settings.Dmm.IO.Timeout = 25000;         // Start acquisition         Settings.Dmm.WriteString("SYST:BEEP"true);            // DMM beep         Settings.Dmm.WriteString("INIT"true);                 // Start         // Show progress bar         Dispatcher.BeginInvoke(new Action(() =>         {             if (Settings.ExtTrigger) pbrAcquisition.IsIndeterminate = true;             else pbrAcquisition.IsIndeterminate = false;                     pbrAcquisition.Visibility = Visibility.Visible;                     tbkAcquisition.Visibility = Visibility.Visible;                     tbkAcquisition.Text = "";                     btnStart.IsEnabled = false;         }));         double time, value = 0;         double totalTime = 0;    // beginning time         // Acquired number of samples         int noChunks;         // For external trigger acquisition should be stopped manually         if (Settings.ExtTrigger) noChunks = Int32.MaxValue;          else noChunks = totNoSamples / Settings.ChunkSize;                 stopWatch.Start();         // Acquire requested number of data chunks and check for abort condition         for (int i = 0; i < noChunks && !Settings.Abort; i++)         {             // Acquire and remove chunk from instrument buffer and save to file             Settings.Dmm.WriteString("DATA:REM? " + Settings.ChunkSize.ToString() + ",WAIT"true);             //Read the result into a ',' separated array             Object[] DCVResult = (object[])Settings.Dmm.ReadList(IEEEASCIIType.ASCIIType_Any, ",;");             for (int j = 0; j < Settings.ChunkSize && !Settings.Abort; j++)             {                 if (Settings.SampleRate == 0) time = 0;                 else time = totalTime + (double)(j + 1) / Settings.SampleRate;                 value = Convert.ToDouble(DCVResult[j]);                 // Save chunk to file                 string line = time.ToString(Settings.SampleRate == 0 ? "F0" : "F6"
CultureInfo
.InvariantCulture) +  "\t" + value.ToString("F6"CultureInfo.InvariantCulture);                 Settings.FileHandle.WriteLine(line);             }             Settings.FileHandle.Flush();             // update time             if (Settings.SampleRate > 0) 
totalTime += (double)(Settings.ChunkSize) / Settings.SampleRate;             // Display progress (for internal trigger remaining time; for external trigger elapsed time)             if (!Settings.ExtTrigger)             {                 int remainingTime = Settings.RecTime - (int)totalTime;                 double progress = 1 - ((double)(remainingTime) / Settings.RecTime);                 Dispatcher.BeginInvoke(new Action(() =>                 {                     pbrAcquisition.Value = 100 * progress;                     tbkAcquisition.Text = "Remaining time: " + remainingTime.ToString() + " s";                 }));                 // Show remaining time at DMM display                 Settings.Dmm.WriteString("DISP:TEXT \"" + remainingTime.ToString() + " s left\n" +                     value.ToString("F4") + " " + Settings.MeasurementUnit + 
"\n\nDAQ by D.A."
 + "\""true);             }             else    // for external trigger display elapsed time             {                 Dispatcher.BeginInvoke(new Action(() =>                 {                     tbkAcquisition.Text = String.Format("Time elapsed: {0:hh\\:mm\\:ss}"
stopWatch.Elapsed);                 }));                 // Show remaining time at DMM display                 Settings.Dmm.WriteString("DISP:TEXT \"" + String.Format("Elapsed: {0:hh\\:mm\\:ss}"
stopWatch.Elapsed) + "\n" + value.ToString("F4") + " " + Settings.MeasurementUnit + 
"\n\nDAQ by D.A."
 + "\""true);             }         }         // Measurement completed or aborted         Settings.FileHandle.Close();         // Double beep         Settings.Dmm.WriteString("SYST:BEEP"true);         Thread.Sleep(500);         Settings.Dmm.WriteString("SYST:BEEP"true);         Settings.Dmm.WriteString("DISP:TEXT \"DONE\""true);         Settings.Dmm.WriteString("*RST"true);         //Reset DMM     }     catch (Exception e)     {         MessageBox.Show("Acquisition error (" + e.Message + ")""Error"MessageBoxButton.OK, 
MessageBoxImage
.Error);     }     finally     {         // Hide progress bar         Dispatcher.BeginInvoke(new Action(() =>         {             btnStart.Background = Brushes.LightGray;             btnStart.IsEnabled = true;             pbrAcquisition.Visibility = Visibility.Hidden;             tbkAcquisition.Visibility = Visibility.Hidden;         }));     } }

Revision history

Version 1.0.3 (2016-11-11)

  • Resistance measurement added (2W and 4W)
  • Measurement ranges grouped by measurement type

Version 1.0.2 (2016-02-26)

  • Unlimited number of samples
  • Support for external trigger source
  • Selectable AC power frequency (50/60 Hz) for accurate timing