|
|
(132 intermediate revisions by one user not shown) |
Line 1: |
Line 1: |
| [[File:826 photo.jpg|thumb|Model 826 board]] | | [[File:826 photo.jpg|thumb|Model 826 board]] |
| | | |
− | This is the technical wiki page for Sensoray's [http://www.sensoray.com/products/826.htm 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. | + | This is the top-level wiki page for Sensoray's [http://www.sensoray.com/products/826.htm 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 with quadrature clock decoders, three-stage watchdog timer with fail-safe controller, and a flexible signal router. |
| + | |
| + | ;Related pages |
| + | Each board subsystem has a dedicated wiki page: |
| + | * [[826 ADC|ADC]] - analog input system |
| + | * [[826 DAC|DACs]] - analog output system |
| + | * [[826 DIOs|DIOs]] - general-purpose digital I/Os |
| + | * [[826 counters|Counters]] - counter/timers, including appnotes for interfacing incremental encoders |
| + | * [[826 watchdog|Watchdog]] - watchdog timer and fail-safe controller |
| | | |
| ;Please note: | | ;Please note: |
− | * Examples are intended to function as described, but this is not guaranteed. If you discover an error, please [http://www.sensoray.com/support/message.htm?s=Web%20feedback inform the webmaster]. | + | * Code and circuit examples are intended to function as described, but this is not guaranteed. If you discover an error, please [http://www.sensoray.com/support/message.htm?s=Web%20feedback inform the webmaster]. |
| * In code examples, error checking has been simplified or omitted for clarity. It is recommended to always perform error checking in your production software. | | * In code examples, error checking has been simplified or omitted for clarity. It is recommended to always perform error checking in your production software. |
| * C language examples depend on header file <code>826api.h</code>, which should be included at the top of your source code like this: | | * C language examples depend on header file <code>826api.h</code>, which should be included at the top of your source code like this: |
| #include "826api.h" | | #include "826api.h" |
| | | |
− | ==Counters== | + | ==Timestamp generator== |
| | | |
− | ===Reading counts===
| + | The timestamp generator is a free-running 32-bit counter that serves as a time reference. The counter increments once per microsecond and overflows (to zero, without notification) every 2<sup>32</sup> µs (approximately 71.6 minutes). It is a binary counter and consequently does not keep track of the date or time-of-day. At any moment, the current count may be sampled; such a sample is called a ''timestamp''. |
| | | |
− | There are two ways to read the counts from a counter. The simplest way is to directly read the counter:
| + | A timestamp is automatically appended to every counter snapshot and to every ADC sample so that application programs can know (to within 1 µs) when each sample was acquired. Also, application programs can read the timestamp generator at any time to get a timestamp. |
− | | + | |
− | uint counts;
| + | |
− | S826_CounterRead(0, 0, &counts); // Fetch the current counts from counter 0.
| + | |
− | printf("Counts = %d\n", counts); // Display the counts.
| + | |
− | | + | |
− | Often it's necessary to know exactly when the counts were sampled. To do this under software control, you must either read the counter at an exact moment in time or record the exact time when the counter is read. Fortunately there's a much easier and more accurate way: trigger a snapshot and then, at your leisure, read the simultaneously captured counts and [[826#Timestamp generator|timestamp]] — accurate to within 1 µs:
| + | |
− | | + | |
− | uint c, t; // simultaneously-sampled counts and timestamp
| + | |
− |
| + | |
− | S826_CounterSnapshot(0, 0); // Trigger a snapshot on counter 0.
| + | |
− | S826_CounterSnapshotRead(0, 0, &c, &t, NULL, 0); // Fetch the snapshot.
| + | |
− | printf("Counts = %d at timestamp = %d\n", c, t); // Display counts and timestamp.
| + | |
− | | + | |
− | The first method bypasses the snapshot FIFO and thus allows you to immediately read the current counts at any time. However, when using the second method, note that the FIFO may not be empty when you trigger the snapshot. In such cases, you must read all previous snapshots from the FIFO before you can read the one you just triggered. If you're not sure the FIFO was empty and you don't care about previous snapshots, you can use the following code to discard all previous snapshots and receive the one you just triggered:
| + | |
− | | + | |
− | uint c, t; // simultaneously-sampled counts and timestamp
| + | |
− |
| + | |
− | S826_CounterSnapshot(0, 0); // Trigger snapshot.
| + | |
− | while (S826_CounterSnapshotRead(0, 0, &c, &t, NULL, 0) == S826_ERR_OK); // Fetch the newest snapshot.
| + | |
− | printf("Counts = %d at timestamp = %d\n", c, t); // Display counts and timestamp.
| + | |
− | | + | |
− | ===Jamming counts into a counter===
| + | |
− | | + | |
− | To programmatically load a particular counts value into a counter, write the value to the counter's preload0 register and invoke a soft counter preload:
| + | |
− | | + | |
− | // Jam a value into a counter.
| + | |
− | void JamCounts(uint board, uint ctr, uint value)
| + | |
− | {
| + | |
− | S826_CounterPreloadWrite(board, ctr, 0, value); // Write value to preload0 register.
| + | |
− | S826_CounterPreload(board, ctr, 1, 0); // Copy preload0 to counter.
| + | |
− | }
| + | |
− |
| + | |
− | JamCounts(0, 3, 1234); // Example: Jam 1234 into counter3 of board0.
| + | |
− | | + | |
− | ===Event counter===
| + | |
− | | + | |
− | A common counter application is counting external events, where each event is indicated by a pulse. To implement this, connect the TTL/CMOS pulse signal to the counter's ClkA+ input. The following code shows how to configure a counter to tally incoming pulses. | + | |
− | | + | |
− | // Configure board0, counter0 as a pulse counter and start it running.
| + | |
− | S826_CounterModeWrite(0, 0, // Configure counter:
| + | |
− | S826_CM_K_ARISE); // clock = ClkA rising edge
| + | |
− | S826_CM_PX_START); // preload when counter enabled.
| + | |
− | S826_CounterPreloadWrite(0, 0, 0, 0); // Zero counts upon preload.
| + | |
− | S826_CounterStateWrite(0, 0, 1); // Start the pulse counter running.
| + | |
− | | + | |
− | While the pulse counter is running, you can read the event count as explained in [[826#Reading counts|Reading counts]].
| + | |
− | | + | |
− | ===How to use interrupts===
| + | |
− | | + | |
− | :''I see that counters have an "Interrupt System (IRQ)" signal; how do I use this?''
| + | |
− | | + | |
− | An interrupt request (IRQ) is issued when a counter event (e.g., Compare register match) is detected and information about the event (a "snapshot") is stored in the counter's FIFO buffer. Counter IRQs are transparently managed by the <code>S826_CounterSnapshotRead</code> function; this API function enables/disables interrupts and handles IRQs as required so that developers need not be concerned with the complexities of interrupt programming.
| + | |
− | | + | |
− | To wait for the next IRQ, simply call <code>S826_CounterSnapshotRead</code>. The function will return when an event snapshot is available, and will block and allow other threads to run while the event FIFO is empty. See the following sections for examples of how this works.
| + | |
− | | + | |
− | ====Interrupt performance====
| + | |
− | | + | |
− | A number of factors affect how quickly <code>S826_CounterSnapshotRead</code> returns following an IRQ. Real-time operating systems typically provide short, predictable delays, whereas other operating systems cannot guarantee performance. In the latter case (e.g., Linux, Windows), you may be able to speed up responsiveness by increasing the priority of the calling thread.
| + | |
− | | + | |
− | ====Canceling an interrupt wait====
| + | |
− | | + | |
− | Sometimes it's necessary to force <code>S826_CounterSnapshotRead</code> to unblock and return before an IRQ occurs. The calling thread can do this itself by specifying a time limit for the block. But what if a different thread needs to force the unblock? For example, suppose threadA is blocked by <code>S826_CounterSnapshotRead</code> (because it's waiting for an IRQ) and threadB must force threadA to unblock. There are several ways for threadB to accomplish this:
| + | |
− | | + | |
− | * Call <code>S826_CounterWaitCancel</code>. This will cause <code>S826_CounterSnapshotRead</code> to immediately return <code>S826_ERR_CANCELLED</code> to threadA.
| + | |
− | * Call <code>S826_CounterSnapshot</code>. Note that this doesn't actually cancel the block; instead it triggers a snapshot and generates an IRQ. This will cause <code>S826_CounterSnapshotRead</code> to immediately return <code>S826_ERR_OK</code> to threadA, with trigger flag <code>S826_SSRMASK_SOFT</code> set to indicate the snapshot was triggered by software.
| + | |
− | * Call <code>S826_SystemClose</code>. This will cause threadA (and all other threads blocked by the API) to immediately become unblocked; all blocking functions will return <code>S826_ERR_SYSCLOSED</code>.
| + | |
− | | + | |
− | ===Time delay===
| + | |
− | | + | |
− | A counter can be used to delay software execution for a specific amount of time. To implement this, preload the counter with the desired delay time and configure it to capture a snapshot upon counting down to zero. For example, the following function creates a delay timer with 1 µs resolution (because it uses the 1 MHz internal clock; for 20 ns resolution, use the 50 MHz internal clock):
| + | |
− | | + | |
− | // Create a delay timer and start it running.
| + | |
− | int CreateDelay(uint board, uint ctr, uint delay) // delay specified in microseconds
| + | |
− | {
| + | |
− | S826_CounterModeWrite(board, ctr, // Configure counter mode:
| + | |
− | S826_CM_K_1MHZ | // clock source = 1 MHz internal
| + | |
− | S826_CM_UD_REVERSE | // count down
| + | |
− | S826_CM_PX_START | // preload delay counts upon counter enabled
| + | |
− | S826_CM_TD_ZERO); // stop counting when counts reach 0
| + | |
− | S826_CounterPreloadWrite(board, ctr, 0, delay); // Delay time in microseconds.
| + | |
− | S826_CounterSnapshotConfigWrite(board, ctr, // Configure snapshot triggering:
| + | |
− | S826_SSRMASK_ZERO | // capture snapshot when counts reach 0
| + | |
− | (S826_SSRMASK_ZERO << 16), // disable snapshots when counts reach 0
| + | |
− | S826_BITWRITE);
| + | |
− | return S826_CounterStateWrite(board, ctr, 1); // Start the delay timer running.
| + | |
− | }
| + | |
− | | + | |
− | After starting the delay timer, call <code>S826_CounterSnapshotRead</code> to wait for the time interval to elapse:
| + | |
− | | + | |
− | int WaitForDelay(uint board, uint ctr)
| + | |
− | {
| + | |
− | return S826_CounterSnapshotRead(board, ctr, // Block while waiting for timer:
| + | |
− | NULL, NULL, NULL, // ignore snapshot counts, timestamp and reason
| + | |
− | S826_WAIT_INFINITE); // never timeout
| + | |
− | }
| + | |
− | | + | |
− | For example, the following code will block the calling thread for 0.5 seconds while other threads are allowed to run, using counter0 on board number 0:
| + | |
− | | + | |
− | CreateDelay(0, 0, 500000); // Create 0.5 second delay timer and start it running.
| + | |
− | WaitForDelay(0, 0); // Wait for delay time to elapse.
| + | |
− | printf("Delay time has elapsed!");
| + | |
− | | + | |
− | If desired, the calling thread can do some work while the delay timer is running:
| + | |
− | | + | |
− | CreateDelay(0, 0, 500000); // Create 0.5 second delay timer and start it running.
| + | |
− | DoSomeWork(); // Do some work while the timer runs.
| + | |
− | WaitForDelay(0, 0); // Wait for remaining delay time (if it hasn't already elapsed).
| + | |
− | printf("Delay time has elapsed!");
| + | |
− | | + | |
− | In the above example, <code>WaitForDelay</code> may or may not block depending on how long it takes for <code>DoSomeWork</code> to execute. If <code>DoSomeWork</code> takes more than 0.5 s to execute, <code>WaitForDelay</code> will return immediately without blocking.
| + | |
− | | + | |
− | The next example performs work while the delay timer is running, but it doesn't block at all. Instead, it simply polls the timer.
| + | |
− | | + | |
− | CreateDelay(0, 0, 500000); // Create 0.5 second delay timer and start it running.
| + | |
− | while (S826_CounterSnapshotRead(0, 0, NULL, NULL, NULL, 0) == S826_ERR_NOTREADY) {
| + | |
− | DoSomeWork(); // Do some work while the timer runs.
| + | |
− | }
| + | |
− | printf("Delay time has elapsed!");
| + | |
− | | + | |
− | ===Periodic timer===
| + | |
− | | + | |
− | A counter can be used as a periodic timer, which is useful for periodically executing software or triggering ADC conversions. To implement this, configure the counter so that it repeatedly counts down to zero and then preloads (see examples below). The preload value and clock frequency determine the period. The following functions create a timer having 1 µs resolution because they use the 1 MHz internal clock; for higher resolution (20 ns), use the 50 MHz internal clock.
| + | |
− | | + | |
− | ====Hardware timer====
| + | |
− | | + | |
− | The following function will cause a counter to periodically pulse its ExtOut output. The pulses can be internally routed to the ADC and used to trigger conversion bursts as shown in [[826#Periodic ADC conversions (using_counter)|this example]]. If desired, ExtOut may be routed to a DIO for external use, but note that the output pulses are always 20 ns wide regardless of the input clock frequency (for longer pulses, use a [[826#How to configure a counter for PWM operation|PWM generator]]).
| + | |
− | | + | |
− | // Configure a counter as a 20 ns pulse generator and start it running.
| + | |
− | int CreateHwTimer(uint board, uint chan, uint period) // period in microseconds
| + | |
− | {
| + | |
− | S826_CounterModeWrite(board, chan, // Configure counter mode:
| + | |
− | S826_CM_K_1MHZ | // clock source = 1 MHz internal
| + | |
− | S826_CM_PX_START | S826_CM_PX_ZERO | // preload @start and counts==0
| + | |
− | S826_CM_UD_REVERSE | // count down
| + | |
− | S826_CM_OM_NOTZERO); // ExtOut = (counts!=0)
| + | |
− | S826_CounterPreloadWrite(board, chan, 0, period); // Set period in microseconds.
| + | |
− | return S826_CounterStateWrite(board, chan, 1); // Start the timer running.
| + | |
− | }
| + | |
− | | + | |
− | ====Software timer====
| + | |
− | | + | |
− | This function configures a counter to capture snapshots at regular intervals:
| + | |
− | | + | |
− | // Configure a counter as a periodic software timer and start it running.
| + | |
− | int CreateTimer(uint board, uint chan, uint period) // period in microseconds
| + | |
− | {
| + | |
− | S826_CounterModeWrite(board, chan, // Configure counter mode:
| + | |
− | S826_CM_K_1MHZ | // clock source = 1 MHz internal
| + | |
− | S826_CM_PX_START | S826_CM_PX_ZERO | // preload @start and counts==0
| + | |
− | S826_CM_UD_REVERSE); // count down
| + | |
− | S826_CounterPreloadWrite(board, chan, 0, period); // Set period in microseconds.
| + | |
− | S826_CounterSnapshotConfigWrite(board, chan, // Generate IRQ when counts==0.
| + | |
− | S826_SSRMASK_ZERO, S826_BITWRITE);
| + | |
− | return S826_CounterStateWrite(board, chan, 1); // Start the timer running.
| + | |
− | }
| + | |
− | | + | |
− | While the timer is running, a thread can call the following function to wait for the next snapshot. Other threads are allowed to run while <code>S826_CounterSnapshotRead()</code> blocks the calling thread, resulting in efficient, event-driven operation.
| + | |
− | | + | |
− | // Wait for the next timer period. Returns 0=ok, else=826 error code.
| + | |
− | int WaitForTimer(uint board, uint chan)
| + | |
− | {
| + | |
− | return S826_CounterSnapshotRead(board, chan, // Block until next period.
| + | |
− | NULL, NULL, NULL, // ignore snapshot values
| + | |
− | S826_WAIT_INFINITE);
| + | |
− | }
| + | |
− | | + | |
− | ===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:
| + | |
− | | + | |
− | [[#Software timer|CreateTimer]](0, 0, 10000); // Create 10 ms (10000 us) periodic timer and start it running.
| + | |
− | | + | |
− | Now run the following loop, which will execute <code>PeriodicFunction</code> at 100 Hz. Note that other threads can do useful work when <code>PeriodicFunction</code> is not executing.
| + | |
− | | + | |
− | // Repeat while no errors detected:
| + | |
− | while (![[#Software timer|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 timer by calling <code>S826_CounterSnapshotRead</code> with a zero timeout:
| + | |
− | | + | |
− | 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 not yet a new period
| + | |
− | DoSomeOtherStuff(); // do other processing.
| + | |
− | else // Else wait was cancelled or error detected, so
| + | |
− | break; // exit the polling loop.
| + | |
− | }
| + | |
− | | + | |
− | ===Frequency counter===
| + | |
− | | + | |
− | The frequency of an external digital signal can be precisely measured using just one counter channel. To do this, connect the external signal to the ClkA+ input and route the internal tick generator to the Index input. The selected tick signal is used as a counting gate and it also determines the sampling rate and measurement resolution. The sampling rate is equal to the tick frequency, whereas resolution is inversely proportional to the tick frequency.
| + | |
− | | + | |
− | The following function shows how to configure a counter for frequency measurement.
| + | |
− | | + | |
− | // Configure a frequency counter and start it running.
| + | |
− | int CreateFrequencyCounter(uint board, uint chan, uint ticksel)
| + | |
− | {
| + | |
− | if ((ticksel < S826_CM_XS_R1HZ) || (ticksel > S826_CM_XS_1MHZ))
| + | |
− | return S826_ERR_S826_ERR_VALUE; // Abort if invalid tick selector.
| + | |
− | S826_CounterModeWrite(board, chan, // Configure counter:
| + | |
− | S826_CM_K_ARISE | // clock = ClkA (external digital signal)
| + | |
− | ticksel | // route tick generator to Index input
| + | |
− | S826_CM_PX_IXRISE); // preload upon tick rising edge
| + | |
− | S826_CounterPreloadWrite(board, chan, 0, 0); // Reset counts upon tick rising edge.
| + | |
− | S826_CounterSnapshotConfigWrite(board, chan, // Acquire counts upon tick rising edge.
| + | |
− | S826_SSRMASK_IXRISE, S826_BITWRITE);
| + | |
− | return S826_CounterStateWrite(board, chan, 1); // Start the frequency counter running.
| + | |
− | }
| + | |
− | | + | |
− | While the frequency counter is running, a thread can call the following function to wait for the next sample. Other threads are allowed to run while it waits for the next sample to arrive.
| + | |
− | | + | |
− | // Receive the next frequency sample.
| + | |
− | int ReadFrequency(uint board, uint chan, uint *sample)
| + | |
− | {
| + | |
− | return S826_CounterSnapshotRead(board, chan, // Block until next sample
| + | |
− | sample, NULL, NULL, // and then receive accumulated counts.
| + | |
− | S826_WAIT_INFINITE);
| + | |
− | }
| + | |
− | | + | |
− | For example, the following code will measure frequency 10 times per second using counter0 on board 0. Note that the first sample is discarded as it may not be valid.
| + | |
− | | + | |
− | // Measure frequency 10 times per second.
| + | |
− | uint counts;
| + | |
− | CreateFrequencyCounter(0, 0, S826_CM_XS_10HZ); // Create frequency counter and start it running.
| + | |
− | ReadFrequency(0, 0, NULL); // Discard first sample.
| + | |
− | while (!ReadFrequency(0, 0, &counts)) { // Wait for next sample; if no errors then
| + | |
− | printf("Frequency = %d Hz\n", counts * 10); // display measured frequency.
| + | |
− | }
| + | |
− | | + | |
− | ===Routing a counter output to DIO pins===
| + | |
− | | + | |
− | The following function will route a counter's ExtOut signal to a DIO pin, which is useful when a physical output is needed (e.g., PWMs, one-shots).
| + | |
− | | + | |
− | int RouteCounterOutput(uint board, uint ctr, uint dio)
| + | |
− | {
| + | |
− | uint data[2]; // dio routing mask
| + | |
− | if ((dio >= S826_NUM_DIO) || (ctr >= S826_NUM_COUNT))
| + | |
− | 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.
| + | |
− | }
| + | |
− | | + | |
− | Each DIO is associated with a specific counter and can only be routed to that counter's ExtOut (see <code>S826_DioOutputSourceWrite</code> documentation for details). 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 also to dio11.
| + | |
− | | + | |
− | <code>RouteCounterOutput</code> is not thread-safe because it uses an unprotected read-modify-write (RMW) sequence to access and configure the DIO signal router. If multiple threads will be calling this function then a mutex should be used to ensure thread safety.
| + | |
− | | + | |
− | ===One-shot===
| + | |
− | | + | |
− | A counter can be used to implement a "one-shot" that outputs a pulse when triggered. The trigger can be an external signal (applied to the counter's IX input) or another counter's ExtOut signal. The one-shot can also be triggered by software by invoking a counter preload. The output pulse appears on the counter's ExtOut signal, which can be routed to a DIO pin and/or another counter's Index input, if desired. The following function will configure a counter as a one-shot, with pulse width (<code>ontime</code>) specified in microseconds.
| + | |
− | | + | |
− | // Configure a counter as as a one-shot -----------
| + | |
− | // trig specifies the trigger source: 0 = external (IX input); 2-7 = counter[trig-2] ExtOut.
| + | |
− | int CreateOneShot(uint board, uint ctr, uint trig, uint ontime)
| + | |
− | {
| + | |
− | S826_CounterModeWrite(board, ctr, // Configure counter:
| + | |
− | S826_CM_K_1MHZ | // clock = internal 1 MHz
| + | |
− | S826_CM_UD_REVERSE | // count down
| + | |
− | trig | // route trig to Index input
| + | |
− | S826_CM_PX_IXRISE | // preload @ trig rising edge
| + | |
− | S826_CM_TE_PRELOAD | // enable upon preload
| + | |
− | S826_CM_TD_ZERO | // disable when counts reach 0
| + | |
− | S826_CM_OM_NOTZERO); // ExtOut = (counts!=0)
| + | |
− | S826_CounterPreloadWrite(board, ctr, 0, ontime); // Pulse width in microseconds.
| + | |
− | return S826_CounterStateWrite(board, ctr, 1); // Enable the 1-shot.
| + | |
− | }
| + | |
− | | + | |
− | The above function can be used to implement a retriggerable or non-retriggerable one-shot. These examples show how to create a 700 µs externally-triggered one shot using counter0, with output on dio0:
| + | |
− | | + | |
− | // Implement a retriggerable one-shot.
| + | |
− | [[826#Routing a counter output to DIO pins|RouteCounterOutput]](0, 0, 0); // Route one-shot (counter0) output to dio0.
| + | |
− | CreateOneShot(0, 0, 0, 700); // Create 700 us retriggerable one-shot.
| + | |
− | | + | |
− | // Implement a non-retriggerable one-shot.
| + | |
− | [[826#Routing a counter output to DIO pins|RouteCounterOutput]](0, 0, 0); // Route one-shot (counter0) output to dio0.
| + | |
− | CreateOneShot(0, 0, // Create 700 us non-retriggerable one-shot.
| + | |
− | S826_CM_NR_NORETRIG,
| + | |
− | 700);
| + | |
− | | + | |
− | ===Period measurement===
| + | |
− | | + | |
− | A counter can be used to measure the period of a digital signal applied to its IX+ input. It does this by counting internal clocks during one complete cycle of the input signal. At the end of each cycle, the accumulated counts are captured in a snapshot and the counter is zeroed to start the next measurement. Since a new measurement begins at the end of each input cycle, the sampling rate will equal the signal's input frequency.
| + | |
− | | + | |
− | The signal's period is indicated by the snapshot counts, so a higher clock frequency will result in a greater number of counts and higher resolution. In the example functions below, set hires=0 to select the 1 MHz internal clock (provides 1 µs resolution for periods up to approximately 71.6 minutes) or set hires=1 to select the 50 MHz internal clock (provides 20 ns resolution for periods up to approximately 1.43 minutes).
| + | |
− | | + | |
− | // Create a period counter and start it running.
| + | |
− | int CreatePeriodCounter(uint board, uint ctr, int hires)
| + | |
− | {
| + | |
− | S826_CounterModeWrite(board, ctr, // Configure the period counter:
| + | |
− | (hires ? // select internal clock based on resolution:
| + | |
− | S826_CM_K_50MHZ : // for high res (20 ns) use 50 MHz
| + | |
− | S826_CM_K_1MHZ) | // for low res (1 us) use 1 MHz
| + | |
− | S826_CM_UD_NORMAL | // count up
| + | |
− | S826_CM_XS_EXTNORMAL | // connect index input to IX (external signal)
| + | |
− | S826_CM_PX_IXRISE); // preload upon signal rising edge
| + | |
− |
| + | |
− | S826_CounterPreloadWrite(board, ctr, 0, 0); // Preload 0 at start of each cycle.
| + | |
− | S826_CounterSnapshotConfigWrite(board, ctr, // Snapshot upon signal rising edge.
| + | |
− | S826_SSRMASK_IXRISE, S826_BITWRITE);
| + | |
− | return S826_CounterStateWrite(board, ctr, 1); // Enable period measurement.
| + | |
− | }
| + | |
− | | + | |
− | // Read measured period in seconds.
| + | |
− | int ReadPeriod(uint board, uint ctr, double *period, int hires)
| + | |
− | {
| + | |
− | uint counts;
| + | |
− | int errcode = S826_CounterSnapshotRead(board, ctr, // Block until next sample available.
| + | |
− | &counts, NULL, NULL, S826_WAIT_INFINITE);
| + | |
− | *period = counts * (hires ? 2e-8 : 1e-6);
| + | |
− | return errcode;
| + | |
− | }
| + | |
− | | + | |
− | // Example: Measure period of external digital signal.
| + | |
− | double period; // period in seconds
| + | |
− | int hires = TRUE;
| + | |
− | CreatePeriodCounter(0, 0, hires); // Create high-resolution (20 ns) period counter.
| + | |
− | ReadPeriod(0, 0, &period, hires); // Discard the first sample (may not be a complete cycle).
| + | |
− | ReadPeriod(0, 0, &period, hires);
| + | |
− | printf("Signal period (in seconds) = %f", period);
| + | |
− | | + | |
− | ===Incremental encoders===
| + | |
− | | + | |
− | [[File:IncrementalEncoder.jpg|right|200px|An incremental encoder]]
| + | |
− | Every counter channel provides comprehensive support for incremental encoders, including:
| + | |
− | * Differential line receivers
| + | |
− | * Noise filters
| + | |
− | * High-speed (50 MS/s) quadrature clock decoder
| + | |
− | * 32-bit position counter
| + | |
− | * 32-bit timestamps with 1 µs resolution
| + | |
− | * Dual compare registers
| + | |
− | * Dual preload registers
| + | |
− | * Flexible event triggering system
| + | |
− | * 16-deep event FIFO
| + | |
− | * 5 V encoder power
| + | |
− | | + | |
− | ====Programming fundamentals====
| + | |
− | | + | |
− | :''Which API 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:
| + | |
− | | + | |
− | S826_CounterModeWrite(0, 0, S826_CM_K_QUADX4); // Configure counter0 as incremental encoder interface.
| + | |
− | S826_CounterStateWrite(0, 0, 1); // Start tracking encoder position.
| + | |
− | | + | |
− | * You can read the current encoder position on demand, either by reading the counts directly or by invoking and reading a snapshot, as explained in [[826#Reading counts|Reading counts]].
| + | |
− | | + | |
− | * Two snapshots allow you to precisely measure speed -- see [[826#Measuring speed|Measuring speed]] for details.
| + | |
− | | + | |
− | * The encoder counts can be changed by software (see [[826#Jamming counts into a counter|Jamming counts into a counter]]). This is typically done when the encoder is stopped at a known position, but not while the encoder is moving as doing so could disrupt position tracking.
| + | |
− | | + | |
− | ====Using interrupts with encoders====
| + | |
− | | + | |
− | The following example uses hardware interrupts to block the calling thread until the encoder counts reach a particular value. Other threads are allowed to run while the calling thread blocks. A snapshot is captured when the target count is reached. The snapshot generates an interrupt request, which in turn causes <code>S826_CounterSnapshotRead()</code> to return. The example ignores the snapshot counts (which will always equal the target value as explained [[826#Snapshot counts upon match|here]]), the [[826#timestamp generator|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 snapshot:
| + | |
− | S826_SSRMASK_MATCH0, // upon 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");
| + | |
− | | + | |
− | The next example waits for 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 snapshots are enabled for matches to both Compare registers. To set a limit on how long to wait, a maximum wait time is specified by <code>tmax</code> when calling <code>S826_CounterSnapshotRead</code>. Since a snapshot can now be triggered by multiple events, it's necessary to know which event triggered a snapshot in order to decide how to handle it; this is indicated by the snapshot's reason flags.
| + | |
− | | + | |
− | // Wait for counts to reach 3000 or 4000, or for 10 seconds to elapse, whichever comes first.
| + | |
− |
| + | |
− | uint counts; // encoder counts when the snapshot was captured
| + | |
− | uint tstamp; // 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 snapshot:
| + | |
− | S826_SSRMASK_MATCH0 // upon counts==3000
| + | |
− | | S826_SSRMASK_MATCH1, // or counts==4000
| + | |
− | S826_BITWRITE); // disable all other snapshot triggers
| + | |
− | errcode = S826_CounterSnapshotRead(0, 0, // Wait for snapshot:
| + | |
− | &counts, &tstamp, &reason, // receive the snapshot info here
| + | |
− | 10000000); // timeout if wait exceeds 10 seconds (10,000,000 us)
| + | |
− |
| + | |
− | switch (errcode) { // Decode and handle the snapshot:
| + | |
− | case S826_ERR_NOTREADY: // snapshot not available (wait timed out), so
| + | |
− | S826_CounterRead(0, 0, &counts); // read counts manually
| + | |
− | printf("Timeout -- counts didn't hit limits within 10 seconds; current counts = %d", counts);
| + | |
− | break;
| + | |
− | case S826_ERR_OK:
| + | |
− | if (reason & S826_SSRMASK_MATCH0)
| + | |
− | printf("Counter reached 3000 counts at timestamp %d", tstamp);
| + | |
− | if (reason & S826_SSRMASK_MATCH1)
| + | |
− | printf("Counter reached 4000 counts at timestamp %d", tstamp);
| + | |
− | break;
| + | |
− | default:
| + | |
− | printf("Error detected: errcode=%d", errcode);
| + | |
− | }
| + | |
− | | + | |
− | ====Measuring speed====
| + | |
− | | + | |
− | Speed measurement depends on the ability to sample displacement (position) at known times. Counter snapshots are ideally suited for this because they include simultaneously-sampled displacement and time information (counts and [[826#timestamp generator|timestamp]], respectively). Two snapshots allow you to precisely measure the average speed over a time interval. The interval is determined by the snapshot times: it begins when the first snapshot is captured and ends when the second snapshot is captured.
| + | |
− | | + | |
− | =====Measuring speed on demand=====
| + | |
− | | + | |
− | Here's a simple way to precisely measure speed over a 100 ms (approximate) interval:
| + | |
− | | + | |
− | uint c0, t0; // first snapshot (simultaneously sampled counts and timestamp)
| + | |
− | uint c1, t1; // second snapshot
| + | |
− | uint t; // elapsed time between snapshots (in microseconds)
| + | |
− |
| + | |
− | S826_CounterSnapshot(0, 0); // Trigger first snapshot.
| + | |
− | Sleep(100); // Wait for end of measurement interval (not recommended for production code).
| + | |
− | S826_CounterSnapshot(0, 0); // Trigger second snapshot.
| + | |
− |
| + | |
− | S826_CounterSnapshotRead(0, 0, &c0, &t0, NULL, 0); // Read first snapshot.
| + | |
− | S826_CounterSnapshotRead(0, 0, &c1, &t1, NULL, 0); // Read second snapshot.
| + | |
− |
| + | |
− | t = t1 - t0; // Compute measurement interval.
| + | |
− | printf("Measurement period = %d µs\n", t);
| + | |
− | printf("Average speed = %d counts per second\n", (1000000 * (c1 - c0)) / t);
| + | |
− | | + | |
− | =====Periodic speed measurement=====
| + | |
− | | + | |
− | To set this up, configure the counter to periodically capture snapshots. Each time a new snapshot arrives, the new and previous snapshots are used to precisely compute speed. 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 example produces a speed measurement exactly ten times per second. Note: you can change the sampling period as needed (e.g., to change measurement resolution).
| + | |
− | | + | |
− | // Measure and display speed 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 speed; // Speed in user units (e.g., meters/second).
| + | |
− |
| + | |
− | // Configure counter0 as encoder interface; automatically snapshot every 0.1 seconds.
| + | |
− | S826_CounterModeWrite(0, 0, // Configure counter mode:
| + | |
− | S826_CM_K_QUADX4 | // clock source = external quadrature x4
| + | |
− | S826_CM_XS_10HZ); // route internal 10 Hz clock to IX
| + | |
− | S826_CounterSnapshotConfigWrite(0, 0, 0x10, 0); // 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 snapshot, then compute and display speed.
| + | |
− | S826_CounterSnapshotRead(0, 0, &counts[1], &tstamp[1], NULL, S826_WAIT_INFINITE);
| + | |
− | cnts_per_us = (double)(counts[1] - counts[0]) / (tstamp[1] - tstamp[0]);
| + | |
− | speed = DISP_PER_CNT * 1000000 * cnts_per_us; // convert to meters/second
| + | |
− | printf("%f\n", speed);
| + | |
− | counts[0] = counts[1]; // Prepare for next snapshot.
| + | |
− | tstamp[0] = tstamp[1];
| + | |
− | }
| + | |
− | | + | |
− | If IX is already being used, create a thread that executes approximately 10 times per second. Each time the thread runs, call <code>S826_CounterSnapshot</code> to trigger a snapshot. In this case the sample rate will not be exactly 10 Hz, but speed will still be measured precisely because timestamps are included in the snapshots.
| + | |
− | | + | |
− | uint c0, t0; // previous snapshot
| + | |
− | uint c1, t1; // current snapshot
| + | |
− |
| + | |
− | S826_CounterSnapshot(0, 0); // Trigger and read the initial "previous" snapshot.
| + | |
− | S826_CounterSnapshotRead(0, 0, &c0, &t0, NULL, 0);
| + | |
− |
| + | |
− | while (1) { // Repeat every 0.1 seconds (approximately):
| + | |
− | Sleep(100); // Wait for next sample time.
| + | |
− | S826_CounterSnapshot(0, 0); // Trigger and read the "current" snapshot.
| + | |
− | S826_CounterSnapshotRead(0, 0, &c1, &t1, NULL, 0);
| + | |
− | printf("Speed = %d counts per second\n", (1000000 * (c1 - c0)) / (t1 - t0));
| + | |
− | c0 = c1; // Prepare for next snapshot.
| + | |
− | t0 = t1;
| + | |
− | }
| + | |
− | | + | |
− | ====Precise homing====
| + | |
− | | + | |
− | To achieve high-accuracy homing, it's necessary to register the encoder counts with minimal delay when a pulse is issued by the home position sensor. This becomes increasingly important as speed increases and is critical in high-performance systems that seek home at high speeds. To facilitate this, the counters feature the ability to automatically register the home position with a maximum delay of 20 ns.
| + | |
− | | + | |
− | The following example shows how to precisely register the home position at any traversal speed. It does this by configuring the counter to snapshot the counts upon rising edge of the Home signal. The home sensor is connected to the counter's IX+ terminal, which is internally routed to the counter's index input. The counter is "armed" to enable sampling upon Home's first rising edge. When the edge is detected, a snapshot is captured and the counter is automatically "disarmed" so that subsequent edges won't trigger snapshots.
| + | |
− | | + | |
− | First, configure the counter to snapshot the counts upon Home rising edge. Note that the snapshot doesn't affect counting -- the counter will continue to track position without interruption:
| + | |
− | | + | |
− | S826_CounterSnapshotConfigWrite(0, 0, // Set up snapshot triggering for counter0 (on board0):
| + | |
− | S826_SS_FIRST_IXRISE, // arm for snapshot upon first IX rising edge (auto-disarm upon snapshot)
| + | |
− | S826_BITSET); // leave other triggering rules intact
| + | |
− | | + | |
− | Now seek the home position. The program can either block or poll while waiting for the Home signal. When blocking is used, other threads are allowed to run while the blocked thread waits for the signal:
| + | |
− | | + | |
− | uint offset;
| + | |
− | S826_CounterSnapshotRead(0, 0, &offset, NULL, NULL, S826_INFINITE_WAIT); // Block while waiting for Home signal.
| + | |
− | printf("Exact counts at home position = %d.\n", offset);
| + | |
− | | + | |
− | Alternatively, the program can poll while waiting for the Home signal:
| + | |
− | | + | |
− | uint offset;
| + | |
− | while (S826_CounterSnapshotRead(0, 0, &offset, NULL, NULL, 0) == S826_ERR_NOTREADY) { // While home signal not yet detected ...
| + | |
− | // do some other things
| + | |
− | }
| + | |
− | printf("Exact counts at home position = %d.\n", offset);
| + | |
− | | + | |
− | =====Resetting the counter=====
| + | |
− | | + | |
− | If desired, the captured homing counts value can be immediately used to correct all subsequent position samples. Note that this does not reset the hardware counts; it performs a software correction by subtracting the homing offset counts:
| + | |
− | | + | |
− | uint curposn;
| + | |
− | S826_CounterRead(0, 0, &curposn); // Get current position.
| + | |
− | uint position = curposn - offset; // Compute corrected position.
| + | |
− | | + | |
− | The counts value can be used this way even if the encoder is still moving. However, it may be necessary to defer such usage to avoid motion control disruption, as explained below.
| + | |
− | | + | |
− | =====Avoiding PID loop disruption=====
| + | |
− | | + | |
− | It might seem like it would be ideal to be able to simply zero the counts when the Home signal is detected, but it's often impractical to do so because the sudden counts change could disrupt PID loop control. To solve this problem, use the above code (or similar) to snapshot the counts and notify the application when Home is detected. Upon receiving the notification, the application will terminate the Home seek operation and turn off the motor. When the encoder stops, turn off the loop controller and "retroactively" zero the counts by taking into account the the captured counts and the distance the encoder has moved since Home was detected.
| + | |
− | | + | |
− | This could be done as shown above (by correcting every reading), but since the encoder has stopped, it's now safe to read-modify-write the counts, thus making further software corrections unnecessary:
| + | |
− | | + | |
− | uint curposn;
| + | |
− | S826_CounterRead(0, 0, &curposn); // Sample current position.
| + | |
− | void [[826#Jamming counts into a counter|JamCounts]](0, 0, curposn - offset); // Correct current position and jam it into counter.
| + | |
− | | + | |
− | From now on, the application can read the corrected counts directly from the counter.
| + | |
− | | + | |
− | ====Output pulse at specific position====
| + | |
− | | + | |
− | 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 [[826#Using external pull-up resistors|Using external pull-up resistors]] for details) and thereby ensure proper pulse formation.
| + | |
− | | + | |
− | The following code will generate a 20 ns output pulse when the counts reach 5000, using counter0 and dio0 on board number 0. A snapshot is captured when the pulse is issued; this is required to produce the pulse but the snapshot data is not used in this example.
| + | |
− | | + | |
− | // Output a 20 nanosecond pulse when counts reach 5000 ----------
| + | |
− |
| + | |
− | // Configure counter0 as encoder interface; output 20ns pulse when counts reach 5000.
| + | |
− | S826_CounterModeWrite(0, 0, // Configure counter mode:
| + | |
− | S826_CM_K_QUADX4 | // clock = external quadrature x4
| + | |
− | S826_CM_OM_MATCH); // pulse ExtOut upon snapshot.
| + | |
− | S826_CounterCompareWrite(0, 0, 0, 5000); // Load target counts into compare0 reg.
| + | |
− | S826_CounterSnapshotConfigWrite(0, 0, 1, 2); // Snapshot when counts reach target.
| + | |
− | [[826#Routing a counter output to DIO pins|RouteCounterOutput]](0, 0, 0); // Route counter0 output to dio0.
| + | |
− | S826_CounterStateWrite(0, 0, 1); // Start tracking encoder position.
| + | |
− | | + | |
− | =====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 uses a second counter to stretch the pulse width: it will output a 500 µs pulse on dio0 when 5000 counts is reached.
| + | |
− | | + | |
− | // Output a 500 microsecond pulse when counts reach 5000 ----------
| + | |
− |
| + | |
− | // Configure counter0 as one-shot, used to stretch output pulses from counter1.
| + | |
− | [[826#One-shot|CreateOneShot]](0, 0, 3, 500); // Create 500 us 1-shot, trig=ExtOut1.
| + | |
− | [[826#Routing a counter output to DIO pins|RouteCounterOutput]](0, 0, 0); // Route one-shot output to dio0.
| + | |
− |
| + | |
− | // Configure counter1 as encoder interface; output 20ns pulse upon compare0 match.
| + | |
− | S826_CounterModeWrite(0, 1, // Configure counter mode:
| + | |
− | S826_CM_K_QUADX4 | // clock = external quadrature x4
| + | |
− | S826_CM_OM_MATCH); // pulse ExtOut upon snapshot.
| + | |
− | S826_CounterCompareWrite(0, 0, 0, 5000); // Load target counts into compare0 reg.
| + | |
− | S826_CounterSnapshotConfigWrite(0, 0, 1, 2); // Snapshot when counts reach target.
| + | |
− | S826_CounterStateWrite(0, 1, 1); // Start tracking encoder position.
| + | |
− | | + | |
− | ====Output pulse every N encoder pulses====
| + | |
− | | + | |
− | The following code will generate an output pulse every 20K encoder counts, using counter0 and dio0 on board number 0, while tracking encoder position. A snapshot is captured each time an output pulse is issued, which is used to set up the next target counts.
| + | |
− | | + | |
− | Note that the pulse width will always 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 as explained in [[826#Using external pull-up resistors|Using external pull-up resistors]]. If a longer pulse is needed, a second counter can be used to stretch counter0's output pulse as shown in [[826#Programmable pulse width|Programmable pulse width]].
| + | |
− | | + | |
− | If it isn't necessary to track encoder position then it's recommended to use the simpler method shown in [[826#Without tracking|Without tracking]].
| + | |
− | | + | |
− | // Output a 20 ns pulse every 20000 encoder counts while tracking position ----------
| + | |
− |
| + | |
− | #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.
| + | |
− | S826_CounterModeWrite(0, 0, // Configure counter mode:
| + | |
− | S826_CM_K_QUADX4 | // clock = external quadrature x4
| + | |
− | S826_CM_OM_MATCH); // pulse ExtOut when counts reach target
| + | |
− | S826_CounterSnapshotConfigWrite(0, 0, 1, 2); // Enable snapshots upon counts match.
| + | |
− | [[826#Routing a counter output to DIO pins|RouteCounterOutput]](0, 0, 0); // Route counter0 output to dio0.
| + | |
− | 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=====
| + | |
− | | + | |
− | 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.
| + | |
− | | + | |
− | // Output a 500 microsecond pulse every 20000 encoder counts while tracking position ----------
| + | |
− |
| + | |
− | #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 one-shot, used to stretch output pulses from counter1.
| + | |
− | [[826#One-shot|CreateOneShot]](0, 0, 3, 500); // Create 500 us 1-shot, trig=ExtOut1.
| + | |
− | [[826#Routing a counter output to DIO pins|RouteCounterOutput]](0, 0, 0); // Route one-shot output to dio0.
| + | |
− |
| + | |
− | // Configure counter1 as encoder interface; output 20ns pulse upon compare0 match.
| + | |
− | S826_CounterModeWrite(0, 1, // Configure counter mode:
| + | |
− | S826_CM_K_QUADX4 | // clock = external quadrature x4
| + | |
− | S826_CM_OM_MATCH); // pulse ExtOut when counts reach target
| + | |
− | S826_CounterSnapshotConfigWrite(0, 1, 1, 2); // Enable snapshots upon compare0 match.
| + | |
− | S826_CounterStateWrite(0, 1, 1); // Start tracking encoder position.
| + | |
− |
| + | |
− | do { // Repeat forever ...
| + | |
− | S826_CounterCompareWrite(0, 1, 0, targ); // Program target counts and wait for match.
| + | |
− | int errcode = S826_CounterSnapshotRead(0, 1, NULL, NULL, NULL, S826_WAIT_INFINITE);
| + | |
− | targ += PULSE_INTERVAL; // Bump target counts.
| + | |
− |
| + | |
− | // TODO: PERFORM ANY OTHER REQUIRED PROCESSING
| + | |
− |
| + | |
− | } while (errcode == S826_ERR_OK);
| + | |
− | | + | |
− | =====Without tracking=====
| + | |
− | | + | |
− | If position tracking is not required then there's no need to reload the Compare0 register for each output pulse (as in the above examples). Instead, configure the counter to automatically preload zero counts upon Compare0 match. Counter snapshots must be enabled to generate an output pulse upon Compare0 match, but in this example the snapshots are not used (the snapshot FIFO is allowed to fill and harmlessly overflow). If a wider output pulse is needed, use a one-shot to stretch the 20 ns output pulse, as shown in the previous above.
| + | |
− | | + | |
− | // Output a 20 ns pulse every 20000 encoder counts (without position tracking) ----------
| + | |
− |
| + | |
− | // Configure counter0 as encoder interface; output 20ns pulse upon compare0 match.
| + | |
− | S826_CounterModeWrite(0, 0, // Configure counter mode:
| + | |
− | S826_CM_K_QUADX4 | // clock = external quadrature x4
| + | |
− | S826_CM_OM_MATCH | // pulse ExtOut when counts==compare0
| + | |
− | S826_CM_PX_MATCH0); // reset counts to 0 when counts==compare0
| + | |
− | S826_CounterCompareWrite(0, 1, 0, 20000); // Set compare0 value to 20000.
| + | |
− | S826_CounterPreloadWrite(board, 0, 0, 0); // Set preload0 value to zero.
| + | |
− | S826_CounterSnapshotConfigWrite(0, 0, 1, 2); // Enable snapshots upon counts match.
| + | |
− | [[826#Routing a counter output to DIO pins|RouteCounterOutput]](0, 0, 0); // Route ExtOut to dio0.
| + | |
− | S826_CounterStateWrite(0, 0, 1); // Start the counter running.
| + | |
− | | + | |
− | Here's a trick you can use to generate wide output pulses with a single counter: have the counter count ''down'' to zero and then preload. Both preload registers are used, alternately, with ExtOut active when Preload1 is selected (similar to a PWM generator). Snapshots are no longer needed to generate output pulses because ExtOut is controlled by the preload selector. In the following example a pulse will be output every 20000 encoder counts and the pulse will be active for 100 encoder counts. Note that the encoder direction must not change.
| + | |
− | | + | |
− | // Output a pulse every 20000 encoder counts with pulse width = 100 counts ----------
| + | |
− |
| + | |
− | #define PULSE_INTERVAL 20000 // output a DIO pulse every 20,000 encoder counts
| + | |
− | #define PULSE_WIDTH 100 // pulse width = 100 counts
| + | |
− |
| + | |
− | #define PL0 (PULSE_INTERVAL - PULSE_WIDTH - 1) // Preload0 counts = counts prior to pulse
| + | |
− | #define PL1 (PULSE_WIDTH - 1) // Preload1 counts = counts during pulse
| + | |
− |
| + | |
− | // Configure counter0 as encoder interface; output 20ns pulse upon compare0 match.
| + | |
− | S826_CounterModeWrite(0, 0, // Configure counter mode:
| + | |
− | S826_CM_K_QUADX4 | // clock = external quadrature x4
| + | |
− | S826_CM_UD_REVERSE | // count down
| + | |
− | S826_CM_BP_BOTH | // alternate between preload0 and preload1
| + | |
− | S826_CM_OM_PRELOAD | // assert ExtOut when preload0 is selected
| + | |
− | S826_CM_PX_ZERO); // preload when counts reach 0
| + | |
− | S826_CounterPreloadWrite(board, 0, 0, PL0); // Set preload0 value to pulse activation counts.
| + | |
− | S826_CounterPreloadWrite(board, 0, 1, PL1); // Set preload1 value to pulse width.
| + | |
− | [[826#Routing a counter output to DIO pins|RouteCounterOutput]](0, 0, 0); // Route ExtOut to dio0.
| + | |
− | S826_CounterStateWrite(0, 0, 1); // Start the counter running.
| + | |
− | | + | |
− | ====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 emitters/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.
| + | |
− | | + | |
− | [[File:826 encoder FLT.png|400px|center|alt=Using an 826 counter channel to monitor an incremental encoder's active-low FAULT output]]
| + | |
− | | + | |
− | 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 to go active. The counter is configured to count up at 1 MHz. A preload is triggered whenever IX is high, which holds the counts at 0, 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, // Configure counter0 mode:
| + | |
− | S826_CM_K_1MHZ | // count up at 1 MHz
| + | |
− | S826_CM_PX_IXHIGH); // preload while IX is high
| + | |
− | S826_CounterSnapshotConfigWrite(board, chan, // Capture snapshot upon:
| + | |
− | S826_SSRMASK_MATCH0 | (S826_SSRMASK_MATCH0 << 16) | // first compare0 match
| + | |
− | S826_SSRMASK_IXRISE | // all IX rising edges
| + | |
− | S826_SSRMASK_IXFALL, // all IX falling edges
| + | |
− | S826_BITWRITE);
| + | |
− | S826_CounterPreloadWrite(board, chan, 0, 0); // Set preload0 to 0.
| + | |
− | S826_CounterCompareWrite(board, chan, 0, 1); // Set compare0 to 1.
| + | |
− | S826_CounterStateWrite(board, chan, 1); // Enable counter.
| + | |
− | 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;
| + | |
− | }
| + | |
− | | + | |
− | ====Interfacing touch-trigger probes====
| + | |
− | | + | |
− | [[File:TouchProbe.jpg|thumb|A touch-trigger probe]]
| + | |
− | Touch-trigger probes (such as those made by Renishaw, Tormach, Marposs, etc.) are commonly used in CNC machine tools and coordinate measuring machines (CMMs). In a typical application, three or more incremental encoders monitor the position of a probe as it moves relative to a workpiece. The probe will output a digital signal when its stylus contacts the workpiece. When this happens, the stylus location must be determined by sampling all of the encoder counters.
| + | |
− | | + | |
− | At lower traverse speeds and in cases where moderate accuracy is acceptable, a probe-triggered interrupt service routine (ISR) can be used to sequentially sample the counters. This is feasible because ISR overhead and ISR-induced measurement errors (due to trigger latency and sampling skew) are negligible in such cases. However, these issues become significant when high accuracy and throughput is required. Consequently, in high-performance machines a hardware mechanism is needed that will simultaneously sample all counters with very low trigger latency.
| + | |
− | | + | |
− | =====High-performance measurement=====
| + | |
− | | + | |
− | Model 826 directly supports high-performance probe measurement by providing the ability to simultaneously sample up to six encoder counters with sub-microsecond trigger latency. In the following example, the probe's trigger signal is connected to dio17 (an arbitrary choice), and consecutive counters (starting with counter0) are assigned to the encoders of a 3-axis probe. The counters' high-speed FIFOs will simultaneously capture all encoder positions within 20 ns of receiving a probe trigger.
| + | |
− | | + | |
− | First we will define a data structure and function to receive coordinates. The function returns the sampled encoder counts along with a code (<code>trig</code>) that indicates what caused the counts to be sampled (<code>trig</code> may be set to NULL if the software doesn't need to know what triggered the sample).
| + | |
− | | + | |
− | typedef struct POINT { // Probe coordinates (extensible to higher dimensions)
| + | |
− | uint x;
| + | |
− | uint y;
| + | |
− | uint z;
| + | |
− | } POINT;
| + | |
− |
| + | |
− | int InputTouch(POINT *p, uint *trig)
| + | |
− | {
| + | |
− | // Wait for a snapshot, then get X coordinate and snapshot's trigger source.
| + | |
− | int errcode = S826_CounterSnapshotRead(0, 0, &p->x, NULL, trig, S826_WAIT_INFINITE);
| + | |
− |
| + | |
− | // Get Y and Z coordinates; no need to wait because they've already been triggered.
| + | |
− | if (errcode == S826_ERR_OK) {
| + | |
− | errcode = S826_CounterSnapshotRead(0, 1, &p->y, NULL, NULL, 0);
| + | |
− | if (errcode == S826_ERR_OK)
| + | |
− | errcode = S826_CounterSnapshotRead(0, 2, &p->z, NULL, NULL, 0);
| + | |
− | }
| + | |
− | return errcode;
| + | |
− | }
| + | |
− | | + | |
− | This function will set up the probe interface and start it running:
| + | |
− | | + | |
− | CreateProbeInterface(uint trig, uint dio)
| + | |
− | {
| + | |
− | for (int i = 0; i < 3; i++) { // For each axis, assign a counter and configure it:
| + | |
− | S826_CounterExtInRoutingWrite(0, i, dio); // Route dio to counter's ExtIn input.
| + | |
− | S826_CounterSnapshotConfigWrite(0, i, trig, 0); // Capture counts upon these events.
| + | |
− | S826_CounterModeWrite(0, i, S826_CM_K_QUADX4); // Counter clock = external quadrature x4.
| + | |
− | S826_CounterStateWrite(0, i, 1); // Start tracking encoder position.
| + | |
− | }
| + | |
− | }
| + | |
− | | + | |
− | Now put it all together. This code will instantiate the probe interface and then loop while it processes touch events:
| + | |
− | | + | |
− | // Implement a high-performance touch probe interface --------
| + | |
− |
| + | |
− | #define PROBE_DIO 17 // The probe signal is connected to this DIO (0-47)
| + | |
− | #define PROBE_EDGE 0x20 // Trigger on this probe edge (0x20=falling, 0x40=rising)
| + | |
− |
| + | |
− | POINT point; // touch coordinates
| + | |
− |
| + | |
− | CreateProbeInterface(PROBE_EDGE, PROBE_DIO); // Config probe interface and start it running.
| + | |
− | // Process triggers from the probe:
| + | |
− | while (InputTouch(&point, NULL) == S826_ERR_OK) // Wait for next touch and get coordinates.
| + | |
− | ProcessTouchPoint(&point); // TODO: PROCESS TOUCH (e.g., add to point list).
| + | |
− | | + | |
− | ;Halting the probe interface
| + | |
− | | + | |
− | This expanded version of the previous example provides an easy way to halt the probe interface:
| + | |
− | | + | |
− | // High-performance touch probe interface with Halt function --------
| + | |
− |
| + | |
− | POINT point; // touch coordinates
| + | |
− | uint trig; // trigger flags
| + | |
− |
| + | |
− | CreateProbeInterface(PROBE_EDGE, PROBE_DIO); // Config probe interface and start it running.
| + | |
− | // Process triggers from the probe:
| + | |
− | while (InputTouch(&point, &trig) == S826_ERR_OK) { // Wait for next touch and get coordinates.
| + | |
− | if (trig & S826_SSRMASK_SOFT) // If snapshot triggered by software then
| + | |
− | break; // halt probe interface.
| + | |
− | ProcessTouchPoint(&point); // TODO: PROCESS TOUCH (e.g., add to point list).
| + | |
− | }
| + | |
− | | + | |
− | To halt the probe interface, simply trigger a soft snapshot on counter0:
| + | |
− | | + | |
− | S826_CounterSnapshot(0, 0);
| + | |
− | | + | |
− | =====Handling false triggers=====
| + | |
− | | + | |
− | Touch-trigger probes commonly detect false triggers due to vibration, contact bounce, or both. Most probes internally debounce detected triggers to reduce the frequency of false output triggers. This debouncing increases the trigger latency, which in turn causes measurement error because the probe is moving while the trigger is being delayed. To mitigate this, systems will typically maintain constant probe speed while seeking a touch and then, when the debounced trigger has been received and all encoders have been sampled, estimate the actual touch coordinates via extrapolation (using the probe calibration).
| + | |
− | | + | |
− | Unfortunately, even internally debounced probes will sometimes output false triggers. This problem is especially prevalent in high-vibration CNC machines, and in both CNC and CMM systems when low-latency probes (ones with very fast or no internal debounce) break contact with a workpiece.
| + | |
− | | + | |
− | An obvious solution is to suppress false triggers by externally debouncing the probe. Since the probe signal enters the 826 board via a DIO, an easy way to do this is to use the DIO's built-in debounce filter. However, external debouncing will introduce additional trigger latency, which defeats the purpose of high-speed probes and further reduces the accuracy of lower-performance probes. Furthermore, probe speed cannot be allowed to change until debouncing completes — an inconvenient constraint that becomes a performance bottleneck when high throughput is required. What is needed is a way to record the probe position at the leading edge of a touch so that later, when the touch has been validated, its precise position will already be known.
| + | |
− | | + | |
− | Model 826 offers an elegant solution to this problem which produces exact touch coordinates while at the same time rejecting false triggers. Also, since coordinates are captured at the leading edge of a touch, it's no longer necessary to maintain constant probe speed during touch acquisition. This method capitalizes on counter features used in the previous example (simultaneously sampling multiple counters; storing a sequence of samples from each counter in a dedicated high-speed FIFO) and the counters' ability to accept simultaneous sampling triggers from multiple sources. In the following example, counter5 is used as a retriggerable one-shot to debounce the probe signal's leading edge. The probe signal must be connected to dio17 as in the previous example, and also to counter5's IX+ input for the debounce function.
| + | |
− | | + | |
− | // High-accuracy touch probe interface with false trigger rejection ---------------
| + | |
− |
| + | |
− | #define PROBE_DIO 17 // The probe signal is connected to this DIO (0-47)
| + | |
− | #define BOUNCE_TIME 100 // Suppress triggers shorter than this (microseconds)
| + | |
− | #define DB_TIMER 5 // Counter used as debounce timer.
| + | |
− |
| + | |
− | POINT point; // Exact probe coordinates at probe trigger rising edge.
| + | |
− | int touching = 0; // Not currently touching.
| + | |
− | uint trig; // Event type that triggered a counter snapshot.
| + | |
− | int i;
| + | |
− | int errcode;
| + | |
− |
| + | |
− | [[826#One-shot|CreateOneShot]](0, DB_TIMER, // Create retriggerable one-shot:
| + | |
− | S826_CM_XS_EXTNORMAL, // trigger source = IX input (probe signal).
| + | |
− | BOUNCE_TIME); // output pulse width = debounce interval.
| + | |
− |
| + | |
− | for (i = 0; i < 3; i++) { // Create encoder interfaces: counter0/1/2 = X/Y/Z axis -----
| + | |
− | S826_CounterExtInRoutingWrite(0, i, // Connect counter's ExtIn input
| + | |
− | PROBE_DIO); // to probe signal.
| + | |
− | S826_CounterModeWrite(0, i, // Configure counter:
| + | |
− | S826_CM_K_QUADX4 | // clock = external quadrature x4.
| + | |
− | (DB_TIMER + 2)); // connect Index input to one-shot ExtOut.
| + | |
− | S826_CounterSnapshotConfigWrite(0, i, // Capture snapshot to FIFO when:
| + | |
− | S826_SSRMASK_EXTRISE | // probe rising edge
| + | |
− | S826_SSRMASK_EXTFALL | // probe falling edge
| + | |
− | S826_SSRMASK_IXFALL, // one-shot trailing edge
| + | |
− | 0);
| + | |
− | S826_CounterStateWrite(0, i, 1); // Start tracking the encoder.
| + | |
− | }
| + | |
− |
| + | |
− | while (1) // Repeat forever ...
| + | |
− | {
| + | |
− | // Wait for next probe snapshot, get its coordinates and trigger source.
| + | |
− | POINT p;
| + | |
− | errcode = InputTouch(&p, NULL, &trig);
| + | |
− | if (errcode != S826_ERR_OK) // Abort if problem reading counters.
| + | |
− | break;
| + | |
− |
| + | |
− | if (reason & S826_SSRMASK_EXTFALL) { // If probe trailing edge,
| + | |
− | touching = 0; // discard coordinates and start next touch acquisition
| + | |
− | }
| + | |
− | else if (reason & S826_SSRMASK_IXFALL) { // If one-shot trailing edge,
| + | |
− | if (touching) { // discard coordinates and, if currently touching,
| + | |
− | // We have a new, valid touch point, so we will now pass the point to the
| + | |
− | // application so it can be processed as needed (e.g., add to point list).
| + | |
− | ProcessTouchPoint(&point); // TODO: PROCESS THE NEW POINT
| + | |
− | touching = 0; // start next touch acquisition
| + | |
− | }
| + | |
− | }
| + | |
− | else if (reason & S826_SSRMASK_IXRISE) { // If probe leading edge,
| + | |
− | point = p; // record the coordinates
| + | |
− | touching = 1; // touch acquisition is in progress
| + | |
− | }
| + | |
− | else
| + | |
− | break; // unexpected snapshot trigger (e.g., fifo overflowed)
| + | |
− | }
| + | |
− | | + | |
− | ====Panel encoders====
| + | |
− | | + | |
− | [[File:PanelMountEncoder.jpg|thumb|Surface-mounted panel encoder with integrated pushbutton]]
| + | |
− | Panel encoders are commonly used as manually operated controls for audio volume, power supply output voltage, and other analog phenomena. Some (like the SMT device shown here) have a built-in pushbutton that can be monitored with an 826 DIO, either by polling the DIO or by taking advantage of the DIO's interrupt-driven [[826#Edge detection|edge detection]] system.{{clear}}
| + | |
− | | + | |
− | =====Encoder-controlled voltage=====
| + | |
− | | + | |
− | The following example shows how to use a panel encoder to control an analog voltage by polling the encoder counts. The encoder is connected to counter0 (via its clkA/clkB inputs) and the output voltage is generated by dac0. The encoder counts are sampled (and the dac is updated accordingly) at 10 Hz.
| + | |
− | | + | |
− | // Use a panel encoder to control a DC voltage -----------------
| + | |
− |
| + | |
− | #define MIN_COUNTS 0 // Minimum dac value, corresponds to 0V.
| + | |
− | #define MAX_COUNTS 65535 // Maximum dac value, corresponds to +10V.
| + | |
− |
| + | |
− | uint counts;
| + | |
− | uint prev = ~0;
| + | |
− |
| + | |
− | // Configure DAC and encoder interface.
| + | |
− | S826_DacRangeWrite(0, 0, 1, 0); // Set dac output range to 0 to +10 V.
| + | |
− | S826_CounterModeWrite(0, 0, // Configure counter:
| + | |
− | S826_CM_K_QUADX4 | // Clock = external quadrature x4.
| + | |
− | S826_CM_XS_10HZ); // Use 10 Hz tick generator as sampling trigger.
| + | |
− | S826_CounterSnapshotConfigWrite(0, 0, // Capture counter snapshot upon
| + | |
− | S826_SSRMASK_IXRISE, 0); // rising edge of sampling trigger.
| + | |
− | S826_CounterStateWrite(0, 0, 1); // Start tracking the encoder position.
| + | |
− |
| + | |
− | // Repeat forever: wait for next sample and then process it.
| + | |
− | while (S826_CounterSnapshotRead(0, 0, &counts, NULL, NULL, INFINITE_WAIT) == S826_ERR_OK) {
| + | |
− | if (counts != prev) { // If counts changed since previous sample ...
| + | |
− | if ((int)counts < MIN_COUNTS) { // Limit counts to legal dac values.
| + | |
− | counts = MIN_COUNTS;
| + | |
− | [[826#Jamming counts into a counter|JamCounts]](0, 0, MIN_COUNTS);
| + | |
− | }
| + | |
− | else if (counts > MAX_COUNTS) {
| + | |
− | counts = MAX_COUNTS;
| + | |
− | [[826#Jamming counts into a counter|JamCounts]](0, 0, MAX_COUNTS);
| + | |
− | }
| + | |
− | S826_DacDataWrite(0, 0, counts, 0); // Program the dac output voltage.
| + | |
− | prev = counts;
| + | |
− | }
| + | |
− | }
| + | |
− | | + | |
− | The next example shows how to do the same thing in an event-driven (vs. polled) way. Connect the encoder A signal to clkA as before and also to dio0. Similarly, connect B to both clkB and dio1. Configure the board to detect all edges on dio0 and dio1 so that, whenever the encoder moves, an interrupt will be generated that triggers a DAC update.
| + | |
− |
| + | |
− | #define MIN_COUNTS 0 // Minimum dac value, corresponds to 0V.
| + | |
− | #define MAX_COUNTS 65535 // Maximum dac value, corresponds to +10V.
| + | |
− |
| + | |
− | uint edges[] = {3, 0};
| + | |
− | S826_DioCapEnablesWrite(0, edges, edges, S826_BITSET); // Detect all dio0 & dio1 edges.
| + | |
− | S826_DacRangeWrite(0, 0, 1, 0); // Set dac output range to 0 to +10 V.
| + | |
− | S826_CounterModeWrite(0, 0, S826_CM_K_QUADX4); // Config counter0 for quadrature decode.
| + | |
− | S826_CounterStateWrite(0, 0, 1); // Start tracking encoder position.
| + | |
− |
| + | |
− | while (TRUE) { // Loop:
| + | |
− | uint counts;
| + | |
− | uint chlist[] = {3, 0};
| + | |
− | S826_DioCapRead(0, chlist, 0, INFINITE_WAIT); // Wait for any dio0/dio1 edge.
| + | |
− | S826_CounterRead(0, 0, &counts); // Read counts.
| + | |
− | if ((int)counts < MIN_COUNTS) { // Limit counts to legal dac values.
| + | |
− | counts = MIN_COUNTS;
| + | |
− | JamCounts(0, 0, counts);
| + | |
− | }
| + | |
− | else if (counts > MAX_COUNTS) {
| + | |
− | counts = MAX_COUNTS;
| + | |
− | JamCounts(0, 0, counts);
| + | |
− | }
| + | |
− | S826_DacDataWrite(0, 0, counts, 0); // Program the dac output voltage.
| + | |
− | }
| + | |
− | | + | |
− | ====Quadrature errors====
| + | |
− | | + | |
− | :''In the event of a noise spike, does the 826 perform any error checking or would this noise just show up as an extra count or 2?''
| + | |
− | | + | |
− | The 826 encoder interfaces will properly handle any noise that doesn't exceed the line receiver electrical ratings. The counts will not change at all if the noise spike is very short (<20ns) and occurs between sampling clocks. Otherwise, if the noise affects only the A or B state then the counts will briefly change by 1 (increment or decrement) and then return to the correct value. If the noise affects both A and B then a quadrature error will be detected and reported (see 826 technical manual section 7.1.2 for details); in this case the counts cannot be relied on to be accurate.
| + | |
− | | + | |
− | ====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 <code>S826_CounterFilterWrite()</code> 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
| + | |
− | | + | |
− | ====Encoder maximum frequency====
| + | |
− | | + | |
− | As explained in [[826#Clock frequency limits|clock frequency limits]], the ClkA Nyquist frequency is 12.5 MHz in quadrature modes and 25 MHz in single-phase modes. Consequently, if you expect ClkA to exceed 12.5 MHz during a particular motion segment then you should use a single-phase clock mode across that entire segment. This is recommended because even though the clock mode can be changed "on the fly" (by calling S826_CounterModeWrite), you will likely accumulate errors if you switch modes while the encoder is rotating.
| + | |
− | | + | |
− | ====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. Encoders may be connected to counter channels 2-5 in a similar fashion.
| + | |
− | | + | |
− | [[File:826 ttl encoder.gif|350px|center|alt=Wiring diagram showing how to connect two TTL/CMOS-compatible single-ended incremental encoders to the 826 board using counter channels 0 and 1]]
| + | |
− | | + | |
− | ===Timing===
| + | |
− | | + | |
− | ====Clock frequency limits====
| + | |
− | | + | |
− | =====Quadrature clocks=====
| + | |
− | | + | |
− | In all quadrature clock modes (x4, x2, and x1) the ClkA and ClkB inputs are simultaneously sampled at 50 Ms/s (every 20 ns). In any two consecutive samples, ClkA may change, or ClkB may change, or neither, but not both. Consequently, the Nyquist frequency of ClkA (or ClkB) is 12.5 MHz (four times the sampling period). This corresponds to a maximum count rate of 50 Mcounts/s, but in practice some margin should always be allowed for differential noise and quadrature clock phase/duty cycle errors.
| + | |
− | | + | |
− | If both ClkA and ClkB change in consecutive samples then it must be assumed that counts have been missed. This event is critically important to the application software because the current counts can no longer be relied on to accurately indicate the encoder position. To account for this, the counter will immediately notify the application software by capturing a snapshot with the S826_SSRMASK_QUADERR bit asserted in the reason flags. The software should check every snapshot for this notification — regardless of the expected reason for the snapshot — in a fashion similar to this:
| + | |
− | | + | |
− | // In quadrature clock modes, always check for quadrature clock errors -------
| + | |
− |
| + | |
− | uint counts, tstamp, reason;
| + | |
− | int errcode = S826_CounterSnapshotRead(board, ctr, // Wait for next snapshot.
| + | |
− | &counts, &tstamp, &reason, S826_WAIT_INFINITE);
| + | |
− | if (reason & S826_SSRMASK_QUADERR) // Check for quadrature clock error.
| + | |
− | printf("Quadrature clock error!");
| + | |
− | else
| + | |
− | // ... process other reason bits normally
| + | |
− | | + | |
− | =====Single-phase clocks=====
| + | |
− | | + | |
− | In single-phase (non-quadrature) modes, ClkB is ignored and the clock decoder samples ClkA at 50 Ms/s (every 20 ns). In any two consecutive samples, ClkA can change at most one time. Since ClkA must be sampled at least every half-cycle, the Nyquist frequency of ClkA is 25 MHz. Note that the counter has no way of knowing if ClkA exceeds 25 MHz, so it cannot notify the application if this occurs.
| + | |
− | | + | |
− | ====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:
| + | |
− | | + | |
− | [[File:Clock filter general.png|center|External clock delay due to clock sampler and noise filter]]
| + | |
− | | + | |
− | The filter delay can be minimized by setting F=0, which results in the following timing:
| + | |
− | | + | |
− | [[File:Clock filter minimum.png|center|External clock delay due to clock sampler and noise filter]]
| + | |
− | | + | |
− | ====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.
| + | |
− | | + | |
− | [[File:826 counter timing.png|center|Counter timing diagram]]
| + | |
− | | + | |
− | Summary of counter timing parameters:
| + | |
− | | + | |
− | {| class="wikitable"
| + | |
− | ! style="text-align:left;"| Timing Parameter
| + | |
− | ! style="text-align:left;"| Minimum
| + | |
− | ! style="text-align:left;"| 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 [[826#Controlling output rise-time with an external pull-up|speeding up the DIO rise time]].
| + | |
− | | + | |
− | ===PWM operation===
| + | |
− | | + | |
− | ====How to configure a counter for PWM operation====
| + | |
− | | + | |
− | The following functions configure a counter to operate as a PWM generator and start it running. The PWM period is defined by <code>ontime</code> and <code>offtime</code>, which are specified in microseconds. The PWM output appears on the counter's ExtOut signal, which can be routed to a DIO pin if desired.
| + | |
− | | + | |
− | int SetPWM(uint board, uint ctr, uint ontime, uint offtime)
| + | |
− | {
| + | |
− | S826_CounterPreloadWrite(board, ctr, 0, ontime); // On time in us.
| + | |
− | S826_CounterPreloadWrite(board, ctr, 1, offtime); // Off time in us.
| + | |
− | }
| + | |
− |
| + | |
− | int CreatePWM(uint board, uint ctr, uint ontime, uint offtime)
| + | |
− | {
| + | |
− | S826_CounterModeWrite(board, ctr, // Configure counter for PWM:
| + | |
− | S826_CM_K_1MHZ | // clock = internal 1 MHz
| + | |
− | S826_CM_UD_REVERSE | // count down
| + | |
− | S826_CM_PX_START | S826_CM_PX_ZERO | // preload @startup and counts==0
| + | |
− | S826_CM_BP_BOTH | // use both preloads (toggle)
| + | |
− | S826_CM_OM_PRELOAD); // assert ExtOut during preload0 interval
| + | |
− | SetPWM(board, ctr, ontime, offtime); // Program initial on/off times.
| + | |
− | }
| + | |
− |
| + | |
− | int StartPWM(uint board, uint ctr)
| + | |
− | {
| + | |
− | return S826_CounterStateWrite(board, ctr, 1); // Start the PWM generator.
| + | |
− | }
| + | |
− | | + | |
− | // Example: Configure counter0 and dio0 as a PWM generator; ontime = 900, offtime = 500 microseconds.
| + | |
− | CreatePWM(0, 0, 0, 900, 500); // Configure counter0 as PWM.
| + | |
− | [[826#Routing a counter output to DIO pins|RouteCounterOutput]](0, 0, 0); // Route counter0 output to dio0.
| + | |
− | StartPWM(0, 0); // Start the PWM running.
| + | |
− | | + | |
− | ====Fail-safe PWM generator====
| + | |
− | | + | |
− | :''I'm using a counter-based PWM 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:
| + | |
− | | + | |
− | [[File:826PwmWatchdog.gif|550px|center|alt=Fail-safe PWM generator]]
| + | |
− | | + | |
− | Before enabling the PWM generator or watchdog, program the desired PWM failsafe level into the DIO channel's <code>SafeData</code> register (see [[826#Programming safemode states|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 <code>SafeEnable</code> 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 <code>S826_WatchdogKick()</code>. 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 <code>SafeData</code> register, which in turn will halt the motor.
| + | |
− | | + | |
− | #define CRASH_DET_SECONDS 0.5 // Halt motor if program fails to kick watchdog within this time.
| + | |
− |
| + | |
− | uint dio_mask[2]= {1, 0}; // dio bitmask for PWM output (dio0)
| + | |
− | uint wdtime[5] = { // watchdog timing:
| + | |
− | (uint)(50000000 * (CRASH_DET_SECONDS)), // timer0
| + | |
− | 1, 1, 0, 0}; // watchdog interval // others
| + | |
− |
| + | |
− | // Create a fail-safe PWM generator using counter0 and dio0.
| + | |
− | [[826#How_to_configure a counter for PWM operation|CreatePWM]](0, 0, 900, 500); // Config counter0 as PWM w/initial 0.9/0.5 ms on/off times.
| + | |
− | [[826#Routing a counter output to DIO pins|RouteCounterOutput]](0, 0, 0); // Route counter0 output to DIO 0.
| + | |
− | S826_SafeWrenWrite(0, S826_SAFEN_SWE); // Enable writes to watchdog and SafeData.
| + | |
− | S826_DioSafeWrite(0, dio_mask, S826_BITCLR); // Force PWM output to '0' (+5V) when program crashes.
| + | |
− | 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_SafeWrenWrite(0, S826_SAFEN_SWD); // Prevent errant writes to watchdog and SafeData.
| + | |
− | [[826#How_to_configure a counter for PWM operation|StartPWM]](0, 0); // Start the PWM generator.
| + | |
− | | + | |
− | ====Phase-locked PWM outputs====
| + | |
− | | + | |
− | Some applications require multiple, phase-locked PWM outputs. Although the 826's counters do not directly implement 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 clock generator can be implemented using three counters and two DIOs as shown in the example below. Except for DIO load connections, no external wiring is required (counter and DIO interconnects are established by the board's programmable signal router).
| + | |
− | | + | |
− | [[File:826 quadrature gen.gif|500px|center|alt=Quadrature generator]]
| + | |
− | | + | |
− | Counter0 is a PWM running at 50% duty cycle, which directly generates the quadrature "A" output clock on dio0. Counter2 is used to hold off the "B" generator after "A" rises, to create the 90° phase angle between "A" and "B" clocks. Counter1 is a one-shot that generates the "B" clock on dio1; it does this by issuing a pulse when triggered by Counter2.
| + | |
− | | + | |
− | This quadrature generator has 1 µs resolution because the counters are using the board's internal 1 MHz clock. If higher resolution is needed, the functions can be modified to use the internal 50 MHz clock.
| + | |
− | | + | |
− | // Generate A/B quadrature clocks ---------
| + | |
− |
| + | |
− | #define PERIOD 600 // Output period in microseconds.
| + | |
− | #define HP ((PERIOD) / 2) // Half-period.
| + | |
− | #define QP ((PERIOD) / 4) // Quarter-period.
| + | |
− |
| + | |
− | [[826#How to configure a counter for PWM operation|CreatePWM]](0, 0, HP, HP); // Generate "A" clock with counter0.
| + | |
− | [[826#One-shot|CreateOneShot]](0, 2, 2, QP); // Generate phase shift with counter2.
| + | |
− | [[826#One-shot|CreateOneShot]](0, 1, 4, HP); // Generate "B" clock with counter1.
| + | |
− | [[826#Routing a counter output to DIO pins|RouteCounterOutput]](0, 0, 0); // Route "A" clock (ExtOut0) to dio0.
| + | |
− | [[826#Routing a counter output to DIO pins|RouteCounterOutput]](0, 1, 1); // Route "B" clock (ExtOut1) to dio1.
| + | |
− | [[826#How_to_configure a counter for PWM operation|StartPWM]](0, 0); // Start the quadrature generator.
| + | |
− | | + | |
− | =====Example: 3-phase controller=====
| + | |
− | | + | |
− | Here's a way to implement a 3-phase PWM generator using five counters and three DIOs:
| + | |
− | | + | |
− | [[File:826 3Phase pwm.png|500px|center|alt=3-phase PWM generator]]
| + | |
− | | + | |
− | ===High resolution pulse-burst generator===
| + | |
− | | + | |
− | Many applications (e.g., radar, laser) require a fixed number of precisely-timed pulses. This example shows how to generate a burst of pulses with selectable resolution of 20 ns or 1 µs. It uses two counters (counter0 and counter1) to generate the pulse burst and one DIO (dio0) to output the pulses. The DIO must be externally connected to counter1's clkA+ input to allow it to control the pulse count. A snapshot is used to signal burst completion, which allows other threads to run while the burst is in progress. A pulse burst can be triggered by software (by calling OutputBurst) or by an external signal applied to counter1's IX+ input (optional).
| + | |
− | | + | |
− | // Create a pulse burst generator.
| + | |
− | // res - timing resolution: set to 1 (microsecond) or 20 (nanoseconds).
| + | |
− | void CreateBurstGenerator(uint board, int res)
| + | |
− | {
| + | |
− | S826_CounterModeWrite(board, 0, // Config counter0 as PWM generator:
| + | |
− | ((res == 1) ? // select clock based on resolution:
| + | |
− | S826_CM_K_1MHZ : // 1 us -> internal 1 MHz
| + | |
− | S826_CM_K_50MHZ) | // 20 ns -> internal 50 MHz
| + | |
− | S826_CM_UD_REVERSE | // count down
| + | |
− | S826_CM_BP_BOTH | // use both preloads (toggle)
| + | |
− | S826_CM_XS_EXTOUT(1) | // connect index input to ExtOut1 (pulse gate)
| + | |
− | S826_CM_PX_IXRISE | S826_CM_PX_ZERO | // preload @ index^ and counts=0
| + | |
− | S826_CM_TE_IXRISE | // enable counting upon index^
| + | |
− | S826_CM_TD_IXFALL | // disable counting upon index falling edge
| + | |
− | S826_CM_OM_PRELOAD); // assert ExtOut during preload0 interval
| + | |
− |
| + | |
− | S826_CounterModeWrite(board, 1, // Config counter1 as pulse counter (PWM enable):
| + | |
− | S826_CM_K_ARISE | // clock = external clkA (wire from ExtOut0)
| + | |
− | S826_CM_UD_REVERSE | // count down
| + | |
− | S826_CM_TE_PRELOAD | // enable counting upon preload
| + | |
− | S826_CM_PX_IXRISE | // preload @ index^ (allows hardware triggering)
| + | |
− | S826_CM_TD_ZERO | // disable counting upon counts=0
| + | |
− | S826_CM_OM_NOTZERO); // assert ExtOut while counts!=0
| + | |
− |
| + | |
− | S826_CounterSnapshotConfigWrite(board, 1, // Assert IRQ when a burst has completed.
| + | |
− | S826_SSRMASK_ZERO,
| + | |
− | S826_BITWRITE);
| + | |
− |
| + | |
− | [[826#Routing a counter output to DIO pins|RouteCounterOutput]](board, 0, 0); // Route PWM output to dio0.
| + | |
− | S826_CounterStateWrite(board, 0, 1); // Enable PWM generator.
| + | |
− | S826_CounterStateWrite(board, 1, 1); // Enable the pulse counter.
| + | |
− | }
| + | |
− |
| + | |
− | // Specify attributes of next pulse burst.
| + | |
− | // npulses - number of pulses in burst.
| + | |
− | // period - pulse period in res units (e.g., when res=20, period=5 -> 5*20ns = 100ns).
| + | |
− | // width - pulse width in res units (e.g, when res=1, width=4 -> 4*1us = 4us).
| + | |
− | void ConfigBurst(uint board, int npulses, uint period, uint width)
| + | |
− | {
| + | |
− | S826_CounterPreloadWrite(board, 0, 0, width); // Set pulse width (in res units).
| + | |
− | S826_CounterPreloadWrite(board, 0, 1, // Set pulse period (in res units).
| + | |
− | period - width);
| + | |
− | S826_CounterPreloadWrite(board, 0, 0, npulses); // Set pulse count.
| + | |
− | S826_CounterStateWrite(board, 0, 1); // Enable PWM generator.
| + | |
− | S826_CounterStateWrite(board, 1, 1); // Enable the pulse counter.
| + | |
− | }
| + | |
− |
| + | |
− | void OutputBurst(uint board)
| + | |
− | {
| + | |
− | S826_CounterPreload(board, 1, 1, 0); // Trigger a pulse burst.
| + | |
− | S826_CounterSnapshotRead(board, 1, NULL, // Block until burst completes.
| + | |
− | NULL, NULL, S826_WAIT_INFINITE);
| + | |
− | }
| + | |
− | | + | |
− | // Example usage:
| + | |
− | CreatePulseGenerator(0, 20); // Create a pulse-burst generator w/20 ns resolution.
| + | |
− | ConfigBurst(0, 99, 25, 3); // Burst attributes: 99 pulses @ 2.0 MHz, pulse width = 60 ns.
| + | |
− | OutputBurst(0); // Output the burst.
| + | |
− | | + | |
− | ===Stepper motor control===
| + | |
− | | + | |
− | [[File:StepperMotorDriver.png|thumb|Input section of a basic stepper motor driver]]
| + | |
− | A stepper motor driver has two digital inputs: DIR specifies the rotation direction, and STEP causes the motor to rotate by a fixed-angle "step" each time it receives a pulse. To move the motor through a desired angle, it is necessary to assert or negate DIR and then issue a burst of pulses to the STEP input. The total angular displacement is determined by the number of pulses issued on STEP, whereas motor speed is determined by the pulse rate.
| + | |
− | | + | |
− | Model 826 does not include dedicated stepper motor controllers in its repertoire of I/O interfaces, but the resources it does provide can be used to control stepper motors in various ways, as shown in the following sections.
| + | |
− | {{clear}}
| + | |
− | | + | |
− | ====Via pulse burst====
| + | |
− | The following example shows one way to generate DIR and STEP signals for a stepper motor. It uses two counters (counter0 and counter1) to generate a burst of pulses, and two DIOs to convey DIR and STEP to the controller. A counter snapshot is used to signal burst completion, which allows other threads to do productive work while the motor is moving.
| + | |
− | | + | |
− | #define PULSE_WIDTH 5 // STEP pulse width in microseconds
| + | |
− |
| + | |
− | // Create stepper motor driver interface. STEP appears on dio0, DIR on dio1.
| + | |
− | void CreateStepperInterface(uint board)
| + | |
− | {
| + | |
− | [[826#High resolution pulse-burst generator|CreateBurstGenerator]](board, 1); // Create burst generator w/1 us resolution.
| + | |
− | }
| + | |
− |
| + | |
− | // Move the stepper motor.
| + | |
− | void StepperMotorMove(uint board, uint dir, uint period, int npulses)
| + | |
− | {
| + | |
− | uint diomask[] = {2, 0}; // bitmask for dio1 (DIR)
| + | |
− | S826_DioOutputWrite(board, diomask, // Output DIR signal to motor driver.
| + | |
− | dir ? S826_BITSET : S826_BITCLR);
| + | |
− | [[826#High resolution pulse-burst generator|ConfigBurst]](board, npulses, period, PULSE_WIDTH);
| + | |
− | [[826#High resolution pulse-burst generator|OutputBurst]](board);
| + | |
− | }
| + | |
− |
| + | |
− | // Example usage:
| + | |
− | CreateStepperInterface(0); // Create motor driver interface.
| + | |
− | StepperMotorMove(0, 1, 1000, 100); // Move motor cw 100 steps at 1 KHz (1000 us/step).
| + | |
− | StepperMotorMove(0, 0, 800, 50); // Move motor ccw 50 steps at 1.25 KHz (800 us/step).
| + | |
− | | + | |
− | ===Serial data capture===
| + | |
− | | + | |
− | A counter 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 [[826#timestamp generator|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 Index 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.
| + | |
− | | + | |
− | [[File:826 serial data capture.png|500px|center|alt=Serial data capture]]
| + | |
− | | + | |
− | 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 <code>S826_CounterSnapshotRead()</code> 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, I<sup>2</sup>C), apply the clock signal to the counter's ExtIn or Index input and configure the counter to capture snapshots at either the rising or falling edge of the clock signal (whichever edge coincides with 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 [http://www.sensoray.com/downloads/appnote_826_serial_acq.zip 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 [http://www.sensoray.com/downloads/util_826_DecodeCounterMode.zip 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.
| + | |
− | | + | |
− | ===Snapshot counts upon match===
| + | |
− | | + | |
− | In case it's not obvious: 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.
| + | |
− | | + | |
− | ==ADC==
| + | |
− | | + | |
− | ===Basic operation===
| + | |
− | | + | |
− | ;<u>1. Assign slots</u>
| + | |
− | | + | |
− | Each analog measurement requires one timeslot (a "slot"). There are 16 slots (numbered 0 to 15), so you can perform up to 16 measurements in a conversion burst.
| + | |
− | | + | |
− | Start by assigning a slot number to each measurement. It's not required to use all slots, and it's permissible to leave gaps between used slots. For example, you could use only slots 0, 5 and 11 if desired, although it's usually simpler to just use consecutive slots.
| + | |
− | | + | |
− | ;<u>2. Configure slots</u>
| + | |
− | | + | |
− | For every slot you assigned, call <code>S826_AdcSlotConfigWrite</code> to configure the slot for the desired analog input channel (AIN), input range and settling time.
| + | |
− | | + | |
− | {{ImportantNote|You must call <code>S826_AdcSlotConfigWrite</code> once for each slot you are using. Unconfigured slots will measure AIN0 by default.}}
| + | |
− | | + | |
− | Each slot can be used to measure any input channel. For example, the following will configure slot 0 to measure AIN13 using the ±10 V range, with 30 µs delay between input switching and start of conversion:
| + | |
− | | + | |
− | S826_AdcSlotConfigWrite(board, 0, 13, 30, S826_ADC_GAIN_1); // slot 0: measure AIN13 on ±10V range; tsettle = 30µs
| + | |
− | | + | |
− | ;<u>3. Enable slots</u>
| + | |
− | | + | |
− | Call <code>S826_AdcSlotlistWrite</code> to enable all of the slots you are using. The second argument is a set of flags in which each bit is associated with a particular slot (bit0=slot0, bit1=slot1, etc.): '1' enables the slot and '0' disables it. For example, if you are only using slot 0 (e.g., as shown above, to measure AIN13):
| + | |
− | | + | |
− | S826_AdcSlotlistWrite(board, 0x0001, S826_BITWRITE); // enable only slot 0
| + | |
− | | + | |
− | If instead you were using slots 0, 1 and 15, you would do this:
| + | |
− | | + | |
− | S826_AdcSlotlistWrite(board, 0x8003, S826_BITWRITE); // enable slots 0, 1 and 15
| + | |
− | | + | |
− | Or if using all slots, do this:
| + | |
− |
| + | |
− | S826_AdcSlotlistWrite(board, 0xFFFF, S826_BITWRITE); // enable all slots (0 to 15)
| + | |
− | | + | |
− | Note: You can call <code>S826_AdcSlotlistWrite</code> while the ADC is running to dynamically enable/disable particular slots.
| + | |
− | | + | |
− | ;<u>4. Select trigger mode.</u>
| + | |
− | | + | |
− | Use Continuous Mode if you want conversions to run automatically (disables hardware triggering):
| + | |
− | | + | |
− | S826_AdcTrigModeWrite(board, 0); // trigger mode = continuous
| + | |
− | | + | |
− | ;<u>5. Start ADC conversions</u>
| + | |
− | | + | |
− | S826_AdcEnableWrite(board, 1); // start adc conversions
| + | |
− | | + | |
− | ;<u>6. Read ADC data</u>
| + | |
− | | + | |
− | Call <code>S826_AdcRead</code> to receive ADC samples. For example, to read a sample from only slot 0:
| + | |
− | | + | |
− | int samples[16]; // adc samples -- Note: buffer must always be sized for 16 slots
| + | |
− | uint slotlist = 0x0001; // only slot 0 is of interest in this example
| + | |
− | S826_AdcRead(0, samples, NULL, &slotlist, S826_WAIT_INFINITE); // get adc data
| + | |
− | printf("Binary adc data = %d", samples[0] & 0xFFFF);
| + | |
− | | + | |
− | Or you could do this to read all slots:
| + | |
− | | + | |
− | int samples[16]; // adc data
| + | |
− | uint slotlist = 0xFFFF; // read all slots
| + | |
− | S826_AdcRead(0, samples, NULL, &slotlist, S826_WAIT_INFINITE); // get adc data
| + | |
− | for (int i = 0; i < 16; i++)
| + | |
− | printf("AIN%d binary adc data = %d"\n, i, samples[i] & 0xFFFF);
| + | |
− | | + | |
− | ===Converting ADC data to volts===
| + | |
− | | + | |
− | This function can be used to convert an ADC sample to volts:
| + | |
− | | + | |
− | double GetAdcVolts(int sample, uint range) // note: assumes valid range
| + | |
− | {
| + | |
− | short adcdata = (short)(sample & 0xFFFF); // extract binary adc data from sample
| + | |
− | double frac = adcdata * (1.0 / 0x7FFF);
| + | |
− | switch (range) {
| + | |
− | case S826_ADC_GAIN_10: return frac; break; // -1V to +1V
| + | |
− | case S826_ADC_GAIN_5: return frac * 2.0; break; // -2V to +2V
| + | |
− | case S826_ADC_GAIN_2: return frac * 5.0; break; // -5V to +5V
| + | |
− | case S826_ADC_GAIN_1: return frac * 10.0; break; // -10V to +10V
| + | |
− | }
| + | |
− | }
| + | |
− | | + | |
− | Example usage:
| + | |
− | | + | |
− | int samples[16]; // adc sample buffer
| + | |
− | uint slotlist = 0x0001; // only slot 0 is used in this example
| + | |
− | S826_AdcRead(0, samples, NULL, &slotlist, S826_WAIT_INFINITE); // acquire sample
| + | |
− | printf("ADC measured %f volts", GetAdcVolts(samples[0], S826_ADC_GAIN_1));
| + | |
− | | + | |
− | ===Handling ADC interrupts===
| + | |
− | | + | |
− | An interrupt request (IRQ) is generated when the ADC completes a conversion burst. These IRQs are managed by the blocking function <code>S826_AdcRead</code>, which configures ADC interrupts and handles the resulting IRQs as required. To wait for the next IRQ, simply call <code>S826_AdcRead</code>; the function will return when ADC data is available, and will block and allow other threads to run while ADC data is unavailable.
| + | |
− | | + | |
− | 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)
| + | |
− | {
| + | |
− | int errcode;
| + | |
− | int adcdata[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", adcdata[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 the board can internally route it directly to the ADC's trigger input. Also, it's not necessary to generate counter snapshots because the ADC will notify software when a conversion burst has completed.
| + | |
− | | + | |
− | The following code shows how to digitize all 16 analog inputs in a burst, at a rate of <code>period</code> µs/burst. The bursts are triggered by counter0.
| + | |
− | | + | |
− | // Use counter0 to periodically trigger ADC conversions -------------
| + | |
− |
| + | |
− | void StartAdc16(uint period) // Configure/start ADC and trigger generator
| + | |
− | {
| + | |
− | int i;
| + | |
− | for (i = 0; i < 16; i++) // Configure all timeslots: 1 slot per AIN; 20µs/slot settling time.
| + | |
− | S826_AdcSlotConfigWrite(0, i, i, 20, S826_ADC_GAIN_1);
| + | |
− | S826_AdcSlotlistWrite(0, 0xFFFF, S826_BITWRITE); // Enable all 16 timeslots.
| + | |
− | S826_AdcTrigModeWrite(0, 0xB0); // Hardware triggered, source = counter0 ExtOut.
| + | |
− | S826_AdcEnableWrite(0, 1); // Enable ADC conversions.
| + | |
− | [[826#Hardware timer|CreateHwTimer]](0, 0, period); // Create and start the trigger generator.
| + | |
− | }
| + | |
− |
| + | |
− | int ReadAdc16(int *adcbuf)
| + | |
− | {
| + | |
− | uint slotlist = 0xFFFF; // Wait for ADC burst completion.
| + | |
− | return S826_AdcRead(0, adcbuf, NULL, &slotlist, S826_WAIT_INFINITE);
| + | |
− | }
| + | |
− | | + | |
− | In the following example, all 16 AINs are digitized and processed ten times per second.
| + | |
− | | + | |
− | int adcbuf[16]; // sample buffer -- always set size=16 (even if fewer samples needed)
| + | |
− | StartAdc16(100000); // Configure adc; start adc and trigger generator (trig every 100K us).
| + | |
− | while (ReadAdc16(adcbuf) == S826_ERR_OK) { // Repeat forever:
| + | |
− | // 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 <code>S826_AdcRead</code> function is allowed 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 argument <code>tmax=0</code>, 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 <code>S826_AdcRead</code> will return immediately, with <code>slotlist</code> bits indicating the AINs that have new samples waiting in <code>adcbuf</code>. If desired, individual samples may be processed when they arrive in <code>adcbuf</code> (each time <code>S826_AdcRead</code> executes), or the program can wait for all 16 AINs and then process them en masse.
| + | |
− | | + | |
− | In this example 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
| + | |
− | }
| + | |
− | | + | |
− | ===Software triggering===
| + | |
− | | + | |
− | Some applications require that ADC conversion bursts be triggered by software. To set this up, configure the ADC for hardware-triggering and select one of the board's virtual digital outputs as the trigger source. The software can then write to the virtual digital output to start an ADC conversion burst. The following code shows how to use virtual digital output 0 as a software-controlled ADC trigger:
| + | |
− | | + | |
− | #define TSETTLE 7 // Settling delay after switching AIN (in microseconds; adjust as necessary).
| + | |
− | #define SLOTFLAGS 0xFFFF // Timeslot flags: use all 16 timeslots.
| + | |
− | #define VIRTOUT 0 // Use virtual digital output 0 as ADC burst trigger.
| + | |
− |
| + | |
− | void InitAdc(uint board)
| + | |
− | {
| + | |
− | // Configure all timeslots: 1 slot per AIN; constant settling time for all slots.
| + | |
− | int i;
| + | |
− | 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, VIRTOUT + 182); // Use virtual digital output as trigger.
| + | |
− | S826_AdcEnableWrite(board, 1); // Enable conversions.
| + | |
− | }
| + | |
− | | + | |
− | This triggers a conversion burst:
| + | |
− | | + | |
− | void TriggerBurst(uint board)
| + | |
− | {
| + | |
− | S826_VirtualWrite(board, 1 << VIRTOUT, S826_BITSET); // Rising edge of trigger starts conversion.
| + | |
− | S826_VirtualWrite(board, 1 << VIRTOUT, S826_BITCLR); // Falling edge of trigger pulse.
| + | |
− | }
| + | |
− | | + | |
− | This returns True if a conversion burst has completed:
| + | |
− | | + | |
− | int IsBurstDone(uint board)
| + | |
− | {
| + | |
− | uint status;
| + | |
− | S826_StatusRead(board, &status);
| + | |
− | return (status == SLOTFLAGS);
| + | |
− | }
| + | |
− | | + | |
− | This example demonstrates how to use the above code:
| + | |
− | | + | |
− | InitAdc(0);
| + | |
− | while (1) // Repeat forever:
| + | |
− | {
| + | |
− | uint slotlist;
| + | |
− | int slot;
| + | |
− | int adcbuf[16]; // Sample buffer -- always set size=16 (even if fewer samples needed)
| + | |
− |
| + | |
− | TriggerBurst(0); // Trigger a conversion burst.
| + | |
− | do {} while (!IsBurstDone(0)); // Poll until burst completes.
| + | |
− | slotlist = SLOTFLAGS; // Copy ADC data into adcbuf.
| + | |
− | int errcode = S826_AdcRead(0, adcbuf, NULL, &slotlist, 0); // note: tmax=0
| + | |
− |
| + | |
− | for (slot = 0; slot < 16; slot++) // Display all samples.
| + | |
− | printf("Slot %d sample value = %d\n", slot, adcbuf[slot]);
| + | |
− | }
| + | |
− | | + | |
− | ===Anti-aliasing===
| + | |
− | | + | |
− | The 826 analog input system employs a multiplexed successive-approximation ADC to facilitate high speed acquisition of switched inputs. Anti-alias filters are intentionally excluded to allow the highest possible performance in a wide range of applications. Consequently, to avoid aliasing, you must sample each analog input (differential pair) frequently enough to satisfy Nyquist. If, due to high signal frequencies, this isn't possible for a particular input, it is recommended to use an external anti-alias filter to attenuate high-frequency components and thus satisfy Nyquist. Depending on the application, this may be as simple as adding an RC filter across the differential input pair.
| + | |
− | | + | |
− | ===Measuring isolated sources===
| + | |
− | | + | |
− | [[File:AdgGroundReference.gif|thumb|300px|To prevent measurement errors, connect isolated sources to ADC ground directly or through a resistor]]
| + | |
− | | + | |
− | Measurement accuracy can be significantly degraded if an analog input is connected to an unreferenced voltage source such as a battery, thermocouple, or isolated power supply. Since the source is isolated, the analog input has no current return to ground and, as a result, the input signal may be pulled (by protection circuitry) beyond the maximum allowed CMV. The resulting measurement errors can manifest in various ways (e.g., apparent non-linearity or calibration error) and can be difficult to characterize.
| + | |
− | | + | |
− | When measuring an isolated source, be sure to provide an input return to ground by connecting one side of the source to the ADC power supply ground, either directly (as shown in the diagram to the right) or through a resistor. This connection may be made to either the positive or negative terminal of the differential input pair.{{clear}}
| + | |
− | | + | |
− | ===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 [http://www.sensoray.com/products/826.htm 826 product page].
| + | |
− | | + | |
− | ===ADC accuracy specification===
| + | |
− | | + | |
− | Resolution and no missing codes are both 16 bits minimum.
| + | |
− | | + | |
− | {| class="wikitable"
| + | |
− | !rowspan="2"| Parameter
| + | |
− | !colspan="3"| Value
| + | |
− | !rowspan="2"| Units
| + | |
− | !rowspan="2"| 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==
| + | |
− | | + | |
− | ===Bipolar transfer functions===
| + | |
− | | + | |
− | In the DAC bipolar output modes (±5 V, ±10 V), the actual voltage range extends slightly beyond the negative end of the indicated output range:
| + | |
− | | + | |
− | {| class="wikitable"
| + | |
− | |+ ±5 V range
| + | |
− | |-
| + | |
− | ! DAC data
| + | |
− | ! Output (V)
| + | |
− | |-
| + | |
− | | 0x0000
| + | |
− | | -5.0002
| + | |
− | |-
| + | |
− | | 0x0001
| + | |
− | | -5.0
| + | |
− | |-
| + | |
− | | 0x8000
| + | |
− | | 0.0
| + | |
− | |-
| + | |
− | | 0xFFFF
| + | |
− | | +5.0
| + | |
− | |}
| + | |
− | | + | |
− | {| class="wikitable"
| + | |
− | |+ ±10 V range
| + | |
− | |-
| + | |
− | ! DAC data
| + | |
− | ! Output (V)
| + | |
− | |-
| + | |
− | | 0x0000
| + | |
− | | -10.0003
| + | |
− | |-
| + | |
− | | 0x0001
| + | |
− | | -10.0
| + | |
− | |-
| + | |
− | | 0x8000
| + | |
− | | 0.0
| + | |
− | |-
| + | |
− | | 0xFFFF
| + | |
− | | +10.0
| + | |
− | |}
| + | |
− | | + | |
− | ===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 that are outside the specified range):
| + | |
− | | + | |
− | int SetDacOutput(uint board, uint chan, uint range, double volts)
| + | |
− | {
| + | |
− | uint setpoint;
| + | |
− | switch (range) { // conversion is based on dac output 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
| + | |
− | default: return S826_ERR_VALUE; // invalid range
| + | |
− | }
| + | |
− | return S826_DacDataWrite(board, chan, setpoint, 0); // program DAC output and return error code
| + | |
− | }
| + | |
− | | + | |
− | Many applications use a particular, fixed DAC output range, and never change it. In such cases, the range can be "hard coded" when setting the DAC output. For example, if the application uses the ±10 V range:
| + | |
− | | + | |
− | // Program board0, dac0 output to -7.35 V
| + | |
− | int errcode = SetDacOutput(0, 0, S826_DAC_SPAN_10_10, -7.35); // hard-coded ±10 V range
| + | |
− | | + | |
− | Other applications may switch ranges dynamically. In such cases, if the current DAC output range is unknown (e.g., upon warm restart), you can call <code>S826_DacRead</code> first to determine the range:
| + | |
− | | + | |
− | // Program board0, dac0 output to +0.573 V
| + | |
− | uint range, setpoint;
| + | |
− | S826_DacRead(0, 0, &range, &setpoint, 0); // Get the active range (and DAC data, which we ignore).
| + | |
− | SetDacOutput(0, 0, range, 0.573); // Now we can set the DAC output without changing the range.
| + | |
− | | + | |
− | ====Setpoint readback====
| + | |
− | | + | |
− | The following function will return a DAC's programmed output voltage. Note that the returned <code>volts</code> may not exactly match the value specified in the last <code>SetDacOutput</code> call because the output voltage can only be programmed to discrete values.
| + | |
− | | + | |
− | int GetDacOutput(uint board, uint chan, double *volts)
| + | |
− | {
| + | |
− | uint range, setpoint;
| + | |
− | int errcode = S826_DacRead(board, chan, &range, &setpoint, 0); // Get DAC output range & setpoint.
| + | |
− | switch (range) { // Convert binary setpoint to volts:
| + | |
− | case S826_DAC_SPAN_0_5: *volts = setpoint * ( 5.0 / 0xFFFF); break; // 0 to +5V
| + | |
− | case S826_DAC_SPAN_0_10: *volts = setpoint * (10.0 / 0xFFFF); break; // 0 to +10V
| + | |
− | case S826_DAC_SPAN_5_5: *volts = (setpoint - 0x8000) * ( 5.0 / 0x7FFF); break; // -5V to +5V
| + | |
− | case S826_DAC_SPAN_10_10: *volts = (setpoint - 0x8000) * (10.0 / 0x7FFF); break; // -10V to +10V
| + | |
− | }
| + | |
− | return errcode;
| + | |
− | }
| + | |
− | | + | |
− | // Example usage: Read board0, dac0 programmed output voltage.
| + | |
− | double volts;
| + | |
− | if (GetDacOutput(0, 0, &volts) == S826_ERR_OK)
| + | |
− | printf("dac0 output is set to %f volts", volts);
| + | |
− | else
| + | |
− | printf("error reading dac0");
| + | |
− | | + | |
− | ===Control DAC voltage with an incremental encoder===
| + | |
− | | + | |
− | [[826#Encoder-controlled voltage|This example]] shows how to use an incremental shaft encoder to control a DAC's output voltage.
| + | |
− | | + | |
− | ===Connecting to a VFD===
| + | |
− | | + | |
− | In motor-control applications it's common to use a DAC to generate the control voltage for a variable-frequency drive (VFD). When doing this, it's essential to properly wire the ground signals so as to avoid noise and disruptive ground loops. In particular:
| + | |
− | | + | |
− | * Connect the VFD analog ground to an 826 GND pin, which is the analog reference for the DAC output signal.
| + | |
− | * <u>Do not</u> connect the VFD analog ground net to anything else (e.g., chassis ground).
| + | |
− | | + | |
− | The recommended wiring shown below uses the DAC's signal ground (826 GND) as analog reference and avoids ground loops and thus ensures accurate, problem-free motor control:
| + | |
− | | + | |
− | <center>''Recommended wiring''</center>
| + | |
− | [[File:Vfd wiring yes.gif|center|400px|alt=Recommended connections]]
| + | |
− | | + | |
− | | + | |
− | <center>''Don't do this! It will create a ground loop.''</center>
| + | |
− | [[File:Vfd wiring no1.gif|center|400px|alt=Don't do this!]]
| + | |
− | | + | |
− | | + | |
− | <center>''Don't do this either! It has a noisy, inaccurate analog reference.''</center>
| + | |
− | [[File:Vfd wiring no2.gif|center|400px|alt=Don't do this either!]]
| + | |
− | | + | |
− | ===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.
| + | |
− | | + | |
− | ===Short-circuit protection===
| + | |
− | | + | |
− | The board's DAC devices do not have output short-circuit protection. However, each DAC automatically limits its output current to 38 mA. Consequently, a DAC can tolerate intermittent output shorts and will not be damaged as long as the DAC device does not overheat.
| + | |
− | | + | |
− | ===DAC accuracy specification===
| + | |
− | | + | |
− | Resolution and monotonicity are both 16 bits minimum.
| + | |
− | {| class="wikitable"
| + | |
− | !rowspan="2" style="text-align:left;"| PARAMETER
| + | |
− | !rowspan="2" style="text-align:left;"| CONDITIONS
| + | |
− | !colspan="3"| VALUE
| + | |
− | !rowspan="2" style="text-align:left;"| 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<br>10V unipolar range, 25°C<br>5V unipolar range<br>10V unipolar range
| + | |
− | |
| + | |
− | |±80<br>±100<br>±140<br>±150
| + | |
− | |±200<br>±300<br>±400<br>±600
| + | |
− | |µV<br>µV<br>µV<br>µV
| + | |
− | |-
| + | |
− | |V_offset Temperature Coefficient
| + | |
− | |All unipolar ranges
| + | |
− | |
| + | |
− | |±2
| + | |
− | |
| + | |
− | |µV/°C
| + | |
− | |-
| + | |
− | |Bipolar Zero Error
| + | |
− | |All bipolar ranges
| + | |
− | |
| + | |
− | |±2
| + | |
− | |±12
| + | |
− | |LSB
| + | |
− | |}
| + | |
− | | + | |
− | ==DIOs==
| + | |
− | | + | |
− | ===Basic operation===
| + | |
− | | + | |
− | To unconditionally program all DIO outputs, call <code>S826_DioOutputWrite</code> with the <code>mode</code> argument set to <code>S826_BITWRITE</code> (zero). For example, this code will turn on all DIOs:
| + | |
− | | + | |
− | uint dios[] = { // Specify DIOs that are to be turned on (driven to 0 V):
| + | |
− | 0x00FFFFFF, // DIO 0-23
| + | |
− | 0x00FFFFFF // DIO 24-47
| + | |
− | }
| + | |
− | S826_DioOutputWrite(0, dios, S826_BITWRITE); // Turn on all DIOs.
| + | |
− | | + | |
− | This example shows how to turn on DIOs 7, 13 and 38, and turn off all other DIOs:
| + | |
− | | + | |
− | uint dios[] = { // Specify DIOs that are to be turned on:
| + | |
− | (1 << 7) + (1 << 13), // DIOs 7 & 13 are in first 24-bit mask (DIOs 0-23),
| + | |
− | (1 << (38 - 24)) // DIO 38 is in second 24-bit mask (DIOs 24-47).
| + | |
− | }
| + | |
− | S826_DioOutputWrite(0, // Program all DIO outputs on board 0:
| + | |
− | dios, // desired output states
| + | |
− | S826_BITWRITE); // unconditionally change all DIOs
| + | |
− | | + | |
− | To read the pin levels of all DIOs, call <code>S826_DioInputRead</code>. This example shows how to read and display all DIO pins on board 0:
| + | |
− | | + | |
− | int i;
| + | |
− | uint pins[2]; // Buffer for pin states.
| + | |
− | S826_DioInputRead(0, pins); // Read all DIO pin states into buffer.
| + | |
− | for (i = 0; i < 24; i++) // Display states of DIOs 0-23.
| + | |
− | printf("dio%d = %d\n", i, (pins[0] >> i) & 1);
| + | |
− | for (i = 24; i < 48; i++) // Display states of DIOs 24-47.
| + | |
− | printf("dio%d = %d\n", i, (pins[1] >> (i - 24)) & 1);
| + | |
− | | + | |
− | ===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 <code>S826_DioOutputWrite</code> with the <code>mode</code> argument set to <code>S826_BITSET</code> or <code>S826_BITCLR</code>, as shown in this example:
| + | |
− | | + | |
− | // Set and clear some DIOs without affecting other DIOs
| + | |
− | uint maskA[] = {7, 0}; // bitmask for DIOs 0-2
| + | |
− | uint maskB[] = {0, 1}; // bitmask for DIO 24
| + | |
− | S826_DioOutputWrite(0, maskA, S826_BITSET); // Set DIOs 0, 1 and 2.
| + | |
− | S826_DioOutputWrite(0, maskB, S826_BITCLR); // Clear DIO 24.
| + | |
− | | + | |
− | ===Debouncing inputs===
| + | |
− | | + | |
− | [[File:SwitchBounce.png|thumb|Oscilloscope screenshot showing the "bouncing" voltage when a switch turns on]]
| + | |
− | 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 = 1.31 ms).
| + | |
− | 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.
| + | |
− | | + | |
− | ===Edge detection===
| + | |
− | | + | |
− | Event-driven software often must execute code in response to DIO input changes (e.g., when a pushbutton is pressed, a photoelectric sensor is activated, etc.). Polling is not a good way to detect these events because it degrades system responsiveness and tends to make the source code convoluted and difficult to maintain. However, the alternative — using interrupts — can complicate and prolong program development.
| + | |
− | | + | |
− | Fortunately, model 826 provides the best of both worlds: it implements DIO edge detection circuitry with interrupts for efficient event monitoring, 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 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. When the output switches to the ''off'' state, 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).
| + | |
− | | + | |
− | {| class="wikitable"
| + | |
− | ! style="text-align:left;"| External pull-up
| + | |
− | ! style="text-align:left;"| 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 approaches:
| + | |
− | | + | |
− | * If you need precise timing then you can use two counters as shown in [[826#High resolution pulse-burst generator|High resolution pulse-burst generator]].
| + | |
− | | + | |
− | * If high precision is not needed then you could bit-bang the DIO, using a hardware timer (or system <code>Sleep</code> function) to pace the DIO writes like this:
| + | |
− | | + | |
− | int i;
| + | |
− | uint mask[] = {1, 0}; // bitmask for dio0
| + | |
− | [[#Software timer|CreateTimer]](0, 0, 10000); // Create 10 ms timer and start it running.
| + | |
− | for (i = 0; i < 5; i++) {
| + | |
− | [[#Software timer|WaitForTimer]](0, 0); // Wait for timer tick.
| + | |
− | S826_DioOutputWrite(0, mask, S826_BITSET); // Set dio0 active.
| + | |
− | [[#Software timer|WaitForTimer]](0, 0); // Wait for timer tick.
| + | |
− | S826_DioOutputWrite(0, mask, S826_BITCLR); // Set dio0 inactive.
| + | |
− | }
| + | |
− | | + | |
− | ===Example application: I<sup>2</sup>C Emulator===
| + | |
− | | + | |
− | :''Can I use DIOs to communicate with I<sup>2</sup>C devices?''
| + | |
− | | + | |
− | Have a look at [http://www.sensoray.com/downloads/appnote_826_i2c_emulator.pdf Sensoray's I<sup>2</sup>C emulator], which uses two DIOs to bit-bang an I<sup>2</sup>C bus. This open source software implements a full-featured I<sup>2</sup>C 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 I<sup>2</sup>C bus or emulate a slave device, consider [[826#Serial data capture (synchronous)|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?''
| + | |
− | | + | |
− | You may be able to use one of the signals if it has no common-mode voltage and is appropriately terminated but, in general, 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 [[826#Encoder FAULT output|here]].
| + | |
− | | + | |
− | ===Relay rack compatibility===
| + | |
− | | + | |
− | {{ImportantNote|The DIO circuitry on model 826 is not compatible with 15 or 24 VDC modules. When choosing modules for your relay rack, be sure to select modules that are compatible with 5 VDC logic levels.}}
| + | |
− | | + | |
− | Each 50-pin DIO header carries 24 DIO signals that have 5 V logic levels. The header pinout is compatible with the following module racks and other racks that have the same pinouts:
| + | |
− | | + | |
− | Grayhill (various module types):
| + | |
− | * 8 channels: 70RCK8-HL, 70MRCK8-HL, 70GRCK8-HL, 70LRCK8-HL, 70RCK8-DIN
| + | |
− | * 16 channels: 70RCK16-HL, 70MRCK16-HL, 70GRCK16-HL, 70LRCK16-HL, 70RCK16-DIN
| + | |
− | * 24 channels: 70MRCK24-HL, 70LRCK24-HL, 70LRCK24-DIN
| + | |
− | | + | |
− | Opto-22 (type G4 modules):
| + | |
− | * 8 channels: G4PB8
| + | |
− | * 16 channels: G4PB16
| + | |
− | * 24 channels: G4PB24
| + | |
− | | + | |
− | ===Maximum output current===
| + | |
− | | + | |
− | DIOs are subject to two different maximum current limits: individual and group. Each DIO is capable of continuously sinking up to 24 mA; this is the maximum individual current. However, it's not permitted for all DIOs to do this simultaneously because this would exceed the maximum group current.
| + | |
− | | + | |
− | DIO channels are organized into groups as shown below. In any particular group, the group current is the total of all individual currents in the group. For example, in Group4 the group current is the sum of the currents flowing in DIO24 through DIO29.
| + | |
− | | + | |
− | {| class="wikitable"
| + | |
− | ! style="text-align:left;"| Group
| + | |
− | ! style="text-align:left;"| DIOs
| + | |
− | |-
| + | |
− | |0
| + | |
− | |0-5
| + | |
− | |-
| + | |
− | |1
| + | |
− | |6-11
| + | |
− | |-
| + | |
− | |2
| + | |
− | |12-17
| + | |
− | |-
| + | |
− | |3
| + | |
− | |18-23
| + | |
− | |-
| + | |
− | |4
| + | |
− | |24-29
| + | |
− | |-
| + | |
− | |5
| + | |
− | |30-35
| + | |
− | |-
| + | |
− | |6
| + | |
− | |36-41
| + | |
− | |-
| + | |
− | |7
| + | |
− | |42-47
| + | |
− | |}
| + | |
− | | + | |
− | Each group is capable of continuously sinking up to 72 mA. As shown in the following table, all DIOs in a group may be active at the same time if their individual currents are less than or equal to 12 mA (because 6 * 12 ≤ 72). However, when the individual currents are higher than 12 mA, it is not allowed for all DIOs in a group to be active at the same time.
| + | |
− | | + | |
− | {| class="wikitable"
| + | |
− | ! style="text-align:left;"| DIO current (mA)
| + | |
− | ! style="text-align:left;"| Maximum number of active DIOs
| + | |
− | |-
| + | |
− | |i ≤ 12
| + | |
− | |6
| + | |
− | |-
| + | |
− | |12 < i ≤ 14.4
| + | |
− | |5
| + | |
− | |-
| + | |
− | |14.4 < i ≤ 18
| + | |
− | |4
| + | |
− | |-
| + | |
− | |18 < i ≤ 24
| + | |
− | |3
| + | |
− | |}
| + | |
− | | + | |
− | ==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.
| + | |
− | | + | |
− | | + | |
− | <center><i>A robust interface circuit for a 24V E-stop contact</i></center>
| + | |
− | [[File:826_estop.gif|500px|center|alt=A robust, reliable way to condition a 24V E-stop contact]]
| + | |
− | | + | |
− | | + | |
− | 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 ------------
| + | |
− |
| + | |
− | [[826#Edge detection|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: [[#Default safemode states|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:
| + | |
− | | + | |
− | [[#Periodic timer|CreateTimer]](0, 0, 100000); // Execute this loop every 100 milliseconds:
| + | |
− | while (1) {
| + | |
− | S826_WatchdogKick(0, 0x5A55AA5A); // Unconditionally kick the watchdog.
| + | |
− | [[#Periodic timer|WaitForTimer]](0, 0);
| + | |
− | }
| + | |
− | | + | |
− | Here's a slightly more complex version 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.
| + | |
− |
| + | |
− | [[#Periodic timer|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.
| + | |
− | [[#Periodic timer|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
| + | |
− | }
| + | |
− | | + | |
− | ===Output watchdog on a DIO===
| + | |
− | | + | |
− | The outputs from watchdog timer1 and timer2 can be routed to select DIOs. As explained in the API manual (see <code>S826_DioOutputSourceWrite</code>), timer1 can be routed to DIOs 7, 15, 23, 31, 39 and 47, and timer2 can be routed to DIOs 6, 14, 22, 30, 38 and 46.
| + | |
− | | + | |
− | The following code will route the output of timer1 to dio7 so that dio7 will be turned on (driven low, to 0 V) when timer1 times out. As always, timer0 is used to time the kicks. Timer1 is assigned a minimum delay (DELAY1=1) so that it will timeout (and thereby activate dio7) one clock (20 ns) after timer0 times out.
| + | |
− | | + | |
− | #define WD_MILLISECONDS 100 // Watchdog Timer0 will timeout if unkicked for this long
| + | |
− |
| + | |
− | wdtiming[] = {WD_MILLISECONDS * 50000, 1, 1, 0, 0}; // timing parameters
| + | |
− | uint routing[] = {1 << 7, 0}; // mask for dio7
| + | |
− |
| + | |
− | S826_SafeWrenWrite(0, S826_SAFEN_SWE); // Write-enable protected settings.
| + | |
− | S826_WatchdogConfigWrite(0, // Configure watchdog:
| + | |
− | S826_WD_NIE, // connect timer1 to NMI net
| + | |
− | wdtiming); // set timer0 & timer1 intervals
| + | |
− | S826_DioOutputSourceWrite(0, routing); // Route NMI net to dio7.
| + | |
− | S826_WatchdogEnableWrite(0, 1); // Start the watchdog (AND START KICKING!)
| + | |
− | S826_SafeWrenWrite(0, S826_SAFEN_SWD); // Write-disable protected settings.
| + | |
− | | + | |
− | ===Turning off the relay===
| + | |
− | | + | |
− | After the watchdog has activated the Reset Out relay, the application program can deactivate the relay by calling <code>S826_WatchdogEnableWrite()</code> with <code>enable=0</code>, as shown below:
| + | |
− | | + | |
− | S826_WatchdogEnableWrite(0, 0); // Turn off relay on board0.
| + | |
− | | + | |
− | ===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.
| + | |
− | | + | |
− | ===P2 mating connector===
| + | |
− | | + | |
− | These connectors (or equivalents) will mate to the board's three-pin Watchdog Reset Out header (P2):
| + | |
− | * Molex 22-01-2037 (ramp only)
| + | |
− | * Molex 22-01-3037 (ramp + alignment ribs)
| + | |
− | | + | |
− | ==Timestamp generator==
| + | |
| | | |
− | The timestamp generator is a high-resolution "clock" based on a free-running 32-bit counter. The counter increments every microsecond and overflows (to zero, without notification) every 2<sup>32</sup> µs (approximately 71.6 minutes). It is a binary counter and consequently does not keep track of the date or time-of-day.
| + | ===Usage=== |
| | | |
− | The generator's current time is automatically appended to every counter snapshot and every ADC sample so that application programs can know (to within 1 µs) when each sample was acquired. It is particularly useful for precisely measuring the elapsed time between hardware events. Calculation of elapsed time is easy (a single subtraction) as long as the time doesn't exceed 71.6 minutes. It can be used in a variety of ways, including [[826#Measuring speed|measuring speed]] and [[826#Serial data capture|capturing serial data]].
| + | Timestamps are particularly useful for precisely measuring the elapsed time between hardware events. Calculation of elapsed time is easy (a single subtraction) as long as the time interval doesn't exceed 71.6 minutes. It can be used in a variety of ways, including [[826 counters#Measuring speed|measuring speed]] and [[826 counters#Serial data capture|capturing serial data]]. |
| | | |
− | If desired, an application program can also directly read the current time as shown below: | + | If desired, an application program can directly read the current time as shown below: |
| | | |
− | // Read the timestamp generator's current time. | + | // Read the timestamp generator's current count. |
| uint CurrentTimestamp(uint board) | | uint CurrentTimestamp(uint board) |
| { | | { |
Line 2,278: |
Line 40: |
| | | |
| // Example: Use board0 to measure system Sleep() time. | | // Example: Use board0 to measure system Sleep() time. |
− | uint t1, t0 = CurrentTimestamp(0); // Get start time. | + | uint t1, t0 = CurrentTimestamp(0); // Get start time. |
− | Sleep(100); // Sleep approximately 100 ms. | + | Sleep(25); // Sleep approximately 25 ms. |
− | t1 = CurrentTimestamp(0); // Get end time. | + | t1 = CurrentTimestamp(0); // Get end time. |
− | printf("Slept %d µs", t1 - t0); // Display actual sleep time. | + | printf("Slept %d µs", t1 - t0); // Display actual sleep time. |
| | | |
| ==Board ID== | | ==Board ID== |
Line 2,657: |
Line 419: |
| :''Do you recommend specific Linux distributions for use with the 826?'' | | :''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). The 826 driver is compatible to kernel versions 2.6 and higher. | + | 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). The 826 driver is compatible with kernel versions 2.6 and higher. |
| + | |
| + | ====Troubleshooting==== |
| + | |
| + | :''The board worked yesterday but it doesn't work today. I didn't change anything. What could be the problem?'' |
| + | |
| + | It's likely that the operating system upgraded the Linux kernel during an automatic update. See [[Linux Troubleshooting|this appnote]] for details. |
| | | |
| ====Build errors==== | | ====Build errors==== |
Line 2,667: |
Line 435: |
| | | |
| In such cases, it's likely that the 826 driver successfully installed and you are simply seeing a warning. You can confirm this by trying "<code>sudo modprobe s826</code>" and the 826 demo application. | | In such cases, it's likely that the 826 driver successfully installed and you are simply seeing a warning. You can confirm this by trying "<code>sudo modprobe s826</code>" and the 826 demo application. |
| + | |
| + | =====Different gcc version===== |
| + | |
| + | ''Why do I get the following error when building the demo?" |
| + | |
| + | relocation R_X86_64_32S against `.bss' can not be used when making a PIE object; recompile with -fPIE |
| + | |
| + | Answer: You have a newer version of gcc, so you must rebuild the middleware. To do this, switch to the SDK downloads directory and then: |
| + | * call "make lib" |
| + | * cd to middleware directory: "cd middleware" |
| + | * "cp *.a ../demo/" |
| + | * "cd .." |
| + | |
| + | Now rebuild the demo with the new .a files: |
| + | * "make -C demo s826demo" |
| | | |
| ==Remote access== | | ==Remote access== |
Line 2,672: |
Line 455: |
| :''Is there any way to use an 826 with a laptop?'' | | :''Is there any way to use an 826 with a laptop?'' |
| | | |
− | Not directly, because laptops don't provide PCI Express slots. However, it is possible to locate the 826 in a host computer and, with appropriate software, remotely access its interfaces from a laptop (e.g., via Ethernet or USB); in fact, several 826 users have reported that they do exactly that. | + | Not directly, because laptops don't provide exposed PCI Express slots. However, it is possible to locate the 826 in a host computer that does have PCIe slots and, with appropriate software, remotely access its interfaces from a laptop (e.g., via Ethernet or USB). |
| + | |
| + | When designing such a system, it's important to consider that neither Ethernet nor USB are capable of real-time communication with register-based measurement and control hardware. Consequently, depending on the application, this may require the host to offload time-critical I/O functions from the laptop, such as interrupt handling, counter FIFO processing, and low-level register I/O sequences, in order to achieve real-time performance. |
| | | |
− | When designing such a system, it's important to consider that Ethernet and USB are not capable of real-time communication with register-based measurement and control hardware. Consequently, depending on the application, this may require the host to offload time-critical I/O functions from the laptop, such as interrupt handling, FIFO processing, and low-level register I/O sequences, in order to achieve real-time performance.
| + | Functions that are not time-critical can be implemented in various ways. For example, the host computer could run a SNMP agent process that serves as a bridge between Ethernet and the 826. To do this, you will need to create a MIB and implement the associated functions in the agent. |
| | | |
| ==Environmental specifications== | | ==Environmental specifications== |
Line 2,690: |
Line 475: |
| | | |
| ==Migrating from model 626== | | ==Migrating from model 626== |
| + | |
| + | For users who are upgrading PCI systems to PCIe, we recommend model 826 as a replacement for model 626. |
| + | |
| + | ===Porting guide=== |
| + | |
| + | A migration aid is available to help C developers port applications to model 826. The aid consists of C code which provides, when feasible, equivalent 826 API calls for 626 API functions. In cases where equivalent functions are not available, compiler errors and runtime warnings are issued, and tips are given for resolving porting issues. |
| + | |
| + | * [http://www.sensoray.com/downloads/port626.zip 626-to-826 migration aid] |
| | | |
| ===Differences between models 626 and 826=== | | ===Differences between models 626 and 826=== |
| | | |
− | When upgrading your PCI system to PCIe, we recommend model 826 as a replacement for model 626. The following table compares the interfaces on the two boards:
| + | The following table compares the interfaces on the two boards: |
| | | |
| {| class="wikitable" | | {| class="wikitable" |
Line 2,705: |
Line 498: |
| |- | | |- |
| | Counters | | | Counters |
− | | 6 channels<br>24-bit resolution<br>24-bit sample latch | + | | 6 channels (3 A/B pairs)<br>24-bit resolution<br>24-bit sampling latch |
− | | 6 channels<br>32-bit resolution<br>sample FIFO (16-deep) with 32-bit timestamps | + | | 6 channels (identical)<br>32-bit resolution<br>16-deep FIFO with timestamps |
| |- | | |- |
| | GPIOs | | | GPIOs |
Line 2,725: |
Line 518: |
| |- | | |- |
| | Fail-safe controller | | | Fail-safe controller |
− | | None | + | | n/a |
| | Integrated | | | Integrated |
| + | |- |
| + | | Timestamp generator |
| + | | n/a |
| + | | 32 bits, 1 µs resolution |
| |} | | |} |
| | | |
Line 2,733: |
Line 530: |
| :''Do models 826 and 626 have the same connectors and pinouts?'' | | :''Do models 826 and 626 have the same connectors and pinouts?'' |
| | | |
− | Both boards use identical connectors. The pinouts of the digital and counter connectors are identical, but 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). | + | Both boards have identical connector types, and all connector pinouts are identical except for analog connector J1. Model 826 has four additional analog outputs (channels 4-7) on pins 41, 43, 45 and 47. Model 626 uses these pins as remote sense inputs for analog output channels 0-3. |
| + | [[File:826 vs 626 pinouts.gif|left|350px]] |
| + | {{Clear}} |
| | | |
− | ===Using 626 cables with the 826=== | + | ===Software differences=== |
| | | |
− | :''I have a 7505TDIN breakout board and 7501C1 (50-pin cable) for the 626. Can I use these with the 826?''
| + | ;Driver and API |
| + | Models 826 and 626 use different device drivers and APIs. The driver and API for each model is exclusive to that model and is not compatible with the other model. The drivers and APIs are necessarily different due to the expanded capabilities of model 826 and to significant hardware architecture differences between the two models. In addition, the 826 API provides blocking functions to simplify development of both event-driven and polling applications. |
| | | |
− | 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.
| + | ;Application code |
| + | Since the APIs are different, it is necessary to revise application code when upgrading to model 826. Numerous examples can be found in this wiki to help in that endeavor. Also, consider using our [http://www.sensoray.com/downloads/port626.zip 626-to-826 migration aid] for C developers who are moving 626 applications to model 826. |
| | | |
− | ==See also== | + | ===Using 626 cables with the 826=== |
| | | |
− | * [[GPIO interfacing]] - design tips for DIO circuits
| + | :''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. |
| | | |
| | | |
| | | |
| [[Category:826| ]] | | [[Category:826| ]] |
The timestamp generator is a free-running 32-bit counter that serves as a time reference. The counter increments once per microsecond and overflows (to zero, without notification) every 232 µs (approximately 71.6 minutes). It is a binary counter and consequently does not keep track of the date or time-of-day. At any moment, the current count may be sampled; such a sample is called a timestamp.
A timestamp is automatically appended to every counter snapshot and to every ADC sample so that application programs can know (to within 1 µs) when each sample was acquired. Also, application programs can read the timestamp generator at any time to get a timestamp.
Timestamps are particularly useful for precisely measuring the elapsed time between hardware events. Calculation of elapsed time is easy (a single subtraction) as long as the time interval doesn't exceed 71.6 minutes. It can be used in a variety of ways, including measuring speed and capturing serial data.
If desired, an application program can directly read the current time as shown below:
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 a single 826 board, the return value will be (2^ID)
. If you have multiple boards, the return value is the sum of (2^ID)
for each board. You can enter the return value here to quickly determine its meaning.
The circuit board revision (PWB rev) is visible on the solder-side of the 826 board (opposite the mounting bracket, on the bottom corner). S826_VersionRead
returns the PWB rev as a numeric value with decimal range [0:31], which corresponds to a text string in the standard ASME version letter sequence. The following code shows how to convert this 32-bit value to the alphabetic revision code seen on the board:
Sensoray has developed Revision C of the 826 circuit board. This change was necessary due to the impending EOL (end-of-life) of a critical component. Specifically, the critical component (PCI Express interface chip) and FPGA were removed and replaced by a new FPGA, which absorbed the functions of the two removed components.
Rev B and Rev C boards can be used interchangeably in new and existing applications. From an application's perspective, the only detectable differences between Rev B and Rev C boards are the version numbers returned by the API function S826_VersionRead():
The following drawings show the pinouts of the board's header connectors as viewed from the top (component) side of the circuit board:
Each 826 SDK includes a C# demo application. These demos show how to call API functions from C#, and can serve as a useful starting point for a custom application.
In the Linux SDK, a C# GUI demo is available which uses Linux mono. To get the required libraries on Ubuntu, type:
Many of the API functions have pointer arguments. This is no problem for C#, which allows you to pass function arguments by reference. To see how this is done, consider the S826_AdcEnableRead
function:
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).
The VIs may be installed under Labview's instrument library (e.g., "instr.lib\Sensoray 826") or elsewhere if desired. Refer to Labview documentation for information about paths and other relevant topics.
To use an 826 with Matlab you must first install the latest 64-bit versions of the 826 API (s826.dll) and device driver; these are both part of the 826 SDK, which you can obtain from the Downloads tab of the 826 product page. You may then use Matlab's loadlibrary()
function to enable access to the API, and calllib()
to call API functions. The API functions are described in the 826 product manual, which can be found on the 826 product page Documentation tab. The following example illustrates how this works.
Sensoray offers an open-source software development kit for Matlab programmers, which you can obtain from the Downloads tab of the 826 product page. The Matlab SDK includes two files:
Sensoray SDKs do not include a ROS package, but the Linux SDK has everything needed to create one. The simplest way to use ROS with model 826 is to install the Linux 826 device driver and API (shared library), and then call the API functions as shown in the following example. Note that this example is coded in C++, but you can easily call the API functions from Python or any other language.
1. Windows 3.3.4
2. Linux 3.3.5
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:
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:
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.
The basic silent install is invoked by running the following command from command line or batch script:
Please note that the /S is case sensitive and must be upper case.
Yes, in version 3.3.9, the following command will install the required DLLs and system libraries, but no drivers or demo programs.
Unfortunately, there is no way around this. Windows requires confirmation from the user for driver install, even if the driver is signed.
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.
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.
Sensoray has created a Universal driver (UD) for the 826 under OneCoreUAP-based editions of Windows. It is similar to the standard driver, but compiled as Universal. This driver is in our SDK zip file under the "driver/sensoray_826_universal_driver" directory. Installation may be dependent on the specific Windows version. The inf is Windows Universal compatible. Sensoray does not currently have a demonstration Universal Windows App for the 826, but the .NET demo app may be portable.
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). The 826 driver is compatible with kernel versions 2.6 and higher.
It's likely that the operating system upgraded the Linux kernel during an automatic update. See this appnote for details.
In such cases, it's likely that the 826 driver successfully installed and you are simply seeing a warning. You can confirm this by trying "sudo modprobe s826
" and the 826 demo application.
Answer: You have a newer version of gcc, so you must rebuild the middleware. To do this, switch to the SDK downloads directory and then:
Not directly, because laptops don't provide exposed PCI Express slots. However, it is possible to locate the 826 in a host computer that does have PCIe slots and, with appropriate software, remotely access its interfaces from a laptop (e.g., via Ethernet or USB).
When designing such a system, it's important to consider that neither Ethernet nor USB are capable of real-time communication with register-based measurement and control hardware. Consequently, depending on the application, this may require the host to offload time-critical I/O functions from the laptop, such as interrupt handling, counter FIFO processing, and low-level register I/O sequences, in order to achieve real-time performance.
Functions that are not time-critical can be implemented in various ways. For example, the host computer could run a SNMP agent process that serves as a bridge between Ethernet and the 826. To do this, you will need to create a MIB and implement the associated functions in the agent.
For users who are upgrading PCI systems to PCIe, we recommend model 826 as a replacement for model 626.
A migration aid is available to help C developers port applications to model 826. The aid consists of C code which provides, when feasible, equivalent 826 API calls for 626 API functions. In cases where equivalent functions are not available, compiler errors and runtime warnings are issued, and tips are given for resolving porting issues.
Both boards have identical connector types, and all connector pinouts are identical except for analog connector J1. Model 826 has four additional analog outputs (channels 4-7) on pins 41, 43, 45 and 47. Model 626 uses these pins as remote sense inputs for analog output channels 0-3.
Models 826 and 626 use different device drivers and APIs. The driver and API for each model is exclusive to that model and is not compatible with the other model. The drivers and APIs are necessarily different due to the expanded capabilities of model 826 and to significant hardware architecture differences between the two models. In addition, the 826 API provides blocking functions to simplify development of both event-driven and polling applications.
Since the APIs are different, it is necessary to revise application code when upgrading to model 826. Numerous examples can be found in this wiki to help in that endeavor. Also, consider using our 626-to-826 migration aid for C developers who are moving 626 applications to model 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.