.KEYWORD serial
.FLYINGHEAD PROGRAMMING POWER
.TITLE Interrupt-driven serial communications
.DEPT
.SUMMARY Once asked what serial communications was, a young programming student thought long and hard. Then he said, "Get a box of Captain Crunch, open it, and wing the cereal at the kid at the next desk." Needless to say, the young fellow was destined for programming fame and fortune. If you want to learn how to make your program communicate over the serial port without excess battery consumption and without starting a food fight, you should read Programming Technology Editor Alan Jay Weiner’s latest Programming Power column.
.AUTHOR Alan Jay Weiner
In my December 1998 column, I introduced you to the Palm device’s serial port. That version of SerialEcho used polling — it would check every now and then for received characters. As I said then, polling is inefficient; it wastes processor time (and therefore battery power) to check and find that nothing has been received. In some programs, the time wasted checking could be used for other processing.
There’s a better way. When your phone rings, you answer it; interrupting what you’re doing. After the call, you can return to what you were doing before. We can do the same in the Palm device by using some undocumented, but well established APIs (Applications Programming Interfaces).
The low-level serial drivers actually do use interrupts. When the hardware is able to transmit a character or has received a character, it pulls an interrupt, causing the processor to stop what it’s doing and service that interrupt — either receiving or sending a character.
Unfortunately, the application is forced to poll for received characters.
This month I’ll show you how to set up a callback when characters are received on the serial port. This allows the program to sit idle or do other processing while nothing’s waiting to be read in.
If you want to follow along with the actual code, open a new browser window and point your browser at http://www.component-net.com/pp-extras/serial2.html.
.H1 What’s a callback?
When you go to a hotel and ask the desk for a morning wakeup call, that’s a callback. Software callbacks are the same thing — you tell the operating system the address of the callback routine, then later when the conditions are right the operating system calls that routine. In this case, the callback will be executed when characters have come in the serial port. We already use callbacks to set the form event handler. When our application calls FrmSetEventHandler (usually in ApplicationHandleEvent or some similarly named routine) it tells Palm OS the address of the particular form’s event handler. Then when form events occur, and your event loop calls FrmDispatchEvent, guess where it goes? Yup: the form event handler. See? It’s just a callback.
.H1 Use the Source, Luke!
Hidden in the SDK’s header files are many gems of information. There are numerous definitions and API prototypes that don’t appear in the documentation. Over time, some of these have become semi-documented by various people who asked questions and figured out how they worked.
Since they’re not part of the official documentation, there’s a bit of risk in using them, as they may change in future Palm devices. Of course, that can happen (and has!) with the documented APIs also.
Indeed, several of the APIs we’ll use are listed as "for system use only." Nevertheless, they are the only means to accomplish the callback-driven receive. They’ve been mentioned several times on the developer mailing list and while their use hasn’t been encouraged, it hasn’t been discouraged either.
Looking at the SerialMgr.h header file, you’ll see prototypes and definitions for the serial APIs I described in December. But there’s a few additional APIs. These include:
.BEGIN_LIST
.BULLET SerSleep
.BULLET SerWake
.BULLET SerReceiveISP
.BULLET SerReceiveWindowOpen
.BULLET SerReceiveWindowClose
.BULLET SerSetWakeupHandler
.BULLET SerPrimeWakeupHandler
.END_LIST
Additionally, there’s a typedef to define the SerWakeupHandler prototype:
.BEGIN_CODE
typedef void (*SerWakeupHandler) (DWord refCon);
.END_CODE
We’ll use the wakeup handler to set a callback when characters are received, and the receive window APIs to actually get the characters from the serial port’s receive buffer.
Note that these are only available in Palm OS 2 and above. Because of this, any program that uses these will not work on an original Pilot 1000 or Pilot 5000.
.H1 How the Wakeup Handler works
As I described in December’s column, most programs simply poll the serial port every now and then to see if there are any characters received.
In this version of SerialEcho we’ll use SerSetWakeupHandler to tell the low-level serial routines to call us when characters come in the serial port.
When the program starts, it’ll set up the callback then go into the standard event loop. While there’s nothing coming in the serial port the machine just waits in low-power idle mode. As soon as characters come in, the callback gets called (directly from the low-level interrupt service routine) and it posts an event to wake up the normal event loop. It returns to the ISR (interrupt service routine) which completes its work and things continue on as normal. The event just posted gets returned to your event loop. You process it and get the received characters and do what you want with them. Once you’re done, you return to the event loop and go idle again — until the next character comes in the serial port.
Let’s look at what we need to handle all this.
.H1 Initialize the Wakeup Handler
First we need to tell the serial routines to call our callback routine when characters come in. After opening the serial port using the standard SerOpen call, you call SerSetWakeupHandler, giving it the address of the callback routine and a reference number. When the callback is executed, the reference number is passed to it. This allows a single callback to be used for multiple ports; each would have its own reference number. Normally the reference number isn’t needed — after all, there’s only one serial port so you know where things are coming from and what should happen. However, if a future device were to have more than one port, or you want to pass information to the callback routine for whatever reason, you could store the information in a structure and pass a pointer to that structure as the reference number. Or it could be an index into a table. For the example here, we don’t need it so I just set the reference number to zero.
Next we call SerPrimeWakeupHandler to tell when to call the callback. This sets the number of characters to receive after which the callback will be called. In the example, I want to process characters even if only one comes in, so I set to call me after the first character.
If you were reading in fixed-length blocks, you could set the "prime count" to the size of the block — then you’d know you have received at least a full block when your handler is called.
.H1 The Wakeup Handler Awakens
When the "prime count" number of characters has been received, the serial port’s ISR (interrupt service routine) will call your wakeup handler callback. It will disable further callbacks so you needn’t worry about reentrancy (you will need to re-enable the handler again later). It calls your handler with a single argument — the reference number that you set in SerSetWakeupHandler. If you had multiple serial ports and used the same callback handler for all of them, you’d use this number to distinguish between the ports.
.CALLOUT Warning: note that you’re operating in an interrupt-routine context!
Warning: note that you’re operating in an interrupt-routine context! You can do very little at this time! In fact, the only Palm OS API that’s safe is to call is EvtEnqueueKey. No other APIs are interrupt-safe.
EvtEnqueueKey inserts a "key down" event into the event queue. It could enqueue a normal key (as such things as the Fitaly or Jot keyboards do) or it could use a special command key (such as lowBatteryChr, menuChr or ronamaticChr), as some of the Palm internal routines do. We’ll use a special command key like the OS does, but we’ll define our own. As it turns out, all the special keys that Palm uses have a "virtual key" of zero, so we can simply use a different virtual key code. In the example, I use an ASCII code of ‘S’, a virtual key of 0x01, and the commandKeyMask modifier.
After calling EvtEnqueueKey, the wakeup handler returns to the serial ISR.
.H1 The Event Loop Awakens
The key down event that we just posted causes the event loop to wake up — with a keyDownEvent, naturally.
We could let the event be handled as a normal key-down event; typically this is in the form handler. But I wanted to be sure that the normal OS routines never see our special command key — just in case they (or a future machine) would misinterpret it. So I created a special event handler, SerialHandleEvent. This looks for key-down events with our custom key. When it finds one, it processes the received serial characters. The event loop calls SerialHandleEvent before it calls any of the other handlers.
.H1 Getting the Received Characters
In December’s version, we used SerReceiveCheck and SerReceive10 (the Palm OS version 1.0 compatible API) to get the characters that came in the serial port.
There’s an anomaly with the wakeup handler — it seems to break SerReceive and SerReceive10; neither works properly when a wakeup handler is set. Because the wakeup handler is undocumented, I don’t know if this is intentional or not — I just know they haven’t worked when I’ve tried them.
Instead, we can us some other undocumented APIs to get a pointer directly to the receive buffer.
First we call SerReceiveCheck to get a total count of the received chars. Then, instead of calling SerReceive (or SerReceive10), we call SerReceiveWindowOpen. This gets us a pointer to a contiguous block of characters, and the length of that block. This is a pointer directly into the receive ISR’s buffer. Because it’s a circular buffer, the received characters might wrap around — some at the end of the physical buffer, then next at the beginning again. In this case, we’ll get a pointer to the group at the end of the buffer (the next chars to read in). We’ll need to call SerReceiveWindowOpen a second time to get the wrapped part of the buffer.
We call SerReceiveWindowOpen to get the pointer to and length of data. We do as we wish with the data, then call SerReceiveWindowClose, telling it how many characters we actually used. Then, if we didn’t get all the characters indicated in the call to SerReceiveCheck, we’ll call SerReceiveWindowOpen and SerReceiveWindowClose a second time.
Finally, we want to tell the serial ISR to call our callback again, so we call SerPrimeWakeupHandler.
Note that there might be more characters than we ask for (the "prime count") when we process our special keyDownEvent — this example just processes all that are there.
.H1 Conclusion
This article shows you how to use a callback for serial receive. As I mentioned, there’s a bit of risk because it’s undocumented, but it’s been used by other applications and discussed occasionally on the developer mailing list and newsgroups.
Because they use APIs that aren’t in the original Pilot 1000 and 5000, applicationss which use these techniques should check the Palm OS version number. The standard samples do this. In the interest of simplicity, and because of laziness, fighting the flu and impending deadlines, I didn’t put the version checking into this sample application. It didn’t crash on my Pilot 1000, but it didn’t work either — certainly it can’t be expected to always fail gracefully.
.BEGIN_SIDEBAR
.H1 Product availability and resources
Source code to SerialEchoINT is at my web site at http://www.ajw.com/PalmPower/ProgrammingPower/Mar99/SerialEchoINT.zip
.END_SIDEBAR
.BIO Alan Jay Weiner writes software for PalmPilots. His email address is alan@ajw.com.
.DISCUSS http://powerboards.zatz.com/cgi-bin/webx?13@@.ee6cbe4


