Important: The information in this document is obsolete and should not be used for new development.
Using the Serial Driver
The basic steps in using the Serial Driver are
The program shown in Listing 7-1 illustrates these steps. The following sections describe each step in more detail.
- 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.
- 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.- Set the handshaking mode.
- Set the baud rate and data format.
- Read or write the desired data.
- When you are finished using the Serial Driver, terminate any pending I/O with the Device Manager
KillIO
function.- Restore the default input buffer.
- Close the input and output drivers. Always close the input driver first.
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
orPBOpen
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
orportNotCf
. Any other errors, such as attempting to open the.BIn
or.BOut
driver on a Macintosh with only one serial port, return theopenErr
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 acsCode
value of 14. This is equivalent to using theSerHShake
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 theSerReset
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 ManagerPBRead
andPBWrite
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 callPBRead
. You can use theSerGetBuf
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 theSerStatus
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 theSerSetBuf
function. After restoring the default buffer, you can terminate any pending I/O using the Device ManagerKillIO
function. Finally, you should close the input and output drivers using the Device ManagerCloseDriver
orPBClose
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 acsCode
value of 16 and bit 6 set in thecsParam
field. See the section "Low-Level Routines," beginning on page 7-27, for more information.
© Apple Computer, Inc.
3 JUL 1996