Daqctl
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 freq
- "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. |
Usage
Help
Usage: daqctl [OPTION] ... 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
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.
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. You can see a list of examples of this here.
For example, you can write the message to the TCP port:
O2:33%@1Khz
This will set output pin 2 to a duty cycle of 33% at 1khz. The change will take effect immediately in the hardware.
FPGA Core
Register Map
Address | Description | Access | Notes | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Base + 0 | Channel enable #15 (MSB) through #0 (LSB) | Read/Write | 1 to enable | ||||||||||||||||||||||
Base + 2 | Current state | Read/Write |
| ||||||||||||||||||||||
Base + 4 | Sample Period | Read/Write |
| ||||||||||||||||||||||
Base + 6 | Misc | Read/Write |
| ||||||||||||||||||||||
Base + 8 | Interest criteria | Read/Write |
| ||||||||||||||||||||||
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 |