Thread: RS232 Serial Communication Help

  1. #1
    Registered User
    Join Date
    Apr 2011
    Posts
    11

    RS232 Serial Communication Help

    I am designing a program in Xcode on my Mac using C, the program needs to receive power usage data from a wireless node and then insert the data into my database. The connection is using RS232 serial communication. Every 5 seconds a node sends 2 bytes, which my program must check and combine to extract the power usage data. The bytes are organized as follows:

    Byte: X N P _ _ _ _ _
    Bit #:7 6 5 4 3 2 1 0

    X - not used
    N - identifies 1 of 2 nodes
    P - identifies upper or lower byte, 1 = upper and 0 = lower
    The remaining bits are combined to get the power usage data.

    Power Usage = bits 0 through 4 of upper combined with bits 0 through 4 of lower.

    I am receiving the bytes, but my code is not converting the power usage data correctly. I am working with an Engineering team, who have designed and built the nodes. They are developing everything in windows. They have a similar program for testing purposes written in C#. Their communications work fine, and I modeled my byte conversion after theirs. (Not syntax, but the bitwise operations) However, my program only outputs 0.0 for power usage data.

    Can anyone see where I am going wrong here? Thanks in advance.

    Code:
    uint8 chout; // serial input data
    uint8 nodeBuff[2]; // array to hold node byte data
    nodeBuff[0] = 0x00;
    nodeBuff[1] = 0x00;
    int mainfd = 0; // File descriptor
    int nodeNum; // keeps track of which node data refers to
    int usageData; // holds raw power usage data
    double powerData; // holds power usage data to be sent to database
    struct termios options;
    
    mainfd = open_port(); // Get file descriptor for port
    fcntl(mainfd, F_SETFL, FNDELAY); // Configure port reading
    tcgetattr(mainfd, &options); // Get the current options for the port
    	
    // Set the baud rates to 9600
    cfsetispeed(&options, B9600);
    cfsetospeed(&options, B9600);
        
    // Enable the receiver and set local mode
    options.c_cflag |= (CLOCAL | CREAD);
    options.c_cflag &= ~PARENB; // Mask the character size to 8 bits, no parity
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |=  CS8; // Select 8 data bits
    options.c_cflag &= ~CRTSCTS; // Disable hardware flow control
    	
    // Enable data to be processed as raw input
    options.c_lflag &= ~(ICANON | ECHO | ISIG);
    	
    // Set the new options for the port
    tcsetattr(mainfd, TCSANOW, &options);
    Serial Comm Connection Initialization

    Code:
    while (1){
    		
    	read(mainfd, &chout, sizeof(chout)); // Read character from ABU
    		
    	if (chout != 0){
    			
    		// checks for upper byte flag
    		if (((chout & 0x20) == 0x20)) {
    			nodeBuff[0] = chout;
    		} 
    			
    		// else it is the lower byte
    		else{
    			nodeBuff[1] = chout;
    				
    			// combine the upper and lower power usage data
    			usageData = (nodeBuff[0] & 0x1f) << 5; // upper half
    			usageData = usageData + (nodeBuff[1] & 0x1f); // lower half
    			powerData = usageData * 1.83255;
    				
    			// check for node number
    			if ((nodeBuff[0] & 0x40) == 0x40) {
    				nodeNum = 1; // data is for node 1
                                    // add data to database
    			}
    			else {
    				nodeNum = 0; // data is for node 0				
                                    // add data to database
    			}
    		}
    	}
    		
    	chout = 0; // reset serial input data variable
    	usleep(10); // pause for 10 miliseconds
    	
    }
    I get both bytes, but the power usage always ends up 0.0... If this isn't enough information, I'll be happy to post more for you.

    Thanks!

  2. #2
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,660
    > usageData = (nodeBuff[0] & 0x1f) << 5; // upper half
    I think I would do
    usageData = ((int)nodeBuff[0] & 0x1f) << 5; // upper half

    My guess is you're shifting too far in an unsigned char, and losing all the good stuff in the process.

    What do you see with say
    printf("%02x %02x\n", nodeBuff[0], nodeBuff[1] );
    Are the bytes OK?
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  3. #3
    Registered User
    Join Date
    Apr 2011
    Posts
    11
    Thanks for the quick reply!

    The problem is, I don't have the nodes... The engineering team has those, and I only have access to them every so often. The next time I will be able to test the communications will be Sunday evening...
    Last edited by TobiasKillerB; 04-29-2011 at 12:37 PM.

  4. #4
    Registered User
    Join Date
    Apr 2011
    Posts
    11
    Code:
    			// checks for upper byte flag
    			if ((chout & 0x20) == 0x20) {
    				nodeBuff[0] = chout;
    // Point1
    				printf("%02x %02x\n", nodeBuff[0], nodeBuff[1] );
    			} 
    			
    			// else it is the lower byte
    			else{
    				nodeBuff[1] = chout;
    // Point2
    				printf("%02x %02x\n", nodeBuff[0], nodeBuff[1] );
    With Node 0 plugged in and running, it never even got to 'Point2' so I had to add the printf statement at 'Point1' and this is what the console output for five cycles:

    Code:
    Running…
    Modem found with BSD path: /dev/cu.usbserial-A700ewzO
    20 00
    20 00
    20 00
    20 00
    20 00
    kill
    quit

  5. #5
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,660
    Is that good or bad?

    Why do you have this?
    if (chout != 0)

    Do you ever get \0 bytes which are to be ignored?
    Or are you trying to work out whether read was successful nor not, by making chout 0 before you call it?

    Anyway, you should do this
    Code:
    if ( read(mainfd, &chout, sizeof(chout)) == 1 ) {
    }
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  6. #6
    Registered User
    Join Date
    Apr 2011
    Posts
    11
    Thanks for getting back to me.

    Quote Originally Posted by Salem View Post
    Is that good or bad?
    Ya, this is bad... It means that I am only getting 1 byte each time the node sends me a signal, and it should be 2.

    Why do you have this?
    if (chout != 0)

    Do you ever get \0 bytes which are to be ignored?
    Or are you trying to work out whether read was successful nor not, by making chout 0 before you call it?
    At the end of each while loop, I reset chout to 0 before a small sleep time.

    Anyway, you should do this
    Code:
    if ( read(mainfd, &chout, sizeof(chout)) == 1 ) {
    }
    I will give this a try and see how it works.

    Thanks again,
    Tim

  7. #7
    Registered User
    Join Date
    Apr 2011
    Posts
    11
    Well now it is reading in both bytes. But I'm still not getting any information for the power usage.

    Here is my updated code.
    Code:
    	while (1){
    		
    		if (read(mainfd, &chout, sizeof(chout)) == 1){ // Read character from ABU
    			
    			// checks for upper byte flag
    			if ((chout & 0x20) == 0x20) {
    				nodeBuff[0] = chout;
    			} 
    			
    			// else it is the lower byte
    			else if ((chout & 0x20) == 0x00){
    				nodeBuff[1] = chout;
    				printf("%02x %02x\n", nodeBuff[0], nodeBuff[1] );
    				
    				// combine the upper and lower power usage data
    				usageData = (nodeBuff[0] & 0x1f) << 5; // upper half
    				usageData = usageData + (nodeBuff[1] & 0x1f); // lower half
    				powerData = usageData * 1.83255; // converts data into watts for storage
    				
    				// check for node number consistancy
    				if ((nodeBuff[0] & 0x40) == 0x40) {
    					nodeNum = 2; // data is for node 2, database uses node 1 & 2 not node 0 & 1 
    					snprintf(queryString, 999, "INSERT INTO Seconds(Node_Num, Power_Usage) VALUES('%d','%f')", nodeNum, powerData);
    					freeResult(executeQuery(queryString));
    					printf("Node1 = %f.\n\n", powerData);
    				}
    				else if ((nodeBuff[0] & 0x40) == 0x00){
    					nodeNum = 1; // data is for node 1
    					snprintf(queryString, 999, "INSERT INTO Seconds(Node_Num, Power_Usage) VALUES('%d','%f')", nodeNum, powerData);
    					freeResult(executeQuery(queryString));
    					printf("Node0 = %f.\n\n", powerData);					
    				}
    				else { // reset nodeBuff values
    				nodeBuff[0] = 0xff;
    				nodeBuff[1] = 0xff;
    				}
    			}
    			else {
    				
    			}
    //			chout = 0; // reset serial input data variable
    			usleep(10); // pause for 10 miliseconds
    		}

    And here is 2 cycles of output.
    Code:
    20 00
    Node0 = 0.000000.
    20 00
    Node0 = 0.000000.

  8. #8
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,660
    Is your serial line set to 9600 baud, 8 bits, no parity, one stop bit?
    It's a bit strange that it's consistently 20 00 all the way.

    I would look carefully at your settings for c_cflag. Particularly, that stop bits and parity are correct.
    I am slightly suspicious that the &= lines to clear various flags are not clearing enough bits (where certain features occupy multiple bits).

    Also worth checking, the return results of tcset/getattr functions.

    As a debug aid, perhaps look into this
    picocom(8) - Linux man page (source here -> picocom - Minimal dumb-terminal emulation program - Google Project Hosting)
    It (or so it says) is a really simple dumb serial line tool where you can mess about with baud rates, stop bits etc without leaving the program.

    Does the MacOS come with a simple terminal program (much like Windows comes with hyperterminal)?
    This too could be used just to confirm the serial line settings.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  9. #9
    Registered User
    Join Date
    May 2010
    Posts
    4,633
    Have you tried using the blocking mode for the port, to make your program wait for valid data.

    Code:
    fcntl(fd, F_SETFL, 0);
    Reading Data from the Port

    Reading data from a port is a little trickier. When you operate the port in raw data mode, each read(2) system call will return the number of characters that are actually available in the serial input buffers. If no characters are available, the call will block (wait) until characters come in, an interval timer expires, or an error occurs. The read function can be made to return immediately by doing the following:

    fcntl(fd, F_SETFL, FNDELAY);

    The FNDELAY option causes the read function to return 0 if no characters are available on the port. To restore normal (blocking) behavior, call fcntl() without the FNDELAY option:

    fcntl(fd, F_SETFL, 0);

    This is also used after opening a serial port with the O_NDELAY option.
    Also how are actually opening the port? Normally the open command for a Linux is
    Code:
    fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
    And you might want to also want to select RAW input
    Choosing Raw Input

    Raw input is unprocessed. Input characters are passed through exactly as they are received, when they are received. Generally you'll deselect the ICANON, ECHO, ECHOE, and ISIG options when using raw input:

    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    And maybe disable software flow control:
    Setting Software Flow Control

    Software flow control is enabled using the IXON, IXOFF, and IXANY constants:

    options.c_iflag |= (IXON | IXOFF | IXANY);

    To disable software flow control simply mask those bits:

    options.c_iflag &= ~(IXON | IXOFF | IXANY);

    The XON (start data) and XOFF (stop data) characters are defined in the c_cc array described below.
    The quotations are from this page: Serial Programming Guide.

    Jim

  10. #10
    Registered User
    Join Date
    Apr 2011
    Posts
    11
    I added in some more options, and I'm still not getting data. Here is the section of code where I set the flags.
    Code:
    	mainfd = open_port(); // Get file descriptor for port
    	fcntl(mainfd, F_SETFL, 0); // Configure port reading
    	tcgetattr(mainfd, &options); // Get the current options for the port
    	
    	// Set the baud rates to 9600
    	cfsetispeed(&options, B9600);
    	cfsetospeed(&options, B9600);
        
    	// Enable the receiver and set local mode
    	options.c_cflag |= (CLOCAL | CREAD);
    	options.c_cflag &= ~PARENB; // Mask the character size to 8 bits, no parity
    	options.c_cflag &= ~CSTOPB;
    	options.c_cflag &= ~CSIZE;
    	options.c_cflag |=  CS8; // Select 8 data bits
    	options.c_cflag &= ~CRTSCTS; // Disable hardware flow control
    	
    	// Enable data to be processed as raw input
    	options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    	
    	// Disable software flow control
    	options.c_iflag &= ~(IXON | IXOFF | IXANY);
    	
    	// Set the new options for the port
    	tcsetattr(mainfd, TCSANOW, &options);
    And here is the section from open_port() where I open the port:
    Code:
    	fd = open(bsdPath, O_RDWR | O_NOCTTY | O_NDELAY);
    I am still waiting to hear back from the engineering group on the flag settings they are using. I also plugged in node 1 to see if there was any difference in the data it is sending and this is the output that I got:
    Code:
    Modem found with BSD path: /dev/cu.usbserial-A700ewzO
    chout = 60
    chout = 40
    nodeBuff[0] = 60
    nodeBuff[1] = 40
    Node1 = 0.000000.
    
    chout = 20
    chout = 0
    nodeBuff[0] = 20
    nodeBuff[1] = 00
    Node0 = 0.000000.
    
    chout = 60
    chout = 40
    nodeBuff[0] = 60
    nodeBuff[1] = 40
    Node1 = 0.000000.
    
    chout = 20
    chout = 0
    nodeBuff[0] = 20
    nodeBuff[1] = 00
    Node0 = 0.000000.
    I split up the print lines and added some labels, hopefully it makes sense.

    Thanks again to both of you for your help,
    Tim

  11. #11
    Registered User
    Join Date
    Mar 2007
    Posts
    142
    Have you seen this: Working With a Serial Device

    and this comment in the code axample:

    // Get the callout device's path (/dev/cu.xxxxx).
    // The callout device should almost always be
    // used. You would use the dialin device (/dev/tty.xxxxx) when
    // monitoring a serial port for
    // incoming calls, for example, a fax listener.

    When I was writing code for communication with the POS printer over serial port, my main problem was that I used tty and not callout.

    EDIT - In the mean time you posted the message above with this line: /dev/cu.usbserial-A700ewzO
    Last edited by idelovski; 05-02-2011 at 12:34 PM.

  12. #12
    Registered User
    Join Date
    May 2010
    Posts
    4,633
    The only other thing I normally do is set the timeouts since RAW mode is being used.
    Code:
    	options.c_cc[VMIN] = 1;
    	options.c_cc[VTIME] = 50;
    Setting Read Timeouts

    UNIX serial interface drivers provide the ability to specify character and packet timeouts. Two elements of the c_cc array are used for timeouts: VMIN and VTIME. Timeouts are ignored in canonical input mode or when the NDELAY option is set on the file via open or fcntl.

    VMIN specifies the minimum number of characters to read. If it is set to 0, then the VTIME value specifies the time to wait for every character read. Note that this does not mean that a read call for N bytes will wait for N characters to come in. Rather, the timeout will apply to the first character and the read call will return the number of characters immediately available (up to the number you request).

    If VMIN is non-zero, VTIME specifies the time to wait for the first character read. If a character is read within the time given, any read will block (wait) until all VMIN characters are read. That is, once the first character is read, the serial interface driver expects to receive an entire packet of characters (VMIN bytes total). If no character is read within the time allowed, then the call to read returns 0. This method allows you to tell the serial driver you need exactly N bytes and any read call will return 0 or N bytes. However, the timeout only applies to the first character read, so if for some reason the driver misses one character inside the N byte packet then the read call could block forever waiting for additional input characters.
    Jim

  13. #13
    Registered User
    Join Date
    Apr 2011
    Posts
    11
    Quote Originally Posted by jimblumberg View Post
    The only other thing I normally do is set the timeouts since RAW mode is being used.
    Code:
    	options.c_cc[VMIN] = 1;
    	options.c_cc[VTIME] = 50;
    Still nothing... Thanks though

    Tim

  14. #14
    Registered User
    Join Date
    May 2010
    Posts
    4,633
    Have you tried a communication program like Hyper Terminal yet?

    Jim

  15. #15
    Registered User
    Join Date
    Apr 2011
    Posts
    11
    I tried the picocom program Salem suggested, but I couldn't seem to get it working. I will look into Hyper Terminal.

    Tim

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Send and Recive Information through the serial Port RS232
    By amigoloko in forum C# Programming
    Replies: 4
    Last Post: 04-14-2006, 12:34 PM
  2. Serial Communication
    By Korn1699 in forum C# Programming
    Replies: 0
    Last Post: 11-29-2005, 12:50 PM
  3. RS232 Communication in C++
    By Hankyaku in forum C++ Programming
    Replies: 2
    Last Post: 03-23-2003, 03:48 PM
  4. rs232 serial driver
    By GreyMattr in forum C Programming
    Replies: 2
    Last Post: 03-20-2002, 12:55 PM
  5. Serial Communication
    By Unregistered in forum C++ Programming
    Replies: 0
    Last Post: 01-17-2002, 08:36 AM