Overview of the Serial Communication Protocol

Serial on a Server

Any time you are connected to a server over SSH, you are using a serial protocol. Today, it is carried by TCP/IP, along with all of your other network traffic, rather than over a dedicated line, but the principle is the same. When you open a terminal window in your favorite Linux distro, you're actually emulating what used to be a hard-wired serial connection.

You can still set up a hardware serial terminal as a console to your Linux machine. The first step is to get it wired to the computer. You probably have a 9-pin serial port, so you'll need a 9-pin serial cable. Most hardware terminals use a 25-pin connector, so you also need a 9-pin to 25-pin adapter, which you can easily find on Amazon or at your local electronics shop.

Once everything is connected, look at the manual for your terminal to find out how to set up the baud rate and other connection parameters, which should be 115,200 baud, 8 data bits, no parity bit, and 1 stop bit.

Once you are wired up, you need software to present a shell to the terminal over the serial line. A program called agetty should already be available in your distribution or available from the package manager. Open a terminal window on your desktop and type agetty. If it says not enough arguments you're good to go. The man page shows the command format:

agetty [options] port [baud_rate...]

See the "Finding Your Serial Port" box to determine your port name; then, enter something like:

Finding Your Serial Port

Linux reports all of its hardware, including serial ports, as files in /dev. If your computer has a port on the back of the CPU, then it is probably found at /dev/ttyS0. If you're trying to talk to an Arduino, it is probably /dev/ttyAMA0, whereas if you're using a USB dongle, it is probably something like /dev/ttyUSB0. You can find your port quickly and easily as follows:

  1. Make sure your USB serial port or Arduino is unplugged
  2. In a terminal, enter ls -1 /dev/tty*>noUSB.txt
  3. Connect your USB serial port and give it a second or so to register
  4. In the terminal, enter ls -1 /dev/tty*>USB.txt
  5. Finally, enter diff noUSB.txt USB.txt

You'll see something like:

98a99
> /dev/ttyUSB0

In steps 2 and 4 above, you're using ls to get a list of tty devices found in /dev. The -1 requests output of one entry per line instead of output in columns. The >noUSB.txt redirects output to a text file. After connecting a USB device, step 4 makes a new list that will include the device that's just been connected, and step 5, the diff command, compares the two output files.

The first line of output reports that after line 98 in the original file (noUSB.txt) the second file (USB.txt) has an additional (a) line 99. The next line of output shows that line after the chevron. In this case, that's the name of the USB serial port that appeared when the device was connected.

agetty ttyUSB0 115200

If all goes well, a login prompt should appear on your hard-wired terminal (Figure 4). Once you log in, the system's default shell will appear. You can also configure agetty to run a custom handler when something connects (e.g., any kind of service you've written), pass the connection off to another program to start a dial-up Internet connection, or anything along those lines.

Figure 4: The agetty command running on a Wyse serial terminal. Once logged in, this physical terminal will work just like a terminal window.

Curses Library

Whether you're using a hard-wired terminal or just a virtual terminal in a window on your computer, this example will work just fine. Because so many different terminals are available with different sizes, capabilities, and options, the curses library [5] was developed to handle all of the details in the background, so the developer can just write code and not worry about the capabilities of the underlying hardware. If a particular feature isn't available on a terminal, curses will downgrade the features until it can be displayed. If necessary, the output will just be unstyled black and white text.

The Big Print Clock

A popular thing to do in a serial terminal is display big block text several lines tall. The bigPrint.py Python script shows a clock in block text (Figure 5) and is a great example of an application of the curses library.

Figure 5: The Big Print clock running in a terminal window. I didn't have to make any changes to the code between running it on a desktop and in the physical terminal; curses took care of all of the code translations, modes, and so on.

In the Python program in Listing 4, import brings in the curses, time, os, string, and datetime libraries. Note that line 5 is a little different: Rather than importing the entire library, it only imports the datetime module. By default, Python imports entire libraries, so an import in this format allows you to bring in only the parts you need.

Listing 4

clock.py

001 import curses
002 import time
003 import os
004 import string
005 from datetime import datetime
006
007 class console:
008    def __init__ ( self ):
009       self.screen = curses.initscr()
010       curses.noecho()
011       curses.cbreak()
012       self.screen.nodelay ( True )
013       self.screen.keypad ( True )
014
015       self.digits = dict()
016       self.digits [ '0' ] = """
017  ###
018 #   #
019 #   #
020 #   #
021  ###
022 """
023       self.digits [ '1' ] = """
024   #
025  ##
026   #
027   #
028 #####
029 """
030       self.digits [ '2' ] = """
031  ###
032 #   #
033    #
034   #
035 #####
036 """
037       self.digits [ '3' ] = """
038  ###
039 #   #
040    #
041 #   #
042  ###
043 """
044       self.digits [ '4' ] = """
045  #  #
046 #   #
047 #####
048     #
049     #
050 """
051       self.digits [ '5' ] = """
052 #####
053 #
054  ###
055     #
056 ####
057 """
058       self.digits [ '6' ] = """
059  ###
060 #
061 ####
062 #   #
063  ###
064 """
065       self.digits [ '7' ] = """
066 #####
067     #
068    #
069   #
070  #
071 """
072       self.digits [ '8' ] = """
073  ###
074 #   #
075  ###
076 #   #
077  ###
078 """
079       self.digits [ '9' ] = """
080  ###
081 #   #
082  ####
083     #
084  ###
085 """
086       self.digits [ ':' ] = """
087
088   #
089
090   #
091
092 """
093
094    def bigPrint ( self , text , position ):
095       x = position [ 1 ]
096       y = position [ 0 ]
097       originalY = y
098
099       for char in text:
100          if char in self.digits:
101             firstLine = True
102             for line in self.digits [ char ].split ( "\n" ):
103                if firstLine == True:
104                   firstLine = False
105                   continue
106                outLine = ""
107                for char in line:
108                   if char == "#": outLine += chr ( 97 )
109                   else: outLine += char
110                self.screen.addstr ( y , x , "{0:5s}".format ( outLine ) , curses.A_ALTCHARSET )
111                y += 1
112                if y > ( originalY + 4 ): break
113          y = originalY
114          x += 7
115
116    def loop ( self ):
117       looping = True
118       startTime = time.time()
119       nextClock = startTime + 1
120
121       while looping:
122          key = None
123          try:
124             key = self.screen.getch()
125          except:
126             pass
127
128          if key == ord ( " " ):
129             looping = False
130
131          if time.time() > nextClock:
132             now = datetime.now()
133             self.bigPrint ( now.strftime ( "%H:%M:%S" ) , ( 1 , (80-56) / 2 ) )
134             nextClock += 1
135
136 screen = console()
137 screen.loop()

In programming, a class is a set of variables and functions that represent a bigger unit. You can create instances of classes that all have the same capabilities but operate independently. If a program were an orchestra, a class could represent each musician. Here, the console class represents a set of functions to display block text on the screen.

The __init__ function is a special function in a class. It is called automatically whenever the class is instantiated (created for the first time). Similar to setup in the Arduino code, it lets you get the class ready for whatever it needs to do. Here I use it to set up curses.

Line 9 initializes the curses library, and lines 10 and 11 set up how I want curses to function overall. The curses.noecho function says not to echo (print) characters as they are typed; therefore, any characters drawn to the screen have to be put there explicitly. The curses.cbreak line tells curses to return any key presses immediately, instead of waiting for the Enter key to be pressed.

Sometimes you also might want to check to see whether a key is pressed and continue whether it is or not. Curses calls this nodelay. Here, I have to set this mode explicitly on self.screen, the terminal display. In this program, I'm using the entire terminal as a single window, but curses supports splitting it into smaller windows and controlling them individually, so each can react to keys differently; therefore, I have to tell it explicitly which window I want to place in nodelay mode.

The self.screen.keypad line tells curses to interpret escape sequences coming from the terminal automatically. Special keys like the arrow keys, function keys, and Page Up/Down keys usually transmit an escape and then a key code. In keypad mode, you get a constant instead, which is easier to compare and makes a program more readable.

The rest of the __init__ function defines the block characters that will be displayed in the Big Print clock. Line 15 creates a dictionary of definitions. The self.digits key (which is   in line 16) specifies the character being stored. Triple quotes start a block string. Until Python encounters another set of triple quotes, everything will be considered a part of the string. Lines 17-21 are a block of lines and characters that define what should be filled in to draw the character identified in the key.

Each set of seven lines defines the numbers 0 through 9 and a colon. I chose the # character for the blocks, but that choice is arbitrary. The bigPrint method (lines 94-114) shows why.

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Perl: Arduino Project

    With a few lines of self-written firmware and a simple Perl script, Perlmeister Mike Schilli trains one of his Linux computers with a plugged in Arduino board to switch electrical equipment on and off using a relay. Enchanting.

  • Nerf Target Game

    A cool Nerf gun game for a neighborhood party provides a lesson in Python coding with multiple processors.

  • Escape Room Puzzle

    A digital puzzle presents a challenge for young people in an escape room.

  • WiFi Thermo-Hygrometer

    A WiFi sensor monitors indoor humidity and temperature and a Node-RED dashboard reports the results, helping you to maintain a pleasant environment.

  • ESP8266 for WiFi Sniffing

    The ESP8266 is in the core of many IoT devices. Thanks to ESP8266 sniffer mode, you can monitor the WiFi medium for diagnostics and optimization.

comments powered by Disqus

Direct Download

Read full article as PDF:

News