Copyright (c) Hyperion Entertainment and contributors.

Serial Device

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
Warning.png This page is not yet fully updated to AmigaOS 4.x some of the information contained here may not be applicable in part or totally.

Serial Device

The serial device provides a hardware-independent interface to the Amiga’s built-in RS-232C compatible serial port. Serial ports have a wide range of uses, including communication with modems, printers, MIDI devices, and other computers. The same device interface can be used for additional “byte stream oriented devices”—usually more serial ports. The serial device is based on the conventions of Exec device I/O, with extensions for parameter setting and control.

Serial Device Characteristics

Modes Exclusive, Shared Access
Baud Rates 110–292,000
Handshaking Three-Wire, Seven-Wire

Serial Device Commands and Functions

Command Command Operation
CMD_CLEAR Reset the serial port’s read buffer pointers.
CMD_FLUSH Purge all queued requests for the serial device (does not affect active requests).
CMD_READ Read a stream of characters from the serial port buffer. The number of characters can be specified or a termination character(s) used.
CMD_RESET Reset the serial port to its initialized state. All active and queued I/O requests will be aborted and the current buffer will be released.
CMD_START Restart all paused I/O over the serial port. Also sends an “xON”.
CMD_STOP Pause all active I/O over the serial port. Also sends an “xOFF”.
CMD_WRITE Write out a stream of characters to the serial port. The number of characters can be specified or a NULL-terminated string can be sent.
SDCMD_BREAK Send a break signal out the serial port. May be done immediately or queued. Duration of the break (in microseconds) can be set by the application.
SDCMD_QUERY Return the status of the serial port lines and registers, and the number of bytes in the serial port’s read buffer.
SDCMD_SETPARAMS Set the parameters of the serial port. This ranges from baud rate to number of microseconds a break will last.

Device Interface

The serial device operates like the other Amiga devices. To use it, you must first open the serial device, then send I/O requests to it, and then close it when finished. See Exec Device I/O for general information on device usage.

The I/O request used by the serial device is called IOExtSer.

struct IOExtSer
{
    struct   IOStdReq IOSer;
    ULONG    io_CtlChar;    /* control characters */
    ULONG    io_RBufLen;    /* length in bytes of serial read buffer */
    ULONG    io_ExtFlags;   /* additional serial flags */
    ULONG    io_Baud;       /* baud rate */
    ULONG    io_BrkTime;    /* duration of break in microseconds */
    struct   iOTArray  io_TermArray;  /* termination character array */
    UBYTE    io_ReadLen;    /* number of bits per read character */
    UBYTE    io_WriteLen;   /* number of bits per write character */
    UBYTE    io_StopBits;   /* number of stopbits for read */
    UBYTE    io_SerFlags;   /* serial device flags */
    UWORD    io_Status;     /* status of serial port and lines */
};

See the include file devices/serial.h for the complete structure definition.

Opening the Serial Device

Three primary steps are required to open the serial device:

  • Create a message port using AllocSysObject() and the ASOT_PORT type. Reply messages from the device must be directed to a message port.
  • Create an extended I/O request structure of type IOExtSer using AllocSysObject() and the ASOT_IOREQUEST type. AllocSysObject() will initialize the I/O request to point to your reply port.
  • Open the serial device. Call OpenDevice(), passing the I/O request.
struct MsgPort *SerialMP = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
 
if (SerialMP != NULL)
{
    struct IOExtSer *SerialIO = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
        ASOIOR_ReplyPort, SerialMP,
        ASOIOR_Size, sizeof(struct IOExtSer),
        TAG_END);
 
    if (SerialIO != NULL)
    {
        SerialIO->io_SerFlags = SERF_SHARED;  /* Turn on SHARED mode */
        if (IExec->OpenDevice(SERIALNAME, 0, (struct IORequest *)SerialIO, 0) )
            IDOS->Printf("%s did not open\n", SERIALNAME);

During the open, the serial device pays attention to a subset of the flags in the io_SerFlags field. The flag bits, SERF_SHARED and SERF_7WIRE, must be set before open. For consistency, the other flag bits should also be properly set. Full descriptions of all flags will be given later.

The serial device automatically fills in default settings for all parameters—stop bits, parity, baud rate, etc. For the default unit, the settings will come from Preferences. You may need to change certain parameters, such as the baud rate, to match your requirements. Once the serial device is opened, all characters received will be buffered, even if there is no current request for them.

Reading From the Serial Device

You read from the serial device by passing an IOExtSer to the device with CMD_READ set in io_Command, the number of bytes to be read set in io_Length and the address of the read buffer set in io_Data.

#define READ_BUFFER_SIZE 256
char SerialReadBuffer[READ_BUFFER_SIZE]; /* Reserve SIZE bytes of storage */
 
SerialIO->IOSer.io_Length   = READ_BUFFER_SIZE;
SerialIO->IOSer.io_Data     = (APTR)&SerialReadBuffer[0];
SerialIO->IOSer.io_Command  = CMD_READ;
IExec->DoIO((struct IORequest *)SerialIO);

If you use this example, your task will be put to sleep waiting until the serial device reads 256 bytes (or terminates early). Early termination can be caused by error conditions such as a break. The number of characters actually received will be recorded in the io_Actual field of the IOExtSer structure you passed to the serial device.

Writing to the Serial Device

You write to the serial device by passing an IOExtSer to the device with CMD_WRITE set in io_Command, the number of bytes to be written set in io_Length and the address of the write buffer set in io_Data.

To write a NULL-terminated string, set the length to -1; the device will output from your buffer until it encounters and transmits a value of zero (0x00).

SerialIO->IOSer.io_Length   = -1;
SerialIO->IOSer.io_Data     = (APTR)"Life is but a dream.";
SerialIO->IOSer.io_Command  = CMD_WRITE;
IExec->DoIO((struct IORequest *)SerialIO);             /* execute write */

The length of the request is -1, meaning we are writing a NULL-terminated string. The number of characters sent can be found in io_Actual.

Closing the Serial Device

Each OpenDevice() must eventually be matched by a call to CloseDevice(). When the last close is performed, the device will deallocate all resources and buffers.

All IORequests must be complete before CloseDevice(). Abort any pending requests with AbortIO().

if (!(IExec->CheckIO(SerialIO)))
    {
    IExec->AbortIO((struct IORequest *)SerialIO);  /* Ask device to abort request, if pending */
    }
IExec->WaitIO((struct IORequest *)SerialIO);       /* Wait for abort, then clean up */
IExec->CloseDevice((struct IORequest *)SerialIO);

A Simple Serial Port Example

/*
 * Simple_Serial.c
 *
 * This is an example of using the serial device.  First, we will attempt 
 * to create a message port with CreateMsgPort().  Next, we will attempt 
 * to create the IORequest with CreateExtIO().  Then, we will attempt to 
 * open the serial device with OpenDevice().  If successful, we will write 
 * a NULL-terminated string to it and reverse our steps.  If we encounter 
 * an error at any time, we will gracefully exit.
 *
 * Run from CLI only
 */
 
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/io.h>
#include <devices/serial.h>
 
#include <proto/exec.h>
#include <proto/dos.h>
 
int main()
{
struct MsgPort *SerialMP = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
 
/* Create the message port */
if (SerialMP != NULL)
    {
    /* Create the IORequest */
    struct IOExtSer *SerialIO = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
        ASOIOR_ReplyPort, SerialMP,
        ASOIOR_Size, sizeof(struct IOExtSer),
        TAG_END);
 
    if (SerialIO != NULL)
        {
        /* Open the serial device */
        if (IExec->OpenDevice(SERIALNAME, 0, (struct IORequest *)SerialIO, 0))
 
            /* Inform user that it could not be opened */
            IDOS->Printf("Error: %s did not open\n",SERIALNAME);
        else
            {
            /* device opened, write NULL-terminated string */
            SerialIO->IOSer.io_Length   = -1;
            SerialIO->IOSer.io_Data     = (APTR)"Amiga ";
            SerialIO->IOSer.io_Command  = CMD_WRITE;
            if (IExec->DoIO((struct IORequest *)SerialIO))     /* execute write */
                IDOS->Printf("Write failed.  Error - %ld\n", SerialIO->IOSer.io_Error);
 
            /* Close the serial device */
            IExec->CloseDevice((struct IORequest *)SerialIO);
            }
        /* Delete the IORequest */
        IExec->FreeSysObject(ASOT_IOREQUEST, SerialIO);
        }
    else
        /* Inform user that the IORequest could be created */
        IDOS->Printf("Error: Could create IORequest\n");
 
    /* Delete the message port */
    IExec->FreeSysOject(ASOT_PORT, SerialMP);
    }
else
    /* Inform user that the message port could not be created */
    IDOS->Printf("Error: Could not create message port\n");
 
    return 0;
}
DoIO() vs. SendIO()
The above example code contains some simplifications. The DoIO() function in the example is not always appropriate for executing the CMD_READ or CMD_WRITE commands. DoIO() will not return until the I/O request has finished. With serial handshaking enabled, a write request may never finish. Read requests will not finish until characters arrive at the serial port. The following sections will demonstrate a solution using the SendIO() and AbortIO() functions.

Alternative Modes for Serial Input or Output

As an alternative to DoIO() you can use an asynchronous I/O request to transmit the command. Asynchronous requests are initiated with SendIO(). Your task can continue to execute while the device processes the command. You can occasionally do a CheckIO() to see if the I/O has completed. The write request in this example will be processed while the example continues to run:

SerialIO->IOSer.io_Length   = -1;
SerialIO->IOSer.io_Data     = (APTR)"Save the whales! ";
SerialIO->IOSer.io_Command  = CMD_WRITE;
SendIO((struct IORequest *)SerialIO);

printf("CheckIO %lx\n",CheckIO((struct IORequest *)SerialIO));
printf("The device will process the request in the background\n");
printf("CheckIO %lx\n",CheckIO((struct IORequest *)SerialIO));
WaitIO((struct IORequest *)SerialIO);   /* Remove message and cleanup */

Most applications will want to wait on multiple signals. A typical application will wait for menu messages from Intuition at the same time as replies from the serial device. The following fragment demonstrates waiting for one of three signals. The Wait() will wake up if the read request ever finishes, or if the user presses Ctrl-C or Ctrl-F from the Shell. This fragment may be inserted into the above complete example.

/* Precalculate a wait mask for the CTRL-C, CTRL-F and message
 * port signals.  When one or more signals are received,
 * Wait() will return.  Press CTRL-C to exit the example.
 * Press CTRL-F to wake up the example without doing anything.
 * NOTE: A signal may show up without an associated message!
 */

WaitMask = SIGBREAKF_CTRL_C|
            SIGBREAKF_CTRL_F|
             1L << SerialMP->mp_SigBit;

SerialIO->IOSer.io_Command  = CMD_READ;
SerialIO->IOSer.io_Length   = READ_BUFFER_SIZE;
SerialIO->IOSer.io_Data     = (APTR)&SerialReadBuffer[0];
SendIO(SerialIO);

printf("Sleeping until CTRL-C, CTRL-F, or serial input\n");

while (1)
       {
       Temp = Wait(WaitMask);
       printf("Just woke up (YAWN!)\n");

       if (SIGBREAKF_CTRL_C & Temp)
           break;

       if (CheckIO(SerialIO) ) /* If request is complete... */
           {
           WaitIO(SerialIO);   /* clean up and remove reply */          
           printf("%ld bytes received\n",SerialIO->IOSer.io_Actual);
           break;
           }
       }
AbortIO(SerialIO);  /* Ask device to abort request, if pending */
WaitIO(SerialIO);   /* Wait for abort, then clean up */
WaitIO() vs. Remove(). The WaitIO() function is used above, even if the request is already known to be complete. WaitIO() on a completed request simply removes the reply and cleans up. The Remove() function is not acceptable for clearing the reply port; other messages may arrive while the function is executing.

High Speed Operation

The more characters that are processed in each I/O request, the higher the total throughput of the device. The following technique will minimize device overhead for reads:

  • Use the SDCMD_QUERY command to get the number of characters currently in the buffer (see the devices/serial.h Autodocs for information on SDCMD_QUERY).
  • Use DoIO() to read all available characters (or the maximum size of your buffer). In this case, DoIO() is guaranteed to return without waiting.
  • If zero characters are in the buffer, post an asynchronous request (SendIO()) for 1 character. When at least one is ready, the device will return it. Now go back to the first step.
  • If the user decides to quit the program, AbortIO() any pending requests.

Use of BeginIO() with the Serial Device

Instead of transmitting the read command with either DoIO() or SendIO(), you might elect to use the low level BeginIO() interface to a device.

BeginIO() works much like SendIO(), i.e., asynchronously, except it gives you control over the quick I/O bit (IOB_QUICK) in the io_Flags field. Quick I/O saves the overhead of a reply message, and perhaps the overhead of a task switch. If a quick I/O request is actually completed quickly, the entire command will execute in the context of the caller. See the Exec Device I/O for more detailed information on quick I/O.

The device will determine if a quick I/O request will be handled quickly. Most non-I/O commands will execute quickly; read and write commands may or may not finish quickly.

SerialIO.IOSer.io_Flags |= IOF_QUICK;  /* Set QuickIO Flag */

BeginIO((struct IORequest *)SerialIO);
if (SerialIO->IOSer.io_Flags & IOF_QUICK )
    /* If flag is still set, I/O was synchronous and is now finished.
     * The IORequest was NOT appended a reply port.  There is no
     * need to remove or WaitIO() for the message.
     */
    printf("QuickIO\n");
else
     /* The device cleared the QuickIO bit.  QuickIO could not happen
      * for some reason; the device processed the command normally.
      * In this case BeginIO() acted exactly like SendIO().
      */
     printf("Regular I/O\n");
WaitIO(SerialIO);

The way you read from the device depends on your need for processing speed. Generally the BeginIO() route provides the lowest system overhead when quick I/O is possible. However, if quick I/O does not work, the same reply message overhead still exists.

Ending a Read or Write using Termination Characters

Reads and writes from the serial device may terminate early if an error occurs or if an end-of-file (EOF) is sensed. For example, if a break is detected on the line, any current read request will be returned with the error SerErr_DetectedBreak. The count of characters read to that point will be in the io_Actual field of the request.

You can specify a set of possible end-of-file characters that the serial device is to look for in the input stream or output using the SDCDMD_SETPARAMS command. These are contained in an io_TermArray that you provide. io_TermArray is used only when the SERF_EOFMODE flag is selected (see the “Serial Flags” sectionbelow).

If EOF mode is selected, each input data character read into or written from the user’s data block is compared against those in io_TermArray. If a match is found, the IOExtSer is terminated as complete, and the count of characters transferred (including the termination character) is stored in io_Actual.

To keep this search overhead as efficient as possible, the serial device requires that the array of characters be in descending order. The array has eight bytes and all must be valid (that is, do not pad with zeros unless zero is a valid EOF character). Fill to the end of the array with the lowest value termination character. When making an arbitrary choice of EOF character(s), you will get the quickest response from the lowest value(s) available.

/*
 * Terminate_Serial.c
 *
 * This is an example of using a termination array for reads from the serial
 * device. A termination array is set up for the characters Q, E, etx (CTRL-D)
 * and eot (CTRL-C).  The EOFMODE flag is set in io_SerFlags to indicate that
 * we want to use a termination array by sending the SDCMD_SETPARAMS command to
 * the device.  Then, a CMD_READ command is sent to the device with
 * io_Length set to 25.
 *
 * The read will terminate whenever one of the four characters in the termination
 * array is received or when 25 characters have been received.
 *
 * Compile with SAS C 5.10  lc -b1 -cfistq -v -y -L
 *
 * Run from CLI only
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/io.h>
#include <devices/serial.h>

#include <clib/exec_protos.h>
#include <clib/alib_protos.h>

#include <stdio.h>

#ifdef LATTICE
int CXBRK(void) { return(0); }  /* Disable SAS CTRL/C handling */
int chkabort(void) { return(0); }  /* really */
#endif



void main(void)
{
struct MsgPort  *SerialMP;          /* Define storage for one pointer */
struct IOExtSer *SerialIO;         /* Define storage for one pointer */

struct IOTArray Terminators =
{
0x51450403,   /* Q E etx eot */
0x03030303    /* fill to end with lowest value */
};

#define READ_BUFFER_SIZE 25
UBYTE ReadBuff[READ_BUFFER_SIZE];
UWORD ctr;

if (SerialMP=CreatePort(0,0) )
    {
    if (SerialIO=(struct IOExtSer *) CreateExtIO(SerialMP,sizeof(struct IOExtSer)))
        {
        if (OpenDevice(SERIALNAME,0L,(struct IORequest *)SerialIO,0) )
            printf("%s did not open\n",SERIALNAME);
        else
            {
             /* Tell user what we are doing */
             printf("\fLooking for Q, E, EOT or ETX\n");

             /* Set EOF mode flag
              * Set the termination array
              * Send SDCMD_SETPARAMS to the serial device
              */
             SerialIO->io_SerFlags |= SERF_EOFMODE;
             SerialIO->io_TermArray = Terminators;
             SerialIO->IOSer.io_Command  = SDCMD_SETPARAMS;
             if (DoIO((struct IORequest *)SerialIO))
                 printf("Set Params failed ");   /* Inform user of error */
             else
                 {
                 SerialIO->IOSer.io_Length   = READ_BUFFER_SIZE;
                 SerialIO->IOSer.io_Data     = (APTR)&ReadBuff[0];
                 SerialIO->IOSer.io_Command  = CMD_READ;
                 if (DoIO((struct IORequest *)SerialIO))     /* Execute Read */
                     printf("Error: Read failed\n");
                 else
                     {
                      /* Display all characters received */
                      printf("\nThese characters were read:\n\t\t\tASCII\tHEX\n");
                      for (ctr=0;ctr<SerialIO->IOSer.io_Actual;ctr++)
                           printf("\t\t\t  %c\t%x\n",ReadBuff[ctr],ReadBuff[ctr]);
                      printf("\nThe actual number of characters read: %d\n",
                                  SerialIO->IOSer.io_Actual);
                      }
                 }
            CloseDevice((struct IORequest *)SerialIO);
            }

        DeleteExtIO((struct IORequest *)SerialIO);
        }
    else
        printf("Error: Could not create IORequest\n");

    DeletePort(SerialMP);
    }
else
    printf("Error: Could not create message port\n");
}

The read will terminate before the io_Length number of characters is read if a ‘Q’, ‘E’, ETX, or EOT is detected in the serial input stream.

Using Separate Read and Write Tasks

In some cases there are advantages to creating a separate IOExtSer for reading and writing. This allows simultaneous operation of both reading and writing. Some users of the device have separate tasks for read and write operations. The sample code below creates a separate reply port and request for writing to the serial device.

struct IOExtSer *SerialWriteIO;
struct MsgPort  *SerialWriteMP;

/*
 * If two tasks will use the same device at the same time, it is preferred
 * use two OpenDevice() calls and SHARED mode.  If exclusive access mode
 * is required, then you will need to copy an existing IORequest.
 *
 * Remember that two separate tasks will require two message ports.
 */

SerialWriteMP = CreatePort(0,0);
SerialWriteIO = (struct IOExtSer *)
                CreateExtIO( SerialWriteMP,sizeof(struct IOExtSer) );

if (SerialWriteMP && SerialWriteIO )
    {

    /* Copy over the entire old IO request, then stuff the
     * new Message port pointer.
     */

    CopyMem( SerialIO, SerialWriteIO, sizeof(struct IOExtSer) );
    SerialWriteIO->IOSer.io_Message.mn_ReplyPort = SerialWriteMP;

    SerialWriteIO->IOSer.io_Command  = CMD_WRITE;
    SerialWriteIO->IOSer.io_Length   = -1;
    SerialWriteIO->IOSer.io_Data     = (APTR)"A poet's food is love and fame";
    DoIO(SerialWriteIO);
    }
Where’s OpenDevice()? This code assumes that the OpenDevice() function has already been called. The initialized read request block is copied onto the new write request block.

Setting Serial Parameters

When the serial device is opened, default values for baud rate and other parameters are automatically filled in from the serial settings in Preferences. The parameters may be changed by using the SDCMD_SETPARAMS command. The flags are defined in the include file devices/serial.h.

Serial Device Parameters (IOExtSer)

io_CtlChar
Control characters to use for xON, xOFF, INQ, ACK respectively. Positioned within an unsigned longword in the sequence from low address to high as listed. INQ and ACK handshaking is not currently supported.
io_RBufLen
Recommended size of the buffer that the serial device should allocate for incoming data. For some hardware the buffer size will not be adjustable. Changing the value may cause the device to allocate a new buffer, which might fail due to lack of memory. In this case the old buffer will continue to be used.
io_ExtFlags
An unsigned long that contains the flags SEXTF_MSPON and SEXTF_MARK. SEXTF_MSPON enables either mark or space parity. SEXTF_MARK selects mark parity (instead of space parity). Unused bits are reserved.
io_Baud
The real baud rate you request. This is an unsigned long value in the range of 1 to 4,294,967,295. The device will reject your baud request if the hardware is unable to support it.
io_BrkTime
If you issue a break command, this variable specifies how long, in microseconds, the break condition lasts. This value controls the break time for all future break commands until modified by another SDCMD_SETPARAMS.
io_TermArray
A byte-array of eight termination characters, must be in descending order. If the EOFMODE bit is set in the serial flags, this array specifies eight possible choices of character to use as an end of file mark. See the section above titled “Ending a Read Using Termination Characters” and the SDCMD_SETPARAMS summary page in the Autodocs
io_ReadLen
How many bits per read character; typically a value of 7 or 8. Generally must be the same as io_WriteLen.
io_WriteLen
How many bits per write character; typically a value of 7 or 8. Generally must be the same as io_ReadLen.
io_StopBits
How many stop bits are to be expected when reading a character and to be produced when writing a character; typically 1 or 2. The built-in driver does not allow values above 1 if io_WriteLen is larger than 7.
io_SerFlags
See the “Serial Flags” section below.
io_Status
Contains status information filled in by the SDCMD_QUERY command. Break status is cleared by the execution of SDCMD_QUERY.

You set the serial parameters by passing an IOExtSer to the device with SDCMD_SETPARAMS set in io_Command and with the flags and parameters set to the values you want.

SerialIO->io_SerFlags      &= ~SERF_PARTY_ON;   /* set parity off */
SerialIO->io_SerFlags      |= SERF_XDISABLED;   /* set xON/xOFF disabled */
SerialIO->io_Baud           = 9600;             /* set 9600 baud */
SerialIO->IOSer.io_Command  = SDCMD_SETPARAMS;  /* Set params command */
if (DoIO((struct IORequest *)SerialIO))
    printf("Error setting parameters!\n");

The above fragment modifies two bits in io_SerFlags and changes the baud rate. If the parameters you request are unacceptable or out of range, the SDCMD_SETPARAMS command will fail. You are responsible for checking the error code and informing the user.

Proper Time for Parameter Changes. A parameter change should not be performed while an I/O request is actually being processed because it might invalidate the request handling already in progress. To avoid this, you should use SDCMD_SETPARAMS only when you have no serial I/O requests pending.

Serial Flags (Bit Definitions For io_SerFlags)

There are additional serial device parameters which are controlled by flags set in the io_SerFlags field of the IOExtSer structure. The default state of all of these flags is zero. SERF_SHARED and SERF_7WIRE must always be set before OpenDevice(). The flags are defined in the include file devices/serial.h.

Serial Flags (io_SerFlags)

SERF_XDISABLED
Disable the xON/xOFF feature. xON/xOFF must be disabled during XModem transfers.
SERF_EOFMODE
Set this bit if you want the serial device to check input characters against io_TermArray and to terminate the read immediately if an end-of-file character has been encountered. Note: this bit may be set and reset directly in the user’s IOExtSer without a call to SDCMD_SETPARAMS.
SERF_SHARED
Set this bit if you want to allow other tasks to simultaneously access the serial port. The default is exclusive-access. Any number of tasks may have shared access. Only one task may have exclusive access. If someone already has the port for exclusive access, your OpenDevice() call will fail. This flag must be set before OpenDevice().
SERF_RAD_BOOGIE
If set, this bit activates high-speed mode. Certain peripheral devices (MIDI, for example) require high serial throughput. Setting this bit high causes the serial device to skip certain of its internal checking code to speed throughput. Use SERF_RAD_BOOGIE only when you have:
  • Disabled parity checking
  • Disabled xON/xOFF handling
  • Use 8-bit character length
  • Do not wish a test for a break signal
Note that the Amiga is a multitasking system and has immediate processing of software interrupts. If there are other tasks running, it is possible that the serial driver may be unable to keep up with high data transfer rates, even with this bit set.
SERF_QUEUEDBRK
If set, every break command that you transmit will be enqueued. This means that all commands will be executed on a FIFO (first in, first out) basis.
SERF_7WIRE
If set at OpenDevice() time, the serial device will use seven-wire handshaking for RS-232-C communications. Default is three-wire (pins 2, 3, and 7).
SERF_PARTY_ODD
If set, selects odd parity. If clear, selects even parity.
SERF_PARTY_ON
If set, parity usage and checking is enabled. Also see the SERF_MSPON bit described under io_ExtFlags above.

Querying the Serial Device

You query the serial device by passing an IOExtSer to the device with SDCMD_QUERY set in io_Command. The serial device will respond with the status of the serial port lines and registers, and the number of unread characters in the read buffer.

UWORD Serial_Status;
ULONG Unread_Chars;

SerialIO->IOSer.io_Command  = SDCMD_QUERY; /* indicate query */
SendIO((struct IORequest *)SerialIO);

Serial_Status = SerialIO->io_Status; /* store returned status */
Unread_Chars = SerialIO->IOSer.io_Actual; /* store unread count */

The 16 status bits of the serial device are returned in io_Status; the number of unread characters is returned in io_Actual.

Serial Device Status Bits

Bit Active Symbol Function
0 Reserved
1 Reserved
2 high (RI) Parallel Select on the A1000. On the A500 and A2000, Select is also connected to the serial port’s Ring Indicator. (Be cautious when making cables.)
3 low (DSR) Data set ready
4 low (CTS) Clear to send
5 low (CD) Carrier detect
6 low (RTS) Ready to send
7 low (DTR) Data terminal ready
8 high Read overrun
9 high Break sent
10 high Break received
11 high Transmit xOFFed
12 high Receive xOFFed
13-15 (reserved)

Sending the Break Command

You send a break through the serial device by passing an IOExtSer to the device with SDCMD_BREAK set in io_Command. The break may be immediate or queued. The choice is determined by the state of flag SERF_QUEUEDBRK in io_SerFlags.

SerialIO->IOSer.io_Command  = SDCMD_BREAK; /* send break */
SendIO((struct IORequest *)SerialIO);

The duration of the break (in microseconds) can be set in io_BrkTime. The default is 250,000 microseconds (0.25 seconds).

Error Codes from the Serial Device

The serial device returns error codes whenever an operation is attempted.

SerialIO->IOSer.io_Command  = SDCMD_SETPARAMS; /* Set parameters */
if (DoIO((struct IORequest *)SerialIO))
    printf("Set Params failed. Error: %ld ",SerialIO->IOSer.io_Error);

The error is returned in the io_Error field of the IOExtSer structure.

Serial Device Error Codes

Error Value Explanation
SerErr_DevBusy 1 Device in use
SerErr_BaudMismatch 2 Baud rate not supported by hardware
SerErr_BufErr 4 Failed to allocate new read buffer
SerErr_InvParam 5 Bad parameter
SerErr_LineErr 6 Hardware data overrun
SerErr_ParityErr 9 Parity error
SerErr_TimerErr 11 Timeout (if using 7-wire handshaking)
SerErr_BufOverflow 12 Read buffer overflowed
SerErr_NoDSR 13 No Data Set Ready
SerErr_DetectedBreak 15 Break detected
SerErr_UnitBusy 16 Selected unit already in use

Multiple Serial Port Support

Applications that use the serial port should provide the user with a means to select the name and unit number of the driver. The defaults will be “serial.device” and unit number 0. Typically unit 0 refers to the user-selected default. Unit 1 refers to the built-in serial port. Numbers above 1 are for extended units. The physically lowest connector on a board will always have the lowest unit number.

Careful attention to error handling is required to survive in a multiple port environment. Differing serial hardware will have different capabilities. The device will refuse to open non-existent unit numbers (symbolic name mapping of unit numbers is not provided at the device level). The SDCMD_SETPARAMS command will fail if the underlying hardware cannot support your parameters. Some devices may use quick I/O for read or write requests, others will not. Watch out for partially completed read requests; io_Actual may not match your requested read length.

If the Tool Types mechanism is used for selecting the device and unit, the defaults of “DEVICE=serial.device” and “UNIT=0” should be provided. The user should be able to permanently set the device and unit in a configuration file.

Taking Over the Hardware

For some applications use of the device driver interface is not possible. By following the established rules, applications may take over the serial interface at the hardware level. This extreme step is not, however, encouraged. Taking over means losing the ability to work with additional serial ports, and will limit future compatibility.

Access to the hardware registers is controlled by the misc.resource. See the “Resources” chapter, and exec/misc.i for details. The MR_SERIALBITS and MR_SERIALPORT units control the serial registers.

One additional complication exists. The current serial device will not release the misc.resource bits until after an expunge. This code provides a work around:

/*
* A safe way to expunge ONLY a certain device.
* This code attempts to flush ONLY the named device out of memory and
* nothing else.  If it fails, no status is returned (the information
* would have no valid use after the Permit().
*/
#include <exec/types.h>
#include <exec/execbase.h>

void FlushDevice(char *);

extern struct ExecBase *SysBase;

void FlushDevice(name)
char  *name;
{
struct Device *devpoint;

Forbid();   /* ugly */
if (devpoint = (struct Device *)FindName(&SysBase->DeviceList,name) )
    RemDevice(devpoint);
Permit();
}

Advanced Example of Serial Device Usage

/*
 * Complex_Serial.c
 *
 * Complex tricky example of serial.device usage
 *
 * Compile with SAS C 5.10  lc -b1 -cfistq -v -y -L
 *
 * Run from CLI only
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/io.h>
#include <devices/serial.h>

#include <clib/exec_protos.h>
#include <clib/alib_protos.h>

#include <stdio.h>

#ifdef LATTICE
int CXBRK(void) { return(0); }  /* Disable SAS CTRL/C handling */
int chkabort(void) { return(0); }  /* really */
#endif


void main(void)
{
struct MsgPort  *SerialMP;          /* Define storage for one pointer */
struct IOExtSer *SerialIO;         /* Define storage for one pointer */

#define READ_BUFFER_SIZE 32
char SerialReadBuffer[READ_BUFFER_SIZE]; /* Reserve SIZE bytes of storage */

struct IOExtSer *SerialWriteIO = 0;
struct MsgPort  *SerialWriteMP = 0;

ULONG Temp;
ULONG WaitMask;

if (SerialMP=CreatePort(0,0) )
    {
    if (SerialIO=(struct IOExtSer *)
                 CreateExtIO(SerialMP,sizeof(struct IOExtSer)) )
        {
        SerialIO->io_SerFlags=0;    /* Example of setting flags */

        if (OpenDevice(SERIALNAME,0L,SerialIO,0) )
            printf("%s did not open\n",SERIALNAME);
        else
            {
            SerialIO->IOSer.io_Command  = SDCMD_SETPARAMS;
            SerialIO->io_SerFlags      &= ~SERF_PARTY_ON;
            SerialIO->io_SerFlags      |= SERF_XDISABLED;
            SerialIO->io_Baud           = 9600;
            if (Temp=DoIO(SerialIO))
                printf("Error setting parameters - code %ld!\n",Temp);

            SerialIO->IOSer.io_Command  = CMD_WRITE;
            SerialIO->IOSer.io_Length   = -1;
            SerialIO->IOSer.io_Data     = (APTR)"Amiga.";
            SendIO(SerialIO);
            printf("CheckIO %lx\n",CheckIO(SerialIO));
            printf("The device will process the request in the background\n");
            printf("CheckIO %lx\n",CheckIO(SerialIO));
            WaitIO(SerialIO);

            SerialIO->IOSer.io_Command  = CMD_WRITE;
            SerialIO->IOSer.io_Length   = -1;
            SerialIO->IOSer.io_Data     = (APTR)"Save the whales! ";
            DoIO(SerialIO);             /* execute write */


            SerialIO->IOSer.io_Command  = CMD_WRITE;
            SerialIO->IOSer.io_Length   = -1;
            SerialIO->IOSer.io_Data     = (APTR)"Life is but a dream.";
            DoIO(SerialIO);             /* execute write */

            SerialIO->IOSer.io_Command  = CMD_WRITE;
            SerialIO->IOSer.io_Length   = -1;
            SerialIO->IOSer.io_Data     = (APTR)"Row, row, row your boat.";
            SerialIO->IOSer.io_Flags = IOF_QUICK;
            BeginIO(SerialIO);

            if (SerialIO->IOSer.io_Flags & IOF_QUICK )
                {

                /*
                 * Quick IO could not happen for some reason; the device processed
                 *  the command normally.  In this case BeginIO() acted exactly
                 * like SendIO().
                 */

                printf("Quick IO\n");
                }
            else
                {

                /* If flag is still set, IO was synchronous and is now finished.
                 * The IO request was NOT appended a reply port.  There is no
                 * need to remove or WaitIO() for the message.
                 */

                printf("Regular IO\n");
                }

            WaitIO(SerialIO);


            SerialIO->IOSer.io_Command  = CMD_UPDATE;
            SerialIO->IOSer.io_Length   = -1;
            SerialIO->IOSer.io_Data     = (APTR)"Row, row, row your boat.";
            SerialIO->IOSer.io_Flags = IOF_QUICK;
            BeginIO(SerialIO);

            if (0 == SerialIO->IOSer.io_Flags & IOF_QUICK )
                {

                /*
                 * Quick IO could not happen for some reason; the device processed
                 * the command normally.  In this case BeginIO() acted exactly
                 * like SendIO().
                 */

                printf("Regular IO\n");

                WaitIO(SerialIO);
                }
            else
                {

                /* If flag is still set, IO was synchronous and is now finished.
                 * The IO request was NOT appended a reply port.  There is no
                 * need to remove or WaitIO() for the message.
                 */

                printf("Quick IO\n");
                }


            /* Precalculate a wait mask for the CTRL-C, CTRL-F and message
             * port signals.  When one or more signals are received,
             * Wait() will return.  Press CTRL-C to exit the example.
             * Press CTRL-F to wake up the example without doing anything.
             * NOTE: A signal may show up without an associated message!
             */

            WaitMask = SIGBREAKF_CTRL_C|
                        SIGBREAKF_CTRL_F|
                         1L << SerialMP->mp_SigBit;

            SerialIO->IOSer.io_Command  = CMD_READ;
            SerialIO->IOSer.io_Length   = READ_BUFFER_SIZE;
            SerialIO->IOSer.io_Data     = (APTR)&SerialReadBuffer[0];
            SendIO(SerialIO);

            printf("Sleeping until CTRL-C, CTRL-F, or serial input\n");

            while (1)
                   {
                   Temp = Wait(WaitMask);
                   printf("Just woke up (YAWN!)\n");

                   if (SIGBREAKF_CTRL_C & Temp)
                       break;

                   if (CheckIO(SerialIO) ) /* If request is complete... */
                       {
                       WaitIO(SerialIO);   /* clean up and remove reply */

                       printf("%ld bytes received\n",SerialIO->IOSer.io_Actual);
                       break;
                       }
                   }

            AbortIO(SerialIO);  /* Ask device to abort request, if pending */
            WaitIO(SerialIO);   /* Wait for abort, then clean up */


            /*
             * If two tasks will use the same device at the same time, it is preferred
             * use two OpenDevice() calls and SHARED mode.  If exclusive access mode
             * is required, then you will need to copy an existing IO request.
             *
             * Remember that two separate tasks will require two message ports.
             */

            SerialWriteMP = CreatePort(0,0);
            SerialWriteIO = (struct IOExtSer *)
                            CreateExtIO( SerialWriteMP,sizeof(struct IOExtSer) );

            if (SerialWriteMP && SerialWriteIO )
                {

                /* Copy over the entire old IO request, then stuff the
                 * new Message port pointer.
                 */

                CopyMem( SerialIO, SerialWriteIO, sizeof(struct IOExtSer) );
                SerialWriteIO->IOSer.io_Message.mn_ReplyPort = SerialWriteMP;

                SerialWriteIO->IOSer.io_Command  = CMD_WRITE;
                SerialWriteIO->IOSer.io_Length   = -1;
                SerialWriteIO->IOSer.io_Data     = (APTR)"A poet's food is love and fame";
                DoIO(SerialWriteIO);
                }

            if (SerialWriteMP)
                DeletePort(SerialWriteMP);

            if (SerialWriteIO)
                DeleteExtIO(SerialWriteIO);

            CloseDevice(SerialIO);
            }

        DeleteExtIO(SerialIO);
        }
    else
        printf("Unable to create IORequest\n");

    DeletePort(SerialMP);
    }
else
    printf("Unable to create message port\n");
}

Additional Information on the Serial Device

Additional programming information on the serial device can be found in the include files and the Autodocs for the serial device. Both are contained in the SDK.

Includes
devices/serial.h
AutoDocs
serial.doc