Skip to main content
Skip table of contents

10.5 - Setting Up Specific Timer Modes (Applies to UD-Series)

The U3, U6, and UE9, all use the same timer/counter system, but some details are unique to each device to consult the user's guide for each. The number of timers/counters available for the U3, U6, and UE9, are 2/2, 4/2, and 6/2. The UE9 has different timer clock options than the U3 and U6. The UE9 does not support pin-offset, so the timers/counters always start at FIO0.

10.5.1 PWM Out

There are two timer / counter modes for pulse width modulation (PWM), one is 16 bit, the other 8 bit. These, and the available timer clocks are described in your LabJack User's manual and vary depending on the device. The setup, however, is largely the same. Here's some sample script for setting up and starting a PWM8 on FIO4. As always, we assume you've done using() and include() someplace else and defined ID appropriately:

//Set the timer/counter pin offset to 4, which will put the first timer/counter on FIO4.
AddRequest (ID, LJ_ioPUT_CONFIG, LJ_chTIMER_COUNTER_PIN_OFFSET, 4, 0, 0)
//Use the 48 Mhz with divisor timer clock base (U3 or U6).
AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chTIMER_CLOCK_BASE, LJ_tc48MHZ_DIV, 0, 0)
//Set the divisor to 48 so the resulting timer clock is 1 MHz.
AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chTIMER_CLOCK_DIVISOR, 48, 0, 0)
//Enable 1 timer.
AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chNUMBER_TIMERS_ENABLED, 1, 0, 0)
//Configure Timer0 as 8-bit PWM. Frequency will be 1M/256 = 3906 Hz.
AddRequest(ID, LJ_ioPUT_TIMER_MODE, 0, LJ_tmPWM8, 0, 0)
//Set the PWM duty cycle to 50%.
AddRequest(ID, LJ_ioPUT_TIMER_VALUE, 0, 32768, 0, 0)
//Execute the requests.
GoOne(ID)

We've explained most of these commands already. The only one new is the LJ_ioPUT_TIMER_VALUE. This sets the PWM duty cycle. Even though we are using an 8 bit PWM, this takes a 16 bit number. 32768 is half way into a 16 bit unsigned integer, so this results in a 50% duty cycle PWM. The general form of this command is:

LabJack ID, LJ_ioPUT_TIMER_VALUE, Timer #, Value, 0, 0

The two modes are:

LJ_tmPWM8
LJ_tmPWM16

Sample file: LJGuideSamples\TimerPWM.ctl

10.5.2 Period In

There are four Timer modes that allow you to measure the number of clock cycles between consecutive rising or falling edges. Two 16 bit, and two 32 bit. Using these modes is just a matter of performing all the basic steps we've described:

1) Enable a Timer:

AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chNUMBER_TIMERS_ENABLED, 1, 0, 0)

2) Set the mode:

AddRequest(ID, LJ_ioPUT_TIMER_MODE, 0, LJ_tmRISINGEDGES32, 0, 0)

The four modes are:

LJ_tmRISINGEDGES32
LJ_tmFALLINGEDGES32
LJ_tmRISINGEDGES16
LJ_tmFALLINGEDGES16

Note the plural form of Edge!

3) Set the clock frequency and divisor:

// use system clock so it works on U3, U6 and UE9:
AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chTIMER_CLOCK_BASE, LJ_tcSYS, 0, 0)

4) GoOne() to actually execute the commands:

GoOne(ID)

5) Create a channel to read the Timer. I/O Type is Timer, Channel # is the timer #, in this case 0.

The difference between the rising and falling edge versions of these modes is self explanatory. The 32 bit versions allow you to measure longer lengths with higher clock frequencies, and thus higher resolution for long periods, but are subject to small errors because it is interrupt driven. If your lengths are short enough that the edges will always occur within 65535 clock cycles, you should use the 16 bit versions as they are not subject to the interrupt errors.

If you want to read the timer from script, you can use LJ_ioGET_TIMER:

private datain
eGet(ID, LJ_ioGET_TIMER, 0, @datain, 0)

Sample file: LJGuideSamples\TimerPeriodIn.ctl

10.5.3 Duty Cycle In

Duty cycle in is similar to setup as period in. The difference is that duty cycle in returns two values, the number of clock cycles the signal is high and the number of cycles the signal is low packed into one 32 bit number. These two values, therefore, are 16 bit, so you'll need to pick a clock frequency and divisor that won't overflow the 65535 counts possible. Setting up these modes is just a matter of performing all the basic steps we've described:

1) Enable a Timer:

AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chNUMBER_TIMERS_ENABLED, 1, 0, 0)

2) Set the mode:

AddRequest(ID, LJ_ioPUT_TIMER_MODE, 0, LJ_tmDUTYCYCLE, 0, 0)

3) Set the clock frequency and divisor:

// use 48MHz clock base with divisor = 48 to get 1 MHz timer clock:
AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chTIMER_CLOCK_BASE, LJ_tc48MHZ_DIV, 0, 0)
AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chTIMER_CLOCK_DIVISOR, 48, 0, 0)

4) GoOne() to actually execute the commands:

GoOne(ID)

5) Create a channel to read the Timer. I/O Type is Timer, Channel # is the timer #, in this case 0.

The tricky part is actually parsing the data, since it is actually two different values packed into one number. The best way to do this is similar to the way we dealt with resetting counters, by creating extra, psuedo-channels to store the parsed data:

6) Create two more channels, one called TimeHigh, one called TimeLow. Device Type is Test,D# = 0, I/O Type = A to
D, Chan # = 0 and most importantly, Timing = 0.

7) Click Apply to save your new channels, then click on the + next to CHANNELS: in the Workspace, then click on your Timer channel. We'll assume you called that channel RawDuty.

8) Click on the Event tab when the Channel view appears. Enter the follow script to parse the timer reading and click Apply:

TimeHigh.AddValue(RawDuty[0] % 0x10000) // LSW
TimeLow.AddValue(floor(RawDuty[0] / 0x10000)) // MSW

This will split the single 32 bit reading into two separate readings and place them in their own channels.

Sample file: LJGuideSamples\TimerDuty.ctl

10.5.4 Firmware Counter In

This Timer mode works similar to a counter, but uses an interrupt routine to increment the counter so can't handle real high speed counts, and has a bit more internal overhead than a regular counter. Setting it up is basically the same as period in:

1) Enable a Timer:

AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chNUMBER_TIMERS_ENABLED, 1, 0, 0)

2) Set the mode:

AddRequest(ID, LJ_ioPUT_TIMER_MODE, 0, LJ_tmFIRMCOUNTER, 0, 0)

3) GoOne() to actually execute the commands:

GoOne(ID)

4) Create a channel to read the Timer. I/O Type is Timer, Channel # is the timer #, in this case 0.

You can reset the timer to 0 by using LJ_ioPUT_TIMER_VALUE, put please read the section on resetting counters and how to get around it.

AddRequest(ID, LJ_ioPUT_TIMER_VALUE, 0, 0, 0, 0)

Sample file: LJGuideSamples\TimerFirmCount.ctl

10.5.5 Firmware Counter In with Debounce

This Timer mode works the same as Firmware Counter In, but introduces a debounce circuit for mechanical switch
counting. It is really designed for frequencies less than 10hz, mostly push-button and reed-switch detection. Setting it up is similar to the regular Firmware Counter In, but has some extra steps to set the debounce settings:

1) Enable a Timer:

AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chNUMBER_TIMERS_ENABLED, 1, 0, 0)

2) Set the mode:

AddRequest(ID, LJ_ioPUT_TIMER_MODE, 0, LJ_tmFIRMCOUNTERDEBOUNCE, 0, 0)

3) Set the debounce settings to a single 87ms period, positive edges counted:

AddRequest(ID, LJ_ioPUT_TIMER_VALUE, 0, 257, 0, 0)

4) GoOne() to actually execute the commands:

GoOne(ID)

6) Create a channel to read the Timer. I/O Type is Timer, Channel # is the timer #, in this case 0.

You can reset the timer to 0 by using LJ_ioPUT_TIMER_VALUE, put please read the section on resetting counters and how to get around it.

AddRequest(ID, LJ_ioPUT_TIMER_VALUE, 0, 0, 0, 0)

Sample file: LJGuideSamples\TimerFirmCount.ctl

10.5.6 Frequency Out

Frequency out is similar to PWM, but outputs a 50% duty cycle square wave. Because its fixed at 50% duty, a wider range of frequencies are attainable. Setup is similar to PWM, except the Timer value we specify is another divisor for the clock:

//Set the timer/counter pin offset to 4, which will put the first timer/counter on FIO4.
AddRequest (ID, LJ_ioPUT_CONFIG, LJ_chTIMER_COUNTER_PIN_OFFSET, 4, 0, 0)

// use 48MHz clock base with divisor = 48 to get 1 MHz timer clock:
AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chTIMER_CLOCK_BASE, LJ_tc48MHZ_DIV, 0, 0)
AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chTIMER_CLOCK_DIVISOR, 48, 0, 0)

//Enable 1 timer.
AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chNUMBER_TIMERS_ENABLED, 1, 0, 0)

//Configure Timer0 as Frequency out.
AddRequest(ID, LJ_ioPUT_TIMER_MODE, 0, LJ_tmFREQOUT, 0, 0)

//Set the second divisor to 5 (x2), yielding a frequency of 100khz
AddRequest(ID, LJ_ioPUT_TIMER_VALUE, 0, 5, 0, 0)

//Execute the requests.
GoOne(0)

Sample file: LJGuideSamples\TimerFreqOut.ctl

10.5.7 Quadrature

The quadrature Timer mode is designed explicitly for use with quadrature encoders. A quadrature encoder is a device that allows you to determine the absolute position of a rotating shaft. It does this by generating two pulses with each part of a rotation (how small of a rotation a "part" is depends on the encoder). One pulse will come before the other if the shaft is rotating in one direction, and the pulse order is flipped if the shaft is rotating in the other direction. The LabJack quadrature timer reads both these pulses and increments or decrements the timer reading depending on which pulse occurs first.

Because it takes two pulse signals coming in on two wires, the quadrature mode requires two timers, even though there is only one reading. The two timers have to be adjacent pairs, with the even timer as quadrature channel A, and the odd timer as quadrature channel B. Reading either timer returns the same, signed 32 bit count, and writing a zero to either timer resets both. Here's some DAQFactory script to initialize the quadrature mode on timers 0 and 1:

AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chNUMBER_TIMERS_ENABLED, 2, 0, 0)
//Configure Timer0 as quadrature.
AddRequest(ID, LJ_ioPUT_TIMER_MODE, 0, LJ_tmQUAD, 0, 0)
//Configure Timer1 as quadrature.
AddRequest(ID, LJ_ioPUT_TIMER_MODE, 1, LJ_tmQUAD, 0, 0)
GoOne(ID)

As you can see, its one of the easier timers to setup since it doesn't require the internal clock. The easiest way to read the timer is to create a Timer channel: I/O Type is Timer, Channel # is the timer #, in this case 0, Timing can be whatever update interval you would like. Alternatively, you can use LJ_ioGET_TIMER to retrieve the reading from script:

private datain
eGet(ID, LJ_ioGET_TIMER, 0, @datain, 0)

10.5.8 Timer Stop

Timer stop allows you to stop a particular (even numbered) timer after a certain number of pulses is received on the odd numbered timer stop timer pin. This is especially useful when used with frequency or PWM out to drive a stepper motor a certain number of pulses. For example, to generate exactly 1000 pulses on Timer 0, we'd setup timer 0 as frequency out, and timer 1 in timer stop mode and tie the two output pins together. You'll recognize the first part from the frequency out section:

//Set the timer/counter pin offset to 0, which will put the first timer/counter on FIO0.
AddRequest (ID, LJ_ioPUT_CONFIG, LJ_chTIMER_COUNTER_PIN_OFFSET, 0, 0, 0)

// use 48MHz clock base with divisor = 48 to get 1 MHz timer clock:
AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chTIMER_CLOCK_BASE, LJ_tc48MHZ_DIV, 0, 0)
AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chTIMER_CLOCK_DIVISOR, 48, 0, 0)

//Enable 2 timers.
AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chNUMBER_TIMERS_ENABLED, 2, 0, 0)

//Configure Timer0 as Frequency out.
AddRequest(ID, LJ_ioPUT_TIMER_MODE, 0, LJ_tmFREQOUT, 0, 0)

//Set the second divisor to 5, yielding a frequency of 100khz
AddRequest(ID, LJ_ioPUT_TIMER_VALUE, 0, 5, 0, 0)

//Configure Timer1 as timer stop:
AddRequest(ID, LJ_ioPUT_TIMER_MODE, 1, LJ_tmTIMERSTOP, 0, 0)

// set number of pulses:
AddRequest(ID, LJ_ioPUT_TIMER_VALUE, 1, 1000, 0, 0)

//Execute the requests.
GoOne(0)

Once the 1000 pulse are complete, Timer 0 will stop. To restart it, you'll need to reconfigure the timers by simply rerunning the above script.

Add a digital line to control the direction and you have a very easy stepper controller. But if you want it even easier, you can use a Channel event to allow a channel to trigger the pulses. To do this:

1) Create a sequence called PulseOut with the above script, replacing the 1000 in the last AddRequest with NumPulses[0]:

....
AddRequest(ID, LJ_ioPUT_TIMER_MODE, 1, LJ_tmTIMERSTOP, 0, 0)
// set number of pulses:
AddRequest(ID, LJ_ioPUT_TIMER_VALUE, 1, NumPulses[0], 0, 0)
....

2) Create a new channel, call it NumPulses. Device Type = Test, D# = 0, I/O Type = D to A, Chan # = a unique number (if you are using more than 1 Test D/A channel). Click Apply.

3) Click on the + next to CHANNELS: in the Workspace if not already expanded and click on the NumPulses channel.

4) Click on the Event tab, and put this script in:

beginseq(PulseOut)

Now, you can use the various DAQFactory components to simply set the NumPulses channel and the desired length pulse train will be outputted. Just remember that sliders and knobs will continuously update this channel and so are not good for changing NumPulses since the pulse train will likely take longer than the update speed. You can also change NumPulses in script:

NumPulses = 500

Just remember that as soon as NumPulses is set, the pulse train will start.

Sample file: LJGuideSamples\TimerStop.ctl

10.5.9 System Timer In

This mode allows you to read the free-running internal 64 bit system timer. The frequency of this timer is 750khz for the UE9 and 4MHz for the U3. Since DAQFactory's clock is precise to 1 microsecond (1 MHz), and there is a built in latency of a few milliseconds to actually read the LabJack, there are really only two uses for this timer. The first is when doing triggered stream. Here, DAQFactory has no way of determining the time of each scan, so we can use the system timer to apply a high precision time stamp as long as we include the timer in the stream. This is described in the section on Triggered Streaming.

The other use is when you need a high precision time stamp on another timer or counter read. Since all the timer and counter reads are done with a single call to the device, there is no software latency if the desired timers and the timer setup as system timer are read at the same time. This is as simple as making sure all your LJ_ioGET_TIMER_VALUE requests are together. This can also be achieved if you are using Channels to retrieve your timer readings as long as your timers all have the same Timing and Offset.

Depending on how long your experiment runs, you may be able to get away with only SYSTIMERLOW. For the UE9 at 750khz, the low timer will roll over every 5726 seconds, while the U3 at 4MHz rolls over in 1073 seconds. Here's the script to do both low and high system timers for longer experiments:

AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chNUMBER_TIMERS_ENABLED, 2, 0, 0)
//Configure Timer0 as timer low.
AddRequest(ID, LJ_ioPUT_TIMER_MODE, 0, LJ_tmSYSTIMERLOW, 0, 0)
//Configure Timer1 as timer high.
AddRequest(ID, LJ_ioPUT_TIMER_MODE, 1, LJ_tmSYSTIMERHIGH, 0, 0)
GoOne(ID)

Of course you'll probably have more than 2 timers enabled, or perhaps a counter or two.

If you actually need the high double-word of the system timer, you are going to end up with the time spread across two channels. You'll have to combine them. The easiest way is probably to use a calculated V channel. Here's how to do it, plus convert the counts to actual seconds:

1) Right click on CHANNELS: under V: in the Workspace and select Add V Channel. Give it a name, such as LJSystemClock.

2) In the Expression area enter the following and click Apply:

(SysTimerLow + SysTimerHigh << 32) / 4e6

This assumes you named your timer channels SysTimerLow and SysTimerHigh. It also assumes a U3 with a 4Mhz clock. For the UE9, change the 4e6 (4 million) to 750e3 (750 thousand).

You can now use this V channel anywhere you would a regular channel. You just have to prepend V. in front of the channel name:

V.LJSystemClock

Sample file: see Triggered Streaming

Please note that DAQFactory uses 64 bit double precision floating point for all numbers. This representation has 52 bits of precision on the integer side, so you can only really store up to 52 bits of this counter. As the counter gets above 252, you will lose precision in the low order bits.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.