Daqctl

From embeddedTS Manuals

Overview

daqctl is the userspace application to manage the data acquisition core in the FPGA. This facilitates PWM, counters, digital inputs/outputs, and 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.

Upon reaching the end of the 8Kbyte buffer, the address is wrapped back to zero and sampling continues. Software must be able to keep up and read samples before the address wraps around and old samples are overwritten. There will be an irq every 4kb written to the external memory. The IRQ is cleared and re-armed upon reading of the current state reg.

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.

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" - should 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.

The core allows you to set 2 different frequencies. One is dedicated to the PWM timer which will support up to 131071hz. You can also use the ADC sample rate. This core also supports using external triggers from the digital inputs (1-4).

Note: If you change any timer for one channel it will change it for any channels currently using it as well.

Input Channels

Every board that implements the DAQ core uses the same channel structure for sample data.

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 inputs
11 Counter delta #0
12 Counter delta #1
13 Counter delta #2
14 Counter delta #3
15 Glitch reg or timestamp

These inputs will be very similar on most boards, but some inputs may be muxed to provide access to more data. Using libdaqctl will abstract this from you.

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"

Examples

Even without writing code, you can do simple operations like sampling ADC or controlling the PWM channels.

For sampling ADC to a CSV file:

daqctl -c 1-4 -P

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 --pwm=0-6:0%@100hz

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.

ADC sampling

This example shows sampling 2 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=4-5");
        assert(sk != 0);

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

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

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

	close(sk);
        return 0;
}

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.

#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 toggles the brightness of an LED by toggling power fed from an external source.

OUT1+ is connected to the LED -, and OUT- is connected to the external power -. External power + is connected to LED +.

#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

#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;            
}

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.

PWM

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.