Linux Serial Communications

   


The Project Trailblazer engineers found three documents specifically addressing Linux asynchronous serial communications:

  • Gary Frerking's "Serial Programming HOWTO" (www.linuxdoc.org/HOWTO/Serial-Programming-HOWTO.html)

  • David S. Lawyer's "Serial HOWTO" (www.linuxdoc.org/HOWTO/Serial-HOWTO.html)

  • Michael Sweet's "Serial Programming Guide for POSIX Operating Systems" (www.easysw.com/~mike/serial)

Using Linux or, more properly, POSIX serial communications terminology, they searched for code examples that access the serial port's universal asynchronous receiver transmitter (UART) control signals and send and receive buffers. They discovered that accessing the UART's control signals was straightforward and easy to understand. Manipulating the send and receive buffers requires a more in-depth understanding of the Linux serial device driver capabilities. This device driver contains numerous options, beyond basic UART configuration, for control and processing of modem and terminal communications. Most of these options don't apply for Project Trailblazer. The engineers considered writing their own simplified serial communications driver for UART control, but they decided against that after finding C code examples that resemble the required functionality of setSerialSignal, getSerialSignal, and querySerial.

Linux device files provide access to hardware serial ports. The file open command returns a file descriptor that is used for serial port configuration, control, reading, and writing. The following sections demonstrate this through development of the setSerialSignal, getSerialSignal, and querySerial programs.

Setting the Serial Port Control Signals with setSerialSignal

The Project Trailblazer lift access point receives RFID tag information, performs authentication, and displays a permission signal (that is, a red or green light) permitting or denying guest access to the lift. For the sake of simplicity, the engineers decided to use an RS-232 serial port control signal as the permission signal. There's no need to involve another hardware input/output (I/O) port, such as a parallel port, when the access point serial communications between the target and the RFID reader or display don't use hardware flow control or a DTR signal. The serial port signals RTS or DTR provide this single bit of output, to drive the red light/green light permission signal.

The Linux serial driver provides functionality for modem control through the serial port control signals DTR and RTS. DTR controls the on-hook/off-hook modem status, and RTS controls serial data flow control. In most modem communications applications, the program opens a serial port, makes a dialup connection, performs a task, hangs up the connection, and exits. The Linux serial port driver contains code to handle improperly written or terminated serial communication programs by automatically hanging up the phone line when the port is closed: That is, it "drops DTR." DTR becomes 1, asserted, or an RS-232 negative voltage. This poses an electrical limitation on the lift access point hardware design. Opening and closing the serial port can change the permission signal that is going to the access point the red light or the green light. Fortunately, the serial driver has an option to disable this automatic hangup activity at port closure. It is settable through changes to the serial ports configuration's c_cflag. The source code in Listing 6.1 disables the automatic hangup activity at port close and sets the serial port's control signals. The setSerialSignal code is shown in Listing 6.1.

Listing 6.1 The setSerialSignal Program
 /*  * setSerialSignal v0.1 9/13/01  * www.embeddedlinuxinterfacing.com  *  *  * The original location of this source is  * http://www.embeddedlinuxinterfacing.com/chapters/06/setSerialSignal.c  *  * This program is free software; you can redistribute it and/or modify  * it under the terms of the GNU Library General Public License as  * published by the Free Software Foundation; either version 2 of the  * License, or (at your option) any later version.  *  * This program is distributed in the hope that it will be useful, but  * WITHOUT ANY WARRANTY; without even the implied warranty of  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU  * Library General Public License for more details.  *  * You should have received a copy of the GNU Library General Public  * License along with this program; if not, write to the  * Free Software Foundation, Inc.,  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA  */ /* setSerialSignal  * setSerialSignal sets the DTR and RTS serial port control signals.  * This program queries the serial port status then sets or clears  * the DTR or RTS bits based on user supplied command line setting.  *  * setSerialSignal clears the HUPCL bit. With the HUPCL bit set,  * when you close the serial port, the Linux serial port driver  * will drop DTR (assertion level 1, negative RS-232 voltage). By  * clearing the HUPCL bit, the serial port driver leaves the  * assertion level of DTR alone when the port is closed.  */ /* gcc -o setSerialSignal setSerialSignal.c */ #include <sys/ioctl.h> #include <fcntl.h> #include <termios.h> /* we need a termios structure to clear the HUPCL bit */ struct termios tio; int main(int argc, char *argv[]) {   int fd;   int status;   if (argc != 4)   {     printf("Usage: setSerialSignal port                  DTR RTS\n");     printf("Usage: setSerialSignal /dev/ttyS0|/dev/ttyS1 0|1 0|1\n");     exit( 1 );   }   if ((fd = open(argv[1],O_RDWR)) < 0)   {     printf("Couldn't open %s\n",argv[1]);     exit(1);   }   tcgetattr(fd, &tio);          /* get the termio information */   tio.c_cflag &= ~HUPCL;        /* clear the HUPCL bit */   tcsetattr(fd, TCSANOW, &tio); /* set the termio information */   ioctl(fd, TIOCMGET, &status); /* get the serial port status */   if ( argv[2][0] == '1' )      /* set the DTR line */     status &= ~TIOCM_DTR;   else     status |= TIOCM_DTR;   if ( argv[3][0] == '1' )      /* set the RTS line */     status &= ~TIOCM_RTS;   else     status |= TIOCM_RTS;   ioctl(fd, TIOCMSET, &status); /* set the serial port status */   close(fd);                    /* close the device file */ } 

The setSerialSignal program requires three command-line parameters: the serial port to use (/dev/ttyS0 or /dev/ttyS1), the assertion level of DTR (1 or 0), and the assertion level of RTS (1 or 0). Here are the steps to compile and test setSerialSignal:

  1. Compile setSerialSignal.c:

     root@tbdev1[505]: gcc -o setSerialSignal setSerialSignal.c 
  2. Connect a voltmeter to ground and to the DTR signal on serial port 0 (ttyS0). DTR is your PC's COM1 DB-9 connector pin 4.

    TIP

    DB-9 connectors have tiny numbers printed in the connector plastic next to the pins.

  3. Run setSerialSignal to set DTR:

     root@tbdev1[506]: ./setSerialSignal /dev/ttyS0 1 0 
  4. Your voltmeter should read a negative voltage between 3V and 15V. Setting DTR, using 1 as a command-line parameter, results in a negative RS-232 voltage on the actual DTR pin.

    TIP

    An RS-232 breakout box simplifies debugging serial communication programs.6 However, using a RS-232 breakout box with LEDs could lower your signal's voltage. Many RS-232 driver chips can't source enough current to drive the LEDs in the breakout box. If you're experiencing problems, you might want to use a wire to make the DB-9 connections instead of a breakout box.

  5. Run setSerialSignal to clear DTR:

     root@tbdev1[507]: ./setSerialSignal /dev/ttyS0 0 0 
  6. Your voltmeter should read a positive voltage between +3V and +15V. Clearing DTR, using 0 as a command-line parameter, results in a positive RS-232 voltage on the actual DTR pin.

  7. Connect a voltmeter to ground and to the RTS signal on serial port 0 (ttyS0). RTS is your PC's COM1 DB-9 connector pin 7.

  8. Run setSerialSignal to set RTS:

     root@tbdev1[508]: ./setSerialSignal /dev/ttyS0 0 1 
  9. Your voltmeter should read a negative voltage between 3V and 15V. Setting RTS, using 1 as a command-line parameter, results in a negative RS-232 voltage on the actual RTS pin.

  10. Run setSerialSignal to clear RTS:

     root@tbdev1[509]: ./setSerialSignal /dev/ttyS0 0 0 
  11. Your voltmeter should read a positive voltage between +3V and +15V. Clearing RTS, using 0 as a command-line parameter, results in a positive RS-232 voltage on the actual RTS pin.

Reading the Serial Port Control Signals with getSerialSignal

The getSerialSignal program returns the state, asserted (1) or not asserted (0), of any RS-232 serial port control input signal: DSR, CTS, Data Carrier Detect (DCD), or Ring (RI). getSerialSignal opens a specified serial port, which is supplied as a command-line parameter, and then uses the system call ioctl to determine the serial port control line status and returns an individual signal state, which is also supplied as a command-line parameter. Listing 6.2 shows the getSerialSignal source code.

Listing 6.2 The getSerialSignal Program
 /*  * getSerialSignal v0.1 9/13/01  * www.embeddedlinuxinterfacing.com  *  * The original  location of this source is  * http://www.embeddedlinuxinterfacing.com/chapters/06/getSerialSignal.c  *  *  * Copyright (C) 2001 by Craig Hollabaugh  *  * This program is free software; you can redistribute it and/or modify  * it under the terms of the GNU Library General Public License as  * published by the Free Software Foundation; either version 2 of the  * License, or (at your option) any later version.  *  * This program is distributed in the hope that it will be useful, but  * WITHOUT ANY WARRANTY; without even the implied warranty of  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU  * Library General Public License for more details.  *  * You should have received a copy of the GNU Library General Public  * License along with this program; if not, write to the  * Free Software Foundation, Inc.,  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA  */ /* getSerialSignal  * getSerialSignal queries the serial port's UART and returns  * the state of the input control lines. The program requires  * two command line parameters: which port and the input control  * line (one of the following: DSR, CTS, DCD, or RI).  * ioctl is used to get the current status of the serial port's  * control signals. A simple hash function converts the control  * command line parameter into an integer.  */ /* gcc -o getSerialSignal getSerialSignal.c */ #include <sys/ioctl.h> #include <fcntl.h> /* These are the hash definitions */ #define DSR 'D'+'S'+'R' #define CTS 'C'+'T'+'S' #define DCD 'D'+'C'+'D' #define RI  'R'+'I' int main(int argc, char *argv[]) {   int fd;   int status;   unsigned int whichSignal;   if (argc != 3)     {     printf("Usage: getSerialSignal /dev/ttyS0|/dev/ttyS1 DSR|CTS|DCD|RI \n");     exit( 1 );     } /* open the serial port device file */   if ((fd = open(argv[1],O_RDONLY)) < 0) {     printf("Couldn't open %s\n",argv[1]);     exit(1);   } /* get the serial port's status */   ioctl(fd, TIOCMGET, &status); /* compute which serial port signal the user asked for  * using a simple adding hash function  */   whichSignal = argv[2][0]  + argv[2][1] +  argv[2][2]; /* Here we AND the status with a bitmask to get the signal's state  * These ioctl bitmasks are defined in /usr/include/bits/ioctl-types.h*/   switch (whichSignal) {     case  DSR:       status&TIOCM_DSR ? printf("0"):printf("1");       break;     case  CTS:       status&TIOCM_CTS ? printf("0"):printf("1");       break;     case  DCD:       status&TIOCM_CAR ? printf("0"):printf("1");       break;     case  RI:       status&TIOCM_RNG ? printf("0"):printf("1");       break;     default:       printf("signal  %s unknown, use DSR, CTS, DCD or RI",argv[2]);       break;   }   printf("\n"); /* close the device file */   close(fd); } 

The getSerialSignal program requires two command-line parameters: the serial port to use (/dev/ttyS0 or /dev/ttyS1) and the control signal (DSR, CTS, DCD, or RI). Here are the steps to compile and test getSerialSignal:

  1. Compile getSerialSignal.c:

     root@tbdev1[510]: gcc -o getSerialSignal getSerialSignal.c 
  2. Use the serial port's DTR signal to drive the RI signal. Connect DTR, DB-9 pin 4, to RI, DB-9 pin 9.

  3. Measure the DTR signal voltage; it should be between +3V and +15V. This is assertion level 0.

    TIP

    You might get an open file error if the device file permissions aren't set for read/write access by everyone. Check the file permissions and give everyone read and write access by using the chmod command:

     chmod a+w+r /dev/ttyS0 

  4. Use getSerialSignal to query the assertion level of the RI signal:

     root@tbdev1[512]: ./getSerialSignal /dev/ttyS0 RI 0 

    getSerialSignal returns 0, which is correct for a positive voltage on the RI line.

  5. Now use the serial port's TX signal to drive the RI signal. Connect TX DB-9 pin 3 to RI DB-9 pin 9.

  6. Measure the TX signal voltage; it should be between 3V and 15V. This is assertion level 1.

  7. Use getSerialSignal to query the assertion level of the RI signal:

     root@tbdev1[513]: ./getSerialSignal /dev/ttyS0 RI 1 

    getSerialSignal returns 1, which is correct for a negative voltage on the RI line.

How the File open System Call Affects DTR and RTS Signals

While they were debugging, the engineers noticed a peculiar serial driver behavior that required further research. The Linux serial port driver contains functionality for modem control, using the DTR and RTS signals. The serial driver file open system call either asserts or de-asserts both DTR and RTS. You can see this by scanning /usr/src/linux/drivers/char/serial.c for the DTR and modem control register (MCR). In particular, these lines in the startup function define what gets written to the MCR:

 info->MCR = 0; if (info->tty->termios->c_cflag & CBAUD)         info->MCR = UART_MCR_DTR | UART_MCR_RTS; 

Why is this important? Obtaining a serial port file descriptor by using the open command results in DTR and RTS either being set or cleared, depending on whether a baud rate is set. The startup function doesn't query the MCR to preserve the DTR or RTS status.

Use of DTR and RTS modem control signals allows for two single-bit outputs. Clearing the HUPCL bit in the serial port's c_cflags register preserves the individual state of DTR and RTS at file closure. However, the serial port file open system call in serial.c either clears or sets both DTR and RTS. This doesn't preserve the state of DTR or RTS. Applications that require DTR and RTS to operate completely individually should query and set the MCR directly (that is, they should not do so by using the serial port driver) or you can modify serial.c to suit your requirements. Regardless of direct MCR communication or use of the serial port driver, serial communication using the driver requires a file open system call that could change DTR and/or RTS without your knowledge.

This limitation does not affect the Project Trailblazer hardware design for using DTR or RTS control signals to drive the lift access point permission signal. The access point displays a stop indication the majority of the time, and it displays a go indication only momentarily, upon authentication. Using the DTR output signal and clearing the HUPCL bit from serial port's c_cflags variable results in a 0 DTR output state (that is, a positive voltage) across multiple openings and closings of the serial port.

Providing Serial Communication for bash Scripts, Using querySerial

The querySerial program provides serial communication functionality for bash scripts. Project Trailblazer uses querySerial to communicate with the lift access point input and output hardware the RFID tag reader and the message display. The querySerial command line requires the port, the baud rate, the timeout, and a command to be sent. querySerial performs the following procedures:

  1. It opens the requested port.

  2. It sets the baud rate.

  3. It configures the input buffer.

  4. It sets noncanonical mode.

  5. It sets the timeout.

  6. It sends the user supplied command.

  7. It awaits a timeout.

  8. It returns the received characters.

  9. It exits.

These operations are similar to those for polled serial communications, except for the noncanonical mode setting. As previously mentioned, the Linux serial driver contains code for modem control and terminal operation. To optimize communications over slow serial links, this driver has a mode called canonical that performs various character translations, echoing, command-line processing, and other manipulations. Project Trailblazer doesn't require any serial communications processing. Therefore, the serial port should be configured in noncanonical mode.

querySerial is loosely based on the "Serial Programming HOWTO."4 It is not optimized for high-performance/low-latency communications, but merely shows the simplest way to send a serial command and await a response. The "Serial Programming HOWTO" clearly explains four noncanonical configurations that use VMIN and VTIME. Use of VMIN and VTIME may reduce timeout conditions of a serial communication application. Sweet's "Serial Programming Guide for POSIX Operating Systems"5 is another excellent document that explains serial communications, port configuration, modem communication, and I/O control. Listing 6.3 shows the querySerial program.

Listing 6.3 The querySerial Program
 /*  * querySerial v0.1 9/17/01  * www.embeddedlinuxinterfacing.com  *  * The original  location of this source is  * http://www.embeddedlinuxinterfacing.com/chapters/06/querySerial.c  *  *  * Copyright (C) 2001 by Craig Hollabaugh  *  * This program is free software; you can redistribute it and/or modify  * it under the terms of the GNU Library General Public License as  * published by the Free Software Foundation; either version 2 of the  * License, or (at your option) any later version.  *  * This program is distributed in the hope that it will be useful, but  * WITHOUT ANY WARRANTY; without even the implied warranty of  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU  * Library General Public License for more details.  *  * You should have received a copy of the GNU Library General Public  * License along with this program; if not, write to the  * Free Software Foundation, Inc.,  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA  */ /* querySerial  * querySerial provides bash scripts with serial communications. This  * program sends a query out a serial port and waits a specific amount  * of time then returns all the characters received. The command line  * parameters allow the user to select the serial port, select the  * baud rate, select the timeout and the serial command to send.  * A simple hash function converts the baud rate  * command line parameter into an integer.  */ /* gcc -o querySerial querySerial.c */ #include <stdio.h> #include <sys/ioctl.h> #include <fcntl.h> #include <termios.h> #include <stdlib.h> /* These are the hash definitions */ #define USERBAUD1200 '1'+'2' #define USERBAUD2400 '2'+'4' #define USERBAUD9600 '9'+'6' #define USERBAUD1920 '1'+'9' #define USERBAUD3840 '3'+'8' struct termios tio; int main(int argc, char *argv[]) {   int fd, status, whichBaud, result;   long baud;   char buffer[255];   if (argc != 5)   {     printf("Usage: querySerial port speed timeout(mS) command\n");     exit( 1 );   } /* compute which baud rate the user wants using a simple adding  * hash function  */   whichBaud = argv[2][0] + argv[2][1];   switch (whichBaud) {     case USERBAUD1200:       baud = B1200;       break;     case USERBAUD2400:       baud = B2400;       break;     case USERBAUD9600:       baud = B9600;       break;     case USERBAUD1920:       baud = B19200;       break;     case USERBAUD3840:       baud = B38400;       break;     default:       printf("Baud rate %s is not supported, ");       printf("use 1200, 2400, 9600, 19200 or 38400.\n", argv[2]);       exit(1);       break;   } /* open the serial port device file  * O_NDELAY - tells port to operate and ignore the DCD line  * O_NOCTTY - this process is not to become the controlling  *            process for the port. The driver will not send  *            this process signals due to keyboard aborts, etc.  */   if ((fd = open(argv[1],O_RDWR | O_NDELAY | O_NOCTTY)) < 0)   {     printf("Couldn't open %s\n",argv[1]);     exit(1);   } /* we are not concerned about preserving the old serial port configuration  * CS8, 8 data bits  * CREAD, receiver enabled  * CLOCAL, don't change the port's owner  */   tio.c_cflag = baud | CS8 | CREAD | CLOCAL;   tio.c_cflag &= ~HUPCL; /* clear the HUPCL bit, close doesn't change DTR */   tio.c_lflag = 0;       /* set input flag noncanonical, no processing */   tio.c_iflag = IGNPAR;  /* ignore parity errors */   tio.c_oflag = 0;       /* set output flag noncanonical, no processing */   tio.c_cc[VTIME] = 0;   /* no time delay */   tio.c_cc[VMIN]  = 0;   /* no char delay */   tcflush(fd, TCIFLUSH); /* flush the buffer */   tcsetattr(fd, TCSANOW, &tio); /* set the attributes */ /* Set up for no delay, ie nonblocking reads will occur.    When we read, we'll get what's in the input buffer or nothing */   fcntl(fd, F_SETFL, FNDELAY); /* write the users command out the serial port */   result = write(fd, argv[4], strlen(argv[4]));   if (result < 0)   {     fputs("write failed\n", stderr);     close(fd);     exit(1);   } /* wait for awhile, based on the user's timeout value in mS*/   usleep(atoi(argv[3]) * 1000); /* read the input buffer and print it */   result = read(fd,buffer,255);   buffer[result] = 0; // zero terminate so printf works   printf("%s\n",buffer); /* close the device file */   close(fd); } 

The querySerial program requires four command-line parameters: the serial port to use (/dev/ttyS0 or /dev/ttyS1), the baud rate (1200, 2400, 9600, 19200, or 38400), a timeout value in milliseconds, and the command to be sent. querySerial sends the command at the baud rate selected, waits the specified amount of time, and returns all the characters received on the serial port. You can test querySerial directly on tbdev1, without using a target board. Here are the steps to compile and test querySerial:

  1. Compile querySerial.c:

     root@tbdev1[510]: gcc -o querySerial querySerial.c 
  2. Physically connect tbdev1's serial ports together, using a null modem adapter and minicom.

  3. In one console window, run minicom and configure it by typing CTRL-a and then o. Then select Serial Port Setup. Set SERIAL DEVICE to /dev/ttyS0 and the Bps/Par/Bits to 1200 8N1.

  4. In another console window, run querySerial to send a command out ttyS1 to ttyS0, which then shows up in the minicom window. To do this, issue the following command:

     root@tbdev1[517]: querySerial /dev/ttyS1 1200 5000 "this is a test" 

    The string "this is a test" should show in the minicom window. The querySerial timeout, which is set to 5000 milliseconds, gives you 5 seconds to type a reply.

  5. In the minicom window, type "got it". If you do this within 5 seconds, you should see this at the bash prompt:

     root@tbdev1[518]: querySerial /dev/ttyS0 1200 5000 "this is a test" got it root@tbdev1[519]: 

    If you don't finish typing got it within 5 seconds, querySerial returns what you did type in 5 seconds.


       
    Top


    Embedded LinuxR. Hardware, Software, and Interfacing
    Embedded LinuxR. Hardware, Software, and Interfacing
    ISBN: N/A
    EAN: N/A
    Year: 2001
    Pages: 103

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net