Re: Serial port cominication problem in linux
- From: floyd@xxxxxxxxxx (Floyd L. Davidson)
- Date: Mon, 22 May 2006 16:13:43 -0800
You've multiposted this to more than one newsgroup. That is
what crossposting is for, and it should be used.
findyasir@xxxxxxxxx wrote:
Hi all,
I am having problem comunication with serial port in linux. I have
tested the serial port with attaching a serial modem and dialing the
numbers with wvdial. it works okie but when i connect HSM to the serial
I'm not sure what "HSM" means...
port it sends the message but i did not get a reply. mean it always
being blocked in the read function. The same hsm is working fine in
unix. so there is no problem with hsm. I am using the code as given
Linux *is* a unix. Whatever it is that you think you tested
this code on did *not* work using the same code! (You've
probably copied it incorrectly.)
Lets go over it, pedantically... (very pedantically, in fact).
main()
{
That may "work", but it is very poor programming style.
int main(void)
{
int fd,c, res,i;
FILE *td;
Here is another style issue.
Low level access to files is through "file descriptors". They are
accessed via the open(2) function. Typically the variable used is
named "fd", or has that as part of the symbol name. For example
input_fd, out_fd, mdm_fd, etc etc.
High level access, via the STDIO library, accesses file through a
pointer to a FILE struct. Typically the variable used is named
"fp", or has that as part of the symbol name.
Which is to say that your "FILE *td" is using a variable name
that should only be used for a file descriptor instead of for a
FILE pointer.
struct termios oldtio,newtio;
char buf[255];
printf("Going to open HSM port:\n");
td=fopen("test.log","a");
It is essential to test for errors to assure that the call to
fdopen(3) actually did succeed.
fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY );
You really should not use embedded strings that way. At
a minimum, use a #define.
You also should open the device in non-blocking mode, otherwise
it will probably hang right at this point and never go further,
unless your RS-232 cabling or the modem configurations is
non-standard (if carrier detect is not active, this call will
block).
Also, it is best to modularize the various parts of the program,
hence the open(2) call should probably be in a separate function.
You can call it from main() like this (with a couple of other
defines that you might want to use too).
#define MODEM_PORT "/dev/ttyS0"
#define LOG_FILE "./text.log"
#define BAUD B9600
....
int main(void)
{
int fd;
...
fd = port_open(MODEM_PORT);
if (fd < 0) {
fprintf(td,"Can not open the comport:\n");
printf("Can not open the comport:\n");
perror("MODEM_PORT");
exit(EXIT_FAILURE);
}
...
return EXIT_SUCCESS;
}
Here is a suitable port_open() function,
int
serial_open(char *device)
{
int fd, oldflags;
/* O_NONBLOCK allows open even with no carrier detect */
if (-1 != (fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK))) {
/* clear O_NONBLOCK to allow read() and write() to block */
if ((-1 != (oldflags = fcntl(fd, F_GETFL, 0))) &&
(-1 != fcntl(fd, F_SETFL, oldflags & ~O_NONBLOCK))) {
/* flush input and output */
if (-1 == tcflush(fd, TCIOFLUSH)) {
close(fd);
return -1;
}
} else {
close(fd);
return -1;
}
}
return fd;
}
if (fd <0) {
fprintf(td,"Can not open the comport:\n");
printf("Can not open the comport:\n");
perror("/dev/ttyS0");
exit(-1);
The argument to exit(2) is used as an 8 bit value; hence -1 will
actually be 255. To be correct per the ISO C Standard, and thus
totally portable, you should only use EXIT_FAILURE and
EXIT_SUCCESS. That only gains portability to VMS though, so it
isn't really important in a program doing system calls! But
only values between 0 and 255 should be used.
}
Note that I would put all of this termios configuration into
a separate function too.
tcgetattr(fd,&oldtio); /* save current port settings */
There is *no* point in saving and restoring the old termios value.
If the program crashes or is killed with SIGKILL those values will
not be restored. Hence any program following the use of this one
must *necessarily* be prepared to reconfigure termios anyway.
bzero(&newtio, sizeof(newtio));
Use memset(3), not bzero().
memset(&newtio, 0, sizeof newtio);
Note that technically this violates the POSIX standard.
However, the difference is not going to cause you as many
problems as will following POSIX...
newtio.c_cflag = B9600 | CRTSCTS | CS8 | CLOCAL | CREAD;
Do not set the port speed via the cflag member of struct termios.
See below for the way to do it.
Likewise, you are enabling hardware flow control, but disabling
all modem control lines! If you want flow control (and indeed you
probabl do), this is what you want,
newtio.c_cflag = CRTSCTS | CS8 | CREAD;
newtio.c_iflag = IGNPAR;
You probably want this,
newtio.c_iflag = IGNPAR | IGNBRK;
newtio.c_oflag = 0;
/* set input mode (non-canonical, no echo,...) */
newtio.c_lflag = 0;
newtio.c_cc[VTIME] = 0; /* inter-character timer unused */
All three of the above values have previously been set to 0, so
this code is redundant.
newtio.c_cc[VMIN] = 1; /* blocking read until 5 chars
received */
Obviously the comment is wrong. Note that you *do* want to set
this to either 1 or 0, not to 5.
To set the bit rate, do this:
#define BAUD B9600
....
int port_config(int fd)
{
...
if (0 > cfsetispeed(&newtio, BAUD)) {
return -1;
}
if (0 > cfsetospeed(&newtio, BAUD)) {
return -1;
}
return 0;
}
tcflush(fd, TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);
You really should check this call for errors. Note though that
even if there is no error indicated it does *not* mean that it
did in fact set all of the values! It will return 0 if *any*
value is correctly set. Nobody is much concerned with that, but
if you want to actually verify correct configuration it gets
fairly complicated. Regardless you do want to at least know if
it failed entirely.
fprintf(td,"Going to send the Data to Hsm:\n");
printf("Going to send the Data to Hsm:\n");
if(write(fd,"00703C3C30",10)<=0){
fprintf(td,"Message Can not be sent to Hsm:\n");
printf("Message Can not be sent to Hsm:\n");
}
fprintf(td,"Message sent to Hsm:\n");
printf("Message sent to Hsm:\n");
fprintf(td,"\n***********************************************:\n");
printf("\n***********************************************:\n");
Okay, the above should write to the device.
I don't know what this "hsm" is, but my bet is that whatever
way it responds to the above data will be a string of characters.
However, your code below is going to be executed before that
string is sent. It will therefore block while waiting for
data.
The termios settings chosen disabled the timer, and set the
minimum number of bytes to return at 1. The call to read(2)
sets the maximum bytes to return at 254. The effects of this
configuration are as follows:
1) the call to read(), if there is data available when it
is called, will return up to a maximum of 254 characters,
if that many have been buffered.
2) the call to read(), if there is no data available when
it is called, will block until data becomes available.
A) if no data ever becomes available, read() will block
forever.
B) if a single byte is received by the serial port, that
will cause read() to immediately return with 1 byte.
Almost certainly your program is going to call read(2) *before*
the hsm device has time to send data to the port. That means
the read() call will block, and when the first byte of the
response is available it will be returned. You will *never see*
the rest of the data with the code below.
fprintf(td,"Going to read Data from Hsm:\n");
printf("Going to read Data from Hsm:\n");
res = read(fd,buf,254);
buf[res]=0;
fprintf(td,"Byte Read :%d\n",res);
printf("Byte Read :%d\n",res);
fprintf(td,"Data Read :%s\n", buf);
printf("Data Read :%s\n", buf);
Instead of what you have, you probably want to do something in a
loop that continues to look for data. Typically that would be
timed so that it waits at least a minimum time to see a response
from the device, and then either looks for a known data frame
termination or for some predetermined time interval as an
indication that all data has been received. Which you want to
do depends on what this "hsm" device does.
I would suggest that instead of setting a minimum number of
characters to be read in the port configuration, you might want
instead to set a time interval. You can then call read() in a
loop, with a delay between calls.
Or you can disable the timer entirely, by setting both VTIME
and VMIN to 0, and use your own timer.
...
/*
* when VTIME is greater than 0 and VMIN is 0, the
* timer functions as a read timer rather than as
* an inter-byte timer.
*/
newtio.c_cc[VTIME] = 1; /* 1/10th of a second timer */
newtio.c_cc[VMIN] = 0;
...
unsigned int bytes;
bytes = get_data(fd, buf, 254);
if (bytes < 0) {
...
}
...
WARNING: This code is *not* tested, and probably contains
serious bugs, such as off-by-one errors, etc. It is intended as
an example that can be used when c_cc[VTIME] and c_cc[VMIN] are
both set to zero.
/* these values must be larger than 10 */
#define PRE_TIMEOUT 5000 /* maximum milliseconds before data */
#define POST_TIMEOUT 100 /* minimum milliseconds after data */
#include <time.h>
unsigned int
get_data(int fd, char *buf, int size)
{
int bytes, cnt = 0, timerflag = 0;
unsigned int offset;
struct timespec tv;
tv.tv_sec = 0;
tv.tv_nsec = 10000 /* 10 ms, minimum granularity on some systems */
while (1) {
/* quite when we have enough data */
if (size < 1) {
break;
}
/* pre data timer */
if (!timerflag && cnt >= PRE_TIMEOUT / (tv.tv_nsec / 1000)) {
break;
}
/* post data timer */
if (timerflag && cnt >= POST_TIMEOUT / (tv.tv_nsec / 1000)) {
break;
}
if (0 > (bytes = read(fd, buf + offset, 1))) {
return -1;
}
if (bytes > 0) {
timerflag = 1;
cnt = 0;
--size;
++offset;
continue;
}
++cnt;
nanosleep(*tv, NULL);
}
return offset;
}
tcsetattr(fd,TCSANOW,&oldtio);
As noted, unnecessary.
--
Floyd L. Davidson <http://www.apaflo.com/floyd_davidson>
Ukpeagvik (Barrow, Alaska) floyd@xxxxxxxxxx
.
- Follow-Ups:
- Re: Serial port cominication problem in linux
- From: findyasir
- Re: Serial port cominication problem in linux
- References:
- Serial port cominication problem in linux
- From: findyasir
- Serial port cominication problem in linux
- Prev by Date: Re: alarm() - what am I doing wrong???
- Next by Date: Re: GUI Development Options
- Previous by thread: Serial port cominication problem in linux
- Next by thread: Re: Serial port cominication problem in linux
- Index(es):
Relevant Pages
|