Serial Port Programming in Windows and Linux
Serial Port Programming in Windows and Linux
Maxwell Walter
November 7, 2003
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
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;
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
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.
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
References
10