Escolar Documentos
Profissional Documentos
Cultura Documentos
1 Introduction
The overall goal of this (self study) lab is to get aquainted with serial port
programming. The specific task is to write a small program for writing to and
reading from the serial port, and to get two computers to communicate via a
null modem cable. This is a preliminary step towards communication between
the host (PC) and the embedded system (AVR) in the lab project.
2 Preliminaries
2.1 Test serial port and cable
A first step is to verify that the serial port and the cable works, using tools
provided with the system. On Windows, there is a terminal program called
Hyperterminal.
We will use this program to test the port and cabling using a method called
loopback testing. By connecting the Rx and Tx pins directly, the computer
can talk to itself via the serial port. This is a common technique for testing
communication links. It is also a good example of the testing practise of starting
from a working system and extending in small steps.
Do the following:
Start Hyperterminal, and configure the connection for 2400 8N1 with no
flow control
With nothing plugged into the serial port, type on your keyboard. Nothing
should appear in the terminal. Please note that this step is important.
Verifying that it doesnt work makes sure that you are actually testing
what you think you are testing. In this case, you may have Local echo
turned on, giving you a false positive.
Plug in your null modem cable, and connect the Rx and Tx pins (pins 2
and 3), or use a loopback test plug.
Type on your keyboard, and verify that you receive what you type.
With that test completed, we can be confident that the hardware and operating
system is working correctly, and that we are using the right serial port (if there
1
are multiple ports on the computer). Now, we can go on to write a program for
accessing the serial port.
that open and close the serial device. The serial_init function configures the
port for 2400 bps, 8 bits, no parity, 1 stop bit (commonly abbreviated 2400-8N1)
and opens it for reading and writing. For the interested, the low-level operations
involved are described in Appendix A.
An example main program that only opens and closes the first serial port is:
# include " serialport . h "
main ()
{
int fd = serial_init ( " / dev / ttyS0 " ) ;
seria l_clean up ( fd ) ;
}
where /dev/ttyS0 is the device file for the first serial port, i.e., equal to COM1
in the Windows naming scheme.
1 POSIX, an acronym for Portable Operating System Interface, is a family of standards
specified by the IEEE for maintaining compatibility between operating systems. For Microsoft
Windows, Cygwin provides a largely POSIX-compliant environment.
2 Depending on system configuration, the end-of-line (EOL) marker may be either a single
newline character (\n) or a newline and a carriage return (\n\r). If you dont understand
this, dont worry, just use read() and write your own input parsing.
2
2.2.2 Raw file access using file descriptors
The low-level functions for reading and writing a file are read() and write(),
which read(write) at most nbyte bytes from(to) the resource behind file descrip-
tor fd. The prototypes are:
ssize_t read ( int fd , void * buf , size_t nbyte ) ;
ssize_t write ( int fd , const void * buf , size_t nbyte ) ;
Please note that neither the C compiler, nor the runtime system, does any
range checks, so its up to the programmer to ensure that the size of buf is
at least nbyte, or memory used by other variables may get overwritten with
undefined consequences.
We let examples from the manual pages illustrate the usage. The first exam-
ple reads data from the file associated with the file descriptor fd into the buffer
pointed to by buf.
The second example writes data from the buffer pointed to by buf to the
file associated with the file descriptor fd.
Note the operator sizeof() for getting the size (in bytes) of a data type or
statically known3 array, and the convenient functions strcpy() and strlen()
(from string.h) for copying and determining the length (the lenght of the string
itself, not the size of the array containing it) of a null-terminated string.
To make the code portable, the integer types size_t (from sys/types.h)
for unsigned sizes and ssize_t (from unistd.h) for signed sizes (used since
read() and write() can return a negative value on error) are used.
3 That means that is works for a variable defined with a statically known size (like
char buf[20]) but not objects refereced by pointers or dynamically allocated buffers. For
this course, it is recommended to stick to the former method of allocating buffers.
3
2.3 Structured stream I/O
Above the low-level read() and write() functions, the standard library stdio.h
provides powerful functions (e.g., fprintf() and fscanf())for formatted I/O,
including formatting integers numbers as decimal or hexadecimal strings, real
numbers, etc. It also includes the function getc() which reads the next (un-
signed) character from a stream and casts it to an int, and the function fgets()
which reads a string from the stream until a newline or end of file (EOF) is en-
countered and stores it in a buffer.
where fd is the file descriptor and mode is a string specifying the mode of access.
In this course we will typically use "r+", meaning reading and writing.
There is also a function
int fclose ( FILE * fp ) ;
for flushing and closing the stream fp and then closing the underlying file de-
scriptor. Upon successful completion 0 is returned. Otherwise, EOF is returned
and errno is set to indicate the error.
Then we have the functions for reading one character and a line, respectively,
from stream;
int fgetc ( FILE * stream )
char * fgets ( char *s , int size , FILE * stream )
fgetc() reads the next character from stream and returns it as an unsigned
char cast to an int, or EOF on end of file or error.
fgets() reads at most one less than size characters from stream and stores
them into the buffer pointed to by s. Reading stops after an EOF or a
newline. If a newline is read, it is stored into the buffer. A terminating null
byte (\0) is stored after the last character in the buffer. fgets returns s on
success, and NULL on error or when end of file occurs while no characters
have been read.
where stream is the stream to access, format is a string specifying the format
of the data, and the remaining arguments (...) depend on the format string.
4
The program
# include < stdio .h >
int main ()
{
int x =17;
int y =428;
int z =1;
double f = 13.37;
double g = 1234.56789;
char * s1 = " hej " ;
char * s2 = " hopp " ;
char b = 0 b10100101 ;
unsigned char ub = 0 b10100101 ;
5
2.3.3 The format specification
The format string is made up of ordinary characters, which are copied as is,
and conversion specifications, which causes conversion of the corresponding ar-
gument (defined by the order). A conversion specification begins with a % and
ends with a characher, where the most common ones are
The conversion specifications also take optional arguments, placed between the
% and the conversion character. They are, in order
A minus sign, making the converted argument left adjusted.
The difference between sprintf() and snprintf() is that the latter takes the
size of the destination, str, as an argument and writes at most size bytes (in-
cluding the terminating null byte (\0))to str. As C has no range checking,
in order to avoid buffer overflows and hard to find errors sprintf() should be
avoided in favour of snprintf().
6
3 Lab task: serial communication
The goal of the exercise is to build a simple chat system, where you can com-
municate between two computers via a serial cable. To reach that goal, and to
get a good, modular, design of your program, it is recommended that you do
this in small steps.
As a general advice, always aim for a modular design, and keep the modules
small and simple, to facilitate debugging and reuse. In this particular case,
implement input and output in separate functions and then use those functions
to build the main program.
That also applies to the development methodology. For the first two steps,
you can test your functions independently by using Hyperterminal on the remote
side. Then verify that they work together by running the sender program (1)
at one end and the receiver program (2) at the other.
Of course, if one only has access to a single computer, one can also use
loopback testing. The advantage of using two computers and a program that is
known to work at one end is that it reduces uncertainty. If a loopback test fails,
there is in general no way to determine if it is the sending or the receiving
routine that does not work.
Following that reasoning, the careful programmer would add one more step
at the top of the above list:
0. Run Hyperterminal on both sides to verify that the serial link is working.
7
A More on serial port configuration
For completeness, this section explains the provided code for opening, closing,
and configuring the serial port device in more detail.
where path is a string with the path to the file, and oflag is an integer whose
bits represent different modes of opening,creation and access to the file. The
return value is the file descriptor or -1 if an error occurred.
Values for oflag are constructed by a bitwise-inclusive OR of flags defined
in i header file (<fcntl.h>).
Applications shall specify exactly one of these three values (file access modes)
In addition to that, more flags can be set. Most of them define how files
shall be created, if existing files shall be overwritten or appended to, etc., but
one flag is interesting for serial ports:
O_NOCTTY If set and path identifies a terminal device, open() shall not
cause the device to become the controlling terminal for the process.
If not set, a Ctrl-C received over the serial line can kill the process. Sometimes,
that is desirable, but often not, as a Ctrl-C may be generated by line noise or
sent in error by the remote end.
When a program no longer needs to use a file, or before exiting, the file must
be closed, in order to release any operating system resources, locks, etc.. If a
serial port file descriptor is not properly closed before exiting, the next time a
program tries to open the serial port it may get an error that the port is in use.
A file is closed with a call to
int close(int fd);
where fd is the file descriptor returned by open, and the return value is 0 on
success, or -1 otherwise.
A.1.1 Example
The following snippet shows how a serial port (/dev/ttyS0 is the device file
representing the first serial port) is opened for reading and writing, and closed.
8
int fd ;
// ...
if ( close ( fd ) <0 ) {
perror ( " closing serial port " ) ;
}
If the return value of open or close is negative, an error has occurred. perror()
is a function printing a message on the standard error output, describing the
last error encountered during a call to a system or library function4 .
A.3 Example
As a complete example of what has been described in this section we give the
serial port setup code provided serialport.c.
4 The mechanism behind this is that when an error occurs, the failing function writes an
error code to the global variable errno, and perror() reads that variable and prints a human-
readable message corresponding to the error code. The error codes are defined in errno.h.
9
# include < sys / types .h >
# include < sys / stat .h >
# include < fcntl .h >
# include < termios .h >
# include < stdlib .h >
# include < strings .h >
# include < stdio .h >
/*
* B A U D R A T E : Set bps rate . You could also use c f s e t i s p e e d and
cfsetospeed .
* CRTSCTS : output h a r d w a r e flow control ( only used if the
cable has
* all n e c e s s a r y lines . )
* CS8 : 8 n1 (8 bit , no parity ,1 stopbit )
* CLOCAL : local connection , no modem contol
* CREAD : enable r e c e i v i n g c h a r a c t e r s
* */
newtio . c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD ;
/*
* IGNPAR : ignore bytes with parity errors
* ICRNL : map CR to NL ( o t h e r w i s e a CR input on the other
computer
* will not t e r m i n a t e input )
* o t h e r w i s e make device raw ( no other input
processing )
* */
newtio . c_iflag = IGNPAR | ICRNL ;
10
/*
* Raw output .
* */
newtio . c_oflag = 0;
/*
* ICANON : enable c a n o n i c a l input
* disable all echo functionality , and don t send
signals to calling program
* */
newtio . c_lflag = ICANON ;
/*
* i n i t i a l i z e all control c h a r a c t e r s
* default values can be found in / usr / include / termios .h , and
are given
* in the comments , but we don t need them here
* */
newtio . c_cc [ VINTR ] = 0; /* Ctrl - c */
newtio . c_cc [ VQUIT ] = 0; /* Ctrl -\ */
newtio . c_cc [ VERASE ] = 0; /* del */
newtio . c_cc [ VKILL ] = 0; /* @ */
newtio . c_cc [ VEOF ] = 4; /* Ctrl - d */
newtio . c_cc [ VTIME ] = 0; /* inter - c h a r a c t e r timer unused */
newtio . c_cc [ VMIN ] = 1; /* b l o c k i n g read until 1 c h a r a c t e r
arrives */
newtio . c_cc [ VSWTC ] = 0; /* \0 */
newtio . c_cc [ VSTART ] = 0; /* Ctrl - q */
newtio . c_cc [ VSTOP ] = 0; /* Ctrl - s */
newtio . c_cc [ VSUSP ] = 0; /* Ctrl - z */
newtio . c_cc [ VEOL ] = 0; /* \0 */
newtio . c_cc [ VREPRINT ] = 0; /* Ctrl - r */
newtio . c_cc [ VDISCARD ] = 0; /* Ctrl - u */
newtio . c_cc [ VWERASE ] = 0; /* Ctrl - w */
newtio . c_cc [ VLNEXT ] = 0; /* Ctrl - v */
newtio . c_cc [ VEOL2 ] = 0; /* \0 */
/*
* now clean the modem line and a c t i v a t e the s e t t i n g s for the
port
* */
tcflush ( fd , TCIFLUSH ) ;
tcsetattr ( fd , TCSANOW , & newtio ) ;
/*
* t e r m i n a l s e t t i n g s done , return file d e s c r i p t o r
* */
return fd ;
}
11
B Serial port communication in Java
In previous years, we have used Java for the host programming. After opening
and configuring the device file, stream I/O is more straight-forward in C, and
using the same language on both the host and the embedded system may allow
some communication code reuse, but if you want to use Java, the instructions
from previous years are included here as a reference. It will not be directly
supported by the lab teachers, though.
12
gnu.io.CommPortIdentifier is the central class for controlling access to com-
munications ports. An application first uses methods in CommPortIden-
tifier to negotiate with the driver to discover which communication ports
are available and then select a port for opening.
ports = CommPortIdentifier.getPortIdentifiers();
while ( ports.hasMoreElements() ) {
cpi = (CommPortIdentifier) ports.nextElement();
if ( cpi.getPortType() == CommPortIdentifier.PORT_SERIAL ) {
System.out.println(cpi.getName());
}
}
}
Some remarks:
The static method CommPortIdentifier.getPortIdentifiers() returns
an enumeration containing all ports in the system.
Since we are only interested in serial ports, we iterate over the enumeration
and select thos ports where the port type is CommPortIdentifier.PORT_SERIAL.
Note the typical iterable idiom: while(x.hasMoreX() {... x.nextX() }.
In a more useful program, you would want to open a particular serial port
(e.g. COM1 or /dev/ttyS0), rather than just printing the port names.
Selecting the proper port can be done by comparing the name to the de-
sired string (e.g., if(cpi.getName().equals("COM1")). Then, the port
is opened by calling cpi.open(String appName, int timeout). Please
see the documentation for details.
port.setSerialPortParams(9600,
SerialPort.DATABITS_8,
13
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
14