Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
SerDemo.c
#include <Types.h> |
#include <SysEqu.h> |
#include <Resources.h> |
#include <QuickDraw.h> |
#include <Fonts.h> |
#include <Events.h> |
#include <Menus.h> |
#include <Controls.h> |
#include <Dialogs.h> |
#include <Memory.h> |
#include <Files.h> |
#include <Devices.h> |
#include <Serial.h> |
#include <Timer.h> |
#include <OSUtils.h> |
#include <GestaltEqu.h> |
#include <Errors.h> |
#include <string.h> |
#define rMonitor 1000 |
#define kSendButton 1 |
#define kStopButton 2 |
#define kQuitButton 3 |
#define kMsgBox 4 |
#define kSendSpinner 5 |
#define kRcvSpinner 7 |
#define kHoldFlag 9 |
#define kBreakButton 10 |
#define rSerDataRsrc 128 |
#define rSpinnerIcon 1000 |
#define rHeldIcon 128 |
#define rNotHeldIcon 129 |
#define rPortOpenALRT 256 |
#define kReset 2 |
#define kCtlEnable 0 |
#define kCtlDisable 255 |
#define kSerBufSize 16384 |
#define kSerRdSize 64 |
#define kSerConfig baud9600 + noParity + data8 + stop10 |
#define kBreakLength 666 /* serial break length in milliseconds */ |
enum {drvrName = 0x12}; /* offset to driver name in 'DRVR' std. header */ |
enum {dOpened = 5, |
dRAMBased = 6, |
drvrActive = 7}; /* Device Manager DCtlFlag bits */ |
enum {kSerStatus = 8, |
kSerClrBrk = 11, /* Serial Driver csCodes */ |
kSerSetBrk = 12, |
kSerHShakeDTR = 14}; |
enum {breakR0 = 128}; /* mask for break bit in SCC RR0 -- See TN #56 */ |
enum {breakErr = 8}; /* mask for break bit in cumErrs -- System 7.0 */ |
typedef struct { |
IOParam fIOParam; |
long appA5; |
} ExtIOParam; |
typedef struct { |
TMTask fTMTask; |
long appA5; |
} ExtTMTask; |
typedef struct { |
unsigned char readR0; |
unsigned char deltaBits; |
short drvrPosting; |
} SERDEventMessage; |
Boolean gHeldOff, |
gAllDone = false, |
gShouldSend = false, |
gReload = false, |
gKillBreak = false, |
gBreakReceived = false; |
Ptr gpSerBuf, *ghSerBuf, |
gpOutputData, *ghOutputData, |
gpBitBucket, *ghBitBucket; |
long gOutputDataSize; |
IOParam *gpSendPB, **ghSendPB; |
short gSysVersion, |
gOutRefNum, gInRefNum, |
gSendCount = 0, |
gRcvCount = 0; |
char *panicString = "Help! I'm stuffed! And here's a bunch of characters to prove it!\n\r"; |
ExtTMTask gUnBreakTask; |
short Initialize (void); |
void CleanUp (void); |
Boolean OpenSERD (void); |
void CloseSERD (void); |
void DoIOStuff (void); |
OSErr PrimeSend (void); |
void CheckSerStatus (void); |
void CheckSerData (long reqBytes); |
pascal Boolean NullGrabber (DialogPtr, EventRecord *evt, short *itemHit); |
pascal void SendCompRout (void); |
pascal void FlagBreakTimeout (void); |
OSErr AssertDrvrOpen (Str255 name, short *refNum); |
ParmBlkPtr GetParmBlkPtr (void) = 0x2008; /* MOVE.L A0,D0 */ |
TMTaskPtr GetTMTaskPtr (void) = 0x2009; /* MOVE.L A1,D0 */ |
#pragma parameter __D0 PBControlImmed(__A0) |
pascal OSErr PBControlImmed(ParmBlkPtr paramBlock) = 0xA204; /* _Control ,IMMED */ |
#pragma parameter __D0 PBStatusImmed(__A0) |
pascal OSErr PBStatusImmed(ParmBlkPtr paramBlock) = 0xA205; /* _Status ,IMMED */ |
void main (void) |
{ |
InitGraf(&qd.thePort); |
InitFonts(); |
InitWindows(); |
InitMenus(); |
InitDialogs(NULL); |
InitCursor(); |
if (Initialize() == noErr) { |
if (OpenSERD()) { /* open the Serial Driver */ |
DoIOStuff(); |
CloseSERD(); /* close the Serial Driver */ |
} |
CleanUp(); |
} |
} |
Boolean OpenSERD (void) |
{ |
OSErr openOutErr, openInErr; |
OSErr setBufErr, setCfgErr, setHskErr; |
SerShk hskFlags; |
long finalTicks; |
Boolean takeOverPort = true; |
Boolean openAOut, openAIn; |
openAOut = AssertDrvrOpen("\p.AOut", &gOutRefNum) == noErr; |
openAIn = AssertDrvrOpen("\p.AIn", &gInRefNum) == noErr; |
if (openAOut || openAIn) { |
if (takeOverPort = CautionAlert(rPortOpenALRT, nil) == kReset) { |
if (openAIn) { |
KillIO(gInRefNum); |
CloseDriver(gInRefNum); |
} |
if (openAOut) { |
KillIO(gOutRefNum); |
CloseDriver(gOutRefNum); |
} |
} |
} |
if (takeOverPort) { |
openOutErr = OpenDriver("\p.AOut", &gOutRefNum); |
openInErr = OpenDriver("\p.AIn", &gInRefNum); |
if (openOutErr == noErr && openInErr == noErr) { |
/* There is a bug in the Macintosh IIfx IOP serial driver, in which */ |
/* SerGetBuf() will always return zero characters, even if characters */ |
/* have been received. The bug is exhibited when a remote serial device */ |
/* attempts to send data to the Macintosh IIfx when its serial driver is */ |
/* not yet open and it holding off data with hardware handshaking. In */ |
/* such a case, data will flow in immediately when the serial driver */ |
/* opens--quickly filling up the default 64-byte buffer. If the buffer */ |
/* fills up before setting a larger buffer with SerSetBuf(), SerGetBuf() */ |
/* "sticks" and stubbornly maintains that there is nothing coming in. */ |
/* At 9600 baud, it takes only a little more than four ticks to fill the */ |
/* input buffer. This application demonstrates the bug by virtue of the */ |
/* following delay. */ |
Delay(5, &finalTicks); |
/* It's always good to first set a non-default input buffer, if desired. */ |
/* There is no output buffering, so specify only the input driver. */ |
setBufErr = SerSetBuf(gInRefNum, gpSerBuf, kSerBufSize); |
hskFlags.fXOn = false; |
hskFlags.fCTS = true; |
hskFlags.xOn = 0x11; |
hskFlags.xOff = 0x13; |
hskFlags.errs = 0; |
if (gSysVersion >= 0x0700) { |
hskFlags.evts = 0; /* I can use new means of break detection. */ |
} |
else { |
hskFlags.evts = breakEvent; /* I need the driver to post break events. */ |
} |
hskFlags.fInX = false; |
hskFlags.fDTR = true; |
/* SerHShake() does not support full DTR/CTS hardware handshaking. You */ |
/* accomplish the same thing and more with a Control call and csCode 14. */ |
/* You only need to specify hskFlags once, to the output driver. */ |
setHskErr = Control(gOutRefNum, kSerHShakeDTR, (Ptr) &hskFlags); |
/* Now reset both input and output drivers with the same configuration. */ |
/* Only a single call to the output driver is necessary to do this. */ |
/* Differing concurrent input/output baud rates are not supported. */ |
setCfgErr = SerReset(gOutRefNum, kSerConfig); |
} |
} |
return takeOverPort; |
} |
void CloseSERD (void) |
{ |
OSErr killErr, closeOutErr, closeInErr; |
killErr = KillIO(gInRefNum); |
closeInErr = CloseDriver(gInRefNum); |
killErr = KillIO(gOutRefNum); |
closeOutErr = CloseDriver(gOutRefNum); |
} |
void CheckSerStatus (void) |
{ |
OSErr checkStatErr, panicErr; |
IOParam altSendPB, *pAltSendPB = &altSendPB; |
CntrlParam statPB; |
SerStaRec serStat; |
statPB.ioCRefNum = gInRefNum; |
statPB.csCode = kSerStatus; |
checkStatErr = PBStatusImmed((ParmBlkPtr) &statPB); |
serStat = *(SerStaRec *) &statPB.csParam; |
/* I check to see if the remote system has told me to stop sending. */ |
gHeldOff = (Boolean) serStat.ctsHold; |
/* Check for errors. */ |
if (serStat.cumErrs & swOverrunErr) |
DebugStr("\pSoftware Overrun Error"); |
else if (serStat.cumErrs & parityErr) |
DebugStr("\pParity Error"); |
else if (serStat.cumErrs & hwOverrunErr) |
DebugStr("\pHardware Overrun Error"); |
else if (serStat.cumErrs & framingErr) |
DebugStr("\pFraming Error"); |
/* If I have System 7.0 or better, I can check directly to see if */ |
/* I've received a break. Usually I don't check for a feature this */ |
/* way, but in this case I have no alternative. */ |
if (gSysVersion >= 0x0700) { |
gBreakReceived = (serStat.cumErrs & breakErr) != 0; |
} |
/* All I do here is send a small "panic" packet of characters back */ |
/* to the remote system when it fills _my_ buffer. I don't actually */ |
/* know the exact state of my buffer, but I can see if I've told */ |
/* the remote system to shut up, indicating that I'm mostly full. */ |
if (checkStatErr == noErr) { |
if (( (unsigned char) serStat.xOffSent & dtrNegated) != 0) { |
pAltSendPB->ioCompletion = NULL; |
pAltSendPB->ioRefNum = gOutRefNum; |
pAltSendPB->ioBuffer = panicString; |
pAltSendPB->ioReqCount = strlen(panicString); |
PBWriteAsync((ParmBlkPtr) pAltSendPB); |
/* The program may hang here if the user quits the */ |
/* remote application first--that could hold off our */ |
/* serial output, leaving a pending asynchronous I/O */ |
/* request and keeping us in an infinite loop. */ |
while (pAltSendPB->ioResult > 0) {} /* I'll fix it later. */ |
/* The reason I do this instead of just calling it synchronously */ |
/* is so that if I do hang, I'll hang in my code for an obvious */ |
/* reason instead of hanging up in the Device Manager. */ |
panicErr = pAltSendPB->ioResult; |
} |
} |
} |
void CheckSerData (long reqBytes) |
{ |
OSErr checkBufErr, serRdErr; |
long charCount; |
// long finalTicks; |
// register long overrun; |
checkBufErr = SerGetBuf(gInRefNum, &charCount); |
if (checkBufErr == noErr) { |
/* The general strategy here is this: if number of available characters */ |
/* meets a certain minimum threshold, then I read in everything in the */ |
/* buffer. If I get delayed, I'll catch up quickly. */ |
if (charCount != 0 && charCount >= reqBytes) { |
/* |
overrun = charCount; |
Control(gOutRefNum, 18, nil); |
Delay(300, &finalTicks); |
Control(gOutRefNum, 17, nil); |
SerGetBuf(gInRefNum, &charCount); |
overrun = charCount - overrun; |
DebugStr("\pSender Halted"); |
*/ |
serRdErr = FSRead(gInRefNum, &charCount, gpBitBucket); |
if (serRdErr == noErr) { |
gRcvCount++; /* increment a counter for the input spinner */ |
} |
} |
} |
} |
void DoIOStuff (void) |
{ |
DialogPtr serMonitor; |
OSErr primeErr; |
short itemHit, itemType; |
ControlHandle sendItem, stopItem, breakItem; |
Handle spinner, flag, item; |
Rect box; |
CntrlParam breakPB; |
serMonitor = GetNewDialog(rMonitor, NULL, NULL); |
SetPort((GrafPtr) serMonitor); |
while (!gAllDone) { |
CheckSerStatus(); |
/* Holding down the mouse button prevents checking the input buffer */ |
/* and forces the input buffer to fill up. This allows experimentation. */ |
if (!Button()) { |
CheckSerData(kSerRdSize); |
} |
if (gShouldSend && gReload) { |
gSendCount++; /* increment a counter for the output spinner */ |
gReload = !gReload; |
} |
/* The break timer simply sets a global flag which I use to indicate when */ |
/* to clear a break condition. Again, I use an immediate Control call, but */ |
/* primarily for consistency, and also to show off. */ |
if (gKillBreak) { |
breakPB.ioCRefNum = gOutRefNum; |
breakPB.csCode = kSerClrBrk; |
PBControlImmed((ParmBlkPtr) &breakPB); /* SerClrBrk(), but IMMED */ |
gKillBreak = !gKillBreak; |
GetDItem(serMonitor, kBreakButton, &itemType, &(Handle) breakItem, &box); |
HiliteControl(breakItem, kCtlEnable); |
} |
/* If another area of the program detects a break, I flag the occurrence here. */ |
if (gBreakReceived) { |
SysBeep(1); |
SysBeep(1); |
gBreakReceived = !gBreakReceived; |
} |
/* Here I update all the buttons and icons. I probably spend too much time */ |
/* doing this when it isn't necessary, but that's not the point. */ |
/* I animate the beach balls by changing the resource IDs of the ICONs in the */ |
/* DITL and invalidating their enclosing rectangles. I also hilight controls */ |
/* appropriately and display annunciators when necessary. */ |
GetDItem(serMonitor, kSendButton, &itemType, &(Handle) sendItem, &box); |
GetDItem(serMonitor, kStopButton, &itemType, &(Handle) stopItem, &box); |
if (gShouldSend) { |
HiliteControl(sendItem, kCtlDisable); |
HiliteControl(stopItem, kCtlEnable); |
} |
else { |
HiliteControl(sendItem, kCtlEnable); |
HiliteControl(stopItem, kCtlDisable); |
} |
spinner = Get1Resource('ICON', rSpinnerIcon + gSendCount % 8); |
GetDItem(serMonitor, kSendSpinner, &itemType, &item, &box); |
SetDItem(serMonitor, kSendSpinner, iconItem, spinner, &box); |
InvalRect(&box); |
spinner = Get1Resource('ICON', rSpinnerIcon + gRcvCount % 8); |
GetDItem(serMonitor, kRcvSpinner, &itemType, &item, &box); |
SetDItem(serMonitor, kRcvSpinner, iconItem, spinner, &box); |
InvalRect(&box); |
if (gShouldSend && gHeldOff) { |
flag = Get1Resource('ICON', rHeldIcon); |
} |
else { |
flag = Get1Resource('ICON', rNotHeldIcon); |
} |
GetDItem(serMonitor, kHoldFlag, &itemType, &item, &box); |
SetDItem(serMonitor, kHoldFlag, iconItem, flag, &box); |
InvalRect(&box); |
/* In lieu of an event loop, I just use a modal dialog with a relatively */ |
/* simple (but unusual) filterProc. This is not a good example of how to */ |
/* write an app. Modal dialogs are evil and to be avoided if possible. */ |
/* Nonetheless, the filterProc is an interesting example unto itself.... */ |
ModalDialog(NullGrabber, &itemHit); |
switch (itemHit) { |
case kStopButton: |
if (gShouldSend) { |
gShouldSend = !gShouldSend; |
} |
break; |
case kSendButton: |
if (!gShouldSend) { |
gShouldSend = !gShouldSend; |
primeErr = PrimeSend(); |
} |
break; |
case kBreakButton: |
/* In another possible Mac IIfx IOP bug, SerSetBrk() called synchronously */ |
/* appears to hang the machine if an async write is pending. Since that is */ |
/* often the case (at least in this application) I work around the problem */ |
/* by making the Control call immediate--this prevents the hang, but also */ |
/* raises another interesting issue about when break is actually asserted. */ |
breakPB.ioCRefNum = gOutRefNum; |
breakPB.csCode = kSerSetBrk; |
PBControlImmed((ParmBlkPtr) &breakPB); /* SerSetBrk(), but IMMED */ |
/* With break asserted, I prime a Time Manager task to flag the end of */ |
/* the break, and disable the Break button so that it cannot be selected */ |
/* again until break is negated. */ |
PrimeTime((QElemPtr) &gUnBreakTask, kBreakLength); |
GetDItem(serMonitor, kBreakButton, &itemType, &(Handle) breakItem, &box); |
HiliteControl(breakItem, kCtlDisable); |
break; |
case kQuitButton: |
gAllDone = true; |
break; |
} |
} |
DisposDialog(serMonitor); |
} |
pascal Boolean NullGrabber (DialogPtr, EventRecord *evt, short *itemHit) |
{ |
EventRecord driverEvent; |
/* Without this filterProc, none of the animation works: */ |
/* In order to keep things rolling along even when there are no events */ |
/* such as mouse clicks or keystrokes, I have to return true in response */ |
/* to null events. This is unusual, but otherwise ModalDialog() "handles" */ |
/* null events by eating them and waiting for something better. This is */ |
/* bad if I need to turn the spinner or clear a break condition. */ |
/* Also, I check for driver events here in order to detect breaks. */ |
if (GetNextEvent(driverMask, &driverEvent)) { |
if ((*(SERDEventMessage *) &driverEvent.message).drvrPosting == gInRefNum) { |
gBreakReceived = ((*(SERDEventMessage *) &driverEvent.message).readR0 & breakR0) != 0; |
} |
} |
if (evt->what == nullEvent) { |
*itemHit = 0; |
return true; |
} |
else { |
return false; |
} |
} |
pascal void SendCompRout (void) |
{ |
long oldA5; |
oldA5 = SetA5(((ExtIOParam *) GetParmBlkPtr())->appA5); /* retrieve A5 */ |
if (gShouldSend && !gAllDone) { |
gReload = true; |
gpSendPB->ioCompletion = (ProcPtr) SendCompRout; |
PBWriteAsync((ParmBlkPtr) gpSendPB); /* this is the self-sustaining part */ |
} |
SetA5(oldA5); |
} |
OSErr PrimeSend (void) |
{ |
gpSendPB->ioCompletion = (ProcPtr) SendCompRout; |
gpSendPB->ioRefNum = gOutRefNum; |
gpSendPB->ioBuffer = gpOutputData; |
gpSendPB->ioReqCount = gOutputDataSize; |
((ExtIOParam *) gpSendPB)->appA5 = SetCurrentA5(); /* completion routine needs A5 */ |
gReload = true; |
return PBWriteAsync((ParmBlkPtr) gpSendPB); /* asynchronous self-sustaining sends */ |
} |
pascal void FlagBreakTimeout (void) |
{ |
long oldA5; |
oldA5 = SetA5(((ExtTMTask *) GetTMTaskPtr())->appA5); /* retrieve A5 */ |
gKillBreak = true; |
SetA5(oldA5); |
} |
OSErr AssertDrvrOpen (Str255 name, short *refNum) |
{ |
DCtlHandle *pUTEntry; |
Ptr pDrvr; |
OSErr result = notOpenErr; /* assume not open */ |
short unitNo; |
char *aDrvrName; |
/* The point here is to determine whether a driver is open, given its name. */ |
/* This allows one to check a driver to see if it's open without hard coding */ |
/* its reference number. (Normally, the way to get the refNum is to open */ |
/* the driver--but that defeats the whole purpose!) */ |
/* This is an extension of the code discussed in Tech Note #71. */ |
*refNum = 0; |
pUTEntry = *(DCtlHandle **) UTableBase; |
for (unitNo = 0; unitNo < *(short *) UnitNtryCnt; unitNo++, pUTEntry++) { |
if (*pUTEntry != nil && **pUTEntry != nil) { |
if (((***pUTEntry).dCtlFlags & 1 << dRAMBased) != 0) |
pDrvr = *(Handle) (***pUTEntry).dCtlDriver; |
else |
pDrvr = (***pUTEntry).dCtlDriver; |
if (pDrvr != nil) { |
aDrvrName = pDrvr + drvrName; |
if (memcmp(aDrvrName, name, 1 + name[0]) == 0) { |
/* We found the one we're after. */ |
*refNum = ~unitNo; |
if (((***pUTEntry).dCtlFlags & 1 << dOpened) != 0) |
result = noErr; |
break; |
} |
} |
} |
} |
return result; |
} |
short Initialize (void) |
{ |
long result; |
OSErr gestErr; |
gestErr = Gestalt(gestaltSystemVersion, &result); |
gSysVersion = result; |
ghSendPB = (IOParam **) NewHandle( sizeof(ExtIOParam)); |
if (ghSendPB != NULL) { |
MoveHHi((Handle) ghSendPB); |
HLock((Handle) ghSendPB); |
gpSendPB = *ghSendPB; |
} |
else { |
return MemError(); |
} |
ghSerBuf = NewHandle(kSerBufSize); |
if (ghSerBuf != NULL) { |
MoveHHi((Handle) ghSerBuf); |
HLock((Handle) ghSerBuf); |
gpSerBuf = *ghSerBuf; |
} |
else { |
DisposHandle((Handle) ghSendPB); |
return MemError(); |
} |
ghBitBucket = NewHandle(kSerBufSize); |
if (ghBitBucket != NULL) { |
MoveHHi(ghBitBucket); |
HLock(ghBitBucket); |
gpBitBucket = *ghBitBucket; |
} |
else { |
DisposHandle((Handle) ghSendPB); |
DisposHandle((Handle) ghSerBuf); |
return MemError(); |
} |
ghOutputData = Get1Resource('sDAT', rSerDataRsrc); |
if (ghOutputData != NULL) { |
MoveHHi(ghOutputData); |
HLock(ghOutputData); |
gpOutputData = *ghOutputData; |
gOutputDataSize = SizeResource(ghOutputData); |
} |
else { |
DisposHandle((Handle) ghSendPB); |
DisposHandle((Handle) ghSerBuf); |
DisposHandle(ghBitBucket); |
return ResError(); |
} |
/* I use a Time Manager task to time breaks. This is where I install it. */ |
gUnBreakTask.fTMTask.tmAddr = FlagBreakTimeout; |
gUnBreakTask.appA5 = SetCurrentA5(); |
InsTime((QElemPtr) &gUnBreakTask); |
return noErr; |
} |
void CleanUp (void) |
{ |
/* I remove the Time Manager task before exiting. */ |
RmvTime((QElemPtr) &gUnBreakTask); |
DisposHandle((Handle) ghSendPB); |
DisposHandle(ghSerBuf); |
DisposHandle(ghBitBucket); |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14