Hardware interrupt for synchronous data acquisition
Asked Answered
K

4

8

I am looking for a simple means of triggering my data acquisition software using an external TTL pulse. I need to sample data from multiple sources synchronously with a 5 Hz reference clock. The acquisition does not need real-time priority but I want to ensure that my software is triggered as soon as possible and exactly once per external clock cycle. I would prefer to do this by somehow getting an interrupt from the external trigger without needing to use a fast polling loop. As far as I can tell, you can't just use a parallel port pin for an interrupt in a modern OS like Linux. Any ideas?

I am also thinking of generating broadcast packets on my network to notify other machines on the network that a trigger event has occurred. Due to network latency however there may not be enough time available in the 200ms period between triggers to do the acquisition.

Knavery answered 1/7, 2013 at 23:43 Comment(0)
H
10

Rather than use the parallel port, have you considered using a serial device? Since you have a TTL signal, you'll possibly need a level converter to convert TTL to RS232 +/- 12V levels. Once you're using a serial device, you can use the standard serial ioctl() calls to detect a change in control signal status.

Specifically, you could use the TIOCMIWAIT ioctl on the connected serial device to wait for a change on say the DCD line, which you would connect to your clock source.

Your user space application would be blocked waiting in the TIOCMIWAIT ioctl system call until there is a change of status on your clock line, at which point your app would become runnable and return from the ioctl. You might have to take care to ensure that you handle the case where you get a change of status interrupt on both rising and falling edges of your serial control signals. On some UART hardware (eg TL16C554A UART) it's possible that you'll only get an interrupt for a signal transitioning in a single direction. For the TL16C554A for example, the TIOCMIWAIT would only fall through on the rising edge of any Ring Indicate signal change.

Using the serial ioctls in this manner also has the advantage that you could use a USB-Serial dongle that supports TIOCMIWAIT if required (eg PL2303), and still retain user level software compatibility, albeit at the expense of increased latency due to USB.

If you require lower latency than can be achieved through user space, you'd be best to write a kernel driver module which can handle the timing and sampling, but I wouldn't suggest this route unless absolutely needed. It's easier to develop user space code.

Here's some incomplete sample C code snippets for using the TIOCMIWAIT ioctl.

int serial_fd = open(cmdline.device_name, O_RDWR | O_NONBLOCK | O_NOCTTY);
static const unsigned int ri_flag = TIOCM_RNG;

/* Set up serial port here using tcsetattr.  Set CRTSCTS | CLOCAL to ensure status interrupts
 * are generated.
 */

while (1) {
        /* Wait for positive RI transition.  TIOCMIWAIT takes a mask
         * as argument, only returning when the appropriate signal has changed.
         */
        if (ioctl(serial_fd, TIOCMIWAIT, ri_flag)) {
            fprintf(stderr, "ioctl() failed waiting for RI edge [%s]\n", strerror(errno));
            break;
        }

        /* Do sensor sampling here.  You could use TIOCMGET to first verify that
         * the clock line is in the expected state, eg high, before continuing.
         */
}
Hemihedral answered 2/7, 2013 at 2:21 Comment(4)
This is exactly what I would like to do. I was not aware that I could still use the RS232 interrupts over a USB adapter. This certainly simplifies things. It looks like I can easily implement this in Python using question #5905395Knavery
The Python ioctl example worked perfectly with a real serial port! ioctl returned an "Argument error" whenever I tried this with a Keyspan USB adapter, but better to not have the latency anyway and use a real com port. Now just need to build the TTL to +/-12V level shifter.Knavery
@Knavery I had a look in the source, the keyspan driver doesn't support TIOCMIWAIT. Only some usb-serial drivers support this, look for .tiocmiwait in driver files in linux source directory drivers/usb/serial. The PL2303 is a very common usb-serial adaptor. You might be able to find one of these and hack to bypass the +/-12V front end in the dongle. Alternativly, Maxim produce a series of level converter ICs.Hemihedral
To do the level conversion I setRTS(1), setDTR(0) and use a 2N4401 with the collector to RTS and emitter via a 4.7kΩ resistor to DTR. Then drive the base with TTL. Works great. Will have to try out another USB dongle with TIOCMIWAIT.Knavery
A
3

Polling is a fine method for such a slow data rate. Poll at 1 ms. That should be fine. Trying to use a hardware interrupt is going to cause much pain.

Google for "Interrupt Linux GPIO" if you want to do it the hard way. :)

https://developer.ridgerun.com/wiki/index.php/How_to_use_GPIO_signals

Adamant answered 2/7, 2013 at 0:11 Comment(1)
For now I found that one of my USB DAQ modules has a 32bit counter that will trigger directly from my TTL signal. I am able to poll this value in 0.7ms and look for changes.Knavery
W
3

Consider connecting the external pulse source to the 'CD' ping of a real (! not a USB to RS232 converter) serial port. Then you can use the "PPS api" to get an as exact timestamp from which the pin "went high" as possible.

You might need a TTL signal shifter; not all serial ports work correctly with TTL signal levels.

The PPS api is normally used for time keeping. E.g. connect the PPS pin of a GPS module to your pc and let NTP sync with that. This gives you microsecond accuracy.

This PPS api is supposed to be more accurate than any other userspace solution (e.g. the TIOCMIWAIT ioctl) as it is completely handled in the kernel, immediately when the interrupt (as triggered by the CD signal change) comes in. With the ioctl solution you have at least a context switch. I did some testing on a raspberry pi and a userspace solution gives at least 6us jitter.

The PPS api gives you a timestamp from when the pulse was detected.

Why not use a USB to RS232 converter: I read somewhere (in relation to timekeeping) that USB devices are polled once every +/- 1ms. This polling frequency also depends on how busy the system is with other USB devices and I think the internal clock of the USB device may also influence things. I've never measured this though.

Relevant URLS:

Regarding the network functionality: use UDP broadcasts, do not use TCP.

Wrapped answered 5/9, 2013 at 10:1 Comment(3)
I chose to use CTS (see my answer) for no particular reason. It seemed to me that all of the control lines were treated equivalently with respect to interrupts. I will certainly look into the PPS API and if it can give better timing than my present interrupt based system.Knavery
I did not have any luck with using USB to RS232 converters.Knavery
At some point I should capture the timestamps for each interrupt event to see how much jitter I end up with wrt my precision trigger.Knavery
K
2

I ended up using the serial port CTS line for the trigger using the TIOCMIWAIT ioctl per Austin Phillips answer. Since RS232 requires +/-12V levels I was able to get the necessary power for this level shifter from the other serial control lines.

Level shifter schematic

The Python code to implement this solution can be found in question: Python monitor serial port (RS-232) handshake signals

Knavery answered 5/7, 2013 at 20:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.