http://www.developer.com/

Back to article

Palm OS Communications Primer: Serial Manager


July 28, 2004

A PDA itself is nice toy, maybe even useful for some common tasks such as ToDo or MemoPad applications, but without an option to communicate the to outer world, it's just peace of metal. In this set of articles, we will try to cover the main available communication types that you can use in your own applications.

Serial Manager

Serial Manager is simple in usage and its capabilities. The first thing I woild like to point out is that serial communications under Palm OS are synchronous. That keeps all the business really simple. All Serial Manager's API calls may be divided into several logical groups:

  • Opening and Closing the Serial Port
  • Status, DeviceInfo, and Configuration
  • Sending and Receiving Data

Let's briefly consider all these function groups.

Group 1: Opening and Closing the Serial Port

As a starting point, you'll be required to open a serial port. Serial Manager API offers you several functions for this purpose:

Err SrmOpen(UInt32 port, UInt32 baud, UInt16 *newPortIdP)
Err SrmExtOpen(UInt32 port, SrmOpenConfigType* configP,
               UInt16 configSize, UInt16 *newPortIdP)
Err SrmExtOpenBackground(UInt32 port, SrmOpenConfigType* configP,
                         UInt16 configSize, UInt16 *newPortIdP)
Err SrmOpenBackground(UInt32 port, UInt32 baud, UInt16 *newPortIdP)

All of them take a port number (either logical port number or 4-character port name) and return the Port ID for usage with other functions. You also may initially configure opening port providing either baud rate or configuration data. You will find common port constants in Table 1:

Using 4-character constants for physical hardware port names (refer to SystemResources.h for details) is not a good practice because it may be not present on some devices. There are also several virtual ports, as shown in Table 2:

Constant Value Description
sysFileCVirtIrComm 'ircm' A virtual serial cable over an IrDA link using the IRComm protocol. It can only be used to talk to another IRComm device.
sysFileCVirtRfComm 'rfcm' RFCOMM (Bluetooth) virtual port plug-in.
sysFileCBtConnectPanelHelper 'btcp' Bluetooth Connection Panel helper application.

You can open a serial port in two different modes: foreground and background. Background ports relinquish control when another task opens the port with the SrmOpen or SrmExtOpen call. It's not recommended to keep a serial port in open mode longer than really neccessary because it consumes battery power. Finally, after the port is not needed, you may close it by making a SrmClose call.

SerOpen calls may return serErrAlreadyOpen error. It means that some other application is using it at this time. A good practice is to notify the user and close the port with SerClose.

Group 2: Configuration and Status

This functional group includes the following calls:

Err SrmGetDeviceCount(UInt16 *numOfDevicesP)
Err SrmGetDeviceInfo(UInt32 deviceID, DeviceInfoType *deviceInfoP)
Err SrmGetStatus(UInt16 portId, UInt32 *statusFieldP, UInt16 *lineErrsP)
Err SrmClearErr (UInt16 portId)
Err SrmSetReceiveBuffer(UInt16 portId, void *bufP, UInt16 bufSize);
Err SrmControl(UInt16 portId, UInt16 op, void *valueP, UInt16 *valueLenP)
Err SrmCustomControl(UInt16 portId, UInt16 opCode, UInt32 creator,
                     void* valueP, UInt16* valueLenP)

Here we will discuss only the SrmControl function. Through it, you can configure the serial port. The valueP parameter depends on the specified op code (see SrmCtlEnum for all possible values). The following sample code demonstrates a simple settings definition:

UInt32 dwFlags = srmSettingsFlagBitsPerChar8 | srmSettingsFlagStopBits1;
UInt16 wValueSize = sizeof(dwFlags);
SrmControl(srmCtlSetFlags, &dwFlags, wValueSize);

The SrmCustomControl call in turn gives you an opportunity to communicate to the virtual driver in response to the specific op codes. All the rest of the functions are trivial enough, so we will move to the next section.

Group 3: Sending and Receiving Data

A wide family of Send/Receive functions allows you to carry out data exchange in different ways. Actually, all functions in this group may be broken into several categories:

UInt32 SrmSend (UInt16 portId, const void *bufP, UInt32 count, Err *errP)
Err SrmSendWait(UInt16 portId)
Err SrmSendCheck(UInt16 portId, UInt32 *numBytesP)
Err SrmSendFlush(UInt16 portId)

UInt32 SrmReceive(UInt16 portId, void *rcvBufP, UInt32 count,
                  Int32 timeout, Err *errP)
Err SrmReceiveWait(UInt16 portId, UInt32 bytes, Int32 timeout)
Err SrmReceiveCheck(UInt16 portId,  UInt32 *numBytesP)
Err SrmReceiveFlush(UInt16 portId, Int32 timeout)

Err SrmReceiveWindowOpen(UInt16 portId, UInt8 **bufPP, UInt32 *sizeP);
Err SrmReceiveWindowClose(UInt16 portId, UInt32 bytesPulled);

Err SrmSetWakeupHandler(UInt16 portId, WakeupHandlerProcPtr procP,
                        UInt32 refCon);
Err SrmPrimeWakeupHandler(UInt16 portId, UInt16 minBytes);

First, you can bluntly use a Send/Receive buffer. In addition, if you need to check the input/output queue or wait for completion of the transmition, xxxCheck and xxxWait will fulfill your requirements. xxxFlush purges the FIFO transmit queue.

Now, let's consider some RX queue-related issues. Its default size is 512 bytes; you may change it by a SrmSetReceiveBuffer call. Your application is responsible to release this buffer by setting its size to zero, because Serial Manager won't do it. If you're planning to transmit large amount of data, think about sending/receiving it by blocks, say 1K, otherwise buffer overflows, and you will get an error serLineErrorSWOverrun.

You may want to know when Serial Manager sent all data or put something into receiving buffer. Well, you can do it pretty simply. In the case of reading data, that's probably the best way to handle it. Your application will need to pass two steps. The first one is to call SrmReceiveWait. This will block all the processes during the required timeout or data arriving. Note, that the timeout is applied for every new incoming byte; in other words, you may control maximum delay between two incoming bytes of data. In case of SrmSendWait "timeout" means "send buffer is empty." SrmReceiveWait also saves the PDA's battery because processor is put into a low-power state. The second step is trivial: Yes, you simply read data that is ready to be received. If there were any errors during transmitting, you should call SrmClearErr to clean them up.

For lengthly Serial I/O operations, your application should take care of at least two things. One is to handle user input during transmitting, either by calling EvtSysEventAvail or EvtGetEvent. Doing it with at least every 'timeout' will make the user's life much happier. In addition, if you have an I/O session that should not be interrupted, you should prevent the PDA from switching to automatic sleeping mode, which occurs when no user input is available. Calling EvtReserAutoOffTimer once per minute is a good solution in such cases.

Serial Manager also provides direct access to its receive queue. This method is not communly used, but it gives a "backdoor" solution. Thus, call SrmReceiveWindowOpen/SrmReceiveWindowClose when you really need it.

And, finally, you can set up Serial Manager to notify your application when a preset amount of data is ready to be received. SrmSetWakeupHandler calls for a callback in your program (WakeupHandlerProcPtr proc parameter), which in turn gets refCon as the only parameter. The minimal thethreshold is defined by the SrmPrimeWakeupHandler function.

Some Samples

To illustrate all the dry theory from above, take a look at the following example. For more details, please refer to the attached sample project.

static void OnSerialIO()
{
   FieldType * field = GetObjectPtr<FieldType>(MainDescriptionField);
   if (field)
   {
      FldDelete(field, 0, 0xFFFF);
      FldDrawField(field);
   }

   Char szError[128];
   MemSet(szError,sizeof(szError),0);

   Char szAnswer[128];
   MemSet(szAnswer,sizeof(szAnswer),0);

   Char szBuffer[128];
   MemSet(szBuffer,127,0x30);
   szBuffer[127] = 0;
   MemMove(szBuffer,"Sample Message",StrLen("Sample Message"));

   UInt16 dwSize = StrLen(szBuffer) + 1;

   UInt16 nInsPos = 0;
   Char szInfo[1024];
   StrPrintF(szInfo,"PackSize = %u\nMessage = %s\n",dwSize,szBuffer);
   if (field)
   {
      nInsPos = Log(field, szInfo, nInsPos);
   }

   UInt16 nPortId;
   Err err = SrmOpen(0x8000,9600,&nPortId);
   if ( err != errNone )
   {
      StrPrintF(szInfo,"SrmOpen returned error = 0x%X - %s\n",err,
                GetSerialErrorText(err, szError));
      if (field)
      {
         nInsPos = Log(field, szInfo, nInsPos);
      }
      return;
   }
   UInt32 nBytes = SrmSend(nPortId, &dwSize, sizeof(dwSize), &err);
   StrPrintF(szInfo,"1. SrmSend returned %lu\n",nBytes);
   if (field)
   {
      nInsPos = Log(field, szInfo, nInsPos);
   }

   if ( err != errNone )
   {
      StrPrintF(szInfo,"1. SrmSend returned error = 0x%X - %s\n",
                err,GetSerialErrorText(err, szError));
      if (field)
      {
         nInsPos = Log(field, szInfo, nInsPos);
      }
      return;
   }

   err = SrmReceiveWait(nPortId,3,5 * SysTicksPerSecond());
   if ( err != errNone )
   {
      StrPrintF(szInfo,"1. SrmReceiveWait returned error =
                0x%X - %s\n",err,GetSerialErrorText(err, szError));
      if (field)
      {
         nInsPos = Log(field, szInfo, nInsPos);
      }
      return;
   }
   nBytes = SrmReceive(nPortId, (void*)szAnswer, sizeof(szAnswer),
                       5 * SysTicksPerSecond(), &err);
   if ( err != errNone )
   {
      StrPrintF(szInfo,"1. SrmReceive returned error = 0x%X - %s\n",
                err,GetSerialErrorText(err, szError));
      if (field)
      {
         nInsPos = Log(field, szInfo, nInsPos);
      }
      return;
   }
   StrPrintF(szInfo,"1. SrmReceive returned buffer %s\n",szAnswer);
   if (field)
   {
      nInsPos = Log(field, szInfo, nInsPos);
}

   nBytes = SrmSend(nPortId, szBuffer, StrLen(szBuffer) + 1, &err);
   StrPrintF(szInfo,"2. SrmSend returned %lu\n",nBytes);
   if (field)
   {
      nInsPos = Log(field, szInfo, nInsPos);
   }

   if ( err != errNone )
   {
      StrPrintF(szInfo,"2. SrmSend returned error = 0x%X - %s\n",
                err,GetSerialErrorText(err, szError));
      if (field)
      {
         nInsPos = Log(field, szInfo, nInsPos);
      }
      return;
   }

   SrmClose(nPortId);
}

This snippet just transmits data by the simplest way. The sample project also contains the MFC application as PC-side stuff.

Download

Download the accompanying code's zip file here.

About the Author

Alex Gusev started to play with mainframes in the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers' lives in the mobile jungles a little bit simpler.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date