Serial port access C code blocks at 1st read, why?

152 views
Skip to first unread message

Chin Fang

unread,
Nov 1, 2002, 5:07:16 PM11/1/02
to
Recently, I got a TempTrax 2000 F temperature sensor (
http://www.sensatronics.com/TempTrax/). Right now, it is plugged in
the serial port B of a 170Mhz SS5, running Solaris 7.

uname -a gives:

SunOS moo 5.7 Generic_106541-19 sun4m sparc SUNW,SPARCstation-5.

I would like to poll the sensor periodically via the above host's
serial port B for environmental monitoring purposes. As the SS5 runs
a highly stripped OS, I decided to write a C code for polling.

Since I have not done serial programming before, so I first reviewed
the following:

o chapter 11 of R. Stevens Advanced Programming in the UNIX Environment;
o Serial Programming Guide for POSIX Operating System:
http://www.easysw.com/~mike/serial/serial.html;
o Celeste's Tutorials on Solaris/SunOS Modems and Terminals
http://www.stokely.com/unix.serial.port.resources/tutorials.html;
o a sample code snappet from the vendor written for a different OS;

and then wrote up a small piece of C code (see below) to poll the
sensor. As far as I can tell, I have followed the instructions in the
first three references (also Solaris man pages for termio(7I) too).

But my code, although it compiles clearningly without any warnings
(gcc -g -pedantic -Wall; gcc 2.95.2), blocks at the first read
statement in read_output(). I have adjusted its open statement, and
the two control constants VMIN and VTIME in various combos to no avail
so far.

The entire code is attached below. I have attempted to make it as
clean and readable as possible.

I would appreciate any hints why the code blocks at the 1st read. I
would also be thankful to anyone who points out other mistakes in my
code.

BTW,

1. for code testing, I (as root) ran the executable in emacs gdb mode.
The code writes without any problem to the opened port.
2. the sensor itself works well. After adding the following to the SS5's
/etc/remote:
temptrax:\
:dv=/dev/cua/a:br#9600:el=^C^S^Q^U^D:ie=%$:oe=^D:

I can access the sensor via tip without a hitch. The unit does
everything it is advertised to do.

Regards,

Chin Fang
fang...@stanford.edu
---------------------------------------------------------------------------
/* $Id: temptrax.c,v 1.36 2002/11/01 21:22:03 fangchin Exp $
*
* Notes:
*
* (1) to test the executable compiled from the code, one must have
* access privilages to the intended device file.
* (2) the code is SUN Solaris specific.
* (3) TempTrax 2000F related info:
*
* The following are from:
*
* http://www.sensatronics.com/TempTrax/directaccess.html
*
* (a) RS232 communication settings:
*
* o 9600 baud
* o 8 data bits
* o no parity
* o 1 stop bit
* o no flow control
* o DTR on (logic "1" or high)
*
* (b) wiring requirements:
*
* Only TXD, RXD, GND, and DTR are used by the TempTrax 2000F
* temperature sensor, which gets power from the DTR.
*
* (c) output formats:
*
* After opening up the port and settting DTR high, send any
* character to the serial port will generate the following output,
* one per line, 3 lines total:
*
* reading from probe 1 (e.g 76.8)
* reading from probe 2 (e.g -99.9)
* Bat Ok
*
*/
#include <stdio.h> /* Standard input/output definitions */
#include <string.h> /* String function definitions */
#include <unistd.h> /* UNIX standard function definitions */
#include <fcntl.h> /* File control definitions */
#include <errno.h> /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */

#include <stdlib.h>
#include <limits.h>

/*
* Speeds are defined in <sys/termios.h>, which is included by
* <termios.h>
*/

#define BAUDRATE B9600

#define _POSIX_C_SOURCE 1 /* POSIX compliant source */

#define FALSE 0
#define TRUE 1

#define REPLY_LCNT 3 /* 1st probe reading, 2nd probe reading, and Bat OK */
#define READ_TIMEOUT 500 /* in ms; used in usleep(3C) */

int open_port( char *device );
int init_port( int fd );
int send_input( int fd, char *string );
int send_char( int fd, int c );
int read_output( int fd, int timeout, char *buf );

int main( int argc, char **argv )
{
int fd; /* tty descriptor */
int lcnt = 0;
int cnt = 0;
char buf[ MAX_INPUT ];

if( argc < 2 )
{
printf( "TempTrax sensor command line direct access utility\n" );
printf( "usage: temptrax <device>\n" );
exit( EXIT_FAILURE );
}

fd = open_port( argv[ 1 ] );
init_port( fd );

if( send_input( fd, " " ) <= 0 ) /* actually, any char would do */
{
printf( "Can't send to %s.\n", argv[ 1 ] );
close( fd );
exit( EXIT_FAILURE );
}

for( lcnt = 0; lcnt < REPLY_LCNT; lcnt++ )
{
cnt = read_output( fd, READ_TIMEOUT, buf );

if( cnt == -1 )
{
printf( "Timeout.\n" );
close( fd );
exit( EXIT_FAILURE );
}

*( buf + cnt ) = '\0';
printf( "%s", buf );
}

close( fd );
exit( EXIT_SUCCESS );
}

/*
* open_port() opens a given device. It returns the file descriptor on
* success or -1 on error. Adopted from
* http://www.easysw.com/~mike/serial/serial.html
*/
int open_port(char *device)
{
int fd; /* file descriptor for the port */

fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) /* could not open the port */
perror( device );
else
fcntl(fd, F_SETFL, 0);

return (fd);
}

/*
* init_port() does the following tasks:
*
* 1. sets 'control options' of the opened device to 9600,8,n,1,
* no hardware flow control;
* 2. sets 'local options' to raw input;
* 3. sets 'input options' to no parity checking and no software flow control;
* 4. sets 'output options' to raw;
* 5. raises the DTR of the given device to 1 (i.e. on);
*/
int init_port( int fd )
{
int arg = TIOCM_DTR;
struct termios tio;

/* get the current serial port settings */
tcgetattr( fd, &tio );

/* task 1 - control options
* o baud rate ............... 9600
* o character size .......... 8 bit
* o parity checking ......... none
* o hardware flow control ... off
*/
cfsetispeed( &tio, BAUDRATE );
cfsetospeed( &tio, BAUDRATE );
tio.c_cflag |= CS8;
tio.c_cflag &= ~PARENB;
tio.c_cflag &= ~CSTOPB;
tio.c_cflag &= ~CSIZE;
tio.c_cflag &= ~CRTSCTS;

/* task 2 - local options - raw input */
tio.c_cflag &= ~( ICANON | ECHO | ECHOE | ISIG );

/* task 3 - input options */
tio.c_cflag &= ~( IGNPAR | IXON | IXOFF | IXANY | IGNBRK );
/*
* If some data is available, read returns up to the number of
* bytes requested. If no data is available, read returns 0
* immediately. See R. Stevens APUE p. 353.
*/
tio.c_cc[VMIN] = 0;
tio.c_cc[VTIME] = 0;

/* task 4 - output options */
tio.c_cflag &= ~OPOST;

/* clean the modem line and activate the settings for the port */
tcflush( fd, TCIFLUSH);
tcsetattr( fd, TCSANOW, &tio);

/* task 5 - raise DTR to 1 */
ioctl( fd, TIOCMBIS, &arg);

/*
* give it 3 seconds before returning. See:
* http://www.stokely.com/unix.serial.port.resources/tutorials.html
*/
usleep( 3000000 );

return( 0 );
}

/*
* send_input() is a wrapper of the function send_char() (see below),
* which writes a single character to an opened device at a time.
*
* TempTrax uses a simple protocol in which only a simple char input is
* sufficient to trigger it to do a temperature probe.
*
* But, with send_input(), a user has the ability to use a string to
* trigger the TempTrax to do several probes in succession instead.
*/
int send_input( int fd, char *string )
{
int n = 0;
int cnt = strlen( string );

for( n = 0; n < cnt; n++ )
send_char( fd, ( int )*( string + n ) );

return( cnt );
}

int send_char( int fd, int c )
{
int n = 0;
unsigned char cbuf[ MAX_INPUT ];

cbuf[ 0 ] = c;

n = write( fd, cbuf, 1 );

if( n != 1 )
return( -1 );

return( 0 );
}

/*
* read_output() reads whatever ouptut that a TempTrax has put into the
* output queue (before it's overflown).
*/
int read_output( int fd, int timeout, char *buf )
{
int wait = 100, no_time = timeout, /* in ms, since usleep is used */
pos = 0, res = 0, ret = 0;

while( TRUE )
{
res = read( fd, ( void *)( buf + pos ), 1 );

if( res == 0 ) /* then we have no input. Lets wait a bit.. */
{
usleep( wait );
no_time -= wait;

if( no_time <= 0 )
{
ret = -1;
break;
}
}
else
{
if( *( buf + pos ) == '\n' )
{
*( buf + ++pos ) = '\0';
ret = pos;
break;
}

pos++;

/* For output, ther are no constants defining its size that
* are acceptable to a program. See R. Steven's APUE
* p. 327. So, we use 253 here.
*/
if( pos >= 253 )
{
ret = pos;
break;
}
}
}

return( ret );
}

Reply all
Reply to author
Forward
0 new messages