Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: Devices /
Chapter 7 - Serial Driver


Using the Serial Driver

The basic steps in using the Serial Driver are

  1. Open the output device driver for the serial port, then open the input device driver. Always open both drivers, even if you only need one.
  2. Optionally, allocate a buffer that is larger than the default 64-byte input buffer, and then use the SerSetBuf function to select the alternate buffer.
  3. Set the handshaking mode.
  4. Set the baud rate and data format.
  5. Read or write the desired data.
  6. When you are finished using the Serial Driver, terminate any pending I/O with the Device Manager KillIO function.
  7. Restore the default input buffer.
  8. Close the input and output drivers. Always close the input driver first.

The program shown in Listing 7-1 illustrates these steps. The following sections describe each step in more detail.

Listing 7-1 Using the Serial Driver

PROGRAM UsingTheSerialDriver;
{An example of the basic steps required to set up and use the Serial Driver.}
{ Note that all function calls demonstrated here are synchronous and thus   }
{ should not be called at interrupt time.                                   }
USES
   Serial;

VAR
   gOutputRefNum:    Integer;    {output driver reference number}
   gInputRefNum:     Integer;    {input driver reference number}
   gInputBufHandle:  Handle;     {handle to my input buffer}
   gOSErr:           OSErr;      {function results}


PROCEDURE MyOpenSerialDriver;
{Use the Device Manager OpenDriver function to open the drivers.}
BEGIN
   gOSErr := OpenDriver('.AOut', gOutputRefNum); {always open output first}
   IF gOSErr = noErr THEN
      gOSErr := OpenDriver('.AIn', gInputRefNum);{thenopentheinputdriver}
END;

PROCEDURE MyChangeInputBuffer;
{Replace the default input buffer with a larger buffer.}
CONST
   kInputBufSize = 1024; {size of my input buffer in bytes}
BEGIN
   gInputBufHandle := NewHandle(kInputBufSize); {allocate storage}
   HLock(gInputBufHandle); {lock it}
   SerSetBuf(gInputRefNum, gInputBufHandle^, kInputBufSize); {set the buffer}
END;

PROCEDURE MySetHandshakeOptions;
{Set flow control method and other options. Note that you only need to set}
{ the output driver; the settings are reflected on the input side.}
VAR
   mySerShkRec: SerShk; {serial handshake record}
BEGIN
   WITH mySerShkRec DO
      BEGIN
         fXOn := 0;     {turn off XON/XOFF output flow control}
         fCTS := 0;     {turn off CTS/DTR flow control}
         errs := 0;     {clear error mask}
         evts := 0;     {clear event mask}
         fInX := 0;     {turn off XON/XOFF input flow control}
         fDTR := 0;     {turn off DTR input flow control}
      END;
   {Use control call 14 instead of the SerHShake function}
   { because it allows control over DTR handshaking.}
   gOSErr := Control(gOutputRefNum, 14, @mySerShkRec); {csCode = 14}
END;

PROCEDURE MyConfigureThePort;
{Set baud rate and data format.  Note that you only need to set the}
{ output driver; the settings are reflected on the input side.}
CONST
   kConfigParam = baud2400+data8+noParity+stop10; {create bit field}
BEGIN
   gOSErr := SerReset(gOutputRefNum, kConfigParam); {configure the port}
END;

PROCEDURE MySendMessage;
{Use the Device Manager PBWrite function to send data to the output driver.}
VAR
   myMessage:        Str255;        {the data to send}
   myMsgLen:         LongInt;       {number of bytes to send}
   myParamBlock:     ParamBlockRec; {parameter block forthePBWritefunction}
   myPBPtr:          ParmBlkPtr;    {pointer to the parameter block}
BEGIN
   myMessage := 'The Eagle has landed.';
   myMsgLen := Length(myMessage);   {get the size of the message string}
   WITH myParamBlock DO {fill in required fields of the parameter block}
      BEGIN
         ioRefNum := gOutputRefNum;    {write to the output driver}
         ioBuffer := @myMessage[1];    {pointer to the data}
         ioReqCount := myMsgLen;       {number of bytes to send}
         ioCompletion := NIL;          {no completion routine specified}
         ioVRefNum := 0;               {not used by the Serial Driver}
         ioPosMode := 0;               {not used by the Serial Driver}
      END;
   myPBPtr := @myParamBlock
   gOSErr := PBWrite(myPBPtr, FALSE);  {synchronous Device Manager request}
END;

PROCEDURE MyReceiveMessage;
{Use the Device Manager PBRead function to read data from the input driver.}
VAR
   myBuffer:         Str255;        {a buffer to receive the data}
   myReadCount:      LongInt;       {number of bytes to read}
   myParamBlock:     ParamBlockRec; {parameter block forthePBReadfunction}
   myPBPtr:          ParmBlkPtr;    {pointer to the parameter block}
BEGIN
   myBuffer := '';
   myReadCount := 0;
   gOSErr := SerGetBuf(gInputRefNum, myReadCount); {determine how many bytes}
                                                   { are in the input buffer}
   IF myReadCount > 0 THEN
      BEGIN
         WITH myParamBlock DO{fillinrequiredfieldsof the parameterblock}
            BEGIN
               ioRefNum := gInputRefNum;  {read from the input driver}
               ioBuffer := @myBuffer[1];  {pointer to my data buffer}
               ioReqCount := myReadCount; {number of bytes to read}
               ioCompletion := NIL;       {no completion routine specified}
               ioVRefNum := 0;            {not used by the Serial Driver}
               ioPosMode := 0;            {not used by the Serial Driver}
            END;
         myPBPtr := @myParamBlock;
         gOSErr := PBRead(myPBPtr, FALSE);{synchronousDeviceManagerrequest}
      END;
END;

PROCEDURE MyRestoreInputBuffer;
{Restore the default input buffer.}
BEGIN
   SerSetBuf(gInputRefNum, gInputBufHandle^, 0); {0 means restore default}
   HUnlock(gInputBufHandle); {release my old buffer}
END;

PROCEDURE MyCloseSerialDriver;
{Use the Device Manager KillIO function to terminate all current and pending}
{ operations, then close the drivers. Note that you only need to call KillIO}
{ on the output driver to terminate both input and output operations.}
BEGIN
   gOSErr := KillIO(gOutputRefNum); {terminate all pending I/O operations}
   IF gOSErr = noErr THEN
      gOSErr := CloseDriver(gInputRefNum); {close the input driver first}
   IF gOSErr = noErr THEN
      gOSErr := CloseDriver(gOutputRefNum); {then close the output driver}
END;

BEGIN {UsingTheSerialDriver}
   MyOpenSerialDriver;              {open the output and input drivers}
   MyChangeInputBuffer;             {replace the default input buffer}
   MySetHandshakeOptions;           {select flow control method}
   MyConfigureThePort;              {set baud rate and data format}
   MySendMessage;                   {send some bytes to the output driver}
   MyReceiveMessage;                {read some bytes from the input driver}
   MyRestoreInputBuffer;            {restore the default input buffer}
   MyCloseSerialDriver;             {terminate I/O and close the drivers}
END.

Opening the Serial Driver

Because the Serial Driver uses separate device drivers for the input and output functions, you need to open both drivers for two-way communication. On Macintosh computers with two serial ports, you access the modem port through the .AIn and .AOut drivers, and the printer port through the .BIn and .BOut drivers.

On computers with a single serial port, such as the Macintosh PowerBook Duo models, the serial port can be used for either modem or printer connections. There is only one serial channel on these models, which you access through the .AIn and .AOut drivers.

You open the serial port drivers using the Device Manager OpenDriver or PBOpen functions. You should always open the output driver first because the Serial Driver initializes its local variables for both the input and output drivers when you open the output driver. Opening the output driver also installs interrupt handlers and allocates and locks buffer storage for both input and output.

When the Serial Driver receives an open request it first verifies that the serial port is available and correctly configured. If the port is unavailable or not configured, the Serial Driver returns the result code portInUse or portNotCf. Any other errors, such as attempting to open the .BIn or .BOut driver on a Macintosh with only one serial port, return the openErr result code.

When a device driver is opened successfully, the Device Manager returns a driver reference number, which you use to identify the driver in subsequent I/O requests. Although the reference numbers of the serial input and output drivers have remained constant for some time, you should not assume these values are fixed. Because future versions of the Operating System may assign other reference numbers to these drivers, your application should always use the reference numbers returned by the Device Manager.

Because of hardware differences between the serial ports in some Macintosh models, you should use the printer port for output-only connections to devices such as printers, at a maximum data rate of 9600 baud. The printer port is not recommended for two-way communication at data rates above 300 baud.

Note
If AppleTalk is active you cannot open the printer port for serial communication unless AppleTalk is using an alternate connection, such as EtherTalk or TokenTalk.

Specifying an Alternate Input Buffer

An optional but recommended practice is to increase the size of the input driver's buffer. The default buffer size, 64 bytes, is not always sufficient for sustained transfers at data rates above 300 baud. A larger buffer will help avoid buffer overruns and consequent loss of data. You can specify a buffer size of up to 32 KB, but 1 to 2 KB is usually sufficient.

You use the SerSetBuf function to specify an alternate input buffer, and also to reset the default buffer. To ensure compatibility and avoid heap fragmentation you must reset the default buffer before closing the input driver.

Setting the Handshaking Options

The recommended method of setting handshaking options is to send a control request to the output driver, with a csCode value of 14. This is equivalent to using the SerHShake function, but allows you to select DTR handshaking. To specify the desired options, you pass the following data structure to the driver:

TYPE SerShk = 
   PACKED RECORD
      fXOn: Byte; {XON/XOFF output flow control enabled flag}
      fCTS: Byte; {CTS hardware handshake enabled flag}
      xOn:  Char; {XON character}
      xOff: Char; (XOFF character}
      errs: Byte; {error mask for input errs that cause abort}
      evts: Byte; {mask for status changes that cause events}
      fInX: Byte; {XON/XOFF input flow control flag}
      fDTR: Byte; {DTR input flow control (for csCode=14 only)}
   END;
The Data Terminal Ready (DTR) signal is normally asserted when the Serial Driver is opened and negated when it is closed. You can change this behavior using one of several control routines described in the section "Low-Level Routines," beginning on page 7-27.

The fields of the SerShk data structure are described in the section "Serial Driver Reference," beginning on page 7-18.

Setting the Baud Rate and Data Format

When you open the Serial Driver it configures the selected port with default settings of 9600 baud, 8 data bits, no parity bit, and 2 stop bits. You can change these settings using the SerReset function, described on page 7-19.

Reading and Writing to the Serial Ports

Once you have configured the serial port, you can read and write data using the Device Manager PBRead and PBWrite functions. These functions can be called either synchronously or asynchronously, as described in the chapter "Device Manager" in this book.

Synchronous I/O Requests

When you make a synchronous request to a device driver, the Device Manager places your request at the end of the driver's I/O queue and does not return control to your application until the request completes. To avoid hanging, your application needs to take steps to ensure that a request will complete properly before calling the Device Manager.

For example, because the PBRead function requires you to specify the number of bytes to be read, you need to determine how many bytes are in the input driver's buffer before you call PBRead. You can use the SerGetBuf function to determine how many characters are in the input buffer, as shown in Listing 7-1.

If you try to read more bytes than are available in the input buffer, the driver waits until it receives enough characters to satisfy your request. If the external serial device does not send the required number of bytes, there is no way for your application regain control of the processor or terminate the read request.

Similarly, the PBWrite function will not complete until the specified number of bytes have been transmitted to the external serial device. If the external device is holding off transfers through hardware or software handshaking, the Device Manager will never return control to your application. You can use the SerStatus function, described on page 7-25, to query the status of the output driver and determine if output is suspended by handshaking.

For more information about how synchronous I/O requests are processed, see the chapter "Device Manager" in this book.

Asynchronous I/O Requests

Asynchronous execution allows your application to continue to process user input or perform other tasks while waiting for serial I/O requests to complete. To take full advantage of asynchronous operation you should supply a completion routine for the Device Manager to call when an asynchronous request completes. You should also implement a timer function to notify your application if a request is not satisfied within a reasonable period.

See the chapter "Device Manager" in this book for information about how asynchronous I/O requests are processed.

Closing the Serial Driver

Before closing the Serial Driver you must restore the default input buffer using the SerSetBuf function. After restoring the default buffer, you can terminate any pending I/O using the Device Manager KillIO function. Finally, you should close the input and output drivers using the Device Manager CloseDriver or PBClose functions.

Synchronous Clocking

Although the Serial Driver does not support synchronous communication protocols, it does allow you to select an external timing signal for synchronous clocking between the sender and receiver. You connect the external timing signal to the handshake input (HSKi) signal on pin 2 of the serial port, and select external clocking by sending a control request to the output driver with a csCode value of 16 and bit 6 set in the csParam field. See the section "Low-Level Routines," beginning on page 7-27, for more information.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
3 JUL 1996