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.
PWM
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.
2 PWM frequencies and ADC sampling
This example shows using the PWM frequency and the ADC sample frequency on different channels, as well as sampling ADC channels 1 and 2.
#include <stdio.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;
// Set channel 0 to 1khz with a duty cycle of 500khz
daqctl_setoutput(sk, 0, PWM_FREQ(1000) | PWM_DUTY(50));
// Set channel 1 to 2khz from ADC core
daqctl_setoutput(sk, 1, PWM_DUTY_PCT(30) | PWM_FREQ_IS_SAMPLERATE);
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.
#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;
}
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 |