Thread: Can't Read From Serial Port

  1. #1
    Registered User HalNineThousand's Avatar
    Join Date
    Mar 2008
    Posts
    43

    Can't Read From Serial Port

    I'm new to C and C++ and I'm using it to communicate with a device (a HD Radio) hooked up to the serial port. I know this should be simple stuff and that it's just a matter of opening the serial port like a file and reading from/writing to it. I have a program in Perl that can send commands to the device and another that reads the replies from the device. I can run them both at the same time in separate windows and watch the listener give me the reply codes when I enter a command through the sender.

    I would have preferred to write this in just C, but I'll be forwarding code to another project that will be using it and they've said they need the libraries in C++, so I'm using C++, even though the methods I'm using are not C++ specific.

    I have a sender program in C++ that sends commands to the device and I have a listener in C++ that is not receiving the data properly. Ideally I want the listener to block while waiting for a new byte from the serial port because it'll be one of two threads and it'll spend most of its time blocked (overall, not much data comes back in), which makes thread management easier.

    Last night I was finally able to get something from the serial port, but it was all 0x00, which is not what I know was being sent.

    I've also found that the C++ sender was working fine without me setting any serial port parameters, but that's probably because they were still set from the Perl program. As I said, the Perl listener is working, but the C++ one is not. Here's the code to set up the serial port from Perl:

    Code:
    	$port = "/dev/ttyS0";
    	$baud = "115200";
    	$ob = Device::SerialPort->new ($port) || die "Can't Open $port: $!";
    	$ob->baudrate($baud) || die "failed setting baudrate";
    	$ob->parity("none") || die "failed setting parity";
    	$ob->databits(8) || die "failed setting databits";
    	$ob->handshake("none") || die "failed setting handshake";
    	$ob->write_settings || die "no settings";
    Here's the code I'm using to open and read from the serial port in C++:

    Code:
    #include <iostream>
    #include <string>
    #include <vector>
    #include <fcntl.h>
    #include <termios.h>
    
    void openinfile() {
    	long BAUD = B115200;
    	long DATABITS = CS8;
    	long STOPBITS = 0;
    	long PARITYON = 0;
    	long PARITY = 0;
    	struct termios options;
    	
    	cout << "Opening port for listening: " << serdev << endl;
    //serdev is already set to /dev/ttyS0, serfd is an int
    	serfd = open(serdev.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
    	tcgetattr(serfd, &options);
    	options.c_cflag |= B115200;
    	options.c_cflag &= ~PARENB;
    	options.c_cflag &= ~CSTOPB;
    	options.c_cflag &= ~CSIZE;
    	options.c_cflag |= CS8;
    	options.c_cflag |= (CLOCAL | CREAD);
    // 	options.c_iflag &= ~(IXON | IXOFF | IXANY);
    	int rc = tcsetattr(serfd,TCSANOW ,&options);
    	cout << "Attr return code: " << rc << endl;
    //Return code is always 0
    
    // 	options.c_cflag |= CRTSCTS;
    	if (serfd == -1 ) {
    		perror("open_port: Unable to open port");
    		exit(1);
    	} else {
    // 		fcntl(serfd, F_SETFL, 0);
    		printf("Port 1 has been sucessfully opened and %d is the file description\n", serfd);
    	}
    	return;
    }
    
    void readinfile() {
    	char cIn, *buff;
    	int i, rd;
    //This next line should make us block until a byte comes in, but
    // with it in place, no data comes in.  With it commented out,
    // all 0x00 bytes are coming in now and not as many as should be for
    // the data coming back from the device.
    // 	fcntl(serfd, F_SETFL, 0);
    	while (true) {
    		rd = read(serfd, buff, 10);
    //DEBUG: Remove when I know it's blocking and not looping.
    // 		cout << "Waiting: " << rd << endl;
    		if (rd <= 0)
    			continue;
    //DEBUG: Remove this line once I know it's actually reading in characters!
    // 		cout << "Got a character!\n";
    		cIn = buff[0];
    //chout just prints the character as a 2 digit hex number, the decimal format, then the
    // character itself it it's in printable range.
    		chout(cIn);
    	}
    	return;
    }
    I can post the whole program if needed. It's between 80 - 100 lines long.

    My guess is that I'm doing something or not doing something incredibly obvious that a new C++ programmer might not see but someone with experience will laugh at. If so, it's likely something on the low level end or Linux/serial port specific.

    Why is it I either get no characters at all or only 0x00 from the C++ program?

    I've been using a serial port article at http://www.easysw.com/~mike/serial/serial.html for one source and I've been doing a lot of Googling. It looks to me like it should work, but it doesn't.

    Thanks for any help and links!

  2. #2
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    Check that the serial port is not being held by some other process.

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  3. #3
    Registered User HalNineThousand's Avatar
    Join Date
    Mar 2008
    Posts
    43
    How do I check that? And if it were being held, wouldn't that block the Perl program as well?

  4. #4
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    Quote Originally Posted by HalNineThousand View Post
    How do I check that? And if it were being held, wouldn't that block the Perl program as well?
    Good point.

    What about this idea then:
    Code:
    options.c_cflag &= ~CRTSCTS;
    The reason is that there's nothing in your code CLEARING c_cflag, and CRTSCTS may well be set when you request it from the serial port, and your Perl code certainly sets flowcontrol to "none", which is not the same as "don't set CRTSCTS".

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  5. #5
    Registered User HalNineThousand's Avatar
    Join Date
    Mar 2008
    Posts
    43
    Quote Originally Posted by matsp View Post
    Good point.

    That's a point I had to keep reminding myself: The Perl program works just fine, so there's every reason to expect this program to work.

    What about this idea then:
    Code:
    options.c_cflag &= ~CRTSCTS;
    The reason is that there's nothing in your code CLEARING c_cflag, and CRTSCTS may well be set when you request it from the serial port, and your Perl code certainly sets flowcontrol to "none", which is not the same as "don't set CRTSCTS".
    Okay, tried it and it makes no difference. No output at all.

    Here's an interesting change, though. Out of curiosity, after not seeing a change from that, I uncommented the line:

    Code:
     	options.c_cflag |= CRTSCTS;
    and ran it that way. Not only did I NOT get any data, but the sender, which was still running in another window, could no longer communicate with the device. I figure that's not unexpected, since it basically "pulls the rug out from under" the currently running sender by changing the settings it was expecting to use.

    Is there any chance the output is being buffered and I wouldn't see anything until there were 256 or 512 or some other number of bytes? (I doubt it, but still, just asking...)

    Also, if I run the Perl program first, which sets all the params, then run the listener, should the listener's connection be dealing with whatever settings the Perl program used -- that is, if I don't specify any settings?
    Last edited by HalNineThousand; 03-19-2008 at 10:20 AM. Reason: Tried something else

  6. #6
    Registered User HalNineThousand's Avatar
    Join Date
    Mar 2008
    Posts
    43
    I changed:
    Code:
    rd = read(serfd, buff, 10);
    by changing the 10 to a 1 so I'd be reading one byte at a time. Then I did get data back, but it was all 0xFFFFFFA5. I figure I'm not doing the conversion properly, since it's only one byte (and probably just 0xA5 being written as a negative), but I'm still getting all the same character.

  7. #7
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    What is your sender sending? A constant char, or a packet of some sort?

    Yes, A5 printed as a signed char will be 0xFFFFFFA5.

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  8. #8
    Registered User HalNineThousand's Avatar
    Join Date
    Mar 2008
    Posts
    43
    First, I ran again, with a slight mod, and it's all coming out 0x00 now, but it's the right number of bytes, so at least it's getting characters and I'm willing to accept that I might have messed up the conversion routine.

    The sender is usually sending this string:
    0xA4 0x10 0x02 0x01 0x00 0x00 0x01 0x00 0x00 0x00 0x79 0x03 0x00 0x00 0x00 0x00 0x00 0x00 0x34.

    I'm using that as a test command because it's easy to hear the results. When I sent that, I should have gotten an echo back confirming the device received it. I got the right number of bytes, but all 0x00. I sent a second command and got the same result: the right number of bytes, all 0x00.

    I'm including the conversion/printout function I'm using to output the results, just in case I'm doing something wrong there, but it worked before, with other data, so it shouldn't be a problem.

    Code:
    void chout(char cIn) {
    
    	char c;
    	char chex [10], cdec[10];
    	unsigned int n, iIn;
    	
    	iIn = c;
    	if (iIn == 0xA4) {
    		cout << endl << endl;
    	}
    	sprintf(chex, "0x%2x", iIn);
    	if (chex[2] == ' ')
    		chex[2] = '0';
    	cout << chex << ":" << iIn << " [";
    	if (iIn >= 32 && iIn <= 126) {
    		cout << c;
    	}
    	cout << "], ";
    	return;
    }

  9. #9
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    Code:
    void chout(char cIn) {
    
    	char c;
    	char chex [10], cdec[10];
    	unsigned int n, iIn;
    	
    	iIn = c;
    Is the above red sections influencing the result, you think?

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  10. #10
    Registered User HalNineThousand's Avatar
    Join Date
    Mar 2008
    Posts
    43
    Uh, yeah. I knew that. (He says turning quite red in the face, realizing what an idjit mistake that was.) I'm wondering if working with Java that forces me to set values could have made me lazy, since I missed that c had not been set.

    Okay, so it's reading in data now and I'm down to 2 questions that should resolve it, one's a serial question, one's the conversion.

    1) When I'm converting, I use this:
    Code:
    sprintf(chex, "0x&#37;2x", iIn);
    Which, as I understand it, specifies a 2 byte unsigned hex number. Whether I use %2x or %2X, I still 0xffffffa4 or the same with capital letters/digits. I also do a comparison on the int, and I've tried it as an int and an unsigned in, to 0xA4 (which always signals a new string of data) but it doesn't work. What am I doing wrong that it won't be seen as 0xA4 or just plain 164 instead of as 4294967204 or -92? Even if I try to compare the char version of it to 0xA4, it doesn't match.

    2) I included the commented out line:
    Code:
    fcntl(serfd, F_SETFL, 0);
    which, as I understand it, should make the read() function block until there's data on the serial port, but a cout statement in that loop shows it's not doing that. I want it to block and wait because I want the thread idle unless it's actually processing data. What do I need to do to make sure it blocks while waiting?

    And thanks, by the way, for your help on this. I think the "won't read" issue was because of a mistake I had in waiting for 10 bytes instead of 1 as well as needing to st the flags as you pointed out.

  11. #11
    Registered User HalNineThousand's Avatar
    Join Date
    Mar 2008
    Posts
    43
    Got the conversion issue fixed. I had not realized a char could be unsigned. It didn't quite make sense to me until I looked it over.

    All that's left is getting it to block while waiting for a character.

  12. #12
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by HalNineThousand View Post
    Uh, yeah. I knew that. (He says turning quite red in the face, realizing what an idjit mistake that was.) I'm wondering if working with Java that forces me to set values could have made me lazy, since I missed that c had not been set.
    Compiling with optimizations and warnings turned on should reveal such problems.

  13. #13
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    Add -Wall to your gcc compile line, and you should get more info about various things that MAY be wrong.

    I think you should remove O_NDELAY from your open, and that will fix your "need to wait for input".

    If you still want to time-out on input, you can set the c_cc[VTIME] = n, where n is 0.1s periods that you are willing to way, e.g. 30 will wait for 3 seconds before "giving up".

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  14. #14
    Registered User HalNineThousand's Avatar
    Join Date
    Mar 2008
    Posts
    43
    Quote Originally Posted by matsp View Post
    Add -Wall to your gcc compile line, and you should get more info about various things that MAY be wrong.
    Thanks. I've had to "hit the ground running on this." I had last week and this week to work on it and I've never written a line of C or C++ code before. I started reading, tried a few programs, and found out my book was 10+ years out of date! Since then I've been picking up what I can, which means I miss a LOT. I hope, after this and my next Java project, I have time to actually learn C++. I used to program in Assembler and like having the finer control over the system.

    Quote Originally Posted by matsp View Post
    I think you should remove O_NDELAY from your open, and that will fix your "need to wait for input".
    Eventually I'll be merging the send program and the listener. This brings up another question: When I've got them merged together, I was expecting to open the port just once, since it's read/write. Should I open it twice, once for reading and once for writing? If not, and I only open it once (which seems more logical to me), will not using O_NDELAY effect sending commands to the port?

    Quote Originally Posted by matsp View Post
    If you still want to time-out on input, you can set the c_cc[VTIME] = n, where n is 0.1s periods that you are willing to way, e.g. 30 will wait for 3 seconds before "giving up".
    If, by time out, you mean stop listening after a certain length of time, I don't want to do that. The radio could go for a long time without sending input. If it's an HD station playing classical music, it could go for 10 to 20 minutes before sending a new title or other info. During that time, I just want the listening thread to sit there.

    Thanks, again, for all the help. I'll try leaving out O_NDELAY and see what that does.

  15. #15
    Registered User HalNineThousand's Avatar
    Join Date
    Mar 2008
    Posts
    43
    Just an update:

    It's working fine now, not only on a regular RS232C interface on my computer, but also through a USB port connected to a USB->RS232C adaptor. I eliminated the O_NDELAY from the open statement and it works fine for both reading and writing (but I haven't tried for it to do both at the same time yet) and it seems like it is blocking at the read until a new byte comes in. I'll be testing that again later.

    Thanks for all the help on this!

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Serial Port Questions
    By valaris in forum Tech Board
    Replies: 2
    Last Post: 05-22-2009, 08:26 AM
  2. FTP program
    By jakemott in forum Linux Programming
    Replies: 14
    Last Post: 10-06-2008, 01:58 PM
  3. Serial Port Issues (again!)
    By HalNineThousand in forum Linux Programming
    Replies: 6
    Last Post: 04-09-2008, 08:26 PM
  4. HELP with storing serial port data into txt file
    By inmaterichard in forum C Programming
    Replies: 2
    Last Post: 04-02-2008, 02:20 AM
  5. Serial port read..can someone tell me..
    By Unregistered in forum C Programming
    Replies: 3
    Last Post: 06-27-2002, 08:21 AM