Daqctl: Difference between revisions

From embeddedTS Manuals
m (Links auto-updated for 2022 re-branding ( https://files.embeddedarm.com/ts-arm-sbc/ts-7558-linux/sources/libdaqctl.c →‎ https://files.embeddedTS.com/ts-arm-sbc/ts-7558-linux/sources/libdaqctl.c https://files.embeddedarm.com/ts-arm-sbc/ts-7558-linux/sources/libdaqctl.h →‎ https://files.embeddedTS.com/ts-arm-sbc/ts-7558-linux/sources/libdaqctl.h))
 
(35 intermediate revisions by 2 users not shown)
Line 1: Line 1:
= Overview =
= Overview =
daqctl is the userspace application to manage the data acquisition core in the FPGA.  This facilitates PWM, counters, digital inputs/outputs, and ADC.
The DAQ core is a core used on multiple products such as the [[TS-7520-BOX]], [[TS-7558-BOX]], [[TS-7580-BOX]], and can be ported to our other products.  This core facilitates ADC, DIO, quadrature, and counter sampling, as well as DIO outputs, PWM, and optionally VFD support.  The hardware is accessed using daqctl.
 
= Usage =
== Help ==
  Usage: daqctl [OPTION] ...
  Technologic Systems ADC-75xx core userspace driver utility.
  Example: daqctl --server --speed=2.5khz --chan=0-3,5,7 --gain2x=5
 
    -i, --irq=N            Use IRQ N as ADC IRQ (30)
    -q, --stats            Print server daemon stats
    -s, --speed=FREQ      Use FREQ as default sample frequency (1000)
    -c, --chan=CHANS      Sample only channels CHANS (0-15)
    -x, --exttrig=PIN      Ignore --speed and use external trigger instead
    -g, --gain2x=CHANS    Use high-gain setting on CHANS (TS-7558 only)
    -g, --gain6x=CHANS    Use high-gain setting on CHANS
    -l, --cloop=CHANS      Enable 4-20 ma current loops on CHANS
    -d, --server          Daemonize and run as server
    -I, --bind=IPADDR      Bind server to IPADDR
    -p, --connect=HOST    Connect to remote daqctl service at HOST
    -P, --dumpcsv          Output acquired samples as CSV text
    -b, --dumpbin          Output acquired samples as raw binary
    -1, --single          Print one sample's values
    -D, --pwm=PWMSPEC      Sets PWM outputs according to PWMSPEC
    -h, --help            This help
 
    --irq-on-change=PIN    Flush when digital input PIN (0-56) changes
    --irq-on-glitch=PIN    Flush when digital input PIN (0-56) glitches
    --irq-on-high=PIN      Flush while digital input PIN (0-56) is high
    --irq-on-low=PIN      Flush while digital input PIN (0-56) is low
    --irq-on-quadrature    Flush when a quadrature counter changes value
    --irq-on-quad-dir      Flush when quadrature changes direction
    --irq-on-counter      Flush when a monitored edge counter changes
    --irq-on-any-change    Flush when any digital input changes
    --max-irq-rate=NSAM    IRQ at most every NSAM (1,4,16,64) samples
    --max-irq-rate-always  Always interrupt at max rate
    --min-irq-rate=HZ      Minimum IRQ rate HZ (1000,500,100,0)
    --min-irq-rate-always  Always interrupt at min rate
 
    --mux-counter0=PIN    Use digital input PIN (0-56) for counter#0
    --mux-counter1=PIN    Use digital input PIN (0-56) for counter#1
    --mux-counter2=PIN    Use digital input PIN (0-56) for counter#2
    --mux-counter3=PIN    Use digital input PIN (0-56) for counter#3
    --mux-quad0a=PIN      Use digital input PIN (0-56) for quadrature#0 A
    --mux-quad0b=PIN      Use digital input PIN (0-56) for quadrature#0 B
    --mux-quad1a=PIN      Use digital input PIN (0-56) for quadrature#1 A
    --mux-quad1b=PIN      Use digital input PIN (0-56) for quadrature#1 B
 
  When run as a server, default is to listen at TCP port 7700
 
  The --dumpbin option will send 16-bit binary samples to stdout
  where the 4 MSBs are the channel number and the 12 LSBs is the
  sample value.  This is the same raw format as the port 7700 TCP
  socket interface.
 
  The --mux-* options are valid only for TS-7520/TS-7580.  The TS-7558 does
  not support changing these pins from their defaults.
 
  The PWMSPEC format is CHANS:DUTY%[@FREQ].  Examples: "0-3:57%@100hz",
  "1,3:33.33%@20ms", "1:0x800" (literal 12-bit), or "5:5%@1.81khz"


== ADC ==
== ADC ==
A single high speed 200ksps 12-bit SPI ADC is connected to a 16 channel analog mux.  After the mux, there is an op-amp circuit with a FET connected under FPGA control that adds 2x gain stage before feeding to the SPI National Semiconductor ADC121S021 ADC chip.
The ADC core allows you to sample different voltage ranges, and current loops.  The hardware has an 8KB FIFO to contain the latest values.  After 4kb has been written the hardware core will assert an IRQ, and daqctl will pull the latest samples.


Upon reaching the end of the 8Kbyte buffer, the address is wrapped back to zero and sampling continuesSoftware must be able to keep up and read samples before the address wraps around and old samples are overwrittenThere will be an irq every 4kb written to the external memoryThe IRQ is cleared and re-armed upon reading of the current state reg.
To sample all ADC channels at 100hz to a CSV file:
<source lang=bash>
daqctl --dumpcsv --chan=0-7 --speed=100hz
</source>
This will return the values from 0-4095 to represent the actual voltageRefer to your board manual for the ADC ranges, and for more information on --gain2x and --gain6xThe ADC can sample based off of either the internally configurable sample rate, or by using one of the digital inputs as an external triggerTo sample off of input channel 2: 
<source lang=bash>
daqctl --dumpcsv --chan=0-7 --exttrig=2
</source>


The ADC can sample based off of either the internally configurable sample rate, or by using one of the digital inputs as an external trigger.
You can also read ADC as a current loop between 4mA to 20mA on all of the external ADC channels using the --cloop option.
<source lang=bash>
# Sample channels 0-7.
# 4-7 will use a current loop to read 0-20mA
# 0-3 will use the default voltage range for the board
daqctl --cloop=4-7 --chan=0-7
</source>


== PWM ==
== PWM ==
The PWM values are defined by the [[#PWMSPEC]].  This allows you to set the duty cycle and frequency from the command line, or in C.  Every channel has it's own duty cycle, but the frequencies are grouped.  Every 7 DIO will their own PWM frequency, (outputs 0-6, 7-13, 14-20, ...).  Changing the frequency for any output channel will change it for the group. 
Turn all 8 PWM channels to 30% at 5khz:
<source lang=bash>
daqctl --pwm=0-7:30%@5khz
</source>
You can also use the ADC sample frequency as the PWM frequency.
<source lang=bash>
daqctl --pwm=6:30%@EXT0
</source>
=== PWMSPEC ===
Our PWM core will accept the PWMSPEC format as a simple way to control it.  For example:
Our PWM core will accept the PWMSPEC format as a simple way to control it.  For example:
* "0:33%" - Set duty to 33%, preserving frequency
* "0:33%" - Set duty to 33%, preserving frequency
Line 17: Line 101:
* "0:2048" - Preserves the frequency if just setting the duty cycle
* "0:2048" - Preserves the frequency if just setting the duty cycle
* "0:50%@EXT0" - 50% duty, uses external trig #0 (ADC sample rate)
* "0:50%@EXT0" - 50% duty, uses external trig #0 (ADC sample rate)
* "0:33.333%@100" - should parse decimal also
* "0:33.333%@100" - Can parse decimal also
* "0:50%@100us" - and period specs rather than freq
* "0:50%@100us" - and period specs rather than frequency
* "0-3:50%" - Can take multiple channels
* "0-3:50%" - Can take multiple channels
* "0,2,5-6:50%" - Can accept multiple channel ranges.
* "0,2,5-6:50%" - Can accept multiple channel ranges.


The core allows you to set 2 different frequenciesOne is dedicated to the PWM timer which will support up to 131071hzYou can also use the ADC sample rateThis core also supports using external triggers from the digital inputs (1-4).
== Input Channels ==
Every board that implements the DAQ core uses the same channel structure for sample data.  When using daqctl these are the channels specified with --chan, and correspond with the libdaqctl structure's last[16] variable.
 
{| class="wikitable"
|-
! Channel
! Description
|-
| 0
| ADC Channel 0
|-
| 1
| ADC Channel 1
|-
| 2
| ADC Channel 2
|-
| 3
| ADC Channel 3
|-
| 4
| ADC Channel 4
|-
| 5
| ADC Channel 5
|-
| 6
| ADC Channel 6
|-
| 7
| ADC Channel 7
|-
| 8
| Quadrature counter delta #0
|-
| 9
| Quadrature counter delta #1
|-
| 10
| Current state of all digital inputs
|-
| 11
| Counter delta #0
|-
| 12
| Counter delta #1
|-
| 13
| Counter delta #2
|-
| 14
| Counter delta #3
|-
| 15
| Glitch reg or timestamp
|}
 
= Programming with libdaqctl =
We provide a library for the end user called libdaqctl that you can use to interact with the daqctl TCP server. 
 
[https://files.embeddedTS.com/ts-arm-sbc/ts-7558-linux/sources/libdaqctl.c libdaqctl.c]
[https://files.embeddedTS.com/ts-arm-sbc/ts-7558-linux/sources/libdaqctl.h libdaqctl.h]
 
This will allow simple control to receive callbacks, control the PWM, or take ADC samples.  The following calls are available in libdaqctl:
 
== daqctl_connect ==
<source lang=c>int daqctl_connect(char *hostspec, char *args);</source>
Usually this call only needs to be made once.  Hostspec is the tcp server to connect to where it can find the daqctl server on port 7700.  This allows the libdaqctl application to reside on another system on the same network as the daqctl server.  The args variable takes the same arguments to daqctl.  This is where you can specify which pins to mux for quadrature or counters, set the sample rate, enable gain2x/gain6x on ADC channels, and moreA complete list of arguments is available [[#Help|here]].
 
The return value is the file descriptor to the socket which should be placed in the daqctl structure to 'fd'.
 
== daqctl_pwmspec ==
<source lang=c>int daqctl_pwmspec(int sk, char *spec);</source>
This function is used to set the value of an output using a [[#PWMSPEC|pwmspec]] string.  The sk argument is the file descriptor returned by daqctl_connect.  The spec argument is a null terminated char array that describes one or more PWM outputs.  For example, to set output 6 to a 30% duty cycle at 20hz:
<source lang=c>
daqctl_pwmspec(dq.fd, "6:30%@20hz");
</source>
 
== daqctl_setoutput ==
<source lang=c>
int daqctl_setoutput(int sk, int pin, unsigned int val);
/* Max frequency = 131071 hz,  Max duty = 100% (0xfff) */
#define PWM_DUTY_PCT(x) ((0xfff * (x) / 100 ) << 1)
#define PWM_DUTY(x) ((x) << 1)
#define PWM_FREQ(x) ((x) << 13)
#define PWM_FREQ_IS_SAMPLERATE (1<<30)
#define PWM_FREQ_NOT_SAMPLERATE (3<<30)
</source>
 
Similar to daqctl_pwmspec, this will define the frequency and duty cycle of one pin.  The sk argument is the file descriptor returned by daqctl_connect.  The pin refers to the output channel to control.  The output channels are mapped on the specific product ([[TS-7520-BOX#16x_RJ45|TS-7520]], [[TS-7580-BOX#Terminal_Blocks|TS-7580]], or OUT1-8 on the TS-7558 will map to pin 0-7The value can be set to 0 to set low, or 1 to set highThe value can also be set using on of the convenience functions.
 
PWM_DUTY_PCT allows you to pass the duty cycle as a percent.  For example:
<source lang=c>
// Set 30% duty cycle
daqctl_setoutput(dq.fd, 5, PWM_DUTY_PCT(30));
</source>
 
PWM_DUTY accepts a value of 0-4095 to control the duty cycle.
<source lang=c>
// iterate through all duty cycle settings:
int i;
for(i=0; i < 4095; i++) {
    daqctl_setoutput(dq.fd, 5, PWM_DUTY(i));
    usleep(10000);
}
</source>


  Note: If you change any timer for one channel it will
PWM_FREQ allows you to specify the frequency of a port specified in hz.  The core allows each 8 DIO ids to have one PWM frequency.
  change it for any channels currently using it as well.
<source lang=c>
// channel 5 (0-7) to 2000hz
daqctl_setoutput(dq.fd, 5, PWM_FREQ(2000));


= Usage =
// You can also set a frequency and PWM_DUTY or PWM_DUTY_PCT at the same time:
== Help ==
daqctl_setoutput(dq.fd, 5, PWM_FREQ(1000) | PWM_DUTY_PCT(20));
  Usage: daqctl [OPTION] ...
</source>
  Technologic Systems ADC-7558 core userspace driver utility.
  Example: daqctl --server --speed=13khz --chan=0-3,5,7 --gain2x=5
 
  -i, --irq=N            Use IRQ N as ADC IRQ (30)
  -s, --speed=FREQ      Use FREQ as default sample frequency (1000)
  -c, --chan=CHANS      Sample only channels CHANS (0-15)
  -x, --exttrig=PIN      Override --speed and use external trigger instead
  -g, --gain2x=CHANS    Use high-gain setting on CHANS
  -l, --cloop=CHANS      Enable 4-20 ma current loops on CHANS
  -d, --server          Daemonize and run as server
  -I, --bind=IPADDR      Bind server to IPADDR
  -p, --connect=HOST    Connect to remote daqctl service at HOST
  -P, --dumpcsv          Output acquired samples as CSV text
  -b, --dumpbin          Output acquired samples as raw binary
  -1, --single          Print one sample's values
  -D, --pwm=PWMSPEC      Sets PWM outputs according to PWMSPEC
  -h, --help            This help
 
  --irq-on-change=PIN    Flush when digital input PIN (0-7) changes
  --irq-on-glitch=PIN    Flush when digital input PIN (0-7) glitches
  --irq-on-high=PIN      Flush while digital input PIN (0-7) is high
  --irq-on-low=PIN      Flush while digital input PIN (0-7) is low
  --irq-on-quadrature    Flush when a quadrature counter changes value
  --irq-on-quad-dir      Flush when quadrature changes direction
  --irq-on-counter      Flush when a monitored edge counter changes
  --irq-on-any-change    Flush when any digital input changes
  --max-irq-rate=NSAM    IRQ at most every NSAM (1,4,16,64) samples
  --max-irq-rate-always  Always interrupt at max rate
  --min-irq-rate=HZ      Minimum IRQ rate HZ (1000,500,100,0)
  --min-irq-rate-always  Always interrupt at min rate


== Examples ==
PWM_FREQ_IS_SAMPLERATE will use the ADC sample rate (specified by --speed) as the frequency for an output channel.  Multiple channels can use this frequency.  While normally PWM frequencies are set in groups of 8, this frequency can apply to a single output channel without affecting the group.
Even without writing code, you can do simple operations like sampling ADC or controlling the PWM channels.
<source lang=c>
int sk = daqctl_connect("127.0.0.1", "--speed=5khz");
...
// Using 5khz from the adc sample rate as the PWM output frequency
daqctl_setoutput(dq.fd, 5, PWM_DUTY_PCT(20) | PWM_FREQ_IS_SAMPLERATE);
</source>


For sampling ADC to a CSV file:
PWM_FREQ_NOT_SAMPLERATE will return an output back to its grouped PWM frequency after PWM_FREQ_IS_SAMPLERATE has been set.
<source lang=bash>
<source lang=c>
daqctl -c 1-4 -P
daqctl_setoutput(dq.fd, 5, PWM_FREQ_NOT_SAMPLERATE);
</source>
</source>


Turn all 7 PWM channels (0-6) off (0% duty) (OUT1-OUT4 external and pins 23, 21, and 25 on the JTAG header) and set default PWM
== daqctl_process ==
<source lang=bash>
<source lang=c>int daqctl_process(struct daqctl *d); </source>
daqctl --pwm=0-6:0%@100hz
The daqctl_process will connect to the main daqctl server and get the latest samples from the outputs, as well as update the counters, quadrature, nsamples, and trigger any callbacks that may have been requested.  This will only sample the [[#Input Channels]] that have been specified in daqctl_connect.  The "d" argument is a pointer to the daqctl structure.  This should be cleared before first use, and the "fd" variable should be set with the result of daqctl_connect.
<source lang=c>
int sk;
struct daqctl dq;
bzero(&dq, sizeof(dq));
 
sk = daqctl_connect("127.0.0.1", "--chan=0-7");
assert(sk != 0);
 
// The daqctl structure needs the file descriptor for process calls
dq.fd = sk;
while(1) {
    daqctl_process(&dq);
    /* Process dq.last[0] through dq.last[7] for ADC values */
}
</source>
</source>


= Programming with libdaqctl =
== daqctl_flushreq ==
We provide a library for the end user called libdaqctl that you can use to interact with the daqctl TCP server.   
<source lang=c> int daqctl_flushreq(int sk); </source>
The flushreq call is an advanced call to clear the hardware FIFO for more advanced tuning of your applications sample timing.  This should not be needed for most applications.  See [[#Latency Tuning]] for more information.
 
== struct daqctl  ==
After calling daqctl_process, the daqctl structure will be updated with the latest values.
 
<source lang=c>int fd;</source>
The socket file descriptor returned by daqctl_connect;
 
<source lang=c>unsigned long long nsamples;</source>
Total number of samples taken
 
<source lang=c>long long last[16];</source>
This contains all of the input channels sampled as mapped [[#Input Channels|here]]. 
 
<source lang=c>unsigned short last_16bit[8];</source>
last_16bit[8] contains the result of a DSP low-pass IIR filter that uses multiple readings to attempt to interpolate an extra 4 bits for the ADC Values.
 
<source lang=c>unsigned long long counter[4]; </source>
This contains the counter values when enabled.  Write 0 to reset.
 
<source lang=c>long long quad[2];</source>
These are the latest quadrature values when sampling the quadrature channels.  Write 0 to reset.
 
<source lang=c>void (*posedge)(struct daqctl *, int);</source>
This will trigger a function call after executing daqctl_process with any new positive edges.  See [[#Counters and Callbacks|this]] section for an example of implementing this callback.
 
<source lang=c>void (*negedge)(struct daqctl *, int);</source>
This will trigger a function call after executing daqctl_process with any new negative edges.  See [[#Counters and Callbacks|this]] section for an example of implementing this callback.
 
<source lang=c>void (*analog)(struct daqctl *, int, int);</source>
This will trigger a function call after executing daqctl_process with any new analog valuesSee [[#Counters and Callbacks|this]] section for an example of implementing this callback.


[[ftp://ftp.embeddedarm.com/ts-arm-sbc/ts-7558-linux/sources/libdaqctl.c libdaqctl.c]]
<source lang=c>void (*digital)(struct daqctl *, long long);</source>
[[ftp://oz.embeddedarm.com/ts-arm-sbc/ts-7558-linux/sources/libdaqctl.h libdaqctl.h]]  
This will trigger a function call after executing daqctl_process with any new digital I/O values. See [[#Counters and Callbacks|this]] section for an example of implementing this callback.


This will allow simple control to receive callbacks, control the PWM, or take ADC samples.
<source lang=c>void (*digital_change)(struct daqctl *, long long, long long);</source>
This will trigger a function call after executing daqctl_process with any new digital I/O changes.  See [[#Counters and Callbacks|this]] section for an example of implementing this callback.


== 2 PWM frequencies and ADC sampling ==
= Example Code =
This example shows using the PWM frequency and the ADC sample frequency on different channels, as well as sampling ADC channels 1 and 2.
== ADC sampling ==
This example shows sampling all ADC channels.
<source lang=c>
<source lang=c>
#include <stdio.h>
#include <stdio.h>
#include <assert.h>
#include <strings.h>
#include <strings.h>


#include "libdaqctl.h"
#include "libdaqctl.h"
void daq_print(struct daqctl *dq)
{
        int i;
        for(i = 3; i < 5; i++)
        {
                printf("chan%i=%i\n", i, dq->last[i]);
        }
}


int main(int argc, char **argv)
int main(int argc, char **argv)
Line 110: Line 314:
         // the daqctl application here.  This example only samples  
         // the daqctl application here.  This example only samples  
         // ADC channels 4 and 5
         // ADC channels 4 and 5
         sk = daqctl_connect("127.0.0.1", "--chan=4-5");
         sk = daqctl_connect("127.0.0.1", "--chan=0-7");
        assert(sk != 0);


         // Set channel 0 to 1khz with a duty cycle of 500khz
         // The daqctl structure needs the file descriptor for process calls
         daqctl_setoutput(sk, 0, PWM_FREQ(1000) | PWM_DUTY(50));
         dq.fd = sk;


         // Set channel 1 to 2khz from ADC core
         for(;;){
        daqctl_setoutput(sk, 1, PWM_DUTY_PCT(30) | PWM_FREQ_IS_SAMPLERATE);
                daqctl_process(&dq);


        for(;;)
                for(i = 0; i < 8; i++) {
        {
                        printf("chan%i=%i\n", i, dq.last[i]);
                dq.fd = sk;
                 }
                daqctl_process(&dq);
                 daq_print(&dq);
         }
         }


close(sk);
         return 0;
         return 0;
}
</source>
=== High Speed ADC ===
This ADC can sample up to 200khz when it is only sampling one channel.  When coding for highest speed samples code should be careful to not output the values to places that will limit the speeds.  For example, using printf to display the values as they are available will limit the application to stdout.  Ideally the data can be interpreted as it comes in, or saved to memory and written to the disk in larger chunks.  This example code samples channel 3 as fast as possible and saves it in memory.
<source lang=c>
#include <stdio.h>
#include <assert.h>
#include <strings.h>
#include <sys/time.h>
#include "libdaqctl.h"
#define TEST_SECONDS 10
// Maximum samples is 200khz, but in between a daqctl_process there can be other
// delays from other processes sbus contention so more samples can be returned
// for a single second
int samples[250000 * TEST_SECONDS];
void analog_sample(struct daqctl *dq, int channel, int value)
{
samples[(int)dq->nsamples] = value;
}
int main(int argc, char **argv)
{
int sk;
int i;
struct daqctl dq;
bzero(&dq, sizeof(dq));     
struct timeval start, end;
long elapsed = 0;
sk = daqctl_connect("127.0.0.1", "--chan=3 --speed=200khz");
assert(sk != 0);
// The daqctl structure needs the file descriptor for process calls
dq.fd = sk;
// Set callback for analog values
dq.analog = analog_sample;
gettimeofday(&start, NULL);
for(;;){
gettimeofday(&end, NULL);
elapsed = (end.tv_sec - start.tv_sec) * 1000000 +
  end.tv_usec - start.tv_usec;
if(elapsed > (1000000 * TEST_SECONDS)) break;
daqctl_process(&dq);
}
printf("samples per second: %0.2lf\n", ((long double)dq.nsamples)/TEST_SECONDS);
/*for (i = 0; i <dq.nsamples; i++)
{
printf("%d\n", samples[i]);
}*/
close(sk);
return 0;
}
}
</source>
</source>


== Controlling a Servo ==
== Controlling a Servo ==
In this example I've wired 5v in to OUT1+ (from JTAG pin 26 on the TS-7558), as well as the servo's + wire. OUT- and servo- are connected, and the servo's GND is plugged into one of the ground connectors in P2. The desired behavior is that the servo will rotate back and forth between maximum and minimum and reverting to neutral when a ctrl+c / SIGINT is sent to the application.  
This example controls a non-continuous DC servo motor with a TS-7558.
* 5V is connected to OUT1+ (JTAG pin 26)
* servo's + wire is connected to OUT1+
* servo's signal (or -) wire is connected to OUT1-
* servo's ground is connected to Ground on P2
 
The desired behavior is that the servo will rotate back and forth between maximum and minimum and reverting to neutral when a ctrl+c / SIGINT is sent to the application.  


<source lang=c>
<source lang=c>
Line 191: Line 461:


== Controlling an LED ==
== Controlling an LED ==
This example toggles the brightness of an LED by toggling power fed from an external source.
This example controls the brightness of an LED by toggling power fed from an external source.
* OUT1+ is connected to the LED -
* OUT- is connected to the external power -
* External power + is connected to LED +
 
This will cause the LED to slowly pulse on and off.


<source lang=c>
<source lang=c>
Line 261: Line 536:
</source>
</source>


= FPGA Core =
== Counters and Callbacks ==
== Register Map ==
This example has the board react to a counter value, a DIO, and it sets up callbacks for various I/O.
{| class="wikitable"
 
|-
<source lang=c>
! Address
#include <stdio.h>                 
! Description
#include <string.h>                 
! Access
#include <assert.h>                 
! Notes
#include <signal.h>                 
|-
#include "libdaqctl.h"                 
| Base + 0
 
| Channel enable #15 (MSB) through #0 (LSB)
void negedge(struct daqctl *dq, int pin) {
| Read/Write
        printf("%d: negedge on pin %d\n", (int)dq->nsamples, pin);
| 1 to enable
}
|-
 
| Base + 2
void posedge(struct daqctl *dq, int pin) {
| Current state
        printf("%d: posedge on pin %d\n", (int)dq->nsamples, pin);
| Read/Write
}
|  
 
void analog(struct daqctl *dq, int chan, int val) {
        printf("%d: analog val %x (16bit: %x) on chan %d\n", (int)dq->nsamples, val, dq->last_16bit[chan], chan);
}
 
void digital(struct daqctl *dq, int val) {
        printf("%d: digital val %x\n", (int)dq->nsamples, val);
}
 
void digital_change(struct daqctl *dq, int val, int chg) {
        printf("%d: digital change %x\n", (int)dq->nsamples, chg);
}
                         
static int alarmed;               
void alarmsig(int x) { alarmed = 1; };
 
int main(void) {
        int sk;
        struct daqctl dq;
        int n, o = 0;       
        sk = daqctl_connect("127.0.0.1",             
          "--chan=4-7,10-14 --gain2x=4 --cloop=2-3 --speed=5khz"
          " --irq-on-any-change --max-irq-rate=4 --min-irq-rate=500hz"
          " --pwm=0-6:0%@100hz");             
        assert(sk != -1);     
 
        bzero(&dq, sizeof(dq));       
        dq.fd = sk;         
        dq.negedge = negedge;     
        dq.posedge = posedge;     
        dq.analog = analog;     
        dq.digital = digital;     
        dq.digital_change = digital_change;
        alarm(10);                 
        signal(SIGALRM, alarmsig);
 
        for (;!alarmed;) { 
                n = daqctl_process(&dq);     
               
                if (n <= 0) {
                        fprintf(stderr, "EOF!\n");
                        break; 
                }         
                         
                /* Set PWM output #3 (OUT3) according to last sample of
                * chan #4 (AD1) */
                daqctl_setoutput(sk, 3, PWM_DUTY(dq.last[4]));
                                                                                                                                                                                                                                                                             
                /* Set PWM output #0 according DIO input #0 */
                if ((dq.last[10] & 0x1) != o) {
                        o = dq.last[10] & 0x1;
 
                        if (o) daqctl_setoutput(sk, 0, 1); /* 100% */
                        else daqctl_setoutput(sk, 0, 0); /* 0% */
                }           
                                                                                                                                                                                                                                                                             
                /* If counter for input #0 is greater than 10, set output
                * 1 and reset counter.  If counter for input #1 is greater
                * than 20, reset both counters and clear output 1 */
                if (dq.counter[0] > 10) { /* read and reset counter #0 */
                        dq.counter[0] = 0;
                        daqctl_setoutput(sk, 1, 1);
}         
                         
                if (dq.counter[1] > 20) {
                        dq.counter[0] = dq.counter[1] = 0;
                        daqctl_setoutput(sk, 1, 0);
                }         
}                         
        close(sk);                 
 
        printf("nsamples: %lld in 10 seconds\n", dq.nsamples);
        return 0;           
}
</source>
 
== Quadrature ==
This example uses a Sick DKV60 encoder with a [[TS-7520-BOX]].  The A- is connected to LS_00, B- is connected to LS_01.  This prints out the value whenever there is a change.
 
<source lang=c>
#include <stdio.h>
#include <assert.h>
#include <strings.h>
#include "libdaqctl.h"
int main(int argc, char **argv)
{
        int sk;
        int i;
long long oldquad = 0;
        struct daqctl dq;
        bzero(&dq, sizeof(dq));
 
      sk = daqctl_connect("127.0.0.1", "--mux-quad0a=0 --mux-quad0b=1 --chan=8");
        assert(sk != 0);
        // The daqctl structure needs the file descriptor for process calls
        dq.fd = sk;
        for(;;){
                daqctl_process(&dq);
                if(dq.quad[0] != oldquad) {
printf("quad:%lld\n", dq.quad[0]);
oldquad = dq.quad[0];
}
        }
close(sk);
        return 0;
}
</source>
 
= Advanced =
== TCP Interface ==
The 'daqctl --server' interface opens up port 7700 which can be used for communication remotely, or as generic IPC on the board.  This is also a simple interface for programming with the DAQ core from a language that is not compatible with the C library.
 
For connecting, we still recommend calling 'daqctl' itself as the library does.  This allows you to pass daqctl arguments as daqctl_connect from the C interfaces does. 
<source lang=bash>
# You can omit --connect if it will always be localhost
daqctl --connect=127.0.0.1 --chan=4-7,10-14 --gain2x=4 --cloop=2-3 --speed=5khz
</source>
 
Make sure that you check the return value of daqctl to verify that the arguments are valid.
 
{{Note|Calling daqctl --connect will reset the TCP connection.}}
 
Now open the port 7700 to control the DAQ core.
 
=== Outputs ===
PWM is controlled by writing "O" which specifies an output, followed by the PWMSPEC value.  This must be terminated by a null at the end of the string.  You can see a list of examples of this [[#PWMSPEC|here]].


{| class="wikitable"
For example, you can write the message to the TCP port:
|-
  O2:33%@1Khz
! Bit
! Access
! Description
|-
| 15
| Read/Write
| Sampling Enable
|-
| 14
| Read Only
| Overflow (clears after read)
|-
| 13
| Read/Write
| Interrupt mood (1 is interested)
|-
| 12
| N/A
| Reserved
|-
| 11-0
| Read Only
| Next buffer num to fill (0-4095 for 8kbyte total buffer)
|}


|-
Or with bash utilities:
| Base + 4
<source lang=bash>
| Sample Period
echo -n "O3:0X" | tr X '\000' | nc localhost 7700
| Read/Write
</source>
|


{| class="wikitable"
This will set output pin 2 to a duty cycle of 33% at 1khz.  The change will take effect immediately in the hardware.
|-
! Bit
! Access
! Description
|-
| 15
| Read/Write
| Timescale (1 is ms, 0 is µs)
|-
| 14-0
| Read/Write
| value (4096 is 4097µs period)
|}


|-
=== Sample Format ===
| Base + 6
If you set up daqctl to sample any channels it will continuously output them in a simple format.  Each packet is 16 bits:
| Misc
| Read/Write
|


{| class="wikitable"
{|class=wikitable
|-
! Bits
! Bit
! Access
! Description
! Description
|-
|-
| 15
| 15-12
| Read/Write
| Channel
| External sample trigger enable
|-
| 14-12
| Read/Write
| External trigger input number (0-7) (triggers on posedge)
|-
| 11-8
| Read/Write
| Current loop enable on chans #3 (MSB) - #0 (LSB)
|-
|-
| 7-0
| 0-11
| Read/Write
| 12 bit reading
| Gain 2x enable on chans #7 (MSB) to #0 (LSB)
|}
|}


|-
Any changes to the sample rate or channel selection should be done through the 'daqctl' binary itself.
| Base + 8
 
| Interest criteria
Channel 10 on the TS-7520 and TS-7580 is special since there are up to 56 bits of input and normally only 12 bits of space for the values.  When the sampling channel gets to 10 it writes multiple repeated samples claiming to be channel 10.  Each sample contains only 11 bits of data instead of 12.  Bit 11 will be set until all of the digital inputs values have been sent.  To read all of the digital inputs you should continue to left shift 11 bits of input data until it sees a channel 10 sample with bit 11 cleared.
| Read/Write
|


{| class="wikitable"
Channel 15 normally sets any bit as a 1 if it detects during the course of the immediately preceding sample period any momentary change of a digital input  However if the external trigger is enabled this returns the latched value of a 12-bit up-counter that counts up at the rate of the sample period as programmed in register base + 0x4. When used for the glitch reg functionality, it behaves in a similar way to use channel 10 for input pins > 11.
|-
! Bit
! Description
|-
| 15
| When any digital input changes
|-
| 14
| When any enabled counters change
|-
| 13
| When a quadrature counter changes direction
|-
| 12
| When a quadrature counter changes value
|-
| 11
| When one particular digital input
|-
| 10-9
| 0 - Changes State  
1 - Glitches (a change >= 25ns but <= sample period)  <br />
2 - Is high  <br />
3 - Is low
|-
| 8-5
| If bit 14 is enabled, which digital input? (0-7)
|-
| 4-3
| 0 - 1 sample   
1 - 4 samples <br />
2 - 16 samples <br />
3 - 64 samples
|-
| 2
| Interrupt rate duration 
0 - Until interest criteria is no longer met <br />
1 - Forever  (requires reset via write to bit 13, reg 0x2) <br />
|-
| 1-0
| Interrupt when not interested 
0 - When buffer is half full <br />
1 - Every 1ms <br />
2 - Every 2ms <br />
3 - Every 10ms <br />
|}


|-
You can use libdaqctl.c's daqctl_process() as an example implementation.
| Base + 10
| Sample Period
| Read/Write
| Return core type (0x7558 or 0x1978)
|-
| Base + 12
| PWM output controller
| Read/Write
|
|-
| Base + 14
| Current state of all inputs
| Read Only
|
|}

Latest revision as of 16:31, 17 January 2022

Overview

The DAQ core is a core used on multiple products such as the TS-7520-BOX, TS-7558-BOX, TS-7580-BOX, and can be ported to our other products. This core facilitates ADC, DIO, quadrature, and counter sampling, as well as DIO outputs, PWM, and optionally VFD support. The hardware is accessed using daqctl.

Usage

Help

 Usage: daqctl [OPTION] ...
 Technologic Systems ADC-75xx core userspace driver utility.
 Example: daqctl --server --speed=2.5khz --chan=0-3,5,7 --gain2x=5
 
   -i, --irq=N            Use IRQ N as ADC IRQ (30)
   -q, --stats            Print server daemon stats
   -s, --speed=FREQ       Use FREQ as default sample frequency (1000)
   -c, --chan=CHANS       Sample only channels CHANS (0-15)
   -x, --exttrig=PIN      Ignore --speed and use external trigger instead
   -g, --gain2x=CHANS     Use high-gain setting on CHANS (TS-7558 only)
   -g, --gain6x=CHANS     Use high-gain setting on CHANS
   -l, --cloop=CHANS      Enable 4-20 ma current loops on CHANS
   -d, --server           Daemonize and run as server
   -I, --bind=IPADDR      Bind server to IPADDR
   -p, --connect=HOST     Connect to remote daqctl service at HOST
   -P, --dumpcsv          Output acquired samples as CSV text
   -b, --dumpbin          Output acquired samples as raw binary
   -1, --single           Print one sample's values
   -D, --pwm=PWMSPEC      Sets PWM outputs according to PWMSPEC
   -h, --help             This help
 
   --irq-on-change=PIN    Flush when digital input PIN (0-56) changes
   --irq-on-glitch=PIN    Flush when digital input PIN (0-56) glitches
   --irq-on-high=PIN      Flush while digital input PIN (0-56) is high
   --irq-on-low=PIN       Flush while digital input PIN (0-56) is low
   --irq-on-quadrature    Flush when a quadrature counter changes value
   --irq-on-quad-dir      Flush when quadrature changes direction
   --irq-on-counter       Flush when a monitored edge counter changes
   --irq-on-any-change    Flush when any digital input changes
   --max-irq-rate=NSAM    IRQ at most every NSAM (1,4,16,64) samples
   --max-irq-rate-always  Always interrupt at max rate
   --min-irq-rate=HZ      Minimum IRQ rate HZ (1000,500,100,0)
   --min-irq-rate-always  Always interrupt at min rate
 
   --mux-counter0=PIN     Use digital input PIN (0-56) for counter#0
   --mux-counter1=PIN     Use digital input PIN (0-56) for counter#1
   --mux-counter2=PIN     Use digital input PIN (0-56) for counter#2
   --mux-counter3=PIN     Use digital input PIN (0-56) for counter#3
   --mux-quad0a=PIN       Use digital input PIN (0-56) for quadrature#0 A
   --mux-quad0b=PIN       Use digital input PIN (0-56) for quadrature#0 B
   --mux-quad1a=PIN       Use digital input PIN (0-56) for quadrature#1 A
   --mux-quad1b=PIN       Use digital input PIN (0-56) for quadrature#1 B
 
 When run as a server, default is to listen at TCP port 7700
 
 The --dumpbin option will send 16-bit binary samples to stdout
 where the 4 MSBs are the channel number and the 12 LSBs is the
 sample value.  This is the same raw format as the port 7700 TCP
 socket interface.
 
 The --mux-* options are valid only for TS-7520/TS-7580.  The TS-7558 does
 not support changing these pins from their defaults.
 
 The PWMSPEC format is CHANS:DUTY%[@FREQ].  Examples: "0-3:57%@100hz",
 "1,3:33.33%@20ms", "1:0x800" (literal 12-bit), or "5:5%@1.81khz"

ADC

The ADC core allows you to sample different voltage ranges, and current loops. The hardware has an 8KB FIFO to contain the latest values. After 4kb has been written the hardware core will assert an IRQ, and daqctl will pull the latest samples.

To sample all ADC channels at 100hz to a CSV file:

daqctl --dumpcsv --chan=0-7 --speed=100hz

This will return the values from 0-4095 to represent the actual voltage. Refer to your board manual for the ADC ranges, and for more information on --gain2x and --gain6x. The ADC can sample based off of either the internally configurable sample rate, or by using one of the digital inputs as an external trigger. To sample off of input channel 2:

daqctl --dumpcsv --chan=0-7 --exttrig=2

You can also read ADC as a current loop between 4mA to 20mA on all of the external ADC channels using the --cloop option.

# Sample channels 0-7.
# 4-7 will use a current loop to read 0-20mA
# 0-3 will use the default voltage range for the board
daqctl --cloop=4-7 --chan=0-7

PWM

The PWM values are defined by the #PWMSPEC. This allows you to set the duty cycle and frequency from the command line, or in C. Every channel has it's own duty cycle, but the frequencies are grouped. Every 7 DIO will their own PWM frequency, (outputs 0-6, 7-13, 14-20, ...). Changing the frequency for any output channel will change it for the group.

Turn all 8 PWM channels to 30% at 5khz:

daqctl --pwm=0-7:30%@5khz

You can also use the ADC sample frequency as the PWM frequency.

daqctl --pwm=6:30%@EXT0

PWMSPEC

Our PWM core will accept the PWMSPEC format as a simple way to control it. For example:

  • "0:33%" - Set duty to 33%, preserving frequency
  • "0:33%@100" - Set duty to 33%, frequency at 100Hz
  • "0:33%@1Khz" - Set duty to 33%, frequency at 1Khz
  • "0:0x1230@100" - 0x123 is the 12-bit duty pct (0x123/0x4096)
  • "0:2048" - Preserves the frequency if just setting the duty cycle
  • "0:50%@EXT0" - 50% duty, uses external trig #0 (ADC sample rate)
  • "0:33.333%@100" - Can parse decimal also
  • "0:50%@100us" - and period specs rather than frequency
  • "0-3:50%" - Can take multiple channels
  • "0,2,5-6:50%" - Can accept multiple channel ranges.

Input Channels

Every board that implements the DAQ core uses the same channel structure for sample data. When using daqctl these are the channels specified with --chan, and correspond with the libdaqctl structure's last[16] variable.

Channel Description
0 ADC Channel 0
1 ADC Channel 1
2 ADC Channel 2
3 ADC Channel 3
4 ADC Channel 4
5 ADC Channel 5
6 ADC Channel 6
7 ADC Channel 7
8 Quadrature counter delta #0
9 Quadrature counter delta #1
10 Current state of all digital inputs
11 Counter delta #0
12 Counter delta #1
13 Counter delta #2
14 Counter delta #3
15 Glitch reg or timestamp

Programming with libdaqctl

We provide a library for the end user called libdaqctl that you can use to interact with the daqctl TCP server.

libdaqctl.c libdaqctl.h

This will allow simple control to receive callbacks, control the PWM, or take ADC samples. The following calls are available in libdaqctl:

daqctl_connect

int daqctl_connect(char *hostspec, char *args);

Usually this call only needs to be made once. Hostspec is the tcp server to connect to where it can find the daqctl server on port 7700. This allows the libdaqctl application to reside on another system on the same network as the daqctl server. The args variable takes the same arguments to daqctl. This is where you can specify which pins to mux for quadrature or counters, set the sample rate, enable gain2x/gain6x on ADC channels, and more. A complete list of arguments is available here.

The return value is the file descriptor to the socket which should be placed in the daqctl structure to 'fd'.

daqctl_pwmspec

int daqctl_pwmspec(int sk, char *spec);

This function is used to set the value of an output using a pwmspec string. The sk argument is the file descriptor returned by daqctl_connect. The spec argument is a null terminated char array that describes one or more PWM outputs. For example, to set output 6 to a 30% duty cycle at 20hz:

daqctl_pwmspec(dq.fd, "6:30%@20hz");

daqctl_setoutput

int daqctl_setoutput(int sk, int pin, unsigned int val);
/* Max frequency = 131071 hz,  Max duty = 100% (0xfff) */
#define PWM_DUTY_PCT(x)	((0xfff * (x) / 100 ) << 1)
#define PWM_DUTY(x) ((x) << 1)
#define PWM_FREQ(x) ((x) << 13)
#define PWM_FREQ_IS_SAMPLERATE (1<<30)
#define PWM_FREQ_NOT_SAMPLERATE (3<<30)

Similar to daqctl_pwmspec, this will define the frequency and duty cycle of one pin. The sk argument is the file descriptor returned by daqctl_connect. The pin refers to the output channel to control. The output channels are mapped on the specific product (TS-7520, TS-7580, or OUT1-8 on the TS-7558 will map to pin 0-7. The value can be set to 0 to set low, or 1 to set high. The value can also be set using on of the convenience functions.

PWM_DUTY_PCT allows you to pass the duty cycle as a percent. For example:

// Set 30% duty cycle
daqctl_setoutput(dq.fd, 5, PWM_DUTY_PCT(30));

PWM_DUTY accepts a value of 0-4095 to control the duty cycle.

// iterate through all duty cycle settings:
int i;
for(i=0; i < 4095; i++) {
     daqctl_setoutput(dq.fd, 5, PWM_DUTY(i));
     usleep(10000);
}

PWM_FREQ allows you to specify the frequency of a port specified in hz. The core allows each 8 DIO ids to have one PWM frequency.

// channel 5 (0-7) to 2000hz
daqctl_setoutput(dq.fd, 5, PWM_FREQ(2000));

// You can also set a frequency and PWM_DUTY or PWM_DUTY_PCT at the same time:
daqctl_setoutput(dq.fd, 5, PWM_FREQ(1000) | PWM_DUTY_PCT(20));

PWM_FREQ_IS_SAMPLERATE will use the ADC sample rate (specified by --speed) as the frequency for an output channel. Multiple channels can use this frequency. While normally PWM frequencies are set in groups of 8, this frequency can apply to a single output channel without affecting the group.

int sk = daqctl_connect("127.0.0.1", "--speed=5khz");
...
// Using 5khz from the adc sample rate as the PWM output frequency
daqctl_setoutput(dq.fd, 5, PWM_DUTY_PCT(20) | PWM_FREQ_IS_SAMPLERATE);

PWM_FREQ_NOT_SAMPLERATE will return an output back to its grouped PWM frequency after PWM_FREQ_IS_SAMPLERATE has been set.

daqctl_setoutput(dq.fd, 5, PWM_FREQ_NOT_SAMPLERATE);

daqctl_process

int daqctl_process(struct daqctl *d);

The daqctl_process will connect to the main daqctl server and get the latest samples from the outputs, as well as update the counters, quadrature, nsamples, and trigger any callbacks that may have been requested. This will only sample the #Input Channels that have been specified in daqctl_connect. The "d" argument is a pointer to the daqctl structure. This should be cleared before first use, and the "fd" variable should be set with the result of daqctl_connect.

int sk;
struct daqctl dq;
bzero(&dq, sizeof(dq));

sk = daqctl_connect("127.0.0.1", "--chan=0-7");
assert(sk != 0);

// The daqctl structure needs the file descriptor for process calls
dq.fd = sk;
while(1) {
     daqctl_process(&dq);
     /* Process dq.last[0] through dq.last[7] for ADC values */
}

daqctl_flushreq

 int daqctl_flushreq(int sk);

The flushreq call is an advanced call to clear the hardware FIFO for more advanced tuning of your applications sample timing. This should not be needed for most applications. See #Latency Tuning for more information.

struct daqctl

After calling daqctl_process, the daqctl structure will be updated with the latest values.

int fd;

The socket file descriptor returned by daqctl_connect;

unsigned long long nsamples;

Total number of samples taken

long long last[16];

This contains all of the input channels sampled as mapped here.

unsigned short last_16bit[8];

last_16bit[8] contains the result of a DSP low-pass IIR filter that uses multiple readings to attempt to interpolate an extra 4 bits for the ADC Values.

unsigned long long counter[4];

This contains the counter values when enabled. Write 0 to reset.

long long quad[2];

These are the latest quadrature values when sampling the quadrature channels. Write 0 to reset.

void (*posedge)(struct daqctl *, int);

This will trigger a function call after executing daqctl_process with any new positive edges. See this section for an example of implementing this callback.

void (*negedge)(struct daqctl *, int);

This will trigger a function call after executing daqctl_process with any new negative edges. See this section for an example of implementing this callback.

void (*analog)(struct daqctl *, int, int);

This will trigger a function call after executing daqctl_process with any new analog values. See this section for an example of implementing this callback.

void (*digital)(struct daqctl *, long long);

This will trigger a function call after executing daqctl_process with any new digital I/O values. See this section for an example of implementing this callback.

void (*digital_change)(struct daqctl *, long long, long long);

This will trigger a function call after executing daqctl_process with any new digital I/O changes. See this section for an example of implementing this callback.

Example Code

ADC sampling

This example shows sampling all ADC channels.

#include <stdio.h>
#include <assert.h>
#include <strings.h>

#include "libdaqctl.h"

int main(int argc, char **argv)
{
        int sk;
        int i;
        struct daqctl dq;
        bzero(&dq, sizeof(dq));

        // You can pass any arguments as you could to 
        // the daqctl application here.  This example only samples 
        // ADC channels 4 and 5
        sk = daqctl_connect("127.0.0.1", "--chan=0-7");
        assert(sk != 0);

        // The daqctl structure needs the file descriptor for process calls
        dq.fd = sk;

        for(;;){
                daqctl_process(&dq);

                for(i = 0; i < 8; i++) {
                        printf("chan%i=%i\n", i, dq.last[i]);
                }
        }

	close(sk);
        return 0;
}

High Speed ADC

This ADC can sample up to 200khz when it is only sampling one channel. When coding for highest speed samples code should be careful to not output the values to places that will limit the speeds. For example, using printf to display the values as they are available will limit the application to stdout. Ideally the data can be interpreted as it comes in, or saved to memory and written to the disk in larger chunks. This example code samples channel 3 as fast as possible and saves it in memory.

#include <stdio.h>
#include <assert.h>
#include <strings.h>
#include <sys/time.h>
#include "libdaqctl.h"
 
#define TEST_SECONDS 10

// Maximum samples is 200khz, but in between a daqctl_process there can be other
// delays from other processes sbus contention so more samples can be returned
// for a single second
int samples[250000 * TEST_SECONDS];

void analog_sample(struct daqctl *dq, int channel, int value)
{
	samples[(int)dq->nsamples] = value;
}

int main(int argc, char **argv)
{
	int sk;
	int i;
	struct daqctl dq;
	bzero(&dq, sizeof(dq));      
	struct timeval start, end;
	long elapsed = 0;

	sk = daqctl_connect("127.0.0.1", "--chan=3 --speed=200khz");
	assert(sk != 0);

	// The daqctl structure needs the file descriptor for process calls
	dq.fd = sk;

	// Set callback for analog values
	dq.analog = analog_sample;

	gettimeofday(&start, NULL);
	for(;;){
		gettimeofday(&end, NULL);
		elapsed = (end.tv_sec - start.tv_sec) * 1000000 +
		  end.tv_usec - start.tv_usec;
		if(elapsed > (1000000 * TEST_SECONDS)) break;
		daqctl_process(&dq);
	}
	printf("samples per second: %0.2lf\n", ((long double)dq.nsamples)/TEST_SECONDS);


	/*for (i = 0; i <dq.nsamples; i++)
	{
		printf("%d\n", samples[i]);
	}*/

	close(sk);
	return 0;
}

Controlling a Servo

This example controls a non-continuous DC servo motor with a TS-7558.

  • 5V is connected to OUT1+ (JTAG pin 26)
  • servo's + wire is connected to OUT1+
  • servo's signal (or -) wire is connected to OUT1-
  • servo's ground is connected to Ground on P2

The desired behavior is that the servo will rotate back and forth between maximum and minimum and reverting to neutral when a ctrl+c / SIGINT is sent to the application.

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <signal.h>
#include "libdaqctl.h"

int sigint_rec = 0;

void sigint_cb(int sig);

int main(int argc, char **argv)
{
	char *host;
	if(argc < 2)
		host = "127.0.0.1";
	else
		host = argv[1];

	int daqfd = daqctl_connect(
		host,
		"--pwm=0:100%@20ms");
	assert(daqfd != 0);

	signal(SIGINT, sigint_cb);

	for(;;)
	{
		printf("Maximum\n");
		daqctl_setoutput(daqfd, 0, PWM_DUTY(0x199)); // 2ms
		sleep(1);

		if(sigint_rec)
			break;

		printf("Minimum\n");
		daqctl_setoutput(daqfd, 0, PWM_DUTY(0x51)); // 1ms
		sleep(1);

		if(sigint_rec)
			break;
	}

	printf("Neutral\n");
	daqctl_setoutput(daqfd, 0, PWM_DUTY(0xF5)); // 1.5ms
	sleep(1);

	close(daqfd);

	return 0;
}

void sigint_cb(int sig)
{
	sigint_rec = 1;
}

Controlling an LED

This example controls the brightness of an LED by toggling power fed from an external source.

  • OUT1+ is connected to the LED -
  • OUT- is connected to the external power -
  • External power + is connected to LED +

This will cause the LED to slowly pulse on and off.

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <signal.h>
#include <limits.h>
#include "libdaqctl.h"
 
int sigint_rec = 0;

void sigint_cb(int sig);
 
int main(int argc, char **argv)
{
        unsigned int i = 0;
        char *host;
        int dir = 0;

        if(argc < 2)
                host = "127.0.0.1";
        else
                host = argv[1];
 
        int daqfd = daqctl_connect(
                host,
                "--pwm=0:100%@120hz");
        assert(daqfd != 0);

        signal(SIGINT, sigint_cb);
 
        daqctl_setoutput(daqfd, 0, PWM_DUTY_PCT(0)); // Off
 
        for(;;)
        {
                const int step = 20;

                // This will use the default PWM frequency
                // set in the connect call
                daqctl_setoutput(daqfd, 0, PWM_DUTY(i));

                printf("%i@120hz\n", i);

                if(i == 0 || i + step >= 4095)
                        dir = !dir;

                if(dir)
                        i += step;
                else
                        i -= step;

                if(sigint_rec)
                        break;

                usleep(10);
        }
 
        daqctl_setoutput(daqfd, 0, PWM_DUTY(0)); // Off
        close(daqfd);
 
        return 0;
}
 
void sigint_cb(int sig)
{
        sigint_rec = 1;
}

Counters and Callbacks

This example has the board react to a counter value, a DIO, and it sets up callbacks for various I/O.

#include <stdio.h>                  
#include <string.h>                  
#include <assert.h>                  
#include <signal.h>                  
#include "libdaqctl.h"                  

void negedge(struct daqctl *dq, int pin) {
        printf("%d: negedge on pin %d\n", (int)dq->nsamples, pin);
}

void posedge(struct daqctl *dq, int pin) {
        printf("%d: posedge on pin %d\n", (int)dq->nsamples, pin);
}

void analog(struct daqctl *dq, int chan, int val) {
        printf("%d: analog val %x (16bit: %x) on chan %d\n", (int)dq->nsamples, val, dq->last_16bit[chan], chan);
}

void digital(struct daqctl *dq, int val) {
        printf("%d: digital val %x\n", (int)dq->nsamples, val);
}

void digital_change(struct daqctl *dq, int val, int chg) {
        printf("%d: digital change %x\n", (int)dq->nsamples, chg);
}
                          
static int alarmed;                
void alarmsig(int x) { alarmed = 1; };

int main(void) {
        int sk;
        struct daqctl dq;
        int n, o = 0;        
        sk = daqctl_connect("127.0.0.1",              
          "--chan=4-7,10-14 --gain2x=4 --cloop=2-3 --speed=5khz"
          " --irq-on-any-change --max-irq-rate=4 --min-irq-rate=500hz"
          " --pwm=0-6:0%@100hz");               
        assert(sk != -1);      

        bzero(&dq, sizeof(dq));        
        dq.fd = sk;           
        dq.negedge = negedge;      
        dq.posedge = posedge;      
        dq.analog = analog;       
        dq.digital = digital;      
        dq.digital_change = digital_change;
        alarm(10);                   
        signal(SIGALRM, alarmsig);

        for (;!alarmed;) {  
                n = daqctl_process(&dq);       
                
                if (n <= 0) {
                        fprintf(stderr, "EOF!\n");
                        break;   
                }           
                           
                /* Set PWM output #3 (OUT3) according to last sample of
                * chan #4 (AD1) */
                daqctl_setoutput(sk, 3, PWM_DUTY(dq.last[4]));
                                                                                                                                                                                                                                                                               
                /* Set PWM output #0 according DIO input #0 */
                if ((dq.last[10] & 0x1) != o) {
                        o = dq.last[10] & 0x1;

                        if (o) daqctl_setoutput(sk, 0, 1); /* 100% */
                        else daqctl_setoutput(sk, 0, 0); /* 0% */
                }             
                                                                                                                                                                                                                                                                               
                /* If counter for input #0 is greater than 10, set output
                * 1 and reset counter.  If counter for input #1 is greater
                * than 20, reset both counters and clear output 1 */
                if (dq.counter[0] > 10) { /* read and reset counter #0 */
                        dq.counter[0] = 0;
                        daqctl_setoutput(sk, 1, 1);
}           
                          
                if (dq.counter[1] > 20) {
                        dq.counter[0] = dq.counter[1] = 0;
                        daqctl_setoutput(sk, 1, 0);
                }           
}                          
        close(sk);                   

        printf("nsamples: %lld in 10 seconds\n", dq.nsamples);
        return 0;            
}

Quadrature

This example uses a Sick DKV60 encoder with a TS-7520-BOX. The A- is connected to LS_00, B- is connected to LS_01. This prints out the value whenever there is a change.

#include <stdio.h>
#include <assert.h>
#include <strings.h>
 
#include "libdaqctl.h"
 
int main(int argc, char **argv)
{
        int sk;
        int i;
	long long oldquad = 0;
        struct daqctl dq;
        bzero(&dq, sizeof(dq));

      	sk = daqctl_connect("127.0.0.1", "--mux-quad0a=0 --mux-quad0b=1 --chan=8");
        assert(sk != 0);
 
        // The daqctl structure needs the file descriptor for process calls
        dq.fd = sk;
 
        for(;;){
                daqctl_process(&dq);
                if(dq.quad[0] != oldquad) { 
			printf("quad:%lld\n", dq.quad[0]);
			oldquad = dq.quad[0];
		}
        }
 
	close(sk);
        return 0;
}

Advanced

TCP Interface

The 'daqctl --server' interface opens up port 7700 which can be used for communication remotely, or as generic IPC on the board. This is also a simple interface for programming with the DAQ core from a language that is not compatible with the C library.

For connecting, we still recommend calling 'daqctl' itself as the library does. This allows you to pass daqctl arguments as daqctl_connect from the C interfaces does.

# You can omit --connect if it will always be localhost
daqctl --connect=127.0.0.1 --chan=4-7,10-14 --gain2x=4 --cloop=2-3 --speed=5khz

Make sure that you check the return value of daqctl to verify that the arguments are valid.

Note: Calling daqctl --connect will reset the TCP connection.

Now open the port 7700 to control the DAQ core.

Outputs

PWM is controlled by writing "O" which specifies an output, followed by the PWMSPEC value. This must be terminated by a null at the end of the string. You can see a list of examples of this here.

For example, you can write the message to the TCP port:

 O2:33%@1Khz

Or with bash utilities:

echo -n "O3:0X" | tr X '\000' | nc localhost 7700

This will set output pin 2 to a duty cycle of 33% at 1khz. The change will take effect immediately in the hardware.

Sample Format

If you set up daqctl to sample any channels it will continuously output them in a simple format. Each packet is 16 bits:

Bits Description
15-12 Channel
0-11 12 bit reading

Any changes to the sample rate or channel selection should be done through the 'daqctl' binary itself.

Channel 10 on the TS-7520 and TS-7580 is special since there are up to 56 bits of input and normally only 12 bits of space for the values. When the sampling channel gets to 10 it writes multiple repeated samples claiming to be channel 10. Each sample contains only 11 bits of data instead of 12. Bit 11 will be set until all of the digital inputs values have been sent. To read all of the digital inputs you should continue to left shift 11 bits of input data until it sees a channel 10 sample with bit 11 cleared.

Channel 15 normally sets any bit as a 1 if it detects during the course of the immediately preceding sample period any momentary change of a digital input However if the external trigger is enabled this returns the latched value of a 12-bit up-counter that counts up at the rate of the sample period as programmed in register base + 0x4. When used for the glitch reg functionality, it behaves in a similar way to use channel 10 for input pins > 11.

You can use libdaqctl.c's daqctl_process() as an example implementation.