Serial port programming problems

2010-05-01 Thread Neil O'Brien
I run OpenBSD 4.6 (i386) on a PCEngines ALIX2c3, as a low power
file/web/DHCP server.  I would like to have this machine regularly
retrieve data from an instrument which communicates over RS-232.
I'm using a Prolific USB-RS232 converter (full dmesg for the ALIX
below).

I have no protocol documentation but have been able to reverse engineer
a subset of the instrument's protocol.  I have written a code to
extract data from the instrument which compiles cleanly on OpenBSD,
Linux (gcc) and Solaris x86 (Sun Studio).  On Linux and Solaris,
the resulting binary always reads the data that I want.  On OpenBSD,
the code appears to hang on every second execution, its entry in the
output of top looking similar to:

0  204K  496K idle  ttyin 0:00  0.00% reader

I've come up with a minimal example to demonstrate where the problem
begins, and have pasted it below.  The code sends an initialisation
string to the instrument and should get an ACK (ASCII decimal 6) back.
On Linux it works; on OpenBSD, it works the first time after insertion
of the USB-RS232 converter but not subsequently.

I don't believe this is likely to be a hardware problem with the
USB-RS232 converter, since I successfully used the same one in Linux
and Solaris.  I also think it unlikely to be the USB host controller,
since I have identical behaviour on another OpenBSD box, with a
different USB controller, see lines from its dmesg:

uhci0 at pci0 dev 7 function 2 "Intel 82371AB USB" rev 0x01: irq 11
usb0 at uhci0: USB revision 1.0
uhub0 at usb0 "Intel UHCI root hub" rev 1.00/1.00 addr 1
uplcom0 at uhub0 port 2 "Prolific Technology Inc. USB-Serial Controller
D" rev 1.10/4.00 addr 2
ucom0 at uplcom0

Running on OpenBSD, either on the ALIX or on my other box, the
behaviour I see is:

# cc -Wall -o ex_obsd ex_obsd.c
# ./ex_obsd; sleep 5; ./ex_obsd; sleep 5; ./ex_obsd; sleep 5; ./ex_obsd
1 bytes available, read: 6
1 bytes available, read: 10
Expecting , got 10
1 bytes available, read: 10
Expecting , got 10
1 bytes available, read: 10
Expecting , got 10

Running identical code (save for a change of device name) on Linux
(on another different machine), I get:

$ ./ex_lin; sleep 5; ./ex_lin; sleep 5; ./ex_lin; sleep 5; ./ex_lin 
1 bytes available, read: 6
1 bytes available, read: 6
1 bytes available, read: 6
1 bytes available, read: 6

On OpenBSD, I can get the desired behaviour if I manually unplug and
re-plug the USB to serial converter during each sleep.

I'd be very grateful if anyone has any advice on what I should change
to get the desired behaviour from this code on OpenBSD.

Many thanks in advance.

Neil


My code:
#include   
#include 
#include 
#include  
#include 
#include 
#include 

int open_port(void);/* Opens port, returns file descriptor */
void set_port(int fd);  /* Sets port options on file descriptor fd */

int main(void)
{
  int n,fd;
  char resp;
  char initString[] = {0x0D, 0x01, 0x39, 0x31, 0x38};
  char ack = 0x06;
  char eot = 0x03;

  fd = open_port();
  set_port(fd);
  if(DEBUG != 0) printf("Port has been opened and set up\n");

  // send 918
  n = write(fd,initString,5);
  if (n != 5) {printf("Failed to write init string.\n"); return(-1);}
  
  // See if it replies - want ACK
  n = read(fd,&resp,1);
  printf("%i bytes available, read: %i\n",n,resp);
  if (resp != ack) printf("Expecting , got %i\n",resp);

  // Send EOT
  n = write(fd,&eot,1);
  if (n != 1) {printf("Failed to write .\n"); return(-1);}
  
  // Close the port
  n = close(fd);
  if (n != 0) 
{
  printf("failed to close port: close returned %d\n",n);
  return(-1);
}

  return 0;
}

void set_port(int fd)
{
  struct termios options;
  int status;
  
  // Get current settings for the port
  tcgetattr(fd, &options);
  
  // Set baud rate = 1200
  cfsetispeed(&options, B1200);
  cfsetospeed(&options, B1200);
  
  // Set 7 bits, even parity
  options.c_cflag |= PARENB;
  options.c_cflag &= ~PARODD;
  options.c_cflag &= ~CSTOPB;
  options.c_cflag &= ~CSIZE;
  options.c_cflag |= CS7;
  
  // Raw input
  options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

  // Check and strip parity bit.
  options.c_iflag |= (INPCK | ISTRIP);

  // Disable software flow control 
  options.c_iflag &= ~(IXON | IXOFF | IXANY);

  // Output to be raw
  options.c_oflag &= ~(OPOST | OLCUC | ONLCR | OCRNL );
  
  // Set new options for port
  status = tcsetattr(fd, TCSANOW, &options);
  if (status < 0)
  {
errx(1,"set_port(): failed.");
  }
  
}
  

int open_port(void)
{
  int fd;

  fd = open("/dev/ttyU0", O_RDWR | O_NOCTTY | O_NDELAY);
  if (fd < 0)
  {
errx(1,"open_port: Unable to open /dev/ttyS0.");
  }
  else
  {
fcntl(fd, F_SETFL, 0);
  }
  return (fd);
}

dmesg from ALIX:
OpenBSD 4.6 (GENERIC) #0: Thu Apr  8 14:17:46 BST 2010
r...@alix:/usr/src/sys/arch/i386/compile/GENERIC
cpu0: Geode(TM) Integrated Processor by AMD PCS ("AuthenticAMD"
586-class) 499 MHz
cpu0: FPU,DE,PSE,TSC,MSR,CX8,SEP,PGE,CMOV,CFLUSH,MMX
real mem  = 268009472 (255MB)
avail mem = 250

Re: Serial port programming problems

2010-05-02 Thread Neil O'Brien
On Sat, May 01, 2010 at 15:30:28 -0700, J.C. Roberts wrote:
> >   status = tcsetattr(fd, TCSANOW, &options);
>
> How does it behave if you use "TCSAFLUSH" rather than "TCSANOW" ?

I made that substitution and added a
#define DEBUG 1

The resulting binary sometimes fails to return and I then have to
hit ctrl-c.  I've tried it several times, and not seen any (repeating)
pattern in terms of when it works and when it fails.

Here's some typical output:

# grep TCSA ex_obsd_flush.c
 status = tcsetattr(fd, TCSAFLUSH, &options);
# cc -Wall -o ex_obsd_flush ex_obsd_flush.c
# time ./ex_obsd_flush
Port has been opened and set up
1 bytes available, read: 6
0m1.21s real 0m0.00s user 0m0.00s system
# time ./ex_obsd_flush
Port has been opened and set up
^C0m6.07s real 0m0.00s user 0m0.00s system

# time ./ex_obsd_flush
Port has been opened and set up
^C0m7.49s real 0m0.00s user 0m0.00s system

# time ./ex_obsd_flush
Port has been opened and set up
1 bytes available, read: 6
0m1.21s real 0m0.00s user 0m0.00s system

Following this I had two more failures, one success, one failure and
one success in that order.

--
Neil



Re: Serial port programming problems

2010-05-02 Thread Aaron Mason
On Sun, May 2, 2010 at 5:33 PM, Neil O'Brien  wrote:
> On Sat, May 01, 2010 at 15:30:28 -0700, J.C. Roberts wrote:
>> >   status = tcsetattr(fd, TCSANOW, &options);
>>
>> How does it behave if you use "TCSAFLUSH" rather than "TCSANOW" ?
>
> I made that substitution and added a
> #define DEBUG 1
>
> The resulting binary sometimes fails to return and I then have to
> hit ctrl-c.  I've tried it several times, and not seen any (repeating)
> pattern in terms of when it works and when it fails.
>
> Here's some typical output:
>
> # grep TCSA ex_obsd_flush.c
>  status = tcsetattr(fd, TCSAFLUSH, &options);
> # cc -Wall -o ex_obsd_flush ex_obsd_flush.c
> # time ./ex_obsd_flush
> Port has been opened and set up
> 1 bytes available, read: 6
>0m1.21s real 0m0.00s user 0m0.00s system
> # time ./ex_obsd_flush
> Port has been opened and set up
> ^C0m6.07s real 0m0.00s user 0m0.00s system
>
> # time ./ex_obsd_flush
> Port has been opened and set up
> ^C0m7.49s real 0m0.00s user 0m0.00s system
>
> # time ./ex_obsd_flush
> Port has been opened and set up
> 1 bytes available, read: 6
>0m1.21s real 0m0.00s user 0m0.00s system
>
> Following this I had two more failures, one success, one failure and
> one success in that order.
>
> --
> Neil
>
>

Hi,

Is the system running Linux running on the same type of board as your
OpenBSD system?  If not, try running Linux on a system identical to
the one you're using - see if it's the board causing the problem.

Regards

-- 
Aaron Mason - Programmer, open source addict
I've taken my software vows - for beta or for worse



Re: Serial port programming problems

2010-05-02 Thread Neil O'Brien
On Sun, May 02, 2010 at 20:56:36 +1000, Aaron Mason wrote:
> On Sun, May 2, 2010 at 5:33 PM, Neil O'Brien  wrote:
> > On Sat, May 01, 2010 at 15:30:28 -0700, J.C. Roberts wrote:
> >> >   status = tcsetattr(fd, TCSANOW, &options);
> >>
> >> How does it behave if you use "TCSAFLUSH" rather than "TCSANOW" ?
> >
> > I made that substitution and added a
> > #define DEBUG 1
> >
> > The resulting binary sometimes fails to return and I then have to
> > hit ctrl-c.  I've tried it several times, and not seen any (repeating)
> > pattern in terms of when it works and when it fails.
> >
> Is the system running Linux running on the same type of board as your
> OpenBSD system?  If not, try running Linux on a system identical to
> the one you're using - see if it's the board causing the problem.
> 

Hi Aaron,

No, they are very different machines.  I don't have a spare ALIX system
that I can load Linux onto for testing.  If I can't resolve it any
other way I will switch my current machine to Linux; but having just
got everything set up in OpenBSD, I'd like to avoid that if possible.

I have however tried the code in my initial email on a different
OpenBSD machine (a Dell Optiplex Pentium II, dmesg is at
http://www.tigerturnings.dyndns.dk/dmesg-dell)

In this case I used cua0, the onboard serial port, on an ns16550a
controller.  Running the example from my initial post, with #define
DEBUG 1 added and /dev/ttyU0 replaced by /dev/cua00, I get sporadic
failures:

# time ./ex_obsd_now_cua0 ; sleep 3 ; time ./ex_obsd_now_cua0  
Port has been opened and set up
1 bytes available, read: 6
0m0.12s real 0m0.00s user 0m0.00s system
Port has been opened and set up
^C0m7.00s real 0m0.00s user 0m0.00s system

If I replace TCSANOW with TCSAFLUSH on this machine, it doesn't help,
though the instrument does sometimes send 21 (NAK) which I've never
seen it do when using TCSANOW.

# time ./ex_obsd_flush_cua0   
Port has been opened and set up
1 bytes available, read: 6
0m0.14s real 0m0.00s user 0m0.00s system
# time ./ex_obsd_flush_cua0 
Port has been opened and set up
^C0m3.97s real 0m0.00s user 0m0.01s system

# time ./ex_obsd_flush_cua0 
Port has been opened and set up
^C0m3.65s real 0m0.00s user 0m0.00s system

# time ./ex_obsd_flush_cua0 
Port has been opened and set up
1 bytes available, read: 21
Expecting , got 21
0m0.23s real 0m0.00s user 0m0.00s system

I think that this should rule out any problems with the USB-RS232
converter or its drivers, and with the USB controller on the ALIX
system.

I'd be grateful for any further ideas or suggestions.

-- Neil



Re: Serial port programming problems

2010-05-02 Thread J.C. Roberts
On Sun, 2 May 2010 16:45:44 +0100 "Neil O'Brien"
 wrote:

> On Sun, May 02, 2010 at 20:56:36 +1000, Aaron Mason wrote:
> > On Sun, May 2, 2010 at 5:33 PM, Neil O'Brien
> >  wrote:
> > > On Sat, May 01, 2010 at 15:30:28 -0700, J.C. Roberts wrote:
> > >> >   status = tcsetattr(fd, TCSANOW, &options);
> > >>
> > >> How does it behave if you use "TCSAFLUSH" rather than "TCSANOW" ?
> > >
> > > I made that substitution and added a
> > > #define DEBUG 1
> > >
> > > The resulting binary sometimes fails to return and I then have to
> > > hit ctrl-c.  I've tried it several times, and not seen any
> > > (repeating) pattern in terms of when it works and when it fails.
> > >
> > Is the system running Linux running on the same type of board as
> > your OpenBSD system?  If not, try running Linux on a system
> > identical to the one you're using - see if it's the board causing
> > the problem.
> > 
> 
> Hi Aaron,
> 
> No, they are very different machines.  I don't have a spare ALIX
> system that I can load Linux onto for testing.  If I can't resolve it
> any other way I will switch my current machine to Linux; but having
> just got everything set up in OpenBSD, I'd like to avoid that if
> possible.
> 
> I have however tried the code in my initial email on a different
> OpenBSD machine (a Dell Optiplex Pentium II, dmesg is at
> http://www.tigerturnings.dyndns.dk/dmesg-dell)
> 
> In this case I used cua0, the onboard serial port, on an ns16550a
> controller.  Running the example from my initial post, with #define
> DEBUG 1 added and /dev/ttyU0 replaced by /dev/cua00, I get sporadic
> failures:
> 
> # time ./ex_obsd_now_cua0 ; sleep 3 ; time ./ex_obsd_now_cua0  
> Port has been opened and set up
> 1 bytes available, read: 6
> 0m0.12s real 0m0.00s user 0m0.00s system
> Port has been opened and set up
> ^C0m7.00s real 0m0.00s user 0m0.00s system
> 
> If I replace TCSANOW with TCSAFLUSH on this machine, it doesn't help,
> though the instrument does sometimes send 21 (NAK) which I've never
> seen it do when using TCSANOW.
> 
> # time ./ex_obsd_flush_cua0   
> Port has been opened and set up
> 1 bytes available, read: 6
> 0m0.14s real 0m0.00s user 0m0.00s system
> # time ./ex_obsd_flush_cua0 
> Port has been opened and set up
> ^C0m3.97s real 0m0.00s user 0m0.01s system
> 
> # time ./ex_obsd_flush_cua0 
> Port has been opened and set up
> ^C0m3.65s real 0m0.00s user 0m0.00s system
> 
> # time ./ex_obsd_flush_cua0 
> Port has been opened and set up
> 1 bytes available, read: 21
> Expecting , got 21
> 0m0.23s real 0m0.00s user 0m0.00s system
> 
> I think that this should rule out any problems with the USB-RS232
> converter or its drivers, and with the USB controller on the ALIX
> system.
> 
> I'd be grateful for any further ideas or suggestions.
> 
> -- Neil
> 

I should be able to figure this out, but my C language serial port
hacking skills are a bit rusty. These days, C is only used for
time-critical portions of instrument automation for test/mfg
environments, but for everything else, languages like perl, tcl or
custom/proprietary LabView executables are used.

Lots of instruments speak "skippy" (SCPI) otherwise known as Standard
Commands for Programmable Instruments. Your *UNNAMED* instrument might
speak a custom protocol as you claimed, or it might also speak "skippy"
either natively or with some configuration.

http://en.wikipedia.org/wiki/Standard_Commands_for_Programmable_Instrumentation

If your instrument can speak "skippy" then your life gets a whole lot
easier, so it's definitely worth checking. I'm not sure why you failed
to post the make and model number details of your instrument, but they
would be really helpful.

As I mentioned off list, TCSAFLUSH is often required, but more than
that, it's just good practice to flush the buffers to make sure you're
starting in a known state (e.g. no crap in the buffers).

Both your C code, and not understanding the behavior and output you
posted seems to indicate you followed one of the countless (poorly
written) how-to pages or books like:

http://www.easysw.com/~mike/serial/serial.html

The above has some good spots but it also has plenty of errors and
unfortunately, it is highly ranked by search engines. In your code you
seem to have inherited one of the mistakes, namely you think read(2)
gives you "bytes available" rather than "bytes read" as stated (twice)
in the read(2) man page.

$ man 2 read
...
Upon successful completion, read(), readv(), pread(), and preadv()
return the number of bytes actually read and placed in the
buffer.  The system guarantees to read the number of bytes requested
if the descriptor references a normal file that has that many bytes
left before the end-of- file, but in no other case.
...

The other problem is understanding what is happening. Unless you
specifically configured the descriptor to return immediately, your 
read(2) call will sleep

Re: Serial port programming problems

2010-05-02 Thread J.C. Roberts
On Sun, 2 May 2010 12:01:59 -0700 "J.C. Roberts"
 wrote:

> The other problem is understanding what is happening. Unless you
> specifically configured the descriptor to return immediately, your 
> read(2) call will sleep until it gets the requested number of bytes
> from the descriptor, until an interval timer expires, or until an
> error occurs. Since the instrument is not sending you any data, it
> sleeps until you lose your patience and hit CTL-C to end the program.
> --This should not be a surprise. ;)


I should have referenced your code so the above makes sense.

>> int open_port(void)
>> {
>>   int fd;
>> 
>>   fd = open("/dev/ttyU0", O_RDWR | O_NOCTTY | O_NDELAY);
>>   if (fd < 0)
>>   {
>> errx(1,"open_port: Unable to open /dev/ttyS0.");
>>   }
>>   else
>>   {
>> fcntl(fd, F_SETFL, 0);
>>   }
>>   return (fd);
>> }

Setting O_NDELAY (or better said O_NONBLOCK) on open gets wiped out
when you run "fcntl(fd, F_SETFL, 0)"

I have no idea why you're doing that (probably just mistyped example
code), but if you insist, then you'd actually want:

fcntl(fd, F_SETFL, FNDELAY);
or
fcntl(fd, F_SETFL, FNONBLOCK);

Hence the reason why you're actually blocking on read.

-- 
The OpenBSD Journal - http://www.undeadly.org