Monday, November 25, 2013

Continuous Measurements with a 34461A Digital Multimeter

I've heard this question being asked in the office and in the forums for a while now. How do I continuously sample with the 34461A? Continuously sampling is the term that we use to get the DMM to take readings without filling up it's reading memory. The 34461A has a 1000 reading limit for any single data file. If you are doing long term readings or taking fast readings this can fill up very quickly. But if you are willing to do a little bit of programming, you can bypass this limit and take as many readings as you need...at least until your computer's hard drive fills up.

The key with programming this DMM is SCPI programming. For those of you who aren't familiar with it, SCPI stands for Standard Commands for Programmable Instruments (SCPI). Wikipedia defines it as " (often pronounced "skippy") defines a standard for syntax and commands to use in controlling programmable test and measurement devices". To program using SCPI you need a programming language such as C, Labview, C#, Matlab,  or anything else that can allow you to send text messages to instruments.

The typical method of programming a DMM to take readings is to send the READ? command. This triggers the DMM and does a single reading. Agilent has provided some good examples for programming the Truevolt DMMs. These are available in C#, VB, Labview, and Matlab. You can find the link here: Truevolt DMM programming.

However to do continuous readings you need to use a different set of commands.

TRIGGER:COUNT - this command determines the number of triggers to send to the DMM.
INIT - this command tells the DMM to start its readings
DATA:REM? X, WAIT - This command tells the DMM to take an X number of readings from its reading buffer and send them to the PC. If the number of readings is not available then the WAIT parameter tells the DMM to wait until they are available before sending it over. This command and R? are the the most efficient reading commands for the DMM to process.

These commands are also supported on the 34410A and 34411A. I haven't tested but I think that the below code would work with those models as well. If it doesn't email me and I'll publish an update for those models.

I've pasted an example set of C# code below. If you are just interested in understanding how it works, here is a pseudo code explanation:

CONF:DCV Setup your reading Function
TRIG:COUNT 20000 Take 20000 readings
TRIG:SOUR IMM  Set the trigger sour to internal triggering
INIT  Start the readings
For (i = 0, i<1000, i++) start a for loop that runs 1000 times
    DATA:REM? 20, WAIT   Remove 20 readings at a time
Next i

The purpose of removing 20 readings at a time is to continuously empty the DMM's reading buffer. By removing a relatively small number of readings, this reduces any processing headroom that the DMM needs for data transfer. The benefit is that the DMM's reading speed and accuracy is not affected by the data transfer. The combination of reading 20 readings x 1000 loops gives us 20,000 readings.


Here's an example of the output from the code. This example shows the reading in the first column and the reading number in the second column. This proves that I just took 20,000 readings from my DMM and it took about 6 minutes 40 seconds to gather all those readings.



You can download the project containing the continuous measurements code here. As of the time publication of this, I do not have the code in any other language beside C#. However, you can easily use the SCPI commands in your language of preference.


The 34461A SCPI reference is contained in the Truevolt Operating and Service guide. The link is to the pdf document found on Agilent.com. The SCPI reference starts on page 101.


So without further ado, here is the code in C# to take continuous readings:


using System;

using System.Collections.Generic;

using System.Text;

using Ivi.Visa.Interop;



//'' """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

//''  © Agilent Technologies, Inc. 2013

//''

//'' You have a royalty-free right to use, modify, reproduce and distribute

//'' the Sample Application Files (and/or any modified version) in any way

//'' you find useful, provided that you agree that Agilent Technologies has no

//'' warranty,  obligations or liability for any Sample Application Files.

//''

//'' Agilent Technologies provides programming examples for illustration only,

//'' This sample program assumes that you are familiar with the programming

//'' language being demonstrated and the tools used to create and debug

//'' procedures. Agilent Technologies support engineers can help explain the

//'' functionality of Agilent Technologies software components and associated

//'' commands, but they will not modify these samples to provide added

//'' functionality or construct procedures to meet your specific needs.

//'' """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""





namespace ContinuousSampling

{

    class ContinousSampling

    {

        static void Main(string[] args)

        {

        

            ContinousSampling DmmClass = new ContinousSampling(); //Create an instance of this class so we can call functions from Main



            //For more information on getting started using VISA COM I/O operations see the app note located at:

            //http://cp.literature.agilent.com/litweb/pdf/5989-6338EN.pdf



            Ivi.Visa.Interop.ResourceManager rm = new Ivi.Visa.Interop.ResourceManager(); //Open up a new resource manager

            Ivi.Visa.Interop.FormattedIO488 myDmm = new Ivi.Visa.Interop.FormattedIO488(); //Open a new Formatted IO 488 session

            try

            {

                string DutAddr = "GPIB0::22"; //String for GPIB

                //string DutAddr = ""TCPIP0::169.254.4.61";  //Example string for LAN

                //string DutAddr = "USB0::0x0957::0x1A07::MY53000101"; //Example string for USB

               



                myDmm.IO = (IMessage)rm.Open(DutAddr, AccessMode.NO_LOCK, 2000, ""); //Open up a handle to the DMM

                myDmm.IO.Timeout = 5000;



                //First start with a reset state

                myDmm.IO.Clear(); //Send a device clear first to stop any measurements in process

                myDmm.WriteString("*RST", true); //Reset the device

                myDmm.WriteString("*IDN?", true); //Get the IDN string               

                string IDN = myDmm.ReadString();

                Console.WriteLine(IDN); //report the DMM's identity



                //Configure the 34461 DCV Measurement state

                myDmm.WriteString("CONF:VOLT:DC 10", true);              //Configure DCV, 10V range

                myDmm.WriteString("VOLT:IMP:AUTO OFF", true);       //10M input impedance setting , "ON" sets >10Gohm

                myDmm.WriteString("SENS:VOLT:DC:ZERO:AUTO OFF", true);   // Autozero Off               

                myDmm.WriteString("VOLT:NPLC .02", true);               //Set Integration Time;Resolution to .02 PLC (333uSec)

                DmmClass.CheckDMMError(myDmm);                      //Check the DMM for errors





                //Configure trigger settings

                myDmm.WriteString("TRIG:SOUR IMM", true);                //Set trigger source to internal                               

                myDmm.WriteString("TRIG:DEL:AUTO OFF", true);            //Turn OFF Automatic trigger delay

                myDmm.WriteString("TRIG:DEL .019", true);                   // Set trigger delay to 19.6 msec (Delay + Integration time = 20msec               

                myDmm.WriteString("TRIG:COUN 50000", true);                  //Set trigger count to 50,000               

                DmmClass.CheckDMMError(myDmm);                      //Check the DMM for errors

                            

                //Trigger and make the readings

                myDmm.IO.Timeout = 10000;                             //Allow 10 seconds for DMM to make readings               



                //Here's the code to continously get the readings                               

                List<double> AllReadings = new List<double>();

                //Do a single init to start all the readings. The trigger count set above will ensure that we get our readings

                myDmm.WriteString("INIT", true);    



                DateTime time = DateTime.Now;  //Set a timer to measure the time difference to get all the readings

                int SampleCnt = 0;

                //Get 50,000 readings. We use DATA:REM to get a small chunk of readings at a time so transfer rates

                //won't affect measurements

                for (int i = 0; i<1000; i++)

                {

                   

                    myDmm.WriteString("DATA:REM? 50,WAIT",true);

                    Object[] DCVResult = (object[])myDmm.ReadList(IEEEASCIIType.ASCIIType_Any, ",;"); //Read the result into an array that is , separated

                    foreach (double Reading in DCVResult)

                    {                       

                        AllReadings.Add(Convert.ToDouble(Reading)); //Convert the readings and add it to the AllReadings List if you want a double arry                                             

                    }

                }

                TimeSpan Diff1 = DateTime.Now.Subtract(time); //Get the difference in time that it took to get 50K readings

               

                //Show the readings in a console

                foreach (double rdg in AllReadings)

                {

                    Console.WriteLine(rdg + "," + SampleCnt.ToString());

                    SampleCnt++;

                }

                Console.WriteLine("Total time for readings = " + Diff1); //Finally report the difference in time.

            }

            catch (Exception e)

            {

                Console.WriteLine("Error occured: " + e.Message);

            }

            finally

            {

                //Close out your resources

                try { myDmm.IO.Close(); }

                catch { }

                try { System.Runtime.InteropServices.Marshal.ReleaseComObject(myDmm); }

                catch { }

                try

                {

                    System.Runtime.InteropServices.Marshal.ReleaseComObject(rm);

                }

                catch { }

                Console.WriteLine("Press any key to continue...");

                Console.ReadKey();



            }

        }

        /// <summary>

        /// Checks the error queue and throws an exception if error

        /// </summary>

        /// <param name="myDmm"></param>

        public void CheckDMMError(FormattedIO488 myDmm)

        {



            myDmm.WriteString("SYST:ERR?", true);

            string errStr = myDmm.ReadString();



            if (errStr.Contains("No error")) //If no error, then return

                return;

            //If there is an error, read out all of the errors and return them in an exception

            else

            {

                string errStr2 = "";

                do

                {

                    myDmm.WriteString("SYST:ERR?", true);

                    errStr2 = myDmm.ReadString();

                    if (!errStr2.Contains("No error")) errStr = errStr + "\n" + errStr2;



                } while (!errStr2.Contains("No error"));

                throw new Exception("Exception: Encountered system error(s)\n" + errStr);

            }



        }

    }
}