Serial Port Prototypting
Lots of small embedded/robotic systems make extensive use of serial ports for communication between sensors, actuators, and a central processor of some sort. Despite this being one of the simplest interfaces one can work with, it can still be a serious headache to initially set up, debug, and prototype with. This tutorial is going to look at some handy tools you can use to make prototyping, development, and hardware configuration easier when using a serial port on a Linux box.
Most of the default behaviors for Linux’s serial port functionality were chosen based on the assumption that the port would be used for communication with some kind of external modem or terminal application. While the implementation is flexible enough to give you near complete control of the port, these defaults can be disorienting for first timers.
One of the more annoying behaviors is line termination – depending on your particular OS the serial port might be sticking new line or carriage return characters on the end of your packets without you realizing it. Weird things might also happen when your control program opens or closes the port.
Ideally what you’re after is a ‘raw port’, one which sends only the exact character sequences specified. There’s a few different ways to go about getting this behavior:
Via C Program
From within a C program a serial port’s properties are set through the termios interface.
First, obtain a file handle to the serial device based on the IO characteristic desired:
int fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NONBLOCK); //non-blocking int f d = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_SYNC); //blocking
Then, set up the control data structure and use it to set the baud rate for the port:
struct termios attrs; cfsetispeed(&attrs, B115200); cfsetospeed(&attrs, B115200);
Set the control flags associated with desired port behavior:
attrs.c_cflag &= ~PARENB;
attrs.c_cflag |= PARENB; attrs.c_cflag |= PARODD;
attrs.c_cflag |= PARENB; attrs.c_cflag &= ~PARODD;
|One Stop Bit||
attrs.c_cflag &= ~CSTOPB;
|Two Stop Bits||
attrs.c_cflag |= CSTOPB;
|Set Character/Word Size to N=[5,8] bits||
attrs.c_cflag &= ~CSIZE; attrs.c_cflag |= CSN;
Finally, apply the settings structure to the port to commit the changes.
tcsetattr(fd, TCSANOW, &attrs);
Via Command Line
You can also deal with this settings completely outside of your program, which cuts down on added logic that doesn’t actually add to your controller. Be aware that each time you reboot the machine (or add/remove the USB device when applicable) the settings are going to reset.
To set raw mode and a baud rate, use the following command:
stty -F /dev/ttyS0 raw
|Odd Parity||parenb parodd|
|Even Parity||parenb -parodd|
|One Stop Bit||-cstopb|
|Two Stop Bits||cstopb|
|Set Character/Word Size to N=[5,8] bits||csN|
To set a port to 115200 baud with even parity, a 8 bit character size, and two stop bits:
stty -F /dev/ttyS0 raw 115200 stty -F /dev/ttyS0 parenb -parodd cstopb cs8
Check the stty man page for more information.
Command Line I/O
When you’re first testing a new peripheral, it’s a pain to have to write up a full program in order to send out a simple test string to see if the device works. Fortunately, unix has a solution – echo. echo is used to copy a string specified by the user to stdout (or to some node like /dev/ttyS0 that the user specifies using a pipe). Let’s say I wanted to print “hello world” to a file, I would do so by:
echo "hello world" > somefile
And now somefile will contain the string “hello worldn”. Echo can also be used to push arbitrary binary strings to a file. (Don’t forget, in unix my serial port is also a file.) Let’s say I want to send a byte string of:
[ 0xFF, 0xFF, 0x10, 0x11, 0x12, 0x2D ]
out of /dev/ttyS0. I can do this by writing some C code to push the characters, or I can avoid reinventing the wheel and use echo:
echo -n -e "xFFxFFx10x11x12x2D" > /dev/ttyS0
You’ll notice that two new command line switches were added to my echo call. The first ‘-n’ supresses the automatic newline that echo adds by default to the end of a string. If I’m printing out to a serial port I almost certainly don’t want this behavior, so I use ‘-n’ to turn it off. The second switch ‘-e’ enables the intepretation of “backslash escapes”, these are the format codes like “xFF” that allow us to print non-ascii characters that aren’t available on the keyboard. You can read about all of them in the man page, the one’s you’re most likely to use are:
- xHH – specify a byte with hexadecimal value HH
- NNN – specify a byte with octal value NNN
- n – newline character (equivalent to x0A or 012)
cat /dev/ttyS0 | hexdump
cat /dev/ttyS0 | hexdump | tee somefile
Another painful part of developing for serial devices is unit testing the code. Most times the best way to really exercise code is with other bits of code – before you hook things up to your often expensive hardware. It’s possible to do code testing using either files or pipes as an endpoint (instead of your serial device), but these mechanisms aren’t entirely symmetric in terms of their access mechanisms. Character files don’t track well – input and output tend to step on each other’s toes. Pipes, on the other hand, are unidirectional where a terminal endpoint is bidirectional, forcing the I/O logic to fork at the front end and creating needless headaches. How, then, does one achieve a reasonable simulation of the device from a file access standpoint? Then answer is TTY emulation through socat.
Socat (SOcket CAT) is (quoting the man page) “a command line based utility that establishes two bidirectional byte streams and transfers data between them.” Basically it lets you create all types of pipes, sockets, and other transport mechanisms. For embedded systems work it incredibly useful for its ability to credibly fake a serial port (pty). The following command will create a pair of virtual ptys:
socat -d -d pty,echo=0,raw pty,echo=0,raw
The program will print out the two end points it has created (probably in /dev/pts/, but that depends on your particular system). To one of the end points you can hook your control program. To the other you’ll have to hook up to something that can somewhat credibly fake the I/O characteristic of your test device – or you could just use the cat/echo combination from above. The one hang up with this approach is that the actual ptys allocated are not always consistent – sometimes /dev/pts/1 will be allocated and sometimes /dev/pts/5 will be allocated. This is a function of the state of the OS as a whole and there’s not a lot you can do about it. Socat can, however, create links to the unreliably named pts nodes. The links themselves will be in whatever location is specifed, with the names requested. To do this:
socat pty,echo=0,raw,link=link1 pty,echo=0,raw,link=link2
This will create link1 and link2 in the current directory that will point back to the underlying ptys.
If you’ve written your controller to use stdio to do its communication, you can have socat invoke the program directly and connect the endpoint automatically. To get this behavior do the following:
socat -d -d pty,echo=0,raw "exec:progname
Socat’s command line can also be used to specify whatever configuration parameters the virtual ports might require (the same type of behavior covered in the “Port Configuration” section above.
|Baud Rate||bRATE (ex: b115200)|
|One Stop Bit||cstopb=0|
|Two Stop Bits||cstopb=1|
|Set Character/Word Size to N=[5,8] bits||csN (ex: cs8)|
*(The man page indicates that these options should work, but the version of socat on my system would not accept them – your mileage may vary.)
To create a port pair with 115200 baud with no parity, a 8 bit character size, and two stop bits:
socat pty,echo=0,raw,link=link1,b115200,parenb=0,cs8,cstopb=1 pty,echo=0,raw,link=link2,b115200,parenb=0,cs8,cstopb=1