porting RS232 serial comms code from solaris->qnx

hi,
i’m new to QNX-rtp so bear with me for second. i have
some test code that runs on solaris and talks back and
forth with an external controller via the serial port.
the code compiles and works ok under solaris8 (both
sparc and x86). before i get into the details (and show
you the code) i have to advise you that i’m a mechanical
engineer and thus self taught in c and the POSIX serial
stuff… :*) so be gentle…

so onto the issues:
1)
the code “nearly” compiles on QNX, and i’ve made a
couple of fixes but i’m stuck at what i think are some
OS-specific thingees, for example with FNDELAY
as below.

$ uname -a
QNX dell01 6.1.0 2001/06/25-15:31:48edt x86pc x86

$ gcc aru.c
aru.c: In function serial_open': aru.c:109: FNDELAY’ undeclared (first use in this function)
aru.c:109: (Each undeclared identifier is reported only once
aru.c:109: for each function it appears in.)
aru.c: In function serial_setoptions': aru.c:130: CRTSCTS’ undeclared (first use in this function)
aru.c:137: `ECHOPRT’ undeclared (first use in this function)
$

so then, the relevant code…

int serial_open(char port, int debug)
{
/
Returns the file descriptor on success or -1 on error. */
int fd;
char prefix[32];
char device[32];
char errormsg[128];
strcpy(prefix,"/dev/");
strcpy(device,prefix);
strcat(device,port);
if (debug)
printf(“prefix=%s, port=%s, device=%s\n”,prefix,port,device);

fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd == -1)
{
strcpy(errormsg,"serial_open(): can’t open ");
strcat(errormsg,device);
perror(errormsg);
}
else
fcntl(fd, F_SETFL, FNDELAY); /* thus read rtns 0 if no bytes avail */

return (fd);
}

now then, everything i know about serial comms using the
POSIX interface i learned using this excellent tutorial:
http://www.easysw.com/~mike/serial/serial.html

the snippet above takes advantage of the suggestion here
http://www.easysw.com/~mike/serial/serial.html#2_5_4
to use the FNDELAY parameter to create a situation where
a read to the port returns 0 if no chars are buffered (that is,
it makes the read() non-blocking). an exhaustive search
of the QNX headers turns up no FNDELAY parameter.
so how do i specify non-blocking reads under QNX then?
(n.b.: in solaris, FNDELAY is def’d in sys/file.h)

–.-- break for a beer. --.–

ok, now a snippet which covers the next error messages, re:
aru.c:130: CRTSCTS' undeclared (first use in this function) aru.c:137: ECHOPRT’ undeclared (first use in this function)

…snip…
struct termios options; /* config struct for the port /
struct termios oldoptions; /
a copy so we can restore the port */
…snip…

void serial_setoptions(int fd, int debug)
{
tcgetattr(fd,&options); /* get the current options /
tcgetattr(fd,&oldoptions); /
and make a copy to use later /
if (debug)
{
printf(“iflag- 0%o; oflag- 0%o; cflag- 0%o; lflag- 0%o\n”,
options.c_iflag, options.c_oflag, options.c_cflag,
options.c_lflag);
}
cfsetispeed(&options,B9600); /
set input speed /
cfsetospeed(&options,B9600); /
set output speed */

//control options
options.c_cflag &= ~CSIZE; /* mask the char size /
options.c_cflag |= CS8; /
8 make the char size 8 bits /
options.c_cflag &= ~PARENB; /
N no parity /
options.c_cflag &= ~CSTOPB; /
1 one stop bit /
options.c_cflag &= ~CRTSCTS; /
no HW flow control /
options.c_cc[VMIN] = 0; /
bytes /
options.c_cc[VTIME] = 0; /
ms x 10 */
// VMIN minimum number of bytes read before “read” call returns
// VTIME number of tenths-of-a-second to wait before “read” returns

//local options /* raw input /
options.c_lflag &= ~(ECHONL|ECHOKE|ECHOPRT|ECHOCTL|ECHONL|
IEXTEN|XCASE|ICANON|ECHO|ECHOE|ISIG);
// input options
options.c_iflag &= ~(IGNPAR|INPCK|INLCR|IGNCR|ICRNL|
IUCLC|IMAXBEL); /
ignore parity errors /
options.c_iflag &= ~(IXON|IXOFF|IXANY); /
no SW flow cntl */

//output options
options.c_oflag &= ~OPOST; /* raw out */

tcsetattr(fd,TCSANOW,&options); /* set the new options */
if (debug)
{
printf(“iflag- 0%o; oflag- 0%o; cflag- 0%o; lflag- 0%o\n”,
options.c_iflag, options.c_oflag, options.c_cflag,
options.c_lflag);
}
}

so, “options.c_cflag &= ~CRTSCTS;” is to disable HW flow control.
again, i searched for this #define but couldn’t turn it up in QNX.

ditto for
options.c_lflag &= ~(… ECHOPRT|…)

any ideas, are these just not supported in the POSIX implementation
on QNX? are there any gotchas with respect to deleting them?

#)
if i comment out all of the above errors so cpp goes thru ok, i get
the following (note that there’s no guarantee the executable
would have worked as desired if the compile had succeeded…)
$ gcc aru.c
/tmp/ccXmgnNB.o: In function serial_recvpacket': /tmp/ccXmgnNB.o(.text+0x521): undefined reference to poll’
/tmp/ccXmgnNB.o: In function main': /tmp/ccXmgnNB.o(.text+0xcb3): undefined reference to poll’
collect2: ld returned 1 exit status

now we are starting to get to the real problems, no? :*)
but alas, my poll() under solaris just helps me set up a
rec’v timeout so that my program doesn’t wait forever
when the serial cable is disconnected by some yo yo like
me. now then, how to nicely wait() an app under QNX?

int serial_recvpacket(int fd, int debug, int time_ms)
{
int bytesavail=0, timecounter=0;

timecounter=(int)(time_ms/20);
while ((bytesavail !=6) && timecounter)
{
timecounter–;
poll(0,0,10); /* this is a delay of 20 ms (10+10) */
bytesavail=serial_recvbufchk(serial_fd);
}

if (debug)
printf(“bytes avail=%d\n”,serial_recvbufchk(serial_fd));
if (bytesavail == 6)
{
packet_in.target=serial_recvbyte(fd, debug);
packet_in.source=serial_recvbyte(fd, debug);
packet_in.opcode=serial_recvbyte(fd, debug);
packet_in.data0=serial_recvbyte(fd, debug);
packet_in.data1=serial_recvbyte(fd, debug);
packet_in.cksum=serial_recvbyte(fd, debug);
if (debug)
print_packet(0,packet_in.target,packet_in.source,
packet_in.opcode,packet_in.data0,
packet_in.data1,packet_in.cksum);
return(TRUE);
}
else
{
if (debug)
printf(“rx ← timed out.\n”);
return(FALSE);
}
}

hopefully there is enough info above, also i’ve attached the e
ntire program below, so you can see the polling method used
to get bytes from the serial port.

thanks for any suggestions.
jim
jds-box1 (you-know-what-here) losdos.dyndns.org

ps
i have two other “new abuser” questions…
→ is ksh’s “set -o vi” line editor/command history mode
in any way supported under QNX?
→ what newsgroup do these kinds of questions belong in?




$ cat aru.c

#define version “.99”

/* serial port and packet routines for aru factory test */

#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 <ioctl.h> /
needed for QNX /
#include <errno.h> /
Error number definitions /
#include <termios.h> /
POSIX terminal control definitions /
// #include <sys/filio.h> /
not present on QNX */
#include <sys/file.h>

typedef struct /* this is the aru<–>host 6 byte packet format */
{
unsigned char target;
unsigned char source;
unsigned char opcode;
unsigned char data0;
unsigned char data1;
unsigned char cksum;
} packet;

/* global vars */

packet packet_out; /* packet TO the aru /
packet packet_in; /
packet FROM the aru */

int serial_fd; /* File descriptor for the port */

#define TRUE (1==1)
#define FALSE (1==0)

/* from tripwire.c source – see opcode defs in tripwire source /
#define OPCODE_INIT 0x00
#define OPCODE_SET_RELAY_N 0x01
#define OPCODE_SET_RELAY_ALL 0x02
#define OPCODE_SCAN_INPUT_N 0x03
#define OPCODE_SCAN_INPUT_ALL 0x04
#define OPCODE_SCAN_TEMP 0x05
#define OPCODE_SCAN_VAVB 0x06
#define OPCODE_SCAN_FAN 0x07
#define OPCODE_WARM_BOOT 0x08
#define OPCODE_ECHO 0x09
#define OPCODE_STREAM_TO_LCD 0x0A
#define OPCODE_WRITE_TO_LCD 0x0B
#define OPCODE_DUMP_TO_LCD 0x0C
#define OPCODE_CLEAR_LCD 0x0D
#define OPCODE_PKTS_RX_OK 0x0E
#define OPCODE_PKTS_TX_OK 0x0F
#define OPCODE_PKTS_CKSUM_ERRS 0x10
#define OPCODE_PKTS_RUNT_ERRS 0x11
#define OPCODE_PKTS_INVALID 0x12
#define OPCODE_BIST 0x13 /
not implemented */
#define OPCODE_SET_SERIAL_NO 0x14
#define OPCODE_GET_SERIAL_NO 0x15
#define OPCODE_RESTART_POWER 0x16
#define OPCODE_RESET_POWER 0x17
#define OPCODE_CLEAR_EEPROM 0x18
#define OPCODE_WRITE_EEPROM 0x19
#define OPCODE_READ_EEPROM 0x1A
#define OPCODE_LCD_CONTRAST 0x1B
#define OPCODE_LCD_BACKLIGHT 0x1C
#define OPCODE_KEEPMEALIVE 0x1D

#if (0)
Platform TYPEID_BASE TYPEid (lsb if reset)

PAS ARU 0 either 0 or 1.
BSX ARU 2 either 2 or 3.
Norton ARU 4 either 4 or 5.
Norton.lite ARU 6 either 6 or 7.
Alice ARU 8 either 8 or 9.
Cable ARU 10 either 10 or 11.
#endif

struct termios options; /* config struct for the port /
struct termios oldoptions; /
a copy so we can restore the port */

/* function declarations /


/
function definitions */
int serial_open(char port, int debug)
{
/
Returns the file descriptor on success or -1 on error. */
int fd;
char prefix[32];
char device[32];
char errormsg[128];
strcpy(prefix,"/dev/");
strcpy(device,prefix);
strcat(device,port);
if (debug)
printf(“prefix=%s, port=%s, device=%s\n”,prefix,port,device);

fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd == -1)
{
strcpy(errormsg,"serial_open(): can’t open ");
strcat(errormsg,device);
perror(errormsg);
}
else
fcntl(fd, F_SETFL, FNDELAY); /* thus read rtns 0 if no bytes avail */

return (fd);
}

void serial_setoptions(int fd, int debug)
{
tcgetattr(fd,&options); /* get the current options /
tcgetattr(fd,&oldoptions); /
and make a copy to use later /
if (debug)
{
printf(“iflag- 0%o; oflag- 0%o; cflag- 0%o; lflag- 0%o\n”,
options.c_iflag, options.c_oflag, options.c_cflag,
options.c_lflag);
}
cfsetispeed(&options,B9600); /
set input speed /
cfsetospeed(&options,B9600); /
set output speed */

//control options
options.c_cflag &= ~CSIZE; /* mask the char size /
options.c_cflag |= CS8; /
8 make the char size 8 bits /
options.c_cflag &= ~PARENB; /
N no parity /
options.c_cflag &= ~CSTOPB; /
1 one stop bit /
options.c_cflag &= ~CRTSCTS; /
no HW flow control /
options.c_cc[VMIN] = 0; /
bytes /
options.c_cc[VTIME] = 0; /
ms x 10 */
// VMIN minimum number of bytes read before “read” call returns
// VTIME number of tenths-of-a-second to wait before “read” returns

//local options /* raw input /
options.c_lflag &= ~(ECHONL|ECHOKE|ECHOPRT|ECHOCTL|ECHONL|
IEXTEN|XCASE|ICANON|ECHO|ECHOE|ISIG);
// input options
options.c_iflag &= ~(IGNPAR|INPCK|INLCR|IGNCR|ICRNL|
IUCLC|IMAXBEL); /
ignore parity errors /
options.c_iflag &= ~(IXON|IXOFF|IXANY); /
no SW flow cntl */

//output options
options.c_oflag &= ~OPOST; /* raw out */

tcsetattr(fd,TCSANOW,&options); /* set the new options */
if (debug)
{
printf(“iflag- 0%o; oflag- 0%o; cflag- 0%o; lflag- 0%o\n”,
options.c_iflag, options.c_oflag, options.c_cflag,
options.c_lflag);
}
}

void serial_flush(int fd)
{
tcflush(fd,TCIOFLUSH);
}

void serial_close(int fd)
{
serial_flush(fd); /* flush anything and then /
tcsetattr(fd,TCSANOW,&oldoptions); /
reset the orig options */
close(fd);
}

void print_packet(int txflag,int b0,int b1,int b2,int b3,int b4,int b5)
{
if (txflag)
printf(“tx → 0x%.2x 0x%.2x 0x%.2x 0x%.2x 0x%.2x 0x%.2x\n”,
b0,b1,b2,b3,b4,b5);
else
{
printf(“rx ← 0x%.2x 0x%.2x 0x%.2x 0x%.2x 0x%.2x 0x%.2x”,
b0,b1,b2,b3,b4,b5);
if ((b0^b1^b2^b3^b4)!=b5)
printf(" ** cksum err! (%.2x)\n", b0^b1^b2^b3^b4);
else
printf("\n");
}
}

int serial_sendbyte(int fd, int data)
{
int sent=0;
sent = write(fd,&data,1);
return(sent);
}

int serial_sendpacket(int fd,int debug,
int b0,int b1,int b2,int b3,int b4,int b5)
{
int sent=0;
sent += serial_sendbyte(fd,b0);
sent += serial_sendbyte(fd,b1);
sent += serial_sendbyte(fd,b2);
sent += serial_sendbyte(fd,b3);
sent += serial_sendbyte(fd,b4);
if (!b5)
b5=b0^b1^b2^b3^b4;
sent += serial_sendbyte(fd,b5);
if (debug)
print_packet(1,b0,b1,b2,b3,b4,b5);
return(sent);
}

int serial_recvbufchk(int fd)
{
int bytes=0;
ioctl(fd,FIONREAD,&bytes);
return(bytes);
}

int serial_recvbyte(int fd, int debug)
{
int byte=0,status=0;
status=read(fd,&byte,1); /* 1 if successful, 0 if not /
if (status)
{
if (debug)
printf(“serial rec’d: %.2x \n”,byte);
return(byte);
}
else
return(-1); /
note that this allows a returned null (0x00) */
}

int serial_recvpacket(int fd, int debug, int time_ms)
{
int bytesavail=0, timecounter=0;

timecounter=(int)(time_ms/20);
while ((bytesavail !=6) && timecounter)
{
timecounter–;
poll(0,0,10); /* this is a delay of 20 ms (10+10) */
bytesavail=serial_recvbufchk(serial_fd);
}

if (debug)
printf(“bytes avail=%d\n”,serial_recvbufchk(serial_fd));
if (bytesavail == 6)
{
packet_in.target=serial_recvbyte(fd, debug);
packet_in.source=serial_recvbyte(fd, debug);
packet_in.opcode=serial_recvbyte(fd, debug);
packet_in.data0=serial_recvbyte(fd, debug);
packet_in.data1=serial_recvbyte(fd, debug);
packet_in.cksum=serial_recvbyte(fd, debug);
if (debug)
print_packet(0,packet_in.target,packet_in.source,
packet_in.opcode,packet_in.data0,
packet_in.data1,packet_in.cksum);
return(TRUE);
}
else
{
if (debug)
printf(“rx ← timed out.\n”);
return(FALSE);
}
}

int main(int argc, char *argv)
{
int debug = 0; /
debug flag */
char format = “hex”; / output data format /
int oneline = 0; /
output line format */
char port = “ttya”; / /dev/tty device /
int verbose = 0; /
verbose flag /
int waittime = 1000; /
time in ms to wait for aru to reply /
int reps = 1; /
number of reps /
int interval = 500; /
time between reps */

int index=0;
int c=0;
int opterr=0;
int timedout=0;

packet_out.source = 0; /* source address /
packet_out.target = 1; /
target address */

if (argc <= 3)
{
printf(“alarm relay unit test code, v%s\n”,version);
printf(“usage: %s [options] opcode data0 data1\n”
" options:\n"
" -d enable debug (off)\n"
" -f form output form=(hex),dec,emu\n"
" -l one line output, tab delim (2)\n"
" -p port specifies serial port (ttya)\n"
" -s source specifies source address (0)\n"
" -t target specifies target address (1)\n"
" -v enable verbose mode (off)\n"
" -w time ms to wait for aru to reply (1000)\n"
" -r reps number of repetitions (1)\n"
" -i time ms between repetitions (500)\n"
,argv[0]);
exit(1);
}

while ((c = getopt(argc, argv, “df:i:lp:r:s:t:vw:”)) != -1)
switch (c)
{
case ‘d’:
debug = 1;
break;
case ‘f’:
format = optarg;
break;
case ‘i’:
interval = strtol(optarg,(char **)NULL,0);
break;
case ‘l’:
oneline = 1;
break;
case ‘p’:
port = optarg;
break;
case ‘r’:
reps = strtol(optarg,(char **)NULL,0);
break;
case ‘s’:
packet_out.source=strtol(optarg,(char **)NULL,0);
break;
case ‘t’:
packet_out.target=strtol(optarg,(char **)NULL,0);
break;
case ‘v’:
verbose = 1;
break;
case ‘w’:
waittime=strtol(optarg,(char **)NULL,0);
break;
case ‘?’:
if (isprint (optopt))
fprintf (stderr, “Unknown option -%c'.\n", optopt); else fprintf (stderr, "Unknown option character \x%x’.\n”, optopt);
break;
default:
abort ();
}

if (debug)
{
printf(“debug=\t\t%d\nformat=\t\t%s\noneline=\t%d\n”
“port=\t\t%s\nsource=\t\t%d\ntarget=\t\t%d\n”
“verbose=\t%d\n”,
debug,format,oneline,port,packet_out.source,
packet_out.target,verbose);
//for (index = optind; index < argc; index++)
// printf (“Non-option argument %s\n”, argv[index]);
}

if ((argc-optind)<3)
{
printf("additional parameters required: "
“[options] opcode data0 data1\n”);
exit(1);
}

packet_out.opcode=strtol(argv[optind],(char **)NULL,0);
packet_out.data0=strtol(argv[optind+1],(char **)NULL,0);
packet_out.data1=strtol(argv[optind+2],(char **)NULL,0);

while (reps–)
{
if ((serial_fd=serial_open(port,debug))<0)
exit(1);
serial_setoptions(serial_fd,debug);
serial_flush(serial_fd);

packet_out.cksum= packet_out.source^packet_out.target^
packet_out.opcode^packet_out.data0^packet_out.data1;
serial_sendpacket(serial_fd,verbose,
packet_out.target,packet_out.source,
packet_out.opcode, packet_out.data0,
packet_out.data1,packet_out.cksum);

if (!serial_recvpacket(serial_fd,verbose,waittime))
{
packet_in.data0=0xff;
packet_in.data1=0xff;
timedout=1;
}
serial_close(serial_fd);

if (!strcasecmp(format,“dec”))
printf("%d",packet_in.data0);
if (!strcasecmp(format,“hex”))
printf(“0x%.2x”,packet_in.data0);
if (!strcasecmp(format,“emu”))
print_packet(1,packet_out.target,packet_out.source,
packet_out.opcode,packet_out.data0,
packet_out.data1,packet_out.cksum);
if (strcasecmp(format,“emu”))
if (oneline)
printf("\t");
else
printf("\n");

if (!strcasecmp(format,“dec”))
printf("%d\n",packet_in.data1);
if (!strcasecmp(format,“hex”))
printf(“0x%.2x\n”,packet_in.data1);
if ((!strcasecmp(format,“emu”) && (!timedout)))
print_packet(0,packet_in.target,packet_in.source,
packet_in.opcode,packet_in.data0,
packet_in.data1,packet_in.cksum);
else
if (!strcasecmp(format,“emu”))
printf(“rx ← timed out\n”);
if (reps)
poll(0,0,(interval-10));
}

if (timedout)
exit(1);
else
exit(0);
}

ok, so far i figured out from
http://qdn.qnx.com/support/docs/neutrino_2.11_en/lib_ref/t/tcgetattr.html
that QNX has independently-controlled HW flow control
for the tx and rx directions. so instead of CRTSCTS, one uses
the combination of IHFLOW and OHFLOW for incoming
and outgoing flow control respectively.

the other queries remain open.

jim



jim wrote:


ok, now a snippet which covers the next error messages, re:
aru.c:130: CRTSCTS' undeclared (first use in this function) aru.c:137: ECHOPRT’ undeclared (first use in this function)

…snip…

options.c_cflag &= ~CRTSCTS; /* no HW flow control */

jim <jds-box1@mail.losdos.dyndns.org> wrote:

ok, so far i figured out from
http://qdn.qnx.com/support/docs/neutrino_2.11_en/lib_ref/t/tcgetattr.html
that QNX has independently-controlled HW flow control
for the tx and rx directions. so instead of CRTSCTS, one uses
the combination of IHFLOW and OHFLOW for incoming
and outgoing flow control respectively.

the other queries remain open.

quick answer:

#define FNDELAY O_NONBLOCK
#define CRTSCTS (IHFLOW | OHFLOW)

for “poll”, you have to turn it into “select()”

-xtang

jim



jim wrote:



2)
ok, now a snippet which covers the next error messages, re:
aru.c:130: CRTSCTS' undeclared (first use in this function) aru.c:137: ECHOPRT’ undeclared (first use in this function)

…snip…


options.c_cflag &= ~CRTSCTS; /* no HW flow control */

xtang,
thanks, in fact i got everything to work. the poll() in my
case was just to setup a read timout, instead i used QNX’s
napms() function (which is just delay()). and i used some
#ifdef conditionals to work around the rest of the POSIX
serial issues, so i have one source to compile in both QNX
and solaris environments. thus everything is working well.

ps
i know you from the ipfilter mail list, in fact when i posted
about a “thin unix” a while back, you were the one who
suggested to use QNX … so far i really like the OS and of
course the POSIX development environment/tools. i was
wondering whether you had ported ipfilter > 3.4.6 to QNX
and, if not, do you think it would be possible for me to diff
your QNX source for 3.4.6 against the current (e.g. 3.4.20)
and have a shot at getting it to work out? in other words,
did you do major surgery to get ipf onto QNX or was it
pretty straightforward? i assume that you are using ipf in
user-space mode?

jim



Xiaodan Tang wrote:

jim <> jds-box1@mail.losdos.dyndns.org> > wrote:

ok, so far i figured out from
http://qdn.qnx.com/support/docs/neutrino_2.11_en/lib_ref/t/tcgetattr.html
that QNX has independently-controlled HW flow control
for the tx and rx directions. so instead of CRTSCTS, one uses
the combination of IHFLOW and OHFLOW for incoming
and outgoing flow control respectively.

the other queries remain open.

quick answer:

#define FNDELAY O_NONBLOCK
#define CRTSCTS (IHFLOW | OHFLOW)

for “poll”, you have to turn it into “select()”

-xtang

jim

jim wrote:

ok, now a snippet which covers the next error messages, re:
aru.c:130: CRTSCTS' undeclared (first use in this function) aru.c:137: ECHOPRT’ undeclared (first use in this function)

…snip…


options.c_cflag &= ~CRTSCTS; /* no HW flow control */

jim <jds-box1@mail.losdos.dyndns.org> wrote:

about a “thin unix” a while back, you were the one who
suggested to use QNX … so far i really like the OS and of
course the POSIX development environment/tools. i was

Oh, Great! Welcome to the QNX world :slight_smile:

wondering whether you had ported ipfilter > 3.4.6 to QNX
and, if not, do you think it would be possible for me to diff
your QNX source for 3.4.6 against the current (e.g. 3.4.20)
and have a shot at getting it to work out? in other words,
did you do major surgery to get ipf onto QNX or was it
pretty straightforward? i assume that you are using ipf in
user-space mode?

Yes, it is in user-space. But I don’t think it’ll be a simple
diff-and-patch work.

I already got permit from Darrem to distribute the QNX port,
just need to find sometime to do the new port :slight_smile:

-xtang