Serial C And The Raspberry Pi
Written by Harry Fairhead   
Monday, 29 August 2016
Article Index
Serial C And The Raspberry Pi
Linux Presents A Problem
Setting up
Send and Recieve
Polling for data

 

The Linux driver does a lot of processing on the received data and as a result there are lots of settings that go well beyond the basic baud rate, stop bits,  data bits and parity. However, if we assume that everything is set up reasonably or equivalently if pin  8 and 10 have been connected together for a loopback test, we can already send and receive data: 

char buf[]="hello world";
char buf2[11];
int count = write(sfd, buf, 11);
count=read(sfd,buf2,11);buf2[11]=0;
printf("%s",buf2);
close(sfd);

If you try this out using a loopback connection the chances are you won't see anything from the read. The reason is that by default the serial port echoes the output back to the input, which is okay for connections to a terminal but for a loopback test you usually need to change the configuration -  in principle this is the smallest program that can work with a serial port but not a loopback. 

Notice that we write to and read from the serial port as if it was a file, but of course it isn't and there are some differences. In particular, we can't read back more characters than we wrote in a loopback setup. The read and write function calls are also set up to be blocking - and this often isn't the best way to work. It is important that you know how to handle the fact that you might want to read data, but there might not be any data to be read - see later. 

Setting Up

The default settings work for a loopback, but in most cases we need to talk to another device that often has exact specifications for the serial protocol - baud rate, data bits, parity and stop bits are the most common things we have to set. 

The key to setting any parameters of any device that is being treated as a file, or indeed any file, is to use the ioctl function. This is low level and for the serial device it is easier and more common to use termios which in turn uses the ioctl function to get the job done.

To use termios to configure the serial port we simply fill in the details in a termios struct:

struct termios options;

You can look up all of the possible settings for the stuct's fields - there are a lot. There are also functions that will work with the struct.

There  are a number of fields that can be set, although most of the time you only have to work with a few options.

 
MemberDescription
c_cflag Control options
c_lflag Line options
c_iflag Input options
c_oflag Output options
c_cc Control characters
c_ispeed Input baud (new interface)
c_ospeed Output baud (new interface)

 

The most common are to set the basic operation of the UART using the c_cflag.

Baud rate 

To set the baud rate you can use the functions:

cfsetospeed(&options,speed); //for output speed    cfsetispeed(&options,speed);  //for input speed

where speed is one of:

B0 B50 B75 B110 B134 B150 B200 B300 B600 B1200 B1800 B2400 B4800 B9600 B19200 B38400 B57600 B115200 B230400

There is also a single speed setting function 

cfsetspeed(&options,speed);

The reason you use a function and not simply set the relevant bits is that the way the baud rate is set in different systems varies and the functions cover up the differences. 

Notice that most UARTS can only work at the same speed for input and output and you should set both to the same value unless you know better. 

Data bits

To set the number of data bits you have to directly set a bit mask using CSIZE and values CS5, CS6, CS7, or CS8.

For example:

options.c_cflag &= ~CSIZE;
options.c_cflag |= CS7;

sets the number of data bits to 7.

What is going on here is not hard to understand once you realize that the CS constants are multibit settings. So you first and a bitmask with the current value to zero just the bits in question, then you or the new value you want.

Parity

You can set parity by direct setting of the bits that control it. For example: 

options.c_cflag |= PARENB
options.c_cflag |= PARODD

enables parity and sets it to odd.

Notice that as PARODD and PARENB are single bit settings. You don't have to use a bit mask to zero the location first - just or the set bit with the value and it's done.

To set the opposite of a single bit flag, i.e. to zero the bit in cflag you simply and its not.  That is, you can use &=~PARODD to set even parity and &=~PARENB to set no parity, i.e. to disable parity.

Stop Bits

The default is one stop bit. If you want to set two then you use;

options.c_cflag |= CSTOPB;

or &=~ CSTOPB if you want to explicity clear the stop bit field to set one stop bit. 

There are also two bits we allways have to set in normal operation: 

options.c_cflag |= CLOCAL;
options.c_cflag |= CREAD;

The first ignores the modem control lines and the second enables the read operation.

The only other part of the struct that we generally need to modify is the c_lflag to set local options.

There are two ways the drive handles input - canonical and raw. In canonical mode it works a line at a time, waiting until there is a full line of text, signaled by a carriage return or a line feed, before being sent. In raw mode characters are passed through as they arrive and they are not processed in any way.

For device to device communications raw is the mode you want to use. In addition you also generally want to stop any echo of input to output. You can also opt to handle the software interrupts, the signals, that the serial port generates - usually you don't want to.

To set raw mode with no echo and no signals you use:

options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

Note as the flags are all single bit they can be ored together and applied in a single operation. You can do this with other single bit flags to simplify setting options.

There is a similar setting for raw output:

options.c_oflag |= OPOST;

A simpler way of setting all options so that you have raw mode on input and output is to use the function

cfmakeraw(pointer to termios);

This sets the following options:

c_iflag &= ~(IGNBRK | BRKINT | PARMRK |
           ISTRIP | INLCR | IGNCR | ICRNL | IXON);
c_oflag &= ~OPOST;
c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); c_cflag &= ~(CSIZE | PARENB); c_cflag |= CS8;

The final problem is that we also need to fill in values for all of the other fields in the termios struct and mostly we aren't interested in them. The standard technique is to first fill the termios struct with the current values set on the serial port and then simply modify the ones of interest.

The function:

tcgetattr(filedescriptor, pointer to termios)

will fill the struct with the current values corresponding to the serial port specified by the file descriptor. 

The function:

tcsetattr(filedescriptor, when,pointer to termios)

does the reverse job and stores the values back in the device. 

There is one last matter to be cleared up. When you change the configuration of the serial port it might already be in the middle of sending or receiving some data. You need to specify when you want the changes to take effect. This is what the when parameter specifies and you can usually set it to TCSANOW for an immediate change.

So now our standard algorithm is to get the attributes, modify the ones we want to and then set the attributes back in the device. 

For example to set raw mode. no echo, with 8n1 at 9600 baud you would use:

struct termios options;
tcgetattr(sfd, &options);
cfsetspeed(&options, B9600);
options.c_cflag &= ~CSTOPB;
options.c_cflag |= CLOCAL;
options.c_cflag |= CREAD;
cfmakeraw(&options);
tcsetattr(sfd, TCSANOW, &options);

 

If this program doesn't work and you can't set the parameters of the serial port, the reason is usually that Linux has not relinquished control of the port as a console. 

 


Last Updated ( Thursday, 01 December 2016 )