Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
372 views

Serial Port Programming in Windows and Linux

Serial port programming requires opening, configuring, reading/writing to, and closing the serial port. While operating system dependent, a common API can be created to provide portability. The document details serial port programming steps for Windows and Linux, and provides an example cross-platform header file. It summarizes serial port history and standards. Opening the serial port involves calling CreateFile() in Windows or open() in Linux, returning a handle. Configuration specifies baud rate, data size, parity, flow control, and stop bits.

Uploaded by

akozy
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
372 views

Serial Port Programming in Windows and Linux

Serial port programming requires opening, configuring, reading/writing to, and closing the serial port. While operating system dependent, a common API can be created to provide portability. The document details serial port programming steps for Windows and Linux, and provides an example cross-platform header file. It summarizes serial port history and standards. Opening the serial port involves calling CreateFile() in Windows or open() in Linux, returning a handle. Configuration specifies baud rate, data size, parity, flow control, and stop bits.

Uploaded by

akozy
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 10

Serial Port Programming in Windows and Linux

Maxwell Walter
November 7, 2003

Abstract Serial port programming requires API’s provided


by the underlying operating system to communicate
While devices that use RS−232 and the serial port to with the hardware, making serial port programming
communicate are becoming increasingly rare, it is still operating system dependent and not very portable.
an important skill to have. Serial port programming, However, because communicating with the serial port
at its most basic level, consists of a series of steps itself requires few steps, a library with a common API
regardless of the operating system that one is oper- can be created and separate versions maintained for
ating on. These steps are opening the port, configur- different operating systems. The goal of this docu-
ing the port, reading and writing to the port, and fi- ment is to develop an operating system independent
nally, closing the port. It is possible then to create an class allowing the creation of portable code that uti-
API that contains the functions necessary to success- lizes the serial port.
fully communicate with the serial port. With a single
header file that contains all the functions necessary to
communicate over a serial port, the implementation 2 RS−232 Overview
details can remain platform dependent as a library
that can then be compiled and maintained separately The RS−232 protocol was originally designed in 1969
for each operating system. The actual application can by the Electronic Industries Association (EIA) as a
then use the common header file as its interface to the general method of data transmission between two de-
serial port. This creates a cross-platform serial inter- vices. They wanted to create a specification that
face allowing the creation of code that is more easily would be free of transmission errors while being sim-
portable across operating systems. In this document ple enough that it would be adopted by many de-
the functions and steps necessary to use the serial vice manufactures. The protocol that they developed
port are detailed, and at the end of this document an specified signal levels, timing, mechanical plugs, and
example cross-platform header file is provided. information control protocols. The RS−232 spec-
Keywords: Serial Port, Programming, Linux, Win- ification has gone through three major milestones,
dows namely:

• 1969: The third version of the RS−232 protocol


(originally called RS−232−C) was adopted as a
1 Introduction standard by the industry.

The advent of USB has caused a significant decline • 1987: EIA created a new version of the standard
in the number of devices that communicate using called EIA-232-D.
RS−232, and many motherboards today ship with- • 1991: EIA and Telecommunications Industry
out serial ports. This does not mean, however, that Association created a new version of the stan-
serial port programming is no longer a necessary or dard that they called EIA/TIA-232-E. Most
relevant skill to posses. Many important and useful called the standard RS−232−C or simply
devices still in use today date back to pre-USB times, RS−232.
and there are still some devices that continue to use
the RS−232 protocol for communication. Also, be- Other specifications have been created that aim to
cause there are far fewer devices that communicate remove defects inherent in the original RS−232 spec-
using RS−232, fewer people are exposed to it, and ification including:
thus fewer people learn how to use it, making it an
even more valuable skill. • RS−422: Allows a line impedance of up to 50Ω

1
• RS−423: Minimum line impedance of 450Ω used to open a file. There are restrictions on the
parameters allowed when opening the file however,
• RS−449: High rate of communication speed us- and those will be discussed in the appropriate section
ing a 37 pin plug below. The options are operating system dependent.

[2] [3]
3.1.1 Windows

3 Serial Port Programming The function used to open the serial port in the Win-
dows operating system is CreateFile() which is used
There are four operations that must happen in proper as follows:
order to successfully communicate over the serial
port. These are to open the serial port, configure HANDLE fileHandle;
the serial port, read and write bytes to and from the fileHandle =
serial port, and close the serial port when the task has CreateFile(
been completed. It is important to check the return //the name of the port (as a string)
codes at each stage to ensure that the task has been //eg. COM1, COM2, COM4
completed, because failure at one stage means that gszPort,
the next stage will fail as well. Sanity checking the //must have read AND write access to
return codes is also good coding practice, and throw- //port
ing away important information can lead to program GENERIC_READ | GENERIC_WRITE,
errors that are difficult to trace. //sharing mode
The serial port is a bi-directional port meaning that //ports CANNOT be shared
it can send data in both directions, and, because it //(hence the 0)
has separate pins for transmit and receive, a device 0,
can send and receive data at the same time. Sending //security attributes
and receiving data at the same time is called over- //0 here means that this file handle
lapped communication as opposed to non-overlapped, //cannot be inherited
or blocking communication. This paper will deal 0,
exclusively with non-overlapped communication as //The port MUST exist before-hand
overlapped communication requires very complex ob- //we cannot create serial ports
jects such as threads, mutexes, and semaphores. OPEN_EXISTING,
Also, non-overlapped communication is adequate in //Overlapped/Non-Overlapped Mode.
many situations. //This paper will deal with
//non-overlapped communication.
The two operating systems discussed here, Win- //To use overlapped communication
dows and Linux, both have simple communication //replace 0 with
API’s that facilitate communication over the serial //FFILE_FLAG_OVERLAPPED
port. While certain tasks, such as opening and clos- 0,
ing the serial port, are very similar, other tasks such a //HANDLE of a template file
configuring the serial port are very different. It is our //which will supply attributes and
goal to create a single header file that provides a con- //permissions. Not used with
sistent interface across multiple operating systems, //port access.
requiring only that the programmer link against the 0);
object library for the operating system his program
is compiling on.
CreateFile() returns a HANDLE object that can then
be used to access the port. If CreateFile() fails, the
3.1 Opening the Serial Port HANDLE object that is returned is invalid and va-
lidity can be tested using the following code:
The first step that is necessary to communicate over
the serial port is to actually open the device. Under if (fileHandle == INVALID_HANDLE_VALUE) {
both Windows and Linux, the serial port is treated as //error handling code here
a file, so to open the serial port one calls the functions }

2
Provided that the serial port is successfully opened at which the serial port can send and receive data
the next step is to configure it to the specific appli- and is specified in units of BAUD. Common speeds
cation. are 9600 BAUD, 19200 BAUD, and the current max-
imum of standard serial ports is 115200 BAUD. Data
word size is the size of a piece of the data, usually
3.1.2 Linux
a word, or eight bits. Parity is the type of parity
used, either even, odd, or none, and flow control and
Opening the serial port in Linux is performed in much
the number of stop bits are used to synchronize the
the same way as it is in Windows, using the open()
communication.
command. One caveat to Linux is that the user ID
that the program is running under must be allowed It is also necessary to set the read and write time-
to access the serial port, either by giving the user outs for non-overlapped communication, because in
permission to the serial port, or by changing the per- non-overlapped communication the read and write
missions of the program to allow access to the serial operations block, or do not return, until there is data
port. Code to open the serial port under Linux can available to return. So if a read or write function
is as follows. is called and there is no data available or no device
listening, the program will hang. Setting the read
int fd = and write timeouts ensure that, if there is no device
open( to communicate with the read and write operations
//the name of the serial port will return a failure instead of blocking. It is im-
//as a c-string (char *) portant to note that while it is possible to set the
//eg. /dev/ttys0 wait timeout, unless the host is expecting acknowl-
serialPortName, edgment from the device there will be no write time-
//configuration options outs. Non-overlapped I/O with no flow-control uses
//O_RDWR - we need read no acknowledgment, so for the purpose of this paper
// and write access the wait timeouts are not needed.
//O_CTTY - prevent other
// input (like keyboard) 3.2.1 Windows
// from affecting what we read
//O_NDELAY - We don’t care if In Windows, setting up the serial port requires three
// the other side is steps.
// connected (some devices
// don’t explicitly connect) 1. Create a DCB object and initialize it using the
O_RDWR | O_NOCTTY | O_NDELAY function BuildCommDCB().
);
if(fd == -1) { 2. Set the serial port settings using the initialized
//error code goes here DCB object using the function SetCommState().
}
3. Set the size of the serial port read and write
The serial port is now opened and, in this case, fd is buffers using SetupComm().
a handle to the opened device file. As can be seen, if
the open() function call fails, the device handle is set Code to accomplish this can be found below.
to −1 and by checking the handle against this value
one can determine if an error occurred. DCB dcb; //create the dcb

//first, set every field


3.2 Configuring the Serial Port //of the DCB to 0
//to make sure there are
The next action that must take place before data can //no invalid values
be written to or read from the serial port is to appro- FillMemory(&dcb, sizeof(dcb), 0);
priately configure the port. Port configuration op-
tions include the communication speed, data word //set the length of the DCB
size, parity, flow control, number of stop bits. There dcb.DCBlength = sizeof(dcb);
are other settings that can be used but will not be dis-
cussed here. The communication speed is the speed //try to build the DCB

3
//The function takes a string that //the read line (in ms)
//is formatted as cmt.ReadIntervalTimeout = 1000;
//speed,parity,data size,stop bits
//the speed is the speed //value used to calculate the total
// of the device in BAUD //time needed for a read operation
//the parity is the //which is
// type or parity used // (num bytes to read) * (timeout)
//--n for none // in ms
//--e for even cmt.ReadTotalTimeoutMultiplier = 1000;
//--o for odd
//the data size is the number of bits //This value is added to
// that make up a work (typically 8) //the previous one to generate
//the stop bits is the //the timeout value
// number of stop bits used //for a single read operation (in ms)
// typically 1 or 2 cmt.ReadTotalTimeoutConstant = 1000;
if(!BuildCommDCB("9600,n,8,1", &dcb)) {
return false; //the next two values are the same
} //as their read counterparts, only
//applying to write operations
//set the state of fileHandle to be dcb cmt.WriteTotalTimeoutConstant = 1000;
//returns a boolean indicating success cmt.WriteTotalTimeoutMultiplier = 1000;
//or failure
if(!SetCommState(filehandle, &dcb)) { //set the timeouts of fileHandle to be
return false; //what is contained in cmt
} //returns boolean success or failure
if(!SetCommTimeouts(fileHandle, &cmt)) {
//set the buffers to be size 1024 //error code goes here
//of fileHandle }
//Also returns a boolean indicating
//success or failure Provided all the configuration functions returned suc-
if(!SetupComm(fileHandle, cess, the serial port is now ready to be used to send
//in queue and receive data. It may be necessary, depending
1024, on the application, to re-configure the serial port. It
//out queue may be necessary, for instance, to change the speed
1024)) of the port, or the timeout values in the middle of an
{ application.
return false;
}
3.2.2 Linux
Next, the timeouts of the port must be set. It is im-
Configuration of the serial port under Linux takes
portant to note that if the timeouts of a port are not
place through the use the termios struct, and consists
set in windows, the API states that undefined results
of four steps:
will occur. This leads to difficulty debugging because
the errors are inconsistent and sporadic. To set the
timeouts of the port an object of type COMMTIME- 1. Create the struct and initialize it to the current
OUTS must be created and initialized and then ap- port settings.
plied to the port using the function SetCommTime-
2. Set the speed attribute of the struct to the de-
outs(), as the code below demonstrates.
sired port speed using the functions cfsetispeed()
and cfsetospeed(). While these functions allow
COMMTIMEOUTS cmt; //create the object different reading and writing speed, most hard-
ware and system implementations do not allow
//the maximum amount of time these speeds to be different.
//allowed to pass between
//the arrival of two bytes on 3. Set the timeouts of the port.

4
4. Apply the settings to the serial port. options.c_cflag |= CS8;

While opening the serial port is generally easier in /**********************************


Linux than it is in Windows, configuring it is harder *
as there is more bit-masking involved to change a * odd parity example
single option. While there are convenient functions *
that can be used to set the speed of the port, other **********************************/
options, like parity and number of stop bits, are set //enable parity with PARENB
using the c cflag member of the termios struct, and options.c_cflag |= PARENB
require bitwise operations to set the various settings. //PARAODD enables odd parity
Linux is also only capable of setting the read time- //and with ~PARAODD for even parity
out values. This is set using the c cc member of the options.c_cflag |= PARODD
termios struct which is actually an array indexed by //Only one stop bit
defined values. Please see the code below for an ex- options.c_cflag &= ~CSTOPB
ample. //clear out the current word size
options.c_cflag &= ~CSIZE;
//create the struct //we only have 7-bit words here
struct termios options; //because one bit is taken up
//by the parity bit
//get the current settings of the options.c_cflag |= CS7;
// serial port
tcgetattr(fd, &options); //Set the timeouts
//VMIN is the minimum amount
//set the read and write speed to //of characters to read.
//19200 BAUD options.c_cc[VMIN] = 0;
//All speeds can be prefixed with B //The amount of time to wait
//as a settings. //for the amount of data
cfsetispeed(&options, B19200); //specified by VMIN in tenths
cfsetospeed(&options, B19200); //of a second.
optiont.c_cc[VTIME] = 1;
//now to set the other settings
//here we will have two examples. //CLOCAL means don’t allow
//The first will be no parity, //control of the port to be changed
//the second will be odd parity. //CREAD says to enable the receiver
//Both will assume 8-bit words options.c_cflag |= (CLOCAL | CREAD);

/********************************** //apply the settings to the serial port


* //TCSNOW means apply the changes now
* no parity example //other valid options include:
* // TCSADRAIN - wait until every
**********************************/ // thing has been transmitted
//PARENB is enable parity bit // TCSAFLUSH - flush buffers
//so this disables the parity bit // and apply changes
options.c_cflag &= ~PARENB if(tcsetattr(fd, TCSANOW, &options)!= 0) {
//CSTOPB means 2 stop bits //error code goes here
//otherwise (in this case) }
//only one stop bit
options.c_cflag &= ~CSTOPB
//CSIZE is a mask for all the 3.3 Reading and Writing
//data size bits, so anding
//with the negation clears out After the port is open and configured, a program can
//the current data size setting send and receive data through it. In both Windows
options.c_cflag &= ~CSIZE; and Linux the serial port is treated as a file, and
//CS8 means 8-bits per work the file read and write operations are used to send

5
data to and from the port. These operations differ //be stored in
from standard file input/output operations in that &read,
the number of bytes to be read or written and the //a pointer to an
number of bytes actually read or written are very //overlapped_reader struct
important. //that is used in overlapped
//reading. NULL in out case
It is a good idea to compare the number of bytes
that were actually read or written to the number of NULL);
bytes that were supposed to have been read or writ-
/******************************
ten to insure the correctness of the program and the
accuracy of the data. *
* Writing to a file
*
3.3.1 Windows *******************************/
//the amount of the data actually
Reading and writing to a serial port in Windows is //written will be returned in
very simple and similar to reading and writing to a //this variable
file. In fact, the functions used to read and write DWORD write = -1;
to a serial port are called ReadFile() and WriteFile ReadFile(
respectively. When reading or writing to the serial //the HANDLE that we
port, the programmer provides a pointer to a buffer //are writing to
containing the number of words to be written and the fileHandle,
size of the buffer. The system then returns the actual //a pointer to an array
number of words that have been read or written. The //of words that we
read and write functions return a boolean value, but //want to write
this value does not relate to the success or failure of data,
the operation. Only by checking the actual number //the size of the
of bytes read or written can the actual success of the //array of values to
operation be ascertained. Code to read and write to //be written
a serial port can be found below. size,
//the address of a DWORD
/****************************** //that the number of words
* //actually written will
* Reading from a file //be stored in
* &read,
*******************************/ //a pointer to an
//the amount of the data actually //overlapped_reader struct
//read will be returned in //that is used in overlapped
//this variable //writing. NULL in out case
DWORD read = -1; NULL);
ReadFile(
//the HANDLE that we Because we are using non-overlapped input/output
//are reading from operations the ReadFile() and WriteFile() operations
fileHandle, will block until the requested data is either received
//a pointer to an array or the operation times out.
//of words that we
//want to read
data, 3.3.2 Linux
//the size of the
//array of values to Reading and writing data under Linux is just as easy
//be read as it is under Windows and involves calling the read()
size, and write() functions. The same rule of thumb of
//the address of a DWORD checking the number of requested words to read or
//that the number of words write and the actual number of words that were read
//actually read will or written applies.

6
/********************************** //error code goes here
Reading from the Serial Port }
***********************************/
//fd is the file descriptor to the
3.4.2 Linux
// serial port
//buf is a pointer the array we want to
Closing the serial port on Linux involves a single call
// read data into
to the close() system function, as the following code
//bufSize is the amount of data that we
demonstrates.
// want to read in
int wordsRead = read(fd, buf, bufSize);
//close the serial port
/********************************** if(close(fd) == -1) {
Writing to the Serial Port //error code goes here
***********************************/ }
//write data to the serial port
//fd is the file descriptor of
// the serial port 4 Putting it all Together
//buf is a pointer to the data that
// we want to write to the serial Now that we have an understanding of the necessary
// port steps that must be completed to communicate with
//bufSize is the amount of data that we the serial port in both Windows and Linux, it is time
// want to write to create our cross-platform API to facilitate easily
int wordsWritten = write(fd, buf, bufSize); portable serial port code. Based on the steps outlined
above, our API will require, at minimum, six func-
tions. Two functions to open and to close the serial
3.4 Closing the Serial Port port, one function to set the serial port parameters,
another to set the timeout values, and two functions
Closing the port is very similar to opening it, and to read and write to the serial port. The header file
the system call used to close a file is also used to of our proposed class can be found below.
close the port. It is not strictly necessary to close the
port before closing the application because modern
operating systems like Microsoft Windows and Linux
reclaim all resources used by the application. It is,
however, good practice to explicitly free all resources
used so as to not rely on methods that can’t be con-
trolled. Also, if an application will continue to reside
in memory after it is finished using the port, like a
daemon, then it is necessary to close the port. Oth-
erwise no other application would be able to use the
port while the other application has control of it. For
this reason, explicitly closing the port when it is no
longer in use is a good habit to use.

3.4.1 Windows

Closing the serial port is the easiest task under the


Windows operating system. It consists of calling a
single function, CloseFile(), and checking the return
value. Code that demonstrates this can be found be-
low.

//Close the fileHandle, thus


//releasing the device.
if(!CloseFile(fileHandle)) {

7
#ifndef __CAPSTONE_CROSS_SERIAL_PORT__
#define __CAPSTONE_CROSS_SERIAL_PORT__

/**************************************************************************
This header defines a cross-platform serial interface class
that was developed over the course of the ECE capstone class.

It is intended for demonstration purposes only and, while useful,


is not sufficient for most large applications. While we use these
functions in our project, they most likely do not apply
to all applications, and as always, Your Mileage May Vary (YMMV)

With that in mind, I hope that at the very least, you find this useful.
***************************************************************************/

class crossPlatformSerial {
/**********************************************************************
Default constructor: create an instance of our serial interface
object.
Parameters: None, though it could be convenient to have default
settings specified here as well as possibly a default port name.
Returns: Nothing (it is a constructor)
***********************************************************************/
crossPlatformSerial();

/**********************************************************************
Default deconstructor: delete an instance of out serial interface
object.
Parameters: None (it is a de-constructor)
Returns: Noting (it is a de-constructor)
***********************************************************************/
~crossPlatformSerial();

/**********************************************************************
open: opens the serial port using port-name provided.
Parameters:
portName: an array of chars containing a NULL terminated string
that is the port name that we want to open.
eg. COM1, COM2, COM4
Returns: a boolean indicating success or failure
***********************************************************************/
bool open(char * portName);

/**********************************************************************
close: closes the serial port
Parameters: None. None are needed.
Returns: a boolean indicating success or failure
***********************************************************************/
bool close();

/**********************************************************************
setSettings: Set the settings of the serial port interface. Should
be called after the port is opened because otherwise settings
could be lost.

8
Parameters:
speed: the desired speed of the serial port
wordSize: the size of the data word in bits
stopBits: the number of stop bits
parity: parity to be used. One of
’n’ - None
’e’ - Even
’o’ - Odd
Returns: boolean indicating success or failure
***********************************************************************/
bool setSettings(int speed, int wordSize,
int stopBits, char parity);

/*********************************************************************
setTimeouts: set the read and write timeouts of the serial port
Parameters:
readTimeout: The amount of time to wait for a single word
read to timeout
writeTimeout: The amount of time to wait for a single word
write to timeout
Returns: boolean indicating success or failure
**********************************************************************/
bool setTimeouts(int readTimeout, int writeTimeout);

//NOTE:
// These readBuf and writeBuf functions assume word
// sizes of 8 bits. If that is not the case char *
// cannot be used as the storage medium as chars are
// 8-bits in size.
/**********************************************************************
readBuf: read in a buffer of words
Parameters:
buf: The buffer to read words into
bfSize: the number of words to read. This should be less than
or equal to the size of buf
Returns: the number of words actually read
***********************************************************************/
int readBuf(char * buf, int bufSize);
/**********************************************************************
writeBuf: write out a buffer of words
Parameters:
buf: The buffer to write words from
bfSize: the number of words to write. This should be less than
or equal to the size of buf
Returns: the number of words actually written
***********************************************************************/
int writeBuf(char * buf, int bufSize);
};

#endif

9
This class demonstrates the minimum necessary to [2] fCoder Group International, RS-232-C History,
create a useful serial interface. These function stubs http://www.lookrs232.com/rs232/
can be filled in with the code provided previously in history rs232.htm.
the paper under the appropriate sections. A sepa-
rate binary library would need to be maintained for [3] Wikipedia, RS-232, http://en.wikipedia.org/
each operating system that the code is intended to wiki/RS-232.
run on, though maintaining a library is significantly [4] Allen Denver, Serial Communications in Win32,
easier than maintaining the large code-base of an ap- Dec. 11, 1995, MSDN April 2003.
plication.
In practice, there are other things that are needed
such as a boolean flag indicating whether or not the
serial port is currently open, and some method of
getting the current settings of the serial port device.
It is also important to realize that this API applies
only to non-overlapped communication because over-
lapped communication requires complicated operat-
ing system features such as threads, mutexes, and
semaphores making cross-platform operation much
harder.

5 Conclusion

This document has demonstrated the code necessary


to communicate using a serial port on two different
operating systems, namely Microsoft Windows and
Linux. It has also provided a small amount of infor-
mation on the history of RS−232 as well as a design
for a simple cross-platform serial port interface API.
This API does have limitations in that it only sup-
ports non-overlapped I/O and does not support the
advanced features that the operating systems pro-
vide. It would be possible to create a common header
file that includes overlapped communications by also
creating cross-platform libraries of the other compo-
nents that one needs such as threads, mutexes, and
semaphores. Again, there would always be something
that the operating system is capable of that the li-
brary will not be able to support. In gaining the con-
venience of a cross-platform interface, one loses the
power associated with interfacing with the operating
system directly.

References

[1] Michael R. Sweet, Serial Programming


Guide for POSIX Operating Systems, 1994,
http://www.easysw.com/∼mike/serial/
serial.html.

10

You might also like