826
This is the technical wiki page for Sensoray's model 826, a versatile analog and digital I/O system on a PCI Express board. The board has 48 digital I/Os with edge detection, sixteen 16-bit analog inputs, eight 16-bit analog outputs, six 32-bit counter channels, a watchdog timer with fail-safe controller, and a flexible signal router.
Contents
|
Counters
Snapshot counts upon match
When a snapshot is caused by counts equal to a compare register, the snapshot counts will always equal the compare register value. Similarly, when a snapshot is caused by counts reaching zero, the snapshot counts will always be zero.
How to use interrupts
- I want to use interrupts to perform actions after a time delay. I see that counters have an "Interrupt System (IRQ)" signal but have no idea how to access it.
An interrupt request (IRQ) is generated whenever a counter snapshot is captured. Every IRQ source on the 826 is associated with a blocking API function, which manages IRQs for you so that you need not be concerned with the complexities of interrupts. In the case of counters, the blocking API function is S826_CounterSnapshotRead()
. Simply call this API function and counter IRQs will automatically be configured and handled.
A simple way to implement a delayed interrupt is to preload the counter with the desired time delay, configure it to count down, and have it capture a snapshot (and thus generate an IRQ) when it reaches zero counts. To wait for the interrupt, call S826_CounterSnapshotRead()
with a non-zero tmax
value (maximum wait time, which must be longer than the delay time). The function will return upon interrupt and you can then perform the desired actions. For example:
#include "826api.h" // Wait 0.5 seconds while other threads are allowed to run ------------------ // Configure the delay timer: S826_CounterModeWrite(0, 0, 0x01400020); // Configure counter0: 1 MHz down counter, auto preload @startup. S826_CounterPreloadWrite(0, 0, 0, 500000); // Delay time in microseconds (0.5 seconds). S826_CounterSnapshotConfigWrite(0, 0, // Configure snapshots: S826_SSRMASK_ZERO // capture snapshot when counts==0 | (S826_SSRMASK_ZERO << 16), // disable subsequent snapshots when counts==0 S826_BITSET); // don't alter any other snapshot enables // Now do the delay: S826_CounterStateWrite(0, 0, 1); // Start the delay timer running. S826_CounterSnapshotRead(0, 0, // Block while waiting for timer: NULL, NULL, NULL, // ignore snapshot counts, timestamp and reason S826_WAIT_INFINITE); // don't timeout printf("Delay time has elapsed!"); // TODO: INSERT YOUR DESIRED ACTIONS HERE
Note that the calling thread cannot do anything else while it waits for S826_CounterSnapshotRead()
to return, although other threads can still do productive work.
Periodic timer
A counter can be used as a periodic timer. To implement this, configure the counter so that it repeatedly counts down to zero and then preloads. The preload value determines the time period.
// Configure a counter as a periodic timer and start it running. void CreateTimer(uint board, uint chan, uint period) // period in microseconds { S826_CounterModeWrite(board, chan, 0x01C02020); // 1MHz down cnt; preload @start and ==0. S826_CounterPreloadWrite(board, chan, 0, period); // Set period in microseconds. S826_CounterSnapshotConfigWrite(board, chan, // Snapshot when counts==0. S826_SSRMASK_ZERO, S826_BITWRITE); S826_CounterStateWrite(board, chan, 1); // Start the timer running. }
A snapshot is captured every time zero counts is reached, which causes S826_CounterSnapshotRead()
to return. Prior to the function return, the calling thread is blocked while other threads are permitted to do productive work, resulting in efficient, event-driven operation.
// Wait for the next timer period. Returns 1=ok, 0=error. int WaitForTimer(uint board, uint chan) { return (S826_CounterSnapshotRead(board, chan, // Block until next period. NULL, NULL, NULL, // ignore snapshot values S826_WAIT_INFINITE) == S826_ERR_OK); }
Call a function periodically
- How can I use a counter to periodically call a function?
First, create a timer as shown above. For example, the following code creates a 10 millisecond timer using counter0:
CreateTimer(0, 0, 10000); // Create 10 ms (10000 us) timer and start it running.
Now run the following loop. Note that other threads can run while this thread is waiting for the next timer period.
// Repeat while no errors detected: while (WaitForTimer(0, 0)) { // Block until next timer period. PeriodicFunction(); // Execute the periodic function. }
Alternatively, if you need to perform other processing while waiting for the next period and cannot use another thread to do so, you can poll the counter by calling S826_CounterSnapshotRead()
with a zero (or other finite) wait time:
while (1) { // Repeat forever: uint errcode = S826_CounterSnapshotRead( // Poll to see if period has elapsed (don't block). 0, 0, NULL, NULL, NULL, 0); if (errcode == S826_ERR_OK) // If it's a new period PeriodicFunction(); // execute the periodic function. else if (errcode != S826_ERR_NOTREADY) // Else if fatal error detected break; // exit the polling loop. else // Else DoSomeOtherStuff(); // do other processing. }
Routing a counter output to DIO pins
The following function will route a counter's ExtOut signal to a DIO pin:
int RouteCounterOutput(uint board, uint ctr, uint dio) { uint data[2]; // dio routing mask if ((dio > 47) || (ctr > 5)) return S826_ERR_VALUE; // bad channel number if ((dio & 7) != ctr) return S826_ERR_VALUE; // counter output can't be routed to dio // Route counter output to DIO pin: S826_SafeWrenWrite(board, S826_SAFEN_SWE); // Enable writes to DIO signal router. S826_DioOutputSourceRead(board, data); // Route counter output to DIO data[dio > 23] |= (1 << (dio % 24)); // without altering other routes. S826_DioOutputSourceWrite(board, data); return S826_SafeWrenWrite(board, S826_SAFEN_SWD); // Disable writes to DIO signal router. }
Note that each DIO is associated with a specific counter and can only be routed to that counter's ExtOut. For example, counter3 can be routed to dio 3, 11, 19, 27, 35 or 43, or to any combination of these. This example shows how to route counter3's ExtOut signal to both dio3 and dio11:
RouteCounterOutput(0, 3, 3); // On board number 0, route counter3's ExtOut signal to dio3 RouteCounterOutput(0, 3, 11); // and to dio11.
Incremental encoders
Encoder wiring
- I want to connect two TTL/CMOS incremental encoders to counter channels 0 and 1 — what's the correct way to do this?
Connect the encoders as shown below. If the encoder has a "Z" (index) output, connect Z to the counter's +IX input.
Programming fundamentals
- Which functions should I use for incremental encoders?
The flexible counter architecture allows for many options, but basic operation works as follows:
First configure and enable the counter channel:
#include "826api.h" S826_CounterModeWrite(0, 0, 0x00000070); // Configure counter 0 as incremental encoder interface. S826_CounterStateWrite(0, 0, 1); // Start tracking encoder counts.
To read the instantaneous encoder counts without invoking a snapshot:
uint counts; S826_CounterRead(0, 0, &counts); // Read current encoder counts. printf("Encoder counts = %d\n", counts); // Display encoder counts.
When reading instantaneous counts you may need to know when the counts were sampled. You could rely on your software and operating system to sample the counts at precise times, but there's an easier and more accurate way: trigger a snapshot (via software) and then read the counts and sample time — accurate to within one microsecond:
uint counts; // encoder counts when the snapshot was captured uint timestamp; // time the snapshot was captured S826_CounterSnapshot(0, 0); // Trigger snapshot on counter 0. S826_CounterSnapshotRead(0, 0, // Read the snapshot: &counts, ×tamp, NULL, // receive the snapshot info here 0); // no need to wait for snapshot; it's already been captured printf("Counts = %d at time = %d\n", counts, timestamp);
For example, two snapshots allow you to measure speed:
uint counts0, counts1; // encoder counts when snapshot was captured uint tstamp0, tstamp1; // timestamp when snapshot was captured S826_CounterSnapshot(0, 0); // Trigger first snapshot. // TODO: WAIT AWHILE TO ALLOW ENCODER TO MOVE S826_CounterSnapshot(0, 0); // Trigger second snapshot. S826_CounterSnapshotRead(0, 0, // Read the first snapshot: &counts0, &tstamp0, NULL, // receive the snapshot info here 0); // no need to wait for snapshot; it's already been captured S826_CounterSnapshotRead(0, 0, // Read the second snapshot: &counts1, &tstamp1, NULL, // receive the snapshot info here 0); printf("Speed (counts/second) = %d\n", 1000000 * (counts1 - counts0) / (tstamp1 / tstamp0));
Encoder counts can be changed to an arbitrary value at any time. This is typically done when the encoder is at a known reference position (e.g., at startup or whenever mechanical registration is required), but not at other times as it would disrupt position tracking. To change the counts, write the new counts value to the Preload0 register and then call S826_CounterPreload()
to force a preload:
S826_CounterPreloadWrite(0, 0, 0, 12345); // Write desired counts value (12345) to Preload0 register. S826_CounterPreload(0, 0, 0, 0); // Jam Preload0 value into counter.
Using interrupts with encoders
This code snippet employs hardware interrupts to block the calling thread until the encoder counts equals a particular value. Other threads are allowed to run while the calling thread waits for the counter to reach the target value. A snapshot is captured when the target count is reached. The snapshot generates an interrupt request, which in turn causes S826_CounterSnapshotRead()
to return. The example ignores the snapshot counts (which will always equal the target value as explained here), the timestamp, and the reason code (which will always indicate a Match0 event).
// Wait for counts to reach 5000. Allow other threads to run while waiting. S826_CounterCompareWrite(0, 0, 0, // Set Compare0 register to target value: 5000); // 5000 counts (for this example) S826_CounterSnapshotConfigWrite(0, 0, // Enable snapshots: S826_SSRMASK_MATCH0, // when counts==Compare0 S826_BITWRITE); // disable all other snapshot triggers S826_CounterSnapshotRead(0, 0, // Wait for counter to reach target counts: NULL, NULL, NULL, // ignore snapshot counts, timestamp and reason S826_WAIT_INFINITE); // disable function timeout printf("Counter reached target counts");
In some cases you may want to wait for two different count values at the same time. The following example shows how to wait for the encoder counts to reach an upper or lower threshold (whichever occurs first). Furthermore, it will only wait for a limited amount of time. To set this up, the threshold values are programmed into Compare registers and then snapshots are enabled for matches to both Compare registers. To set a limit on how long to wait, a time limit value is specified by tmax
when calling S826_CounterSnapshotRead
.
Since snapshots can now be caused by different events, we must know what triggered a snapshot in order to decide how to handle it; this is indicated by the snapshot's reason flags. Alternatively, we could use the snapshot counts value, which will always equal the threshold value that triggered the snapshot.
// Wait for counts to reach 3000 or 4000, or 10 seconds to elapse, whichever comes first. uint counts; // encoder counts when the snapshot was captured uint timestamp; // time the snapshot was captured uint reason; // event(s) that caused the snapshot uint errcode; // API error code S826_CounterCompareWrite(0, 0, 0, 3000); // Set Compare0 register to low limit (3000 counts) S826_CounterCompareWrite(0, 0, 1, 4000); // Set Compare1 register to high limit (4000 counts) S826_CounterSnapshotConfigWrite(0, 0, // Enable snapshots: S826_SSRMASK_MATCH0 // when counts==low limit | S826_SSRMASK_MATCH1, // or when counts==high limit S826_BITWRITE); // disable all other snapshot triggers errcode = S826_CounterSnapshotRead(0, 0, // Wait for a snapshot: &counts, ×tamp, &reason, // receive the snapshot info here 10000000); // timeout if wait exceeds 10 seconds (10000000 us) switch (errcode) { // Decode and handle the snapshot: case S826_ERR_NOTREADY: S826_CounterRead(0, 0, &counts); // snapshot not available, so read counts manually printf("Timeout -- counter didn't reach either threshold within 10 seconds; current counts = %d", counts); break; case S826_ERR_OK: if (reason & S826_SSRMASK_MATCH0) printf("Counter reached upper threshold at timestamp %d", timestamp); if (reason & S826_SSRMASK_MATCH1) printf("Counter reached lower threshold at timestamp %d", timestamp); }
Measuring velocity
Velocity measurement depends on the ability to sample displacement (distance traveled) at known times. Counter snapshots are ideally suited for this task because they include both displacement and time information. To set this up, configure the counter to track the encoder position and arrange for it to periodically capture snapshots. Each time a new snapshot arrives, the count/timestamp pairs from the new and previous snapshots can be used to precisely compute velocity.
An easy way to do this is to select the internal tick generator as the IX signal source, and configure the counter to capture snapshots on IX rising edges. The following code shows how to do this:
// Measure and display velocity 10 times per second --------------- #define DISP_PER_CNT 0.005 // Displacement per encoder count (e.g., 0.005 meters) uint tstamp[2]; // Previous and current timestamps. uint counts[2]; // Previous and current encoder counts. double cnts_per_us; // Encoder counts per microsecond. double velocity; // Velocity in user units (e.g., meters/second). #define CTRMODE (S826_CM_K_QUADX4 | S826_CM_XS_10HZ) // Mode: x4 clk, IX <- 10Hz. // Configure counter0 as encoder interface; automatically snapshot every 0.1 seconds. S826_CounterModeWrite(0, 0, CTRMODE); // Config mode. S826_CounterSnapshotConfigWrite(0, 0, 0x10, 2); // Enable snapshots upon IX^. S826_CounterStateWrite(0, 0, 1); // Start tracking encoder position. // Wait for first snapshot (captures starting displacement). S826_CounterSnapshotRead(0, 0, counts, tstamp, NULL, S826_WAIT_INFINITE); while (1) { // Repeat every 0.1 seconds: // Wait for next displacement snapshot, then compute and display velocity. S826_CounterSnapshotRead(0, 0, &counts[1], &tstamp[1], NULL, S826_WAIT_INFINITE); cnts_per_us = (double)(counts[1] - counts[0]) / (tstamp[1] - tstamp[0]); velocity = DISP_PER_CNT * 1000000 * cnts_per_us; // convert to meters/second printf("%f\n", velocity); counts[0] = counts[1]; // Prepare for next snapshot. tstamp[0] = tstamp[1]; }
If IX is already being used, you can create a thread that executes approximately 10 times per second (need not be exact). Each time the thread runs, have it call S826_CounterSnapshot
to trigger a soft snapshot. In this case the sample rate will not be exactly 10 Hz, but velocity will still be measured precisely.
Output pulse every N encoder pulses
A counter can output a pulse on a DIO when the counts reach a particular value. The pulse duration will be 20 ns regardless of how long the counts remain at the target value. Consequently, an external pull-up must be added to the DIO to speed up its rising edge (see Using external pull-up resistors for details) and thereby ensure proper pulse formation.
The following code will generate a 20 ns output pulse every 20K encoder counts, using counter0 and dio0 on board number 0. A snapshot is captured each time an output pulse is issued, which is used to set up the next target counts.
#define PULSE_INTERVAL 20000 // output a DIO pulse every 20,000 encoder counts uint targ = PULSE_INTERVAL; // Generate pulse when encoder counts reach this. // Configure counter0 as encoder interface; output 20ns pulse upon compare0 match. RouteCounterOutput(0, 0, 0); // Route counter0 output to dio0. S826_CounterModeWrite(0, 0, 0x01440070); // Config counter0: x4 clk, pulse @ counts match. S826_CounterSnapshotConfigWrite(0, 0, 1, 2); // Enable snapshots upon counts match. S826_CounterStateWrite(0, 0, 1); // Start tracking encoder position. do { // Repeat forever ... S826_CounterCompareWrite(0, 0, 0, targ); // Program target counts and wait for match. int errcode = S826_CounterSnapshotRead(0, 0, NULL, NULL, NULL, S826_WAIT_INFINITE); targ += PULSE_INTERVAL; // Bump target counts. // TODO: PERFORM ANY OTHER REQUIRED PROCESSING } while (errcode == S826_ERR_OK);
- Programmable pulse width
If desired, a second counter can be used to stretch the pulse to any desired width. To do this, configure the second counter as a one-shot (using the encoder counter's ExtOut as a preload trigger) and program its preload counts to the desired pulse width. In this case the one-shot's (vs. encoder counter's) ExtOut must be routed to the DIO. Note that if rise time is critical, an external pull-up may still be required.
The following code builds on the previous example by using a second counter to stretch the pulse width: it will output a 500 µs pulse on dio0 every 20K encoder counts.
#define PULSE_INTERVAL 20000 // output a DIO pulse every 20,000 encoder counts uint targ = PULSE_INTERVAL; // generate pulse when encoder counts reach this uint diosrc[] = {1, 0}; // signal routing configuration // Configure counter0 as one-shot, used to stretch output pulses from counter1. S826_CounterModeWrite(0, 0, 0x004C8323); // 1MHz clk, IX<-ExtOut1, ExtOut @ !0. S826_CounterPreloadWrite(0, 0, 0, 500); // Stretched pulse width (in microseconds). S826_CounterStateWrite(0, 0, 1); // Enable the 1-shot. RouteCounterOutput(0, 0, 0); // Route one-shot output to dio0. // Configure counter1 as encoder interface; output 20ns pulse upon compare0 match. S826_CounterModeWrite(1, 0, 0x00040070); // x4 clk, ExtOut @ compare0. S826_CounterSnapshotConfigWrite(1, 0, 1, 2); // Enable snapshots upon compare0 match. S826_CounterStateWrite(1, 0, 1); // Start tracking encoder position. do { // Repeat forever ... S826_CounterCompareWrite(1, 0, 0, targ); // Program target counts and wait for match. int errcode = S826_CounterSnapshotRead(1, 0, NULL, NULL, NULL, S826_WAIT_INFINITE); targ += PULSE_INTERVAL; // Bump target counts. // TODO: PERFORM ANY OTHER REQUIRED PROCESSING } while (errcode == S826_ERR_OK);
Encoder FAULT output
- My encoder has a FAULT output. Can the 826 monitor the FAULT signal and notify the processor when it changes state?
Some incremental encoders output a FAULT (or FLT) signal to indicate problems such as damaged bearings, code disc defects/contamination, or malfunctioning LEDs/detectors. The FLT signal is typically conveyed over an RS-422 differential pair, which is compatible with the 826's counter inputs. This hardware compatibility and the counter's flexible architecture allow it to monitor the FLT signal and automatically notify the processor when FLT changes state. Note that the general technique discussed here can be used to monitor the state of any RS-422 signal.
To implement this, connect FLT to the counter's IX input. If FLT is active-low, swap the differential pair between line driver and receiver as shown below (don't swap if active-high) so that a disconnected or unpowered encoder will be reported as a fault.
The following code initializes the counter, starts it running, and then monitors the FLT signal state. It should be run in a dedicated thread because it blocks while waiting for FLT state changes. The counter is configured to count up at 1 MHz. A preload is triggered whenever IX is high, and the first Compare0 match triggers a snapshot; these allow the FLT state to be determined when the counter is enabled. Snapshots are also triggered upon IX rising and falling edges; these are used to notify the processor that FLT has changed state.
int FLT_state = -1; // Public FLT state indicator: -1=unknown, 0=normal, 1=fault // FLT monitor thread: maintains FLT_state and notifies other threads when FLT_state changes. int EncoderFaultMonitor(uint board, uint chan) { uint errcode; S826_CounterModeWrite(board, chan, 0x01010020); // Configure the counter channel. S826_CounterSnapshotConfigWrite(board, chan, 0x00010019, S826_BITWRITE) S826_CounterPreloadWrite(board, chan, 0, 0); S826_CounterCompareWrite(board, chan, 0, 1); S826_CounterStateWrite(board, chan, 1); // Enable counter channel. errcode = ReadFLT(board, chan, 1); // Determine FLT initial state. while (errcode == S826_ERR_OK); // Monitor FLT state transitions. errcode = ReadFLT(board, chan, S826_WAIT_INFINITE); return errcode; }
The following function is called by the above code to determine the state of FLT. Note that you must insert code that notifies other threads (e.g., via semaphore) when FLT_state has changed.
// Wait up to tmax for a FLT state change, then copy the current state to FLT_state. static int ReadFLT(uint board, uint chan, uint tmax) { uint reason; int errcode = S826_CounterSnapshotRead(board, chan, NULL, NULL, &reason, tmax); if (errcode == S826_ERR_NOTREADY) { // If no snapshots captured within finite tmax then errcode = S826_ERR_OK; // cancel error; FLT_state = 1; // counts holding at 0 because FLT asserted. } else if (errcode != S826_ERR_OK) // Else if error detected then FLT_state = -1; // abort and declare FLT state unknown. else // Else FLT_state = ((reason & S826_SSRMASK_IXRISE) != 0); // FLT state determined by snapshot trig. // TODO: NOTIFY OTHER THREADS THAT FLT STATE CHANGED return errcode; }
Unexpected snapshots
- Why do I occasionally get two snapshots (upon counts match) from my incremental encoder when only one is expected?
Assuming your encoder has not changed direction, the unexpected snapshots are probably caused by noise or slow edges on the encoder clock signals. This is possible even when the snapshot timestamps are identical, because encoder clocks are sampled every 20 nanoseconds whereas timestamp counts are incremented only once per microsecond.
If this is what is happening, unexpected snapshots can be prevented by calling S826_CounterFilterWrite()
to establish a clock filter. A small filter value is usually sufficient -- just enough to clean up clock edges, but not so long that valid encoder counts will be missed. For example, this will set the clock filter to 100 ns (index input will not be filtered):
#define FILT_NS 100 // Filter time in ns -- change as desired to multiple of 20. #define FILT_RES 20 // Filter resolution in nanoseconds. #define FILT_CLK (1 << 30) // Bit flag to enable clock filtering. S826_CounterFilterWrite(0, 0, // Activate clock filter on counter 0. FILT_CLK + FILT_NS / FILT_RES);
Another way to handle this is to configure the Match snapshot trigger to become automatically disabled when it fires. Note that if you use this method, you will need to re-enable the Match trigger to capture snapshots of subsequent matches.
S826_CounterSnapshotConfigWrite(0, 5, // Configure snapshots on counter 5: S826_SSRMASK_MATCH0 // enable snapshot upon Match0 (counts==Compare0 register) | (S826_SSRMASK_MATCH0 << 16), // disable subsequent Match0 snapshots upon Match0 S826_BITWRITE); // disable all other snapshot triggers
Timing
Input filters
The ClkA, ClkB and Index inputs are acquired using an internal 50 MHz sampling clock (SysClk) with 3 ns minimum setup time. Consequently, an edge on any of these signals will will be detected 0.3 to 20.3 ns after it occurs. In the below timing diagram, the input edge is detected at SysClk 2 because it occurs after SysClk 1 and ≥ 0.3 ns before SysClk 2.
Each input has a digital filter with programmable time F. A filter's input signal will appear on its output when the input has remained stable for time F. The filter delays the detected edge by F+1 SysClk periods. This diagram show filter timing in the general case:
The filter delay can be minimized by setting F=0, which results in the following timing:
Counter functions
The filtered ClkA/B signals are decoded and used to control the counter. As shown in the following diagram, counts will change one SysClk period after a filter output change and then, one SysClk later, a snapshot is captured to the event FIFO and ExtOut goes active for one SysClk period. If ExtOut is routed to a DIO, it will be delayed one additional SysClk period by the DIO output sampler on its way to the DIO connector pin.
Summary of counter timing parameters:
Timing Parameter | Minimum | Maximum |
---|---|---|
ClkA/ClkB setup time | 3 ns | |
ClkA/ClkB hold time | 0 ns | |
ClkA/ClkB to counts change | 40 ns | 60 ns |
ClkA/ClkB to counts change | 40 ns | 60 ns |
ClkA/ClkB to snapshot | 60 ns | 80 ns |
ClkA/ClkB to ExtOut (internal) | 60 ns | 80 ns |
ClkA/ClkB to ExtOut (DIO pin) | 80 ns | 100 ns |
Using external pull-up resistors
- My PWM stops working when it outputs high frequencies. Why does this happen and how can I prevent it?
The PWM signal (from the counter's ExtOut) is output by a DIO channel. When the DIO driver transitions to the off state, its 10 KΩ source resistance (combined with circuit capacitance) stretches the DIO rise time and thereby delays its transition to logic '1' (see DIO rising edge in the above timing diagram). As the PWM off time decreases, the rise time (which is constant) becomes a higher percentage of the off time. When the off time is too short (i.e., off time < rise time), the PWM output will seem to "stop" because there is not enough time for the signal to reach logic '1'.
This situation can arise when the PWM is operating at high frequencies or generating short positive pulses, and can be avoided by speeding up the DIO rise time.
PWM operation
How to configure a counter for PWM operation
This example shows how to configure counter channel 0 to operate as a PWM generator with the output appearing on DIO channel 0. Note that you must call S826_SafeWrenWrite()
before calling S826_DioOutputSourceWrite()
; if you neglect to do this then the PWM signal will not appear on DIO 0.
#include "826api.h" uint data[2]= {1, 0}; // DIO 0 S826_SafeWrenWrite(0, 2); // Enable writes to DIO signal router. S826_DioOutputSourceWrite(0, data); // Route counter0 output to DIO 0. S826_CounterModeWrite(0, 0, 0x01682020); // Configure counter0 for PWM, with auto-preload when starting. S826_CounterPreloadWrite(0, 0, 0, 900); // On time in us (0.9 ms). S826_CounterPreloadWrite(0, 0, 1, 500); // Off time in us (0.5 ms). S826_CounterStateWrite(0, 0, 1); // Start the PWM generator.
Fail-safe PWM generator
- I'm using a PWM output to control a motor. Is there a way to automatically shut off the motor if my program crashes?
Yes, you can use the watchdog timer and fail-safe controller to force the PWM output to a constant state. To do this, configure the watchdog to activate safemode when it times out, as shown in this simplified block diagram:
Before enabling the PWM generator or watchdog, program the desired PWM failsafe level into the DIO channel's SafeData
register (see Programming safemode states); this specifies the signal that will be sent to your motor controller when your program crashes (which will shut off the motor). Note that the DIO output is active-low. The SafeEnable
register is set to '1' by default, thus enabling fail-safe operation on the DIO channel. Next, program the watchdog interval and start the watchdog running. Finally, start the PWM running.
After enabling the watchdog, your program must periodically kick it to prevent it from timing out, by calling S826_WatchdogKick()
. When your program is running normally, the PWM signal will appear on the DIO pin. If your program crashes (or fails to kick the watchdog in a timely manner), the watchdog will time-out and activate the fail-safe controller. This will switch the DIO pin to the level specified by the SafeData
register, which in turn will halt the motor.
#include "826api.h" #define CRASH_DET_SECONDS 0.5 // Halt motor if program fails to kick watchdog within this time. #define MOTOR_HALT_LEVEL 0 // DIO pin level ('0'=5V, '1'=0V) that will halt motor. uint wdtime[5] = {(uint)(50000000 * (CRASH_DET_SECONDS)), 1, 1, 0, 0}; // watchdog interval uint dio_routing[2]= {1, 0}; // map counter0 to DIO 0 uint safe_data[2]= {MOTOR_HALT_LEVEL, 0}; // fail-safe level // Create a fail-safe PWM generator using counter0 and DIO 0. S826_SafeWrenWrite(0, S826_SAFEN_SWE); // Enable writes to watchdog, router and SafeData. S826_DioSafeWrite(0, safe_data, 2); // Specify DIO state to use when program crashes. S826_DioOutputSourceWrite(0, dio_routing); // Route counter0 output to DIO 0. S826_CounterModeWrite(0, 0, 0x01682020); // Config counter0 for PWM; preload when starting. S826_CounterPreloadWrite(0, 0, 0, 900); // PWM on time in us (0.9 ms). S826_CounterPreloadWrite(0, 0, 1, 500); // PWM off time in us (0.5 ms). S826_WatchdogConfigWrite(0, 0x10, wdtime); // Set wdog interval; trig safemode upon timeout. // Start the PWM generator running. S826_WatchdogEnableWrite(0, 1); // Start watchdog. PROGRAM MUST KICK IT FROM NOW ON! S826_CounterStateWrite(0, 0, 1); // Start the PWM generator.
Phase-locked PWM outputs
Some applications require multiple, phase-locked PWM outputs. Although the 826's counter channels do not directly support phase locking, it is possible to simulate phase-locked PWM outputs by using counters configured as hardware-triggered one-shots. This technique can be used in a variety of ways.
Example: quadrature generator
A quadrature generator can be implemented with three counter channels and two DIOs as shown below. Except for DIO load connections, no external wiring is required (counter and DIO interconnects are established by the board's programmable signal router).
The counter channels are configured as follows:
- CH0 - PWM with output on DIO1.
- CH1 - 1-shot with IndexSource=ExtOut0, preload upon Index. This delays Phase2 wrt Phase1.
- CH2 - 1-shot with IndexSource=ExtOut1, preload upon Index, output on DIO2. This generates the Phase2 output pulse.
Example: 3-phase controller
A 3-phase PWM controller (shown below) can be created by extending the above example. This is implemented with five counter channels and three DIOs (channel numbers are arbitrarily assigned). Except for DIO load connections, no external wiring is required (counter and DIO interconnects are established by the board's programmable signal router).
In the above example, counter channels are configured as follows:
- CH0 - PWM with output on DIO1.
- CH1 - 1-shot with IndexSource=ExtOut0, preload upon Index. This delays Phase2 wrt Phase1.
- CH3 - 1-shot with IndexSource=ExtOut1, preload upon Index. This delays Phase3 wrt Phase2.
- CH2 - 1-shot with IndexSource=ExtOut1, preload upon Index, output on DIO2. This generates the Phase2 output pulse.
- CH4 - 1-shot with IndexSource=ExtOut3, preload upon Index, output on DIO3. This generates the Phase3 output pulse.
Serial data capture
A counter channel can be used to capture serial data by leveraging its snapshot FIFO. When the counter has been appropriately configured and enabled, the computer allows the channel hardware to acquire snapshots while it asynchronously reads and processes snapshots from the FIFO.
Serial data capture (asynchronous)
A timestamp is included in every snapshot, which is especially useful for capturing data from asynchronous sources and irregularly-timed sources such as bar code wands. The basic idea is to apply the serial data signal to the counter's IX or ExtIn input and have the counter automatically capture snapshots at signal edges.
In each snapshot, only the timestamp and reason flags are of interest (counts are ignored). The reason flag indicates whether a snapshot was triggered by rising or falling edge and the timestamp indicates the time when the edge occurred. For example, in the serial data waveform shown below, the first rising edge (A) caused a snapshot to be captured when the timestamp generator value was 100, and the reason code indicates the snapshot was triggered by a rising edge.
Any two consecutive snapshots represent a matched pair of rising/falling or falling/rising edges, corresponding to an interval during which the serial data value was '1' or '0', respectively. For example, in the waveform shown above, snapshots A and B bracket a '1' interval. It is possible to determine the binary value of the serial data from the first reason code, and the duration of the data value from the difference between the timestamps.
To see how this works, consider the above serial data waveform. The counter automatically captures a snapshot for each of the edge events A, B, C and D. When the computer considers snapshots A and B, it determines that the serial data was '1' during interval A-B because A was triggered by a serial data rising edge. Furthermore, it knows that the serial data held at '1' for 300 µs (the difference between the A and B timestamps). Similary, it can determine that interval B-C was a logic '0' lasting 200 µs, and C-D was a 400 µs logic '1'.
For maximum efficiency, consider using a dedicated thread to read the FIFO. This eliminates polling and makes the application event-driven because the thread can wait in S826_CounterSnapshotRead()
for the next snapshot without wasting CPU time. Also, this decouples the timing of serial data acquisition from other tasks, thereby greatly simplifying overall software development and maintenance. If fast processing of the serial data is required, raise the thread priority to an appropriately high level.
Serial data capture (synchronous)
To capture clocked serial data (e.g., SPI, I2C), apply the clock signal to the counter's ExtIn or IX input and configure the counter to capture snapshots at either the rising or falling edge of the clock signal (whichever edge indicates stable data). Apply the data signal to the counter's ClkA input and set the counter's clock mode K=6 (external quadrature clock, x2 multiplier); this will cause the counts to change (it will alternate between two values) upon each data edge.
A serial data bit can be obtained directly from the counts lsb (least significant bit) of each snapshot. Timestamps may be used to identify data packet boundaries, or ignored if frame markers are encoded in the data.
An open-source demo of this technique is available at synchronous serial receiver.
Mode register decoder utility
- Is there an easy way to convert a mode register value to a human readable description of counter settings?
Yes: download Sensoray's counter mode decoder utility program (Windows compatible). Run the program and enter the mode register value to see an English language description of the counter mode settings.
ADC
Handling ADC interrupts
An interrupt request (IRQ) is generated when the ADC subsystem completes a conversion burst. The IRQ works in conjunction with the S826_AdcRead
function. To wait for the next IRQ, simply call S826_AdcRead
; the function will return upon ADC interrupt.
The following example shows how this works. In this example, only slot 0 is being used, and timestamps are not used. Note that the ADC (and its trigger source) must have been previously configured and enabled (see next example).
void AdcHandler(void) { uint errcode; int slotval[16]; // buffer must be sized for 16 slots while (1) { uint slotlist = 1; // only slot 0 is of interest in this example errcode = S826_AdcRead(0, adcdata, NULL, &slotlist, S826_WAIT_INFINITE); // wait for IRQ if (errcode != S826_ERR_OK) break; printf("Raw adc data = %d", slotval[0] & 0xFFFF); } }
Periodic ADC conversions (self-paced)
- Can the ADC periodically acquire samples without using a counter to pace it?
Yes: To do this, configure the ADC for continuous triggering mode and use the slot settling times to control the sampling rate. Note that the sampling period will have a small amount of jitter (≤ 1 µs) because ADC conversion time is 2-3 µs. This example shows how to acquire approximately 20 samples per second from analog input 0:
#define SAMPLING_PERIOD 50000 // Sampling period in microseconds (50000 = 20 samples/s). #define TSETTLE SAMPLING_PERIOD - 3; // Compensate for nominal ADC conversion time. // Configure the ADC subsystem and start it running S826_AdcSlotConfigWrite(board, 0, 0, TSETTLE, S826_ADC_GAIN_1); // measuring ain 0 on slot 0 S826_AdcSlotlistWrite(board, 1, S826_BITWRITE); // enable slot 0 S826_AdcTrigModeWrite(board, 0); // trigger mode = continuous S826_AdcEnableWrite(board, 1); // start adc conversions while (1) { AdcHandler(); // Handle periodic ADC interrupts (using code from earlier example) }
Periodic ADC conversions (using counter)
A counter can be used to periodically trigger ADC bursts. To do this, configure the counter as a periodic timer that outputs a pulse at the end of each period. The output pulse need not be routed to a DIO pin because it can be internally routed to the ADC's trigger input. Also, it's not necessary to generate counter snapshots because the ADC will notify software when a conversion has completed.
The following example shows how to digitize all 16 analog inputs in a burst, at a rate of 10 bursts/second. The bursts are triggered by counter0.
// Use a counter to periodically trigger ADC conversions ------------- int adcbuf[16]; // sample buffer -- always set size=16 (even if fewer samples needed) int i; // Configure the ADC and enable it. for (i = 0; i < 16; i++) // Configure all timeslots: 1 slot per AIN; 20 microseconds/slot settling time. S826_AdcSlotConfigWrite(board, i, i, 20, S826_ADC_GAIN_1); S826_AdcSlotlistWrite(board, 0xFFFF, S826_BITWRITE); // Enable all 16 timeslots. S826_AdcTrigModeWrite(board, 0xB0); // Hardware triggered, source = counter0 ExtOut. S826_AdcEnableWrite(board, 1); // Enable ADC conversions. // Configure the trigger generator (counter 0) and start it running. S826_CounterModeWrite(0, 0, 0x01502020); // Countdown @1MHz; preload @start and cnt==0, pulse ExtOut @cnt==0. S826_CounterPreloadWrite(0, 0, 0, 100000); // ADC trigger period in microseconds (100000 = 0.1 seconds). S826_CounterStateWrite(0, 0, 1); // Enable ADC trigger generator. do { // Repeat forever: uint slotlist = 0xFFFF; // Wait for ADC burst completion. if (S826_AdcRead(board, adcbuf, NULL, &slotlist, S826_WAIT_INFINITE) != S826_ERR_OK) break; // error // TODO: PROCESS ADC SAMPLES }
Oversampling
Oversampling is a useful technique for reducing noise and increasing resolution. Model 826 facilitates oversampling by allowing an analog input (AIN) to be measured multiple times in a single ADC burst. After the burst, the samples can be averaged to obtain an enhanced sample value.
- Example
The following code shows how to oversample two AINs. In each burst, AIN0 is digitized eight times and then AIN1 is digitized eight times. To set this up, each AIN is assigned to eight contiguous slots. For each AIN, the first slot includes a settling time because the ADC input has just switched. The seven subsequent slots do not require settling time (and thus settling times are set to 0 µs) because the input has not switched.
The ADC's internal timing is used to pace conversions, by selecting the continuous triggering mode and assigning appropriate settling times. Slot 0 is assigned a long settling time, which effectively determines the sampling period and also provides a necessary settling delay. Slot 8 has a shorter settling time because it only needs to delay long enough for signal settling.
Note that error checking has been omitted here for clarity, but should always be included in robust application code.
// Settling times in microseconds #define TSETTLE0 10000 // Delay after switching to AIN0 (tweak this to adjust sampling rate). #define TSETTLE1 20 // Delay after switching ADC to AIN1. // Average 8 samples and convert to volts (assumes +/-10V measurement range) #define VOLTS(SUM) ((SUM) * 10.0 / (8.0 * 32768.0)) int slot; int adcbuf[16]; // Sample buffer -- always set size=16 (even if fewer samples needed) // Adc timeslot attributes. struct SLOTATTR { uint chan; // analog input channel uint tsettle; // settling time in microseconds } attr[16] = { // During each burst: {0, TSETTLE0}, // switch to AIN0, delay, then digitize {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, // digitize AIN0 7 more times without delay {1, TSETTLE1}, // switch to AIN1, delay, then digitize {1, 0}, {1, 0}, {1, 0}, {1, 0}, {1, 0}, {1, 0}, {1, 0}}; // digitize AIN1 7 more times without delay // Configure all 16 timeslots. for (slot = 0; slot < 16; slot++) S826_AdcSlotConfigWrite(board, slot, attr[slot].chan, attr[slot].tsettle, S826_ADC_GAIN_1); // Configure adc system and start it running. S826_AdcSlotlistWrite(board, 0xFFFF, S826_BITWRITE); // Enable all 16 timeslots. S826_AdcTrigModeWrite(board, 0); // Select continuous triggering mode. S826_AdcEnableWrite(board, 1); // Start conversions. while (1) // Repeatedly fetch and display oversampled data: { int sum[] = {0, 0}; // Reset sample accumulators (one per AIN). uint slotlist = 0xFFFF; // Wait for ADC burst S826_AdcRead(board, adcbuf, NULL, &slotlist, 1000); // and read samples from 16 slots. for (slot = 0; slot < 16; slot++) // Sum the 8 samples from each AIN sum[slot >> 3] += (short)(adcbuf[slot] & 0xFFFF); // while masking off sample meta-data. // Compute and report measured voltages. printf("AIN0=%3.3fV, AIN1=%3.3fV\n", VOLTS(sum[0]), VOLTS(sum[1])); }
Polled operation
- How can I read analog inputs as fast as possible?
In event-driven applications, the S826_AdcRead
function is configured to block until ADC samples are available. However, blocking necessarily involves context switching, which introduces overhead. To avoid this overhead, blocking must be disabled. This is done by setting tmax=0
, which allows the ADC subsystem to be polled without blocking.
- Example
The following code employs polling to acquire data from all 16 AINs. Each call to S826_AdcRead
will return immediately, with slotlist
bits indicating the AINs that have new samples waiting in adcbuf
. If desired, individual samples may be processed when they arrive in adcbuf
(each time S826_AdcRead
executes), or the program can wait for all 16 AINs and then process them en masse.
A 7 µs settling delay is allowed before each conversion begins. The ADC conversion time is ≤3 µs, so the total time per AIN is ≤10 µs and the burst time for all AINs is ≤160 µs. Note that individual settling times may need adjustment depending on factors such as source impedance and required accuracy.
#define TSETTLE 7 // Settling delay after switching AIN (adjust as necessary). #define SLOTFLAGS 0xFFFF // Timeslot flags: use all 16 timeslots. int i; int adcbuf[16]; // Sample buffer -- always set size=16 (even if fewer samples needed) // Configure all timeslots: 1 slot per AIN; constant settling time for all slots. for (i = 0; i < 16; i++) S826_AdcSlotConfigWrite(board, i, i, TSETTLE, S826_ADC_GAIN_1); // Configure adc system and start it running. S826_AdcSlotlistWrite(board, SLOTFLAGS, S826_BITWRITE); // Enable all 16 timeslots. S826_AdcTrigModeWrite(board, 0); // Select free-running mode. S826_AdcEnableWrite(board, 1); // Start conversions. while (1) // Repeat forever: { uint remaining = SLOTFLAGS; // timeslots that have not yet been read do { uint slotlist = remaining; // Read all available remaining timeslots. int errcode = S826_AdcRead(board, adcbuf, NULL, &slotlist, 0); // note: tmax=0 remaining &= ~slotlist; // OPTIONAL: NEWLY ARRIVED SAMPLES MAY BE PROCESSED WHILE WAITING FOR REMAINING SAMPLES } while (errcode == S826_ERR_NOTREADY); if (errcode != S826_ERR_OK) break; // error // TODO: PROCESS ALL UNPROCESSED SAMPLES IN ADCBUF }
Calibration errors caused by missing shunt
On 826 SDKs earlier than version 3.2.0, analog calibration values will not be applied without J6 (labeled "Calibration Enable") installed. A missing shunt is intended to protect against accidental overwriting of calibration values, but in these SDKs it also prevents the reading of those values. This is resolved in SDK version 3.2.0 and above; in these versions the shunt functions as intended and must be installed only when calibrating the board (though leaving it installed all the time is okay).
If board calibration is incorrect, make sure J6 is installed or upgrade to SDK version 3.2.0 or higher. The 826 SDK can be downloaded from the 826 product page.
Apparent nonlinearity
ADC linearity can be adversely affected by high common-mode voltage (CMV). This can happen when the ADC is used to measure an isolated voltage source such as a battery, thermocouple, or isolated power supply. Since the source is isolated, the CMV may float up or down until it exceeds the maximum allowed CMV of the ADC's input circuitry.
When measuring an isolated source, be sure to connect one side of the source to the ADC power supply ground as shown in the diagram to the right. This will prevent high CMV that might othewise result in apparent non-linearity or calibration errors.ADC accuracy specification
Resolution and no missing codes are both 16 bits minimum.
Parameter | Value | Units | Description | ||
---|---|---|---|---|---|
Min | Typ | Max | |||
Integral Nonlinearity Error | ±0.75 | ±1.5 | LSB | Deviation of ADC transfer function from best-fit line | |
Differential Nonlinearity Error | ±0.5 | ±1.25 | LSB | Deviation of ADC code width from ideal code width | |
Gain Error | ±2 | ±40 | LSB | Deviation of difference between actual level of last data transition and actual level of first transition from difference between ideal levels | |
Gain Error Temperature Drift | ±0.3 | ppm/°C | |||
Zero Error | ±0.8 | mV | Difference between ideal midscale voltage and actual voltage producing the midscale output code | ||
Zero Temperature Drift | ±0.3 | ppm/°C |
Maximum input voltage
The analog inputs accept common mode voltages up to ±12V with no resulting input current or damage. CMV up to ±25V is tolerated continuously, though this will cause currents to flow in the analog inputs. CMV greater than 25V may be tolerated for brief intervals, but this can cause significant currents to flow in the analog inputs and is not specified nor guaranteed to be safe.
DAC
Specifying setpoint in Volts
- Is there a way to program the analog outputs using Volts units?
The following function will set a DAC output to the specified voltage (note: the function does not check for illegal voltage values). If desired, a range
argument may be added to the function's argument list; this will require the caller to correctly specify the output range but will avoid the overhead of S826_DacRead
.
int SetDacOutput(uint board, uint chan, double volts) { uint setpoint; uint range; int errcode = S826_DacRead(board, chan, &range, &setpoint, 0); // get DAC output range switch (range) { case S826_DAC_SPAN_0_5: setpoint = (uint)(volts * 0xFFFF / 5); break; // 0 to +5V case S826_DAC_SPAN_0_10: setpoint = (uint)(volts * 0xFFFF / 10); break; // 0 to +10V case S826_DAC_SPAN_5_5: setpoint = (uint)(volts * 0xFFFF / 10) + 0x8000; break; // -5V to +5V case S826_DAC_SPAN_10_10: setpoint = (uint)(volts * 0xFFFF / 20) + 0x8000; break; // -10V to +10V } if (errcode == S826_ERR_OK) errcode = S826_DacDataWrite(board, chan, setpoint, 0); return errcode; }
Linux Demo Sine Wave Generator counter error (-15)
The Linux sine wave generator demo may experience a timeout and exit with an error code -15 (S826_ERR_FIFOOVERFLOW). This occurs because the priority of the demo thread may be too low for the sample time. Linux is not a RTOS and the process (or interrupt) may be delayed and not complete the DAC output in the specified time.
Older version of the demo will exit when S826_ERR_FIFOOVERFLOW occurs. Later versions of the demo, however, will print an error code and continue outputting the sine wave.
In any case, if the DAC output sampling time requirements are very small and need to be precise, it is recommended to run the process at a higher priority. You may also consider using a low-latency or rt kernel.
To run the demo at a higher priority:
"nice -n 19 ./s826demo"
For Ubuntu low-latency kernel:
"sudo apt-get install linux-lowlatency linux-headers-lowlatency"
For Ubuntu rt kernel:
"sudo apt-get install linux-rt linux-headers-rt"
In extreme high performance cases, you may consider using the raw DAC write command (S826_DacRawWrite) instead of S826_DacDataWrite. You must make sure to understand the DAC ranges before doing so. This should normally not be necessary as S826_DacDataWrite is only marginally slower.
DAC accuracy specification
Resolution and monotonicity are both 16 bits minimum.
PARAMETER | CONDITIONS | VALUE | UNITS | ||
---|---|---|---|---|---|
MIN | TYP | MAX | |||
Integral Nonlinearity | ±2 | LSB | |||
Differential Nonlinearity | ±1 | LSB | |||
Gain Error | ±4 | ±20 | LSB | ||
Gain Temperature Coefficient | ±2 | ppm/°C | |||
Unipolar Zero-Scale Error | 5V unipolar range, 25°C 10V unipolar range, 25°C 5V unipolar range 10V unipolar range |
±80 ±100 ±140 ±150 |
±200 ±300 ±400 ±600 |
µV µV µV µV | |
V_offset Temperature Coefficient | All unipolar ranges | ±2 | µV/°C | ||
Bipolar Zero Error | All bipolar ranges | ±2 | ±12 | LSB |
DIOs
Debouncing inputs
DIOs are commonly used to monitor real-world signals that have glitches or other noise. For example, the electrical contacts of mechanical switches may "bounce" when first connected, thus producing pulses that may be incorrectly interpreted by the application program as state changes. This problem can be solved by implementing a software debounce algorithm, but an easier way is to use the input filters provided by the DIO system.
To use the DIO input filters, simply specify a filter interval and designate the DIOs to be filtered. Choose a filter interval that is long enough to suppress the longest expected noise pulse, but not so long that valid state changes will be missed. The following example shows how to suppress pulses up to 200 µs wide on dio0 and dio2:
// Debounce dio0 and dio2 ------------------ #define FILT_USEC 200 // Desired filter interval (max = 1310 us). uint tfilt = FILT_USEC * 50; // Interval specified as multiple of 20ns. uint enabs[] = {5, 0}; // Enable filters on dio0 and dio2. S826_DioFilterWrite(0, tfilt, enabs); // Activate the DIO filters.
Setting and clearing DIOs
Sometimes it's necessary to set or clear particular DIOs without disturbing others. Typically, this is accomplished with a read-modify-write (RMW) sequence consisting of reading all DIOs into a buffer, modifying the buffer, and then writing the buffer back to the DIOs. This task is further complicated when multiple threads control the DIOs — a common practice in high-performance event-driven applications — because concurrent RMWs on different threads will conflict with each other. To avoid such conflicts, the RMW must be treated as a critical section (e.g., protected by a mutex or semaphore).
Fortunately, the 826 provides a fast, efficient way to set and clear DIOs without the complications of RMW. Simply call S826_DioOutputWrite
with the mode
argument set to S826_BITSET
or S826_BITCLR
, as shown in this example:
// Set and clear some DIOs without affecting other DIOs uint setbits[] = {7, 0}; // modify bits 0-2 uint clrbits[] = {1, 0}; // modify bit 0 S826_DioOutputWrite(0, setbits, S826_BITSET); // Set DIOs 0-2. S826_DioOutputWrite(0, clrbits, S826_BITCLR); // Clear DIO 0.
Edge detection
Application programs often must execute code in response to DIO input changes (e.g., when a button is pressed, a photoelectric sensor is activated, etc.). It's considered bad practice to detect these events via polling because polling wastes CPU time and degrades system responsiveness. However, the alternative — using interrupts — can complicate and prolong program development.
Model 826 provides the best of both worlds: it supports DIO interrupts for efficient event detection, and the API makes the interrupts easy to use. For example, the following function will return when a falling edge is detected on a particular DIO. It is a blocking function, meaning that other threads can run while it waits for the DIO edge.
int WaitForDioFallingEdge(uint board, uint dio) { uint rise[] = {0, 0}; // not interested in rising edge uint fall[] = {(dio < 24) << (dio % 24), (dio > 23) << (dio % 24)}; // interested in falling edge S826_DioCapEnablesWrite(board, rise, fall, S826_BITSET); // Enable falling edge detection. return S826_DioCapRead(board, fall, 0, INFINITE_WAIT); // Block until falling edge. } WaitForDioFallingEdge(0, 29); // Example usage: wait for falling edge on dio29 of board number 0.
Controlling output rise-time with an external pull-up
Each DIO channel has an output driver that actively drives the signal to 0 V in the on state and is high-impedance in the off state. In the off state, the channel's internal 10 KΩ resistor passively pulls up the signal to +5 V. This 10 KΩ source resistance (combined with circuit capacitance) stretches the DIO rise time, which delays its transition to logic '1'. If the rise time is too long, it can be shortened by adding an external pull-up resistor (and reducing external capacitance) on the signal net.
The following table shows nominal rise times for unloaded DIO pins. Lower resistance values may be needed to compensate external circuit capacitance, but do not use an external pull-up resistor having less than 220 Ω (to prevent excessive DIO output current).
External pull-up | DIO rise time |
---|---|
None | 200 ns |
1.2 KΩ | 20 ns |
680 Ω | 10 ns |
Generating a burst of pulses
- How can I make a DIO go active for 10ms and then inactive for another 10ms, and repeat this five times?
There are several ways to do this but the methods you can use depend on a number of factors. Here are two general strategies:
1. If high precision is not needed you can call S826_DioOutputWrite()
multiple times, with 10ms software delays between the calls. The precision of this method may depend on your operating system and CPU load. The code for this method is simple and straightforward.
2. If you need precise timing then you could use two counter channels. For example, using counter channels 0 and 1:
- Counter 0: Configure as PWM generator with 10ms on/off times, with external enable. Connect its enable input to counter 1's output. The goal here is for counter 0 to enable counter 1 to output pulses; counter 0 will enable the PWM generator until 5 pulses have been generated.
- Counter 1: Configure as event down-counter, with preload (value=5) upon enable, with external clock, with output active when counts not zero. Connect its clock input (must be external connection) to counter 0's output. The goal here is for counter 1 to count PWM pulses until it counts from 5 down to 0; it will then set its output low, thus disabling the PWM generator.
Example application: I2C Emulator
- Can I use DIOs to communicate with I2C devices?
Have a look at Sensoray's I2C emulator, which uses two DIOs to bit-bang an I2C bus. This open source software implements a full-featured I2C master emulator with bus arbitration and bus-hang resolver. All 826-specific code resides in a hardware abstraction layer (HAL) — an architectural feature that belongs in all production-quality software.
If you need to monitor an I2C bus or emulate a slave device, consider using a counter to capture synchronous serial data.
Interfacing RS-422 signals
- I need to monitor the state of an RS-422 signal pair. Can I do this with a DIO channel?
DIO channels are TTL/CMOS compatible and therefore not compatible with incoming RS-422 signals. However, it is possible to use a counter channel to monitor the state of an RS-422 signal pair, using the technique described here.
Watchdog timer and fail-safe system
Activating safemode with an E-stop contact
An external emergency stop (E-stop) switch can be used to force analog and digital outputs to fail-safe states. To implement this, you must convert the E-stop signal to active-low TTL/CMOS and apply it to DIO47, so that DIO47 will be driven low when the E-stop button is pressed. The following schematic shows a robust, reliable way to do this for a 24V E-stop contact. This circuit ensures that safemode will be activated if the E-stop pushbutton is pressed, or 24VDC power is lost, or the relay coil opens.
Typically, the application program will configure the fail-safe system during initialization, before I/O operations begin. This is done by programming the analog and digital safemode states, and then "arming" the system by enabling DIO47 to trigger safemode operation. The following example illustrates this process:
// Configure and arm the fail-safe system --------------- int i; // The desired fail-safe output conditions (change as required): uint safeDioEnables[2] = {0x00FFFFFF, 0x00FFFFFF}; // Switch all DIOs to fail-safe states. uint safeDioData[2] = {0, 0}; // Safemode DIO states. uint safeAoutRange[4] = {0, 0, 0, 0}; // Safemode analog output ranges. uint safeAoutLevel[4] = {0, 0, 0, 0}; // Safemode analog output levels. // Allow modifications to fail-safe settings. S826_SafeWrenWrite(0, S826_SAFEN_SWE); // Program analog output fail-safe conditions. for (i = 0; i < S826_NUM_DAC; i++) { S826_DacRangeWrite(0, i, safeAoutRange[i], 1); S826_DacDataWrite(0, i, safeAoutLevel[i], 1); } // Program digital output fail-safe conditions. S826_SafeEnablesWrite(0, safeDioEnables); S826_DioSafeWrite(0, safeDioData, S826_BITWRITE); // Allow the E-stop switch to activate fail-safe operation. S826_SafeControlWrite(0, S826_CONFIG_XSF, S826_BITSET); // Prevent errant software from modifying fail-safe settings. S826_SafeWrenWrite(0, S826_SAFEN_SWD);
In many cases the application program must be alerted when the E-stop pushbutton is pressed, so that it can execute relevant tasks (e.g., record the event to an error log). The following example shows how to detect and handle an E-stop event.
// Detect and handle an E-stop pushbutton press ------------ WaitForDioFallingEdge(0, 47); // Wait for DIO47 falling edge. printf("E-stop pushbutton was pressed!");
Activating safemode with the watchdog
In many control applications, the analog and digital outputs must automatically switch to safe states if software fails to execute normally. This can be implemented by using watchdog Timer0 to activate safemode. To set this up, configure the watchdog and safemode systems during initialization (before I/O operations begin):
#define WD_MILLISECONDS 100 // Watchdog Timer0 will timeout if unkicked for this long wdtiming[] = {WD_MILLISECONDS * 50000, 1, 1, 0, 0}; S826_SafeWrenWrite(0, S826_SAFEN_SWE); // Write-enable watchdog/safemode settings. S826_WatchdogConfigWrite(0, 0x10, wdtiming); // Set t0 interval; t0 triggers safemode. // TODO: Program safemode states S826_WatchdogEnableWrite(0, 1); // Start the watchdog running. S826_SafeWrenWrite(0, S826_SAFEN_SWD); // Write-protect watchdog/safemode settings.
The above code starts the watchdog timer, so the application program must now regularly "kick" the watchdog to prevent a timeout. This is typically done by periodically executing a kick algorithm. The kick algorithm may be simple or complex, depending on the number of running threads and other factors. The simplest algorithm will simply kick the watchdog, unconditionally:
CreateTimer(0, 0, 100000); // Execute this loop every 100 milliseconds: while (1) { S826_WatchdogKick(0, 0x5A55AA5A); // Unconditionally kick the watchdog. WaitForTimer(0, 0); }
Here's a slightly more complex algorithm that monitors the states of two other threads (threadA and threadB). When each monitored thread completes its task, it stores a special value in a reserved memory location. The special values, when OR'ed together, form the value required for a watchdog kick.
int a_kick; // ThreadA stores 0x5A550000 here when it completes. int b_kick; // ThreadB stores 0x0000AA5A here when it completes. CreateTimer(0, 0, 100000); // Execute this loop every 100 milliseconds: while (1) { S826_WatchdogKick(0, a_kick | b_kick); // Kick watchdog if both threads completed. a_kick = b_kick = 0; // Reset completion status. WaitForTimer(0, 0); }
When watchdog timer0 times out, it may be necessary to notify "system health" monitoring software so it can take appropriate corrective action. This is easily done, as shown in the following code:
if (S826_WatchdogEventWait(0, INFINITE_WAIT) == S826_ERR_OK) { // Watchdog timer0 timed out, so take appropriate corrective action }
Turning off the relay
- How do I turn off the Reset Out relay after the watchdog has turned it on?
Call S826_WatchdogEnableWrite()
with enable=0.
Default safemode states
- In safemode, will all outputs default to power-on/reset conditions if safemode data registers have not been programmed?
Yes, because upon power-up or reset, all safemode data registers are initialized to match their runmode counterparts: DIO outputs are initialized to '0' (i.e., outputs will be pulled up to +5V) and analog outputs are initialized to 0V using the 0 to +5V output range.
Programming safemode states
It's recommended to program the safemode data registers even if you will be using default values. This will serve to document fail-safe operation in your code and enable you to easily change safemode states if you need to. The following example shows how to do this for board number 0:
// Program fail-safe states for all analog and digital outputs -------------- int aout; // analog output channel number uint SafeDio[] = {0, 0}; // fail-safe DIO states S826_SafeWrenWrite(0, S826_SAFEN_SWE); // Write-enable safemode data registers. S826_DioSafeWrite(0, SafeDio, S826_BITWRITE); // Program safemode DIO states. for (aout = 0; aout < S826_NUM_DAC; aout++) { // Program safemode analog output condition: S826_DacRangeWrite(0, aout, S826_DAC_SPAN_0_5, 1); // output range S826_DacDataWrite(0, aout, 0, 1); // output voltage } S826_SafeWrenWrite(0, S826_SAFEN_SWD); // Protect safemode data registers.
Software
Board IDs
The "BOARD NUM" switches (at top edge of board near mounting bracket) assign the board ID used by software. The ID is binary coded on the four switches and can be programmed to any value from 0 (default) to 15. A board's ID determines the corresponding bit that will be set to '1' in the value returned by S826_SystemOpen
. If you have one 826 board, the returned value is (2^ID)
. If you have multiple boards, the returned value is the sum of (2^ID)
for each board.
- Examples
- You have one board with ID set to 0, so the value returned by
S826_SystemOpen
will be(2^0) = 1
. - You have two boards with IDs set to 1 and 4, so the value returned by
S826_SystemOpen
will be(2^1)+(2^4) = 2+16 = 18
.
This code snippet will tell you the meaning of the value returned by S826_SystemOpen
:
int id, flags = S826_SystemOpen(); if (flags < 0) printf("S826_SystemOpen returned error code %d", flags); else if (flags == 0) printf("No boards were detected"); else { printf("Boards were detected with these IDs:"); for (id = 0; id < 16; id++) { if (flags & (1 << id)) printf(" %d", id); } }
Custom installation and re-distribution (Windows)
Sensoray's installer uses the NSIS installation system. It is created from a .NSI script. The core API is installed as follows in NSI script code:
Section "Core API" SectionIn RO ${If} ${RunningX64} SetOutPath "$WINDIR\system32"; !insertmacro DisableX64FSRedirection File "..\mid-826\code\Release64\s826.dll"; !insertmacro EnableX64FSRedirection SetOutPath "$WINDIR\SysWOW64"; File "..\mid-826\code\Release\s826.dll"; ${Else} SetOutPath "$WINDIR\system32"; File "..\mid-826\code\Release\s826.dll"; ${EndIf} SectionEnd
The drivers are installed via dpinst.exe in the NSI script as follows:
Section "Drivers" SectionIn RO CreateDirectory "$INSTDIR\driver\x64"; SetOutPath "$INSTDIR\driver\x64"; File "..\cd\driver\x64\dpinst.exe"; File "..\cd\driver\x64\s826.cat"; File "..\cd\driver\x64\s826.inf"; File "..\cd\driver\x64\s826.sys"; File "..\cd\driver\x64\s826filter.cat"; File "..\cd\driver\x64\s826filter.inf"; File "..\cd\driver\x64\s826filter.sys"; File "..\cd\driver\x64\WdfCoInstaller01009.dll"; CreateDirectory "$INSTDIR\driver\x32"; SetOutPath "$INSTDIR\driver\x32"; File "..\cd\driver\x32\dpinst.exe"; File "..\cd\driver\x32\s826.cat"; File "..\cd\driver\x32\s826.inf"; File "..\cd\driver\x32\s826.sys"; File "..\cd\driver\x32\s826filter.cat"; File "..\cd\driver\x32\s826filter.inf"; File "..\cd\driver\x32\s826filter.sys"; File "..\cd\driver\x32\WdfCoInstaller01009.dll"; MessageBox MB_OK "Driver installation dialog will pop-up. Follow the prompts and click Finish when done" ${If} ${RunningX64} ExecWait '"$INSTDIR\driver\x64\dpinst.exe" /f' ${Else} ExecWait '"$INSTDIR\driver\x32\dpinst.exe" /f' ${EndIf} NoInstallDriver: SectionEnd
- What other libraries does the installer install as part of the Core API?
The 826 is compiled with Microsoft Visual Studio C++ 2008. The re-distributables for C++ must be installed. The installer installs this library silently running the command:
"vcredist_x86.exe /q"
and the following additional command on 64-bit systems:
"vcredist_x64.exe /q"
These re-distributables are available from Microsoft for x64 and x86.
- Are any other libraries required? I installed the libraries above, but the demo doesn't work with my custom installer?
The demo is written using .NET libraries (version 3.5). These are also available from Microsoft here. The executable can be silently installed using this command:
"dotnetfx35setup.exe /qb"
- Could I obtain the full 826 NSI script as a template for creating my own installer?
Yes, the full script is available here.
Silent install (Windows)
- I want to run the installer silently. Do you have any way to do this?
There are many options. For re-distribution, you may create your own installation package. Also, starting with version 3.3.9, there are additional command line options to quiet the setup.exe installer from command line or batch file. These options are described below.
- What are the options for silent install?
The basic silent install is invoked by running the following command from command line or batch script:
"setup.exe /S"
Please note that the /S is case sensitive and must be upper case.
- I've pre-installed the drivers and don't want to re-install them during the installation? Is there a command for that?
"setup.exe /S /no_driver=1"
- Is there an additional command to not install the demo programs?
Yes, in version 3.3.9, the following command will install the required DLLs and system libraries, but no drivers or demo programs.
"setup.exe /S /no_driver=1 /no_demos=1"
- I want to install the drivers silently, but there is always a pop-up to verify.
Unfortunately, there is no way around this. Windows requires confirmation from the user for driver install, even if the driver is signed.
- I'm running the setup silently, but it pops up a dialog to confirm if I want to make changes to the PC (User Account Control). How do I prevent this?
Windows controls this through User Access Control. If running the setup from a standard windows console, the Windows User Account Control (UAC) will pop-up. This cannot be by-passed by Sensoray because the installer installs files to system directories.
One work-around is to launch the setup in an Windows Command Prompt Window started in administrator mode (right-click and select "Run As Administrator"). Another approach is to launch the setup as a user with administrator privileges. User access control may also be disabled, but we do not recommend this for security reasons.
Labview
Before running an 826 virtual instrument (VI) under Labview, make sure you install the latest versions of the 826 DLL (s826.dll) and device driver (both are contained in the 826 SDK, which you can obtain from the Downloads tab of the 826 product page). Each VI is a basically a wrapper for a DLL function and consequently the VIs are dependent on the DLL, which in turn depends on the driver. Board hardware and firmware version numbers will be automatically read from the 826 board by software when all dependencies are satisfied -- it is not necessary to manually enter any board selection information except the board number, which is specified by the board's switch settings (factory default is board number 0).
The VIs are not independently documented, but since each VI wraps a DLL function, the DLL documentation effectively explains the function of each associated VI. The DLL documentation can be found in the 826 product manual (download from the 826 product page Documentation tab).
Software updates
1. Windows 3.3.4
- C# demo application added to SDK. Error checking for invalid modes to S826_CounterModeWrite.
2. Linux 3.3.5
- C# GUI demo available, using Linux mono. To get required libraries on Ubuntu, type:
"sudo apt-get install mono-complete"
For a C# development environment, type:
"sudo apt-get install monodevelop"
Resources for custom driver development
- I want to develop my own driver for the 826. Does Sensoray offer any resources for custom driver development?
Yes, we provide these resources free of charge:
- Linux Software Development Kit (SDK) - Includes source code for the 826 driver and middleware, comprising a complete 826 API for Linux. The middleware core is operating system independent and thread-safe, which makes this SDK a great starting point for porting to any operating system. The SDK can be found on the Downloads tab of the 826 product page. The SDK has been carefully designed for reliable operation in multi-threaded and multi-process applications, and consequently it can be easily ported to real-time operating systems.
- Model 826 Technical Manual - This comprehensive manual explains the API and 826 hardware in detail (download from the Documentation tab of the 826 product page).
- Register Map - A map of the board's hardware registers is available here. The registers are accessed through PCI BAR 2. Registers appear in both banked and flat address spaces. The banked space is only required for rev 0 boards; you should use the flat space exclusively if you have a later rev, as this will yield superior performance.
Linux versions
- Do you recommend specific Linux distributions for use with the 826?
We no longer support the obsolete kernel 2.4, but otherwise have no specific recommendation as it depends on the application (e.g., it might be desirable to use a low-latency kernel). We normally test first on Ubuntu LTS, but have a script to test builds on kernel versions 2.6.x, 3.x, and 4.x.
C examples
A variety of C programming examples have been collected together in a common source file to illustrate how to program resources on the 826.
VB.NET demo
To help you jump-start your VB.NET project, we offer the VB.NET demo for model 826. This demo program provides a GUI for nearly every hardware resource on the board. All source files are provided, including a reusable VB module that declares all functions, types, and constants for the 826 API.
Modprobe error
- When I try "
sudo make install
" it reports "modprobe: ERROR: could not insert 's826': Required key not available
". How can I install the driver?
It's likely that the 826 driver is installed and you are actually seeing a warning. You can confirm this by trying "sudo modprobe s826
" and the 826 demo application.
Windows link error
- I'm using VisualStudio (VS) on a 64-bit machine to build a 32-bit application for the 826. VS reports that linking failed because it can't open s826.lib. What could be the problem?
You should use the 32-bit DLL (and its associated LIB) because you are building a 32-bit application (x86). Use the 64-bit version only when building 64-bit apps (x64). To avoid confusion, you can copy the 32-bit DLL and its associated LIB file from the install directory to your project directory, then point the linker to it there. Make sure to also point the debugger to the 32-bit DLL by setting its working directory.
Note: You will always use the 64-bit driver on a 64-bit OS regardless of 32/64-bit application type, but you don't need to select this as it is automatically installed by the SDK installer.
Migrating from model 626
Using 626 cables with the 826
- I have a 7505TDIN breakout board and 7501C1 (50-pin cable) for the 626. Can I use these with the 826?
The 7505TDIN and 7501C are both compatible with the 826. However, we recommend using an 826C2 cable instead of the 7501C because it has a low profile header at one end that results in a denser cable stackup. That said, the 7501C cable can be used if it doesn't cause mechanical interference in your system.
Connector pinout differences
- Do the 826 and 626 have identical connector pinouts?
The digital and counter connector pinouts are identical. Analog connector pinouts differ slightly because the 826 has four additional analog outputs. The analog pinouts are identical except for pins 41, 43, 45 and 47, which convey DAC channel 4-7 outputs on the 826 (vs. remote sense inputs on the 626).
See also
- GPIO interfacing - design tips for DIO circuits