The Palm OS® Serial Manager is responsible for byte-level serial I/O and control of the RS-232, IR, Bluetooth, or USB signals. Under Palm OS Cobalt and later versions of the operating system, the Serial Manager is implemented as a STREAMS driver and a compatibility library that lets you continue to use the Serial Manager API.
IMPORTANT: The Palm OS Cobalt Serial Manager implements the API formerly known as the "New Serial Manager;" functions with names beginning with "
Srm
" are supported. The Old Serial Manager functions—those that begin with "Ser
"—are not supported under Palm OS Cobalt, version 6, and later.
Because the Serial Manager is now based on the Palm OS Cobalt I/O architecture, the concept of "virtual serial drivers" is no longer supported; instead, the Serial Manager works with the I/O Subsystem and the Connection Manager to support communication through any communications interface. The legacy Serial Manager ports, such as serCradlePort
, sysFileCVirtIrComm
, sysFileCVirtRfComm
, and so on are still supported.
To ensure that the Serial Manager does not slow down processing of user events, the Serial Manager receives data asynchronously. The Serial Manager API, however, executes synchronously; if a Serial Manager function blocks during execution, this does not affect the system's ability to keep receiving data.
The Serial Manager functions that send data return as soon as they have handed off all the data to the lower-level IOS STREAMS write queue. The actual transmission of the data will be handled later, asynchronously.
This chapter describes the Serial Manager. It covers the following topics:
- Steps for Using the Serial Manager
- Opening a Port
- Closing a Port
- Configuring the Port
- Sending Data
- Receiving Data
- Serial Manager Tips and Tricks
About the Serial Manager
The Serial Manager provides an interface to communications devices. These communications devices can include a serial port, cradle port, infrared port, USB, Bluetooth, and other devices that are accessible through the Connection Manager. This API provides a degree of compatibility with software written for previous versions of Palm OS.
Once a port is opened, the Serial Manager allocates a structure for maintaining the current information and settings of the particular port. The task or application that opens the port is returned a port ID and must supply the port ID to refer to this port when other Serial Manager functions are called.
Upon closing the port, the Serial Manager deallocates the open port structure and closes the underlying IOS connection.
Note that applications can use the Connection Manager to obtain the proper port name and other serial port parameters that the user has stored in connection profiles for different connection types. For more information, see the book Exploring Palm OS: High-Level Communications for information on the Connection Manager.
Steps for Using the Serial Manager
Regardless of which version of the API you use, the main steps to perform serial communication are the same. They are:
- Open a serial port.
To open a port , you specify which port to open and obtain a port ID that uniquely identifies this connection. You pass that port ID to every other Serial Manager call you make.
See "Opening a Port".
- If necessary, configure the connection.
You might need to change the baud rate or increase the size of the receive queue before you use any other Serial Manager calls. See "Configuring the Port".
- Send or receive data.
See "Sending Data" and "Receiving Data".
- Close the port.
See "Closing a Port".
The next several sections describe these steps in more detail.
TIP: See "Serial Manager Tips and Tricks" for debugging information and information on how to fix common errors.
Opening a Port
The Serial Manager is installed when the device is booted. Before you can use it, however, you must enable the serial hardware by opening a port.
IMPORTANT: Applications that open a serial port are responsible for closing it. Opening a serial port powers up the communications hardware and drains batteries. To conserve battery power, don't keep the port open longer than necessary.
When you attempt to open a serial port, you must check for errors upon return:
- If
errNone
is returned, the port was opened successfully. The application can then perform its tasks and close the port when finished. - If
serErrAlreadyOpen
is returned, the port was already open. This error is returned if one of the underlying drivers involved in the connection is already in use; for example, if an active PPP session is currently using the UART. - If any error is returned, the port was not opened, and the application must not close it.
Opening a Port
To open a port , call the SrmOpen()
function, specifying the port (see "Specifying the Port") and the initial baud rate of the serial interface. SrmOpen
returns a port ID that uniquely identifies this connection. You pass this port ID to all other Serial Manager calls.
The Serial Manager supports USB and Bluetooth connections as well as RS-232 and IR connections. With the Bluetooth and USB protocols, it is often more important to specify the reason why the application is opening the port. The baud rate is unimportant as that is negotiated in USB and Bluetooth protocols. To open a USB or Bluetooth connection, use SrmExtOpen()
instead of SrmOpen()
. This function takes a SrmOpenConfigType
structure, which allows you to specify the purpose of the connection instead of the baud rate.
Once the SrmOpen()
or SrmExtOpen()
call is made successfully, it indicates that the Serial Manager has successfully allocated internal structures to maintain the port and has successfully loaded the serial driver for this port.
UInt16 portId; Boolean serPortOpened = false; err = SrmOpen(serPortCradlePort /* port */, 57600, /* baud */ &portId); if (err) { // display error message here. } //record our open status in global. serPortOpened = true;
Specifying the Port
Ports are specified using a hardware-independent port ID. Palm OS will map them to the correct physical port by locating the appropriate port using the Connection Manager.
See Chapter 4, "Port Constants," for a list of port IDs you can use when opening a serial connection.
Closing a Port
Once an application is finished with the serial port, it must close the port using the SrmClose()
function. If SrmClose()
returns no error, it indicates that the Serial Manager has successfully closed the driver and deallocated the data structures used for maintaining the port.
To conserve battery power, it is important not to leave the serial port open longer than necessary. It is generally better to close and reopen the connection multiple times than it is to leave it open unnecessarily.
Configuring the Port
A newly opened port has the default configuration. The default port configuration is:
- A receive queue of 512 bytes
- CTS/RTS hardware flow control with a 5-second timeout on CTS low
- 1 stop bit
- 8 data bits
- For RS-232 connections, the baud rate you specified when you opened the port.
You can change this configuration if necessary before sending or receiving data.
Using a Custom Receive Queue
The default receive queue size is 512 bytes. If you wish to use a different size of buffer, you can do so by using a custom receive queue.
To use a custom receive queue, an application must:
- Allocate memory for the custom queue; this memory can be allocated using
malloc()
, or can be either a local or global variable. Be aware that the memory must remain in place as long as the buffer is in use. - Call
SrmSetReceiveBuffer()
with the new buffer and the size of the new buffer as arguments. - Restore the default queue before closing the port. That way, any bits sent in have a place to go.
- Deallocate the custom queue after restoring the default queue. The system only deallocates the default queue.
The following code fragment illustrates replacing the default queue with a custom queue.
Listing 2.2 Replacing the receive queue
#define myCustomSerQueueSize 1024 void *customSerQP; // Allocate a dynamic memory chunk for our custom receive // queue. customSerQP = MemPtrNew(myCustomSerQueueSize); // Replace the default receive queue. if (customSerQP) { err = SrmSetReceiveBuffer(portId, customSerQP, myCustomSerQueueSize); } // ... do Serial Manager work // Now restore default queue and delete custom queue. // Pass NULL for the buffer and 0 for bufSize to restore the // default queue. err = SrmSetReceiveBuffer(portId, NULL, 0); if(customSerQP) { MemPtrFree(customSerQP); customSerQP = NULL; }
Changing Other Configuration Settings
To change the other serial port settings, use SrmControl()
.
Listing 2.3 configures the serial port for 19200 baud, 8 data bits, even parity, 1 stop bit, and full hardware handshake (input and output) with a CTS timeout of 0.5 seconds. The CTS timeout specifies the maximum number of system ticks the serial library will wait to send a byte when the CTS input is not asserted. The CTS timeout is ignored if srmSettingsFlagCTSAutoM
is not set.
Listing 2.3 Changing the configuration
status_t err; Int32 paramSize; Int32 baudRate = 19200; UInt32 flags = srmSettingsFlagBitsPerChar8 | srmSettingsFlagParityOnM | srmSettingsFlagParityEvenM | srmSettingsFlagStopBits1 | srmSettingsFlagRTSAutoM | srmSettingsFlagCTSAutoM; Int32 ctsTimeout = SysTicksPerSecond() / 2; paramSize = sizeof(baudRate); err = SrmControl(portId, srmCtlSetBaudRate, &baudRate, ¶mSize); paramSize = sizeof(flags); err = SrmControl(portId, srmCtlSetFlags, &flags, ¶mSize); paramSize = sizeof(ctsTimeout); err = SrmControl(portId, srmCtlSetCtsTimeout, &ctsTimeout, ¶mSize);
If you want to find out what the current configuration is, pass one of the srmCtlGet
... op codes to the SrmControl()
function. For example, to find out the current baud rate, pass srmCtlGetBaudRate
.
Sending Data
To send data, use SrmSend()
. Sending data is performed synchronously. To send data, the application only needs to have an open connection with a port that has been configured properly and then specify a buffer to send. The larger the buffer to send, the longer the send function operates before returning to the calling application. The send function returns the actual number of bytes that were placed in the UART's FIFO. This makes it possible to determine what was sent and what wasn't in case of an error.
Listing 2.4 illustrates the use of SrmSend()
.
UInt32 toSend, numSent; status_t err; Char msg[] = "logon\n"; toSend = StrLen(msg); numSent = SrmSend(portId, msg, toSend, &err); if (err == serErrTimeOut) { //cts timeout detected }
If SrmSend()
returns an error, or if you simply want to ensure that all data has been sent, you can use any of the following functions:
- Use
SrmSendCheck()
to determine how many bytes are left in the FIFO. Note that not all serial devices support this feature.If the hardware does not provide an exact reading, the function returns an approximate number: 8 means full, 4 means approximately half-full. If the function returns 0, the queue is empty.
- The
SrmSendFlush()
function can be used to flush remaining bytes in the FIFO that have not been sent.
Under Palm OS Cobalt, the SrmSendWait()
function no longer waits to ensure that the data has been sent. There is no longer any way to ensure that the data has actually been transmitted. This function's use is discouraged.
Receiving Data
Receiving data is a more involved process because it depends on the receiving application actually listening for data from the port.
To receive data, an application must do the following:
- Ensure that the code does not loop indefinitely waiting for data from the receive queue.
The most common way to do this is to pass a timeout value to
EvtGetEvent()
orIOSPoll()
in your event loop.If your code is outside of an event loop, you can use the
EvtEventAvail()
function to see if the system has an event it needs to process, and if so, callSysHandleEvent()
. - To avoid having the system go to sleep while it's waiting to receive data, an application should call
EvtResetAutoOffTimer()
periodically (or callEvtSetAutoOffTimer()
). For example, the Serial Link Manager automatically callsEvtResetAutoOffTimer()
each time a new packet is received.
TIP: For many applications, the auto-off feature presents no problem. Use
EvtResetAutoOffTimer()
with discretion; applications that use it drain the battery.
- To receive the data, call
SrmReceive()
. Pass a buffer, the number of bytes you want to receive, and the inter-byte timeout in system ticks. This call blocks until all the requested data have been received or an error occurs. This function returns the number of bytes actually received. (The error is returned in the last parameter that you pass to the function.) - If you want to wait until a certain amount of data is available before you receive it, call
SrmReceiveWait()
before you callSrmReceive()
. Specify the number of bytes to wait for, which must be less than the current receive buffer size, and the amount of time to wait in milliseconds. IfSrmReceiveWait()
returnserrNone
, it means that the receive queue contains the specified number of bytes. If it returns anything other thanerrNone
, that number of bytes is not available.SrmReceiveWait()
is useful, for example, if you are receiving data packets. You can useSrmReceiveWait()
to wait until an entire packet is available and then read that packet. - It's common to want to receive data only when the system is idle. In this case, have your event loop respond to the
nilEvent
, which is generated wheneverEvtGetEvent()
times out and another event is not available. In response to this event, callSrmReceiveCheck()
. UnlikeSrmReceiveWait()
,SrmReceiveCheck()
does not block awaiting input. Instead, it immediately returns the number of bytes currently in the receive queue. If there is data in the receive queue, callSrmReceive()
to receive it. If the queue has no data, your event handler can simply return and allow the system to perform other tasks. - Check for and handle error conditions returned by any of the receive function calls as described in "Handling Errors".
IMPORTANT: Always check for line errors. Due to unpredictable conditions, there is no guarantee of success. If a line error occurs, all other Serial Manager calls fail until you clear the error.
For example code that shows how to receive data, see "Receive Data Example".
You can directly access the receive queue using the SrmReceiveWindowOpen()
and SrmReceiveWindowClose()
functions. These functions allow fast access to the buffer to reduce buffer copying.
Handling Errors
If an error occurs on the line, all of the receive functions return the error condition serErrLineErr
. This error will continue to be returned until you explicitly clear the error condition and continue.
To clear line errors, call SrmClearErr()
.
If you want more information about the error, call SrmGetStatus()
before you clear the line.
Listing 2.5 checks whether a framing or parity error has been returned and clears the line errors.
Listing 2.5 Handling line errors
void HandleSerReceiveErr(UInt16 portId, status_t err) { UInt32 lineStatus; UInt16 lineErrs; if (err == serErrLineErr) { SrmGetStatus(portId, &lineStatus, &lineErrs); // test for framing or parity error. if (lineErrs & serLineErrorFraming | serLineErrorParity) { //framing or parity error occurred. Do something. } SrmClearErr(portId); } }
In some cases, you may want to discard any received data when an error occurs. For example, if your protocol is packet driven and you detect data corruption, you should flush the buffer before you continue. To do so, call SrmReceiveFlush()
. This function flushes any bytes in the receive queue and then calls SrmClearErr()
for you.
SrmReceiveFlush()
takes a timeout value as a parameter. If you specify a timeout, it waits that period of time for any other data to be received in the queue and flushes it as well. If you pass 0 for the timeout, it simply flushes the data currently in the queue, clears the line errors, and returns. The flush timeout has to be large enough to flush out the noise but not so large that it flushes part of the next packet.
Receive Data Example
Listing 2.6 shows how to receive large blocks of data using the Serial Manager.
Listing 2.6 Receiving data using the Serial Manager
#include <PalmOS.h> // all the system toolbox headers #include <SerialMgr.h> #define k2KBytes 2048 /************************************************************ * * FUNCTION: RcvSerialData * * DESCRIPTION: An example of how to receive a large chunk of data * from the Serial Manager. This function is useful if the app * knows it must receive all this data before moving on. The * YourDrainEventQueue() function is a chance for the application * to call EvtGetEvent and handle other application events. * Receiving data whenever it's available during idle events * might be done differently than this sample. * * PARAMETERS: * thePort -> valid portID for an open serial port. * rcvDataP -> pointer to a buffer to put the received data. * bufSize <-> pointer to the size of rcvBuffer and returns * the number of bytes read. * ************************************************************/ status_t RcvSerialData(UInt16 thePort, UInt8 *rcvDataP, UInt32 *bufSizeP) { UInt32 bytesLeft, maxRcvBlkSize, bytesRcvd, waitTime, totalRcvBytes = 0; UInt8 *newRcvBuffer; UInt16 dataLen = sizeof(UInt32); status_t* error; // The default receive buffer is only 512 bytes; increase it if // necessary. The following lines are just an example of how to // do it, but its necessity depends on the ability of the code // to retrieve data in a timely manner. newRcvBuffer = MemPtrNew(k2KBytes); // Allocate new rcv buffer. if (newRcvBuffer) // Set new rcv buffer. error = SrmSetReceiveBuffer(thePort, newRcvBuffer, k2KBytes); if (error) goto Exit; else return memErrNotEnoughSpace; // Initialize the maximum bytes to receive at one time. maxRcvBlkSize = k2KBytes; // Remember how many bytes are left to receive. bytesLeft = *bufSizeP; // Only wait 1/5 of a second for bytes to arrive. waitTime = 200; // Now loop while getting blocks of data and filling the buffer. do { // Is the max size larger then the number of bytes left? if (bytesLeft < maxRcvBlkSize) // Yes, so change the rcv block amount. maxRcvBlkSize = bytesLeft; // Try to receive as much data as possible, // but wait only 1/5 second for it. bytesRcvd = SrmReceive(thePort, rcvDataP, maxRcvBlkSize, waitTime, &error); // Remember the total number of bytes received. totalRcvBytes += bytesRcvd; // Figure how many bytes are left to receive. bytesLeft -= bytesRcvd; rcvDataP += bytesRcvd; // Advance the rcvDataP. // If there was a timeout and no data came through... if ((error == serErrTimeOut) && (bytesRcvd == 0)) goto ReceiveError; // ...bail out and report the error. // If there's some other error, bail out. if ((error) && (error != serErrTimeOut)) goto ReceiveError; // Call a function to handle any pending events because // someone might press the cancel button. YourDrainEventQueue(); // Continue receiving data until all data has been received. } while (bytesLeft); ReceiveError: // Clearing the receive buffer can also be done right before // the port is to be closed. // Set back the default buffer when we're done. SrmSetReceiveBuffer(thePort, 0L, 0); Exit: MemPtrFree(newRcvBuffer); // Free the space. *bufSizeP = totalRcvBytes; return error; }
Serial Manager Tips and Tricks
The following tips and tricks help you debug your serial application and help avoid errors in the first place.
Debugging Tips
The following are some tips to help you track down errors while debugging.
- Debug first using the Palm OS Simulator. Debug on the device last.
The Simulator supports all Serial Manager functions and lets you test applications that use the Serial Manager. You can use the desktop computer's serial port to connect to outside devices. For more information on how to set up and use the emulator to debug serial communications, see the Simulator documentation.
- Track communication errors and the amount of data sent and received.
In your debug build, maintain individual counts for the amount of data transferred and for each communication error of interest. This includes timeouts and retries for reliable protocols.
- Use an easily recognizable start-of-frame signature. This helps during debugging of packet-based protocols.
- Implement developer back doors for debugging.
Implement a mechanism to trigger one or more debugging features at runtime without recompiling. For example, you may want to create a back door to disable the receive timeout on one side to prevent it from timing out while you are debugging the other side. Another back door might print some debugging information to the display. For example, your application might look for a pen down event in the upper right corner of the digitizer while the page-up key is being pressed to trigger one of your back doors.
- Use the HotSync log for debug-time error logging on the device.
You may use
DlkSetLogEntry()
to write your debugging messages to the HotSync log on the device. The HotSync log will accept up to 2KB of text. You may then switch to the HotSync application to view the log.
NOTE: Restrict writing to the HotSync log to debugging. Users will not appreciate having your debugging messages in their HotSync log.
Common Errors
Even if you're careful, errors may crop up. Here are some frequently encountered problems and their solutions.
- Nothing is being received
Check for a broken or incorrectly wired connection and make sure the expected handshaking signals are received.
- Garbage is received
- Baud rate mismatch
If the two sides disagree on the baud rate, it may either show up as a framing error, or the number of received characters will be different from the number that was sent.
- Parity error
Parity errors indicate that the data has been damaged. They can also mean that the sender and receiver have not been configured to use the same parity or word length.
- Word-length mismatch
- Framing error
Framing errors indicate a mismatch in the number of bits and are reported when the stop bit is not received when it is expected. This could indicate damaged data, but frequently it signals a disagreement in common baud rate, word length, or parity setting.
- Hardware overrun
The Serial Manager's receive interrupt service routine cannot keep up with incoming data. Enable full hardware handshaking (see "Configuring the Port").
- Software overrun
The application is not reading incoming data fast enough. Read data more frequently, or use hardware flow control. (see "Configuring the Port").