Retriggerable Analog Measurements with National Instruments’ DAQMX

usb6366 retriggerable scope

We’ve been using a National Instruments (NI) system for the last year on a pretty complicated system for one of our clients.  That system converts laser energy to voltage and takes 16 simultaneous 16-bit analog samples.  Each of the 16 channels takes more than a thousand samples per millisecond, and reports the data back to a PC running a Microsoft Visual Studio application.

In our earliest endeavors we ran into problems with timing that stemmed from the National Instruments hardware drivers.  It was no problem to take a single trigger initiated sample of all of our channels.  But when we went to “re-trigger” the analog measurements 5-10 milliseconds would elapse before the sampling occurred.  This would not work for our system.  We needed analog data each and every millisecond, without fail.

The trick to getting this functionality relied on creating a retriggerable digital task, and coupling that with an analog callback event in Visual Studio.  In short you create an analog task in NI’s DAQMX driver software that collects X number of samples and reports them back with the analog callback to your software.  You also create a digital trigger event that generates X number of pulses whenever a rising edge is seen.  The pulses generated by the digital task become the sample clock for the analog task.  Since the digital task is retriggerable, and the analog sampling is also continuous (it just reports when it has a specific number of samples accumulated) this method allows you to collect data continuously.

In the scope capture above you can see the trigger signal (yellow) and the sample clocks (green) generated by the digital task.  The analog signal (purple) is sampled with each rising edge transition of the sample clock.  In this case the scope capture shows 1000 samples, each happening every 0.5uS.

For the test above we used NI’s USB-6366, a pretty powerful tool.  It allows up to 8 channels analog of data to be read simultaneously, as well as providing a number of counters and digital i/o.  Better yet, it can read 2 million samples per second per channel, and gets the data back to you over the USB port.  With a little diligence you could create a nice 8 channel instrument under PC control using the USB-6366.

Below is a screen capture of a simple Visual Studio 2010 program that captures and displays a single channel.  This is the same signal that’s shown in the screen capture of the oscilloscope.

usb6366 vs2010 form1

The source code for this Visual Studio program is here.  The main tasks associated with retriggerable analog measurements are located in the DAQ_Module.vb file.  The task definitions are also shown below for reference.

You’ll need to download and install the DAQMX drivers and include the appropriate namespace in your Visual Studio program.  This example measures voltages in the range of +/-10V and returns a signed INT16 value for each sample.

#Region "DAQ Tasks"

    Public Sub CreateTask_Analog()


            ' myTask_Trigger uses PFIO as an input and starts the analog sample clock for the my_Task analog.  The clock is output on PFI12 which is the counter 0 default output. 
            If runningTask_Trigger Is Nothing Then

                'Create New Task
                myTask_Trigger = New NationalInstruments.DAQmx.Task()

                'Pulse task outputs on PFI12
                myTask_Trigger.COChannels.CreatePulseChannelTime("Dev1/ctr0", "", COPulseTimeUnits.Seconds, COPulseIdleState.Low, AnalogTriggerDelay, (AnalogSampleClock / 2), (AnalogSampleClock / 2))

                'Digital edge and retriggerable
                myTask_Trigger.Triggers.StartTrigger.ConfigureDigitalEdgeTrigger("PFI0", DigitalEdgeStartTriggerEdge.Rising)
                myTask_Trigger.Triggers.StartTrigger.Retriggerable = True

                'Finite pulse train of predetermined length 
                myTask_Trigger.Timing.ConfigureImplicit(SampleQuantityMode.FiniteSamples, AnalogSamplesPerChannel)

                'Configure Delay Property
                myTask_Trigger.COChannels.All.EnableInitialDelayOnRetrigger = True

                ''Set to false to allow on non-GUI thread
                'myTask_Trigger.SynchronizeCallbacks = SynchronizeTasks

                runningTask_Trigger = myTask_Trigger

            End If

            ' myTask_Analog sets up the analog read a single channel channel which is returmed to the asynchronous callback at AnalogInCallback.
            If runningTask_Analog Is Nothing Then

                myTask_Analog = New NationalInstruments.DAQmx.Task()

                myTask_Analog.AIChannels.CreateVoltageChannel("Dev1/ai0", "", CType(-1, AITerminalConfiguration), -10.0, 10.0, AIVoltageUnits.Volts)

                '' You can add more channels but you'll then need to redimension the first element of copyArray and analogsamplesArray
                'myTask_Analog.AIChannels.CreateVoltageChannel("Dev1/ai1", "", CType(-1, AITerminalConfiguration), 0.0, 10.0, AIVoltageUnits.Volts)
                'myTask_Analog.AIChannels.CreateVoltageChannel("Dev1/ai2", "", CType(-1, AITerminalConfiguration), 0.0, 10.0, AIVoltageUnits.Volts)
                'myTask_Analog.AIChannels.CreateVoltageChannel("Dev1/ai3", "", CType(-1, AITerminalConfiguration), 0.0, 10.0, AIVoltageUnits.Volts)
                'myTask_Analog.AIChannels.CreateVoltageChannel("Dev1/ai4", "", CType(-1, AITerminalConfiguration), 0.0, 10.0, AIVoltageUnits.Volts)
                'myTask_Analog.AIChannels.CreateVoltageChannel("Dev1/ai5", "", CType(-1, AITerminalConfiguration), 0.0, 10.0, AIVoltageUnits.Volts)
                'myTask_Analog.AIChannels.CreateVoltageChannel("Dev1/ai6", "", CType(-1, AITerminalConfiguration), 0.0, 10.0, AIVoltageUnits.Volts)
                'myTask_Analog.AIChannels.CreateVoltageChannel("Dev1/ai7", "", CType(-1, AITerminalConfiguration), 0.0, 10.0, AIVoltageUnits.Volts)

                'Use counter:  Continuous samples - Configure Timing Specs generates pulses as sample clock at PFI12 triggered by signal at PFI0. 
                myTask_Analog.Timing.ConfigureSampleClock("PFI12", 2000000, SampleClockActiveEdge.Rising, SampleQuantityMode.ContinuousSamples, 720000)

                ' Verify the Task

                ' Commit the Task

                runningTask_Analog = myTask_Analog

                'Access properties
                myAsyncCallback = New AsyncCallback(AddressOf AnalogInCallback)

                analogRawReader = New AnalogUnscaledReader(myTask_Analog.Stream)
                ' set to true to have the callback on the GUI thread
                analogRawReader.SynchronizeCallbacks = False
                analogRawReader.BeginReadInt16(AnalogSamplesPerChannel, myAsyncCallback, myTask_Analog)

            End If

        Catch exception As DaqException
            Exceptions += "CreateTask_Analog():" + exception.ToString + vbCrLf + vbCrLf

        Catch ex As Exception
            Exceptions = Exceptions + "CreateTask_Analog():" + ex.ToString + vbCrLf + vbCrLf

        End Try

    End Sub

    ' cancel the analog task
    Public Sub UnloadTask_Analog()


            If Not runningTask_Analog Is Nothing Then
                runningTask_Analog = Nothing
                myAsyncCallback = Nothing
            End If

            If Not runningTask_Trigger Is Nothing Then
                runningTask_Trigger = Nothing
            End If

        Catch exception As DaqException
            Exceptions = Exceptions + "UnloadTask():" + exception.ToString + vbCrLf + vbCrLf
        End Try

    End Sub

    ' this callback copies the analog data to an array we can use elsewhere
    Public Sub AnalogInCallback(ByVal ar As IAsyncResult)


            copyArray = analogRawReader.EndReadInt16(ar)
            If analogsampleReady = False Then
                analogsamplesArray = copyArray
                analogsampleReady = True
            End If

            ThreadTracker.AnalogCallBack = Thread.CurrentThread.ManagedThreadId.ToString

            analogRawReader.BeginReadInt16(AnalogSamplesPerChannel, myAsyncCallback, myTask_Analog)

        Catch exception As DaqException
            Exceptions += "AnalogInCallback():" + exception.ToString + vbCrLf + vbCrLf

        Catch ex As Exception
            Exceptions = Exceptions + "AnalogInCallback():" + ex.ToString + vbCrLf + vbCrLf

        End Try

    End Sub

#End Region

The physical connections to the USB-6366 terminal blocks are also pretty straightforward.  PFI12 is shown, and may be probed to verify the sample clock generation, but it is routed internally to act as the DAQ’s sample clock, and no external connections are necessary.


usb6366 connections

Thermistor and Thermocouple Temperature Logger Part 2



This week in the epic adventure I like to call Thermistor and Thermocouple Temperature Logger Part 2, I took a look at the thermistor portion of my circuit.

[Read more…]

Thermistor and Thermocouple Temperature Logger


(Image: scope capture of three T type thermocouples being read with SPI and the MAX31855 ICs)

We’ve been developing a barbecue controller as part of an R&D project.  My portion of the project is interfacing the temperature sensors to the overall system.  I’ve decided to include both thermistors and thermocouple sensors, as well as battery backup and data logging functions.  In the end the project won’t need all of that but  I wanted to come up with something “stand-alone” I could develop.

Thus far I’ve designed the schematic and first revision circuit board.  I’ve been able to test the temperature sensors, but not much else.  I did want to share the MAX31855 interface, which I found pretty cool and a definite time saver.  The Maxim Integrated Circuits’ MAX31855 (datasheet) is a cold-junction compensated thermocouple interface.  It provides a 14-bit signed digital value that is temperature in 0.25 degree C increments with +/-2 degree C accuracy.  You buy a chip specific to the thermocouple you’re using.  In our case I placed 4 of these ICs on the board and selected “T” type thermocouples.  The device uses an SPI interface, although it only outputs data.  You can see a partial schematic below (click to enlarge).



For the microcontroller interface I used the PIC16F1789, a Microchip product.  This part has 12-bit analog to digital converters and operates between 2.5V-5.5V, which works well with the battery powered system I’ve envisioned,

Configuring the SPI interface is pretty straightforward, and here is the code I used to do this.

/* Configure_SPIPort:
   Configures registers used to send and receive using the SPI hardware       */
void Configure_SPIPort(void)
    SSPSTATbits.SMP = 0;  // Input data sampled at middle of data output time
    SSPSTATbits.CKE = 0;   // Transmit occurs on transition from Idle to active clock state

    SSPCONbits.CKP = 0; //  Idle state for clock is a low level
    SSPCONbits.SSPM = 2; //SPI Master mode, clock = FOSC/64
    SSPCONbits.SSPEN = 1; // Enables serial port and configures SCK, SDO, SDI and SS as the source of the serial port pins

    SSPCON2 = 0;
    SSPCON3 = 0;

I’m running the SPI at 500KHz, which is a little fast for some SPI ICs, but the MAX31855 is spec’ed at 5MHz max speed.  The actual communication with a MAX31855 is shown in the code below.  The code is repeated for each thermocouple (with different variables).…

/* Read_Thermocouples:
 Reads temperature signals from MAX31855 using the SPI interface.  
void Read_Thermocouples(void)
    _CS_TEMP1 = 0;  // select the first MAX31855
    SSPBUF = 0xAA;          // doesn't matter what we write but H'AA' easy to see on scope
    while(SSPSTATbits.BF == 0); // wait for 8 bits to clock in
    MyTemp.B[1] = SSPBUF; // save data
    SSPBUF = 0xF0; // doesn't matter what we write but H'F0' easy to see on scope differs / from H'AA'
    while(SSPSTATbits.BF == 0); // wait for 8 bits to clock in
    MyTemp.B[0] = SSPBUF; // save data
    NV.Ints.Thermocouple1 = MyTemp.W[0]; // use union to combine bytes in 16 bit value, stick in reg
    _CS_TEMP1 = 1;   // de-select the MAX31855

    INDICATOR.Bits.THERMO1 = 0; // clear my fault flag
    if (NV.Ints.Thermocouple1 & 0x1 == 1) // check to see if an fault flag was set by MAX31855
        INDICATOR.Bits.THERMO1 = 1; // if so, then set flag in my structure
    NV.Ints.Thermocouple1 = NV.Ints.Thermocouple1>>2; // shift temperature data two to the right
    NV.Ints.Thermocouple1 = NV.Ints.Thermocouple1 & 0x3fff; // make sure upper 2 bits are clear
    if ((NV.Ints.Thermocouple1 & 0x2000) != 0 ) // check sign bit
        NV.Ints.Thermocouple1 += 0xc000; // if sign bit is set then extend sign to all 16 bits
    if (NV.Ints.Thermocouple1 == 0x1fff) // check to see if thermocouple is attached
        INDICATOR.Bits.THERMO1 = 1; // if not then set flag

The code deserves some decryption.  Here are some descriptions of the variables used above.
_CS_TEMP1: A constant that refers to the chip select pin.
INDICATOR.Bits.THERMO1: A variable structure that maintains fault conditions and other flags in the firmware.
NV.Ints.Thermocouple1: A variable structure of integers that can be stored in EEPROM and accessed via a serial UART, this is where the temperature data is stored.
MyTemp: This variable can be a little confusing in this context. It is not related to temperature.  MyTemp is a variable union that I often use as a temporary register to quickly break apart or assemble other variables.  For example, I can read two bytes from the SPI port and place them in byte sized members of the union and then use an integer sized member of the union to copy the bytes as a single 16-bit value.

union LongUnion
    long LW;
    unsigned long uLW;
    int W[2];
    unsigned int uW[2];
    char B[4];
    unsigned char uB[4];
union LongUnion

In my firmware I select the MAX31855 by asserting the chip select pin and then sending hexadecimal 0xAA and 0xF0.  The data I send doesn’t matter, but the PIC16F1789 needs to have the SSBUF register written to in order to begin clocking data out and reading data in.  The SSPSTATbits.BF variable is defined by Microchip in this part’s header file, and will be set when the SPI receive buffer is full.  After 16 bits of data are read the chip is de-selected.  The image at the head of this blog entry shows a scope capture of this process.

Once I’ve read 16 bits I need to convert the data to a temperature value.  Not all of the bits relate to temperature so I need to adjust the values I read somewhat.  Bit 0 is a fault flag (set if there is a fault) and bit 1 is reserved (read as 0).  Additionally, bit 15 is the sign bit (using two’s compliment format this bit is set if the value is negative).  In the firmware I check the fault flag (bit 0) and then shift the 16-bits I read from the MAX31855 two places to the right.  This leaves the sign flag in the 13th bit location.  I clear the upper two bits of the register which is kind of an old school operation.  In assembly code when you shift data the carry bit can be shifted into a register.  In C I’ve never seen a “1” get shifted in, but I’ve been doing this for so many years I just can’t stop.   I then check the sign bit and if it is set I set the two highest bits of the temperature data to maintain the two’s compliment formatting.

This diagram shows the process more visually.


There are some additional points related to the MAX31855 that deserve mention.

First, if the thermocouple is disconnected the datasheet states that the MAX31855 will respond with a cleared sign bit and all other temperature data bits set (0x1FFF in hexadecimal).  I didn’t see that, but also didn’t delve into why it didn’t happen.

There are an additional 16-bits of data that can be read from the MAX31855.  You would read this data by clocking in 16 more bits before setting the chip select pin.   This additional data includes the 12-bit internal temperature of the IC and more details on the nature of any fault that occurs.

Lastly, the MAX31855 calculates temperature when the chip is not selected for SPI communication.  It also requires a maximum of 100mS to take the measurements.  Therefore, the fastest you can sample a thermocouple using this IC is 10Hz.

Dual Axis Solar Panel Tracker / Controller Part 5


The saga of the dual axis solar panel controller continues.  As an R&D project I couldn’t put a lot of time into this design this week.  Here is what I did get accomplished.

[Read more…]

Dual Axis Solar Panel Tracker / Controller Part 4


I finally received the PCB for this design.  And in general it turned out okay.  There were a couple of shortcoming in my design that will force me to to re-spin the PCB, so it’ll be a while before I can finish everything up.  But having the PCB gives me the ability to debug my firmware and test the overall concept for this solar panel tracker.

[Read more…]

Simple and Low Cost Temperature and Humidity Sensor: Part 2


Above you can see the primary circuitry for the simple temperature and humidity sensor I described last week.  The board shown above is part of a client’s design, so there is quite a bit more circuitry than is required for the sensor I described last week.

[Read more…]

Simple and Low Cost Temperature and Humidity Sensor


Here’s a simple and low cost temperature and humidity sensor.  I’ve been developing a control system for an industrial application for one of our clients.  As part of that system we have a microcontroller located in a box with some other equipment, and thought it would be good to include some environmental monitoring.  Mostly we want to know if the things get too hot, or if they start to get wet due to maintenance people spraying down the outside of the equipment boxes (which are supposed to be water proof).

[Read more…]

Dual Axis Solar Panel Tracker / Controller Part 3


I’ve been working on a solar panel tracker/controller as an R&D project (previous blog entries part 1 and part 2).  I was able to finish the circuit board design a couple of weeks ago.  I want to panelize it with some other projects so I haven’t shipped it off yet.

[Read more…]

Voice Recording and Playback with an Arduino (BM023)

Back in the late ‘90s we created some small robots for a Microchip technical conference.  They rolled around and used a servo with an IR sensor mounted on it to detect objects.  They had a bit of a Wall-e look, long before that look was made cool by the movie.  Whenever they detected an object they would play a random message that we recorded onto an application.  I was thinking of those robots when I came up with the single message record/play breakout module.

[Read more…]

Dual Axis Solar Panel Controller / Tracker Part 2


Last week I discussed my latest project, an accelerometer based dual axis solar tracker (that blog is here).  When starting a project its easy to get lost in the details.  For example, this project has a whole host of possible control functions and interfaces.  I’ve always found it useful to start my work on the schematic and hardware.  So that’s what I did.

Since this is an R&D project I decided to use some components I haven’t used before.  This design will control a 24VDC brushed motor, and a 12VDC brushed motor.  I’m going to use two St Microelectronics PN:  VNH3SP30-E.  They’re rated for 40V and 30A (although there’s no way they can handle 30A without melting off the PCB).  One feature I like about these controllers is that they only need a single PWM channel for proportional control.  Two other digital channels are used to provide direction.  Here’s a section of the schematic that shows the connections between a PIC16F1789 and two of the motor controllers (click the image for a better view).


In the end, this control system will be used to control our dual axis controller that uses a worm gear for horizontal movement, and a linear actuator for vertical movement.  Here’s a photo of the assembly in front of the old college text books we could never bring ourselves to throw away.  Both the worm gear and linear actual came with feedback.  The worm gear was very expensive and has an encoder on the motor.  the linear actuator was pretty cheap and came with a potentiometer that failed in about a month.  That was one catalyst for attempting that has the position feedback on the control board via an accelerometer.


So for this first run at this design I generated a schematic (here’s a PDF), bill-of-materials (and another PDF), and a circuit board layout.

The circuit board ended up being about 2.5” x 3.5”.  Here’s what the top copper layer looks like.  The 6 dots on the upper and lower right-hand side of the board are for mounting automotive style blade fuses.

Finally, I ran though the list of components I selected to see if any were not able to operate over the industrial temperature range of –40C to 85C (or -40F to 185F).

Here is the column from my bill-of-materials.


There are a couple of interesting findings.  First, I have an 8MHz oscillator on the design that has the smallest range of operation (-20C to 70C).  I’m actually going to use the internal oscillator available in the microcontroller, and I don’t see clock timing to be an incredibly important issue in this design.  I added the oscillator part to the schematic as kind of a back-up plan but don’t plan on using it.  So I’ll ignore that problem for now.

The next lowest temp. range part is the PDV-P7002, a photocell I will be using to detect daylight.  For a part like this, whose resistance changes with light, I would guess that it goes “out-of-specification” outside of its operating range.  I doubt it quits working.  I’ll have to research that some more, but since I’m using the photocell as a yes/no type input I can accommodate a wide resistance variance.

That leaves me with some ceramic caps that don’t meet my operating temperature range, and I can certainly select a similar part with an extended temperature range, so I’m probably good there.

I guess there was one other issue.  The small metal buttons I have on this design (E-Switch parts) had no temperature rating.  I thought that was interesting.  These are not the kind of buttons you would use in an outdoor design, but no temperature rating?

And now for a sanity check.  Am I really going to run this design between –40C and 85C.  Nope.  This is R&D, it’ll spend its life in my office.  If this were a consulting contract we would design for this temperature but suggest our clients place test fixtures in the intended environment to collect operating data and/or make use of a temperature chamber for extended temperature testing.

That’s as far as I was able to get this week.  I’ll try to take some time over Christmas break to order the circuit board.  I need to panelize it with some other designs so it doesn’t cost an arm and a leg.  Hopefully I can begin writing code in January.