11. The other IO lines

Additionally, there are several mechanisms for handling the 6 other non-data wires, and allowing programs to use these, much like the possibilities for DOS. The first section here is for the Linux systems on PCs, then there are a few notes at the end about other systems, where some of these features also are available.

There are two ways I have found out about. One is using ioperm() to allow access to the IO ports, then inb() and outb() to actually set and reset the bits in the ports as per the details in section 3. This has two big disadvantages: 1. It requires root access for the process to call ioperm(). 2. It does not play well with the regular /dev/ttySx device file handling.

The other way is via ioctl() calls, similar in appearance to the ones used for setting and reading the baud rates. This works well together with the /dev/ttySx device file handling, and does not require any special user access once the /dev/ttySx files themselves are made available for reading and writing.

These ioctl() calls also makes it possible to detect changes on the 4 input lines, and have this trigger some other activity in the program, whether this is reading data or just reacting to a change in the signal and do something else.

Here are some functions that do these fun things. The necessary includes are also shown.

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <termio.h>
#include <unistd.h>
#include <malloc.h>

#include <asm/ioctls.h>
/* #include <asm/termios.h> Already included through termio.h */
#include <linux/serial.h>

extern int errno;

/* Pinouts and bits: 
    Name P/25 P/9 Dr Bitname
    CD   8/25 1/9 in TIOCM_CD
    RXD  3/25 2/9 in
    TXD  2/25 3/9 ut
    DTR 20/25 4/9 ut TIOCM_DTR
    GND  7/25 5/9 --
    DSR  6/25 6/9 in TIOCM_DSR
    RTS  4/25 7/9 ut TIOCM_RTS 
    CTS  5/25 8/9 in TIOCM_CTS
    RI  22/25 9/9 in TIOCM_RNG
*/ 

/* Sleep and continue when one of the 4 control lines have changes on them. 
    Return 1-4 flags to indicate which one. Set the flag to a valid int* for
    checking the line and to NULL to ignore that line.  The function 
    returns -1 for errors and 0 for OK when a line change has been detected. 

    Returned values will be 0 for no change on that line, some non-0 
    indicating how many changes were captured.
*/

int serwaiton1(int eid, int *dcd, int *rng, int *dsr, int *cts)
{
    struct serial_icounter_struct beforec;
    struct serial_icounter_struct afterc;
    int icstatus, flags;

    flags = 0;
    if(dcd) { *dcd = 0; flags |= TIOCM_CD; } 
    if(rng) { *rng = 0; flags |= TIOCM_RNG; } 
    if(dsr) { *dsr = 0; flags |= TIOCM_DSR; } 
    if(cts) { *cts = 0; flags |= TIOCM_CTS; } 

    if(flags == 0) return(-1); /* Nothing to do ! */

    memset(&beforec, 0, sizeof(struct serial_icounter_struct));
    memset(&afterc, 0, sizeof(struct serial_icounter_struct));

    icstatus = ioctl(eid, TIOCGICOUNT, &beforec);
    if(icstatus == -1) return(-1);

    /* Wait on all the flags here */
    icstatus = ioctl(eid, TIOCMIWAIT, flags);
    if(icstatus == -1) return(-1);

    icstatus = ioctl(eid, TIOCGICOUNT, &afterc);
    if(icstatus == -1) return(-1);

    if(dcd) { *dcd = (beforec.dcd != afterc.dcd); }
    if(cts) { *cts = (beforec.cts != afterc.cts); }
    if(rng) { *rng = (beforec.rng != afterc.rng); }
    if(dsr) { *dsr = (beforec.dsr != afterc.dsr); }

    return(icstatus);
}


/* Read the 4 input bits CD RI DSR CTS and return
    the 4 integers set to 0 or non-zero 
    Pass NULL in if there is no interest in any of them.
*/ 
int serwirebits(int eid, int *dcd, int *rng, int *dsr, int *cts)
{
    int flags, sgx; 
    sgx = ioctl(eid, TIOCMGET, &flags);
    if(sgx == -1) return(-1); 

    if(dcd) *dcd = flags & TIOCM_CAR ? 1 : 0; 
    if(rng) *rng = flags & TIOCM_RNG ? 1 : 0; 
    if(dsr) *dsr = flags & TIOCM_DSR ? 1 : 0; 
    if(cts) *cts = flags & TIOCM_CTS ? 1 : 0; 

    return(sgx); 
}

/* Set the two Wire output bits. 
    Writing 1 to these sets Space (positive voltage, L out from a 1489)
    Writing 0 to these sets Mark (negative voltage, H out from a 1489) 

    RTS is pin 4/25 or 7/9
    DTR is pin 20/25 or 4/9
*/ 
int AssertRTS(int eid, int spacestate)
{
    int flags, sgx; 
    sgx = ioctl(eid, TIOCMGET, &flags);

    if(spacestate)
        flags |= TIOCM_RTS;
    else
        flags &= ~TIOCM_RTS;

    sgx = ioctl(eid, TIOCMSET, &flags);
    return(sgx); 
}

int AssertDTR(int eid, int spacestate)
{
    int flags, sgx; 
    sgx = ioctl(eid, TIOCMGET, &flags);

    if(spacestate)
        flags |= TIOCM_DTR;
    else
        flags &= ~TIOCM_DTR;

    sgx = ioctl(eid, TIOCMSET, &flags);
    return(sgx); 
}

The serial ports are named /dev/ttyS0, /dev/ttyS1, and and so on. PCs typically have 2 ports standard, these would be /dev/ttyS0 and /dev/ttyS1. Add-in cards can extend this by more ports, in which case there may be a sharing of interrupts between two or more ports. This actually does seem to complicate things quite a bit. This is the subject of the next section.

HP-UX

As it turns out, a similar mechanism for handling the levels of the control lines is found on hp-ux, under modem(7). This only applies to the ports connected via DB25s, since the RJ11 ones only have the two data lines and no control lines.

The ioctl commands for get and set are called MCGETA and MCSETA; and the wire names are MRTS, MCTS, MDSR, MDCD, MDTR, and MRI. In addition, there is a third output line, "Data Rate Select", on the Port 0 of the 98642A card, to pin 23, which is identified with MDRS. Standard documentation indicates this may be a two-direction line, but it appears to be at least an output on the 98642A.

There is no equivalent of the TIOCMIWAIT; this seems to be something that is implemented on Linux only, and even there, only on the built-in serial ports.

FreeBSD

On FreeBSD, according to tty(4), the TIOCMGET and TIOCMSET ioctl commands also exist for reading the 4 inputs and setting the two outputs, in the same way as shown above here. There is some latching or delay issues with the RI line. Not setting the O_NDELAY option on open causes the TIOCMGET call to hang until the DCD is at Space. There does not seem to be any of the edge-counting or waiting, TIOCMWAIT, functions there however, just the static line reading.

Picotux

On the Picotux devices, which run Linux, their native GPIO functions must be used instead for reading and writing the control lines. On the Picotux 110, or by suitable hardware modifications on the Picotux 112, all the IO-lines that were assigned to be inputs or outputs on the serial port, can be re-assigned; besides there are a few more GPIO lines present, that have nothing to do with the serial port at all.

Raspberry Pi

There is the in-built serial port /dev/ttyAMA0 which normally has a getty process running on it at 115200 baud. Thus we can log-in to the device from here. For the non-networked version this can sometimes be useful.

The transmit and receive signals available run at 3.3V, so for the standard RS232 levels, some sort of level translator is needed.

The other GPIO lines are completely independent of the serial port. Some of these may be put into use for handshaking, but in general they are better used as independent ones.

More on the serial port is here.

More on GPIOs is here.

Pine A64

This device has several serial ports, identified as UARTs numbered from 0 onwards, and with corresponding device files /dev/ttyS# for uart number #. Like the Raspberry Pi and Picotux, the connections are on logic level, 3.3V or even lower, and we need to use a MAX3232 or similar to translate these to Mark and Space.

More on the Pine64's serial ports here.

To 0. Introduction

To 10. Programming on Unix-systems: Linux

To 12. USB-serial converters under Linux.

Table of ASCII codes

To index page