
how-to block ads
|
|
Uniqs: 3996 |
Share Topic  |
 |
|
|
|
 VigThread-safe since 1997Premium join:2004-03-23 San Diego, CA | Linux: Interrupt based on serial port pin I've been unable to find a way to do this, but linux is not my strong suit. Hopefully someone here can help me out.
I have a program that needs to process a data stream coming in through a serial port. The stream is coordinated with a hardware input signal also coming in on one pin of the port. The program needs to act on the serial data when the pin level transitions.
I can detect the level of the pin via normal ioctl() calls, but in order to use that interface it would mean polling until a transition is seen. What I'd really like is to have an interrupt/signal/etc. that fires a handler in my program on pin level transition.
Is there any way of doing this with standard interfaces in linux? I could get what I want by modifying the serial driver (at the expense of portability) but I'd like to avoid that if possible. Does anyone know any technique I could use that keeps me out of the kernel-modding business? | |  SteveI know your IP addressConsultant join:2001-03-10 Yorba Linda, CA kudos:5 1 edit | If it were me, I'd wire that input pin to DCD and have a program open the port not in O_NDELAY mode. It will block until DCD goes high, and it gets SIGHUP when DCD goes low. This could be a kind of watchdog process that turned this open/SIGHUP business into the level or edge triggering you want.
Steve — whose serial I/O kung-fu grip is strong
-- Stephen J. Friedl | Unix Wizard | Microsoft Security MVP | Orange County, California USA | my web site | |  VigThread-safe since 1997Premium join:2004-03-23 San Diego, CA | This is exactly the kind of idea I was looking for. It seems like this technique should be able to do what I want.
Toying with it some, I've not gotten the results I expected. I have DCD wired with an input that's 1 second on, 1 second off. I verified that basic ioctl() calls do in fact show the duty cycle I'd expect to see. However, the open() call fails to block when DCD is low and the signal handler gets nothing when DCD goes from high to low. For some reason, DCD level is having no effect on port management. Maybe I'm missing something simple about how to make DCD relevant in order to use SIGHUP and open() as the detection mechanisms.
For reference, here's the basic example I've been using as a proof of concept.
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/signal.h>
#define DEVICE "/dev/ttyS1"
#define BUF_SIZE 1000
char buf[BUF_SIZE];
int fd = 0;
int sighup_received = 0;
// read the current level from DCD pin
int get_dcd_state(int fd)
{
int serial = 0;
if(ioctl(fd, TIOCMGET, &serial) < 0)
{
printf("ioctl() failed: %d: %s\n", errno, strerror(errno));
return -1;
}
return (serial & TIOCM_CAR) ? 1 : 0;
}
// poll DCD pin state and return when it changes
void poll_for_dcd_transition(int curr_dcd_state)
{
int new_dcd_state = get_dcd_state(fd);
while(new_dcd_state == curr_dcd_state)
{
printf("Waiting for DCD transition: %d\n", new_dcd_state);
// arbitrarily wait 10msec between polls
usleep(10000);
new_dcd_state = get_dcd_state(fd);
}
}
// handle SIGHUP, which is delivered when DCD goes low
void signal_handler(int status)
{
printf("SIGHUP handler invoked, DCD state: %d\n", get_dcd_state(fd));
sighup_received = 1;
}
// install handler for SIGHUP, which comes when DCD goes low
void install_sighup_handler()
{
sighandler_t prev_handler = signal(SIGHUP, signal_handler);
if(prev_handler == SIG_DFL)
{
printf("Old handler was default\n");
}
if(prev_handler == SIG_IGN)
{
printf("Old handler was ignore\n");
}
}
// sample code for reacting to DCD hardware signal
int main(int argc, char** argv)
{
intomode = O_RDONLY;
// open the serial stream
fd = open(DEVICE, omode, 0777);
if(fd < 0)
{
printf("open() failed: %d: %s\n", errno, strerror(errno));
return -1;
}
printf("Device opened, DCD state: %d\n", get_dcd_state(fd));
install_sighup_handler();
// simulate read of data
if(read(fd, buf, BUF_SIZE) < 0)
{
printf("read() failed: %d: %s\n", errno, strerror(errno));
return -1;
}
printf("Data read completed\n");
// save state of DCD line
int dcd_state = get_dcd_state(fd);
if(dcd_state < 0)
{
printf("get_dcd_state() failed\n");
return -1;
}
printf("Starting DCD state: %d\n", dcd_state);
// temporary hack to test DCD high to low transition
if(!dcd_state)
{
printf("DCD low, wait for high then continue\n");
poll_for_dcd_transition(dcd_state);
printf("DCD high, continue\n");
}
sighup_received = 0;
printf("Waiting for SIGHUP\n");
// just block on next input packet, which won't
// come until after next DCD transition happens
if(read(fd, buf, BUF_SIZE) < 0)
{
printf("read() failed: %d: %s\n", errno, strerror(errno));
return 0;
}
// stay alive until terminated by user
printf("End of main() reached, sleeping\n");
while(1)
{
if(sighup_received)
{
sighup_received = 0;
printf("SIGHUP received\n");
}
usleep(5*1000);
}
}
| |  SteveI know your IP addressConsultant join:2001-03-10 Yorba Linda, CA kudos:5 | I'll have to look at this again in the morning (long day), but my suspicion is that you have to explicitly turn off CLOCAL mode - some ports keep this on by default, and it's always been a battle between the three-wire pansies and the far more manly eight-wire guys.
You'd do this one time at the start by opening the port in O_RDWR|O_NDELAY mode, fetch use tcgeta to fetch the current termios stats, turn *off* CLOCAL, then put it back.
What kind of serial port are you using? Which OS exactly?
Steve -- Stephen J. Friedl | Unix Wizard | Microsoft Security MVP | Orange County, California USA | my web site | |  SteveI know your IP addressConsultant join:2001-03-10 Yorba Linda, CA kudos:5 1 edit | Ah: you want to use TIOCMIWAIT.
Ref: »www.linuxjournal.com/node/6226/print (a kernel commentary, but it gives the idea for where else to search) | | |
|  VigThread-safe since 1997Premium join:2004-03-23 San Diego, CA | Yes, TIOCMIWAIT is the answer I was looking for. Unfortunately, it seems not to be very well-documented, but it is implemented for the linux flavor I'm using. (CentOS, a stripped-down RedHat distro)
I've got a working proof of concept. I coded up two implementations: one that polls and one that blocks. Running them side by side, I see the blocking call return just when the transition happens on the polling version.
I haven't examined the fine timing, so I have no idea of the timing granularity of this TIOCMIWAIT technique. I don't need incredible precision, so this will work just fine. I suspect it's good enough for any app that doesn't need outstanding precision, but anything needing to be dead on probably isn't implementing on top of linux anyway.
Thanks for the assistance!
Blocking
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/signal.h>
#define DEVICE "/dev/ttyS1"
// read the current level from DCD pin
int get_dcd_state(int fd)
{
int serial = 0;
if(ioctl(fd, TIOCMGET, &serial) < 0)
{
printf("ioctl() failed: %d: %s\n", errno, strerror(errno));
return -1;
}
return (serial & TIOCM_CAR) ? 1 : 0;
}
// sample code for blocking until DCD state changes
int main(int argc, char** argv)
{
intomode = O_RDONLY;
// open the serial stream
int fd = open(DEVICE, omode, 0777);
if(fd < 0)
{
printf("open() failed: %d: %s\n", errno, strerror(errno));
return -1;
}
printf("Device opened, DCD state: %d\n", get_dcd_state(fd));
// detect DCD changes forever
int i=0;
while(1)
{
printf("%6d DCD state: %d\n", i++, get_dcd_state(fd));
// block until line changes state
if(ioctl(fd, TIOCMIWAIT, TIOCM_CAR) < 0)
{
printf("ioctl(TIOCMIWAIT) failed: %d: %s\n", errno, strerror(errno));
return -1;
}
}
}
Polling
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/signal.h>
#define DEVICE "/dev/ttyS1"
// read the current level from DCD pin
int get_dcd_state(int fd)
{
int serial = 0;
if(ioctl(fd, TIOCMGET, &serial) < 0)
{
printf("ioctl() failed: %d: %s\n", errno, strerror(errno));
return -1;
}
return (serial & TIOCM_CAR) ? 1 : 0;
}
// poll DCD pin state and return when it changes
void poll_for_dcd_transition(int fd, int curr_dcd_state)
{
int new_dcd_state = get_dcd_state(fd);
while(new_dcd_state == curr_dcd_state)
{
printf("Waiting for DCD transition: %d\n", new_dcd_state);
// arbitrarily wait 10msec between polls
usleep(10000);
new_dcd_state = get_dcd_state(fd);
}
}
// sample code for detect DCD transitions via polling
int main(int argc, char** argv)
{
// open the serial stream
int fd = open(DEVICE, O_RDONLY, 0777);
if(fd < 0)
{
printf("open() failed: %d: %s\n", errno, strerror(errno));
return -1;
}
// detect DCD changes forever
while(1)
{
poll_for_dcd_transition(fd, get_dcd_state(fd));
}
}
| |
|