Apple II Technical Notes _____________________________________________________________________________ Developer Technical Support Apple IIgs #18: Do-It-Yourself SCC Access Revised by: Jim Luther July 1990 Written by: Jim Luther, Mike Askins, Matt Deatherage & Jim Mensch June 1987 This Technical Note describes how to install and remove a interrupt handler routine for the Z8530 Serial Communications Controller (SCC) on the Apple IIgs without breaking other parts of the system. This Note includes many suggestions that, if unheeded, could come back to haunt you in the form of bug fixes to your program. Changes since March 1990: Added a method for finding which serial port AppleTalk is using under GS/OS. _____________________________________________________________________________ Free Serial Routines Inside The Z8530 SCC has 2 serial channels, supports several synchronous and asynchronous data communications protocols, and has 9 read registers and 16 write registers per channel. (Compare this to the 5 registers of the 6551 Asynchronous Communications Interface Adapter.) To program the SCC correctly, you must understand five things: the SCC, the Apple IIgs hardware environment in which the SCC lives, the Apple IIgs interrupt handler firmware, the interrupt support provided by the operating system, and the data communication protocol you want to use. If you don't understand all of these components, stick to the serial firmware. The Apple IIgs serial firmware is a robust environment for almost every asynchronous serial programming application. If you want to handle all SCC operations and SCC interrupts on the IIgs without using the serial firmware, then you must really know the firmware won't do the job for you or you wouldn't be going to a lot of trouble to recreate the services the firmware routines already provide. Don't Eat Your Serial with Your Mouth Open Your mother has rules and so does Apple. On many systems, your application may be sharing the SCC chip with System Software such as AppleTalk or the serial firmware. If you want to access the SCC chip directly without breaking the system (or the system breaking you), then follow these simple rules. Rule #1: Before using a serial port, make sure AppleTalk is not already using it. If AppleTalk is active, it uses one of the serial ports. The user selects which serial port AppleTalk uses with the Control Panel. Before using one of the serial ports, you should always check to make sure AppleTalk is not using that port. If AppleTalk is using the serial port your application wants to use, tough luck; tell the user about it, but don't even think about using that port. Under ProDOS 8, use the method shown in the following sample code to determine if AppleTalk is using a serial port: ; ; This routine checks to see which serial port, if any, AppleTalk is using. ; The routine sets a flag byte, ApTalkPort, and the accumulator to indicate ; which port (if any) AppleTalk is using. ; $00 = AppleTalk is not using a serial port ; $01 = AppleTalk is using serial port 1 (printer port) ; $02 = AppleTalk is using serial port 2 (modem port) ; Note: This method should be used under ProDOS 8 only. Under GS/OS, use the ; .AppleTalk driver's GetPort DStatus subcall. ; ; Enter routine in emulation mode ; longa off longi off mcopy 2/AInclude/M16.MiscTool WhichPort start IDROUTINE equ $FE1F returns system ID information stz ApTalkPort default to not AppleTalk jsr IDROUTINE call to the system ID routine cpy #$03 bcs NewIIGS OldIIGS anop this is a pre-ROM 03 IIGS clc to native mode xce rep #$30 16 bit m and x longa on longi on pea $0000 space for result pea $0021 Slot 1 setting _ReadBParam read battery RAM parameter ; (2 byte result left on stack) pea $0000 space for result pea $0027 Slot 7 setting _ReadBParam read battery RAM parameter pla get slot 7 setting (2 bytes) sec emulation mode xce longa off longi off beq FindYourCard AppleTalk is active pla remove slot 1 setting LSB (1 byte) bra OldExit FindYourCard inc ApTalkPort default to port 1 pla is slot 1 "your card"? (1 byte) beq ItsPort2 no, must be port 2 bra OldExit ItsPort2 inc ApTalkPort port 2 is AppleTalk OldExit pla remove slot 1 setting MSB (1 byte) lda ApTalkPort rts return to caller NewIIGS anop ROM 03 or greater IIGS clc to native mode xce rep #$30 16 bit m and x longa on longi on pea $0000 space for result pea $000C port 2 type _ReadBParam read battery RAM parameter ; (2 byte result left on stack) pea $0000 space for result pea $0000 port 1 type _ReadBParam read battery RAM parameter pla get port 1 setting (2 bytes) sec emulation mode xce longa off longi off cmp #$02 is port 1 AppleTalk? bne TryPort2 no inc ApTalkPort yes pla then remove port 2 setting LSB (1 byte) bra NewExit and exit TryPort2 pla get port 2 setting LSB (1 byte) cmp #$02 is port 2 AppleTalk? bne NewExit no lda #$02 yes sta ApTalkPort NewExit pla remove port 2 setting MSB (1 byte) lda ApTalkPort rts return to caller ApTalkPort entry ds 1 will be 0, 1, or 2 end Under GS/OS, use the method shown in the following sample code to determine if AppleTalk is using a serial port: ; ; This routine checks to see which serial port, if any, AppleTalk is using. ; The routine sets a flag byte, ApTalkPort, and the accumulator to indicate ; which port (if any) AppleTalk is using. ; $0000 = AppleTalk is not using a serial port ; $0001 = AppleTalk is using serial port 1 (printer port) ; $0002 = AppleTalk is using serial port 2 (modem port) ; Note: This method should be used under GS/OS only. ; ; Enter routine in native 16 bit mode ; longa on longi on mcopy 2/AInclude/M16.GSOS CheckPort Start GetPort equ $8001 The .AppleTalk DStatus subcall to get ; the port number AppleTalk is currently ; using. phb save data bank phk data bank = code bank plb lda #$0001 start with device #1 sta DIdevNum FindATDriver anop _DInfoGS DInfoParms ;call Dinfo bcs DIError stop searching if error lda DIdeviceIDNum cmp #$001D is it the AppleTalk main driver? beq ATDriverFound yes inc DIdevNum check the bra FindATDriver next device number ATDriverFound anop lda DIdevNum store device number sta DSdevNum in the DStatus parm list _DStatusGS DStatusParms ;call DStatus lda portNum get the port number sta ApTalkPort bra Exit DIError anop ; cmp #$0011 invalid device number, so the ; beq NotFound AppleTalk main driver wasn't found ; ; Add your code to handle any other errors from DInfo here, because the ; end of the device list was not found. NotFound stz ApTalkPort neither port is in use bra Exit Exit anop lda ApTalkPort plb restore data bank rtl return to caller ApTalkPort entry ds 2 will be 0, 1, or 2 DInfoParms anop dc i2'8' pCount = 8 parameters DIdevNum dc i2'1' devNum dc a4'NameBuffer' devName ds 2 characteristics ds 4 totalBlocks ds 2 slotNum ds 2 unitNum ds 2 version DIdeviceIDNum ds 2 deviceIDNum NameBuffer anop dc i2'31' Class 1 input string. Max Length=31 ds 33 DStatusParms anop dc i2'5' pCount = 5 parameters DSdevNum ds 2 devNum dc i2'GetPort' statusCode = GetPort dc a4'GetPortSList' statusList = GetPortSList dc i4'2' requestCount = 2 ds 4 transferCount GetPortSList anop the GetPort subcall's statusList portNum ds 2 $0001 = AppleTalk is using port 1 (printer port) ; $0002 = AppleTalk is using port 2 (modem port) dc i2'0' end Rule #2: Don't use the SCC Interrupt Handler Vector. Contrary to what you may have read in a previous version of this Note, you cannot reliably attach your SCC interrupt handler to the SCC Interrupt Handler Vector (vector reference number $0009). The Apple IIgs serial firmware owns the SCC Interrupt Handler Vector (or at least it thinks it does). Anytime the serial firmware is used, there is a chance that the serial firmware can grab the SCC Interrupt Handler Vector for its use. CDAs and NDAs that print, the Print Manager tool set, the Text tool set, and the generated GS/OS character drivers associated with the serial ports are examples of code that can and do use the serial firmware. The only safe place to connect into the interrupt chain is through the operating system. The ProDOS 8 and GS/OS ProDOS 16 call, ALLOC_INTERRUPT is the correct place to attach your interrupt handler. The GS/OS BindInt call cannot be used to attach your interrupt handler to the SCC Interrupt Handler Vector (VRN $0009) for the same reason that you cannot use the SCC Interrupt Handler Vector directly. Rule #3: Be very, very careful with SCC Write Register 9 (WR9). The Z8530 SCC has four registers which are shared by both channels (ports). Of those four, only two are commonly used in the Apple IIgs, RR3 and WR9. RR3, which only exists in channel A, lets you check the interrupt pending bits for both SCC channels. WR9 is the Master Interrupt Control register for both SCC channels and contains the Reset command bits. You must never reset the channel AppleTalk is using (resetting the channel AppleTalk is using kills AppleTalk). This means you should never perform a Force Hardware Reset command (11xxxxxx to WR9) even though the Z8530 Serial Communications Controller Technical Manual tells you to in the SCC initialization procedure. A hardware reset is performed at system startup, so you shouldn't need to perform a channel reset, even to the channel you are using. The interrupt control bits (bits D5 - D0) in WR9 should not be modified (an exception is when you are installing your own SCC interrupt handler). AppleTalk expects the interrupt control bits to always be 001010. If you find the need to perform a channel reset on your channel, remember that the interrupt control bits are programmed at the same time as a channel reset. Hints for the Serial Adventure Next are a few hints for those who would like to explore the world of knocking on the registers of the Z8530 SCC. Hint #1: Synchronize your code with the SCC logic. Before writing to the SCC chip for the first time, you should make an attempt to ensure your code is synchronized with the SCC's logic. This needs to be done only once when you are initializing the SCC. This can be accomplished with a single read of SCC Read Register 0 (RR0). For example, if you're using serial port 2 (the modem port), the following code reads RR0 of SCC channel B: longa off must be using 8-bit accumulator lda $C038 read RR0 of SCC Channel B Hint #2: Watch out for interrupts from the other SCC channel. Except for RR0, WR0, and the two SCC data registers, all SCC registers are accessed in a two-step process. First, the register number you want to select is written to WR0. After the register number is set, the next read from or write to the command register accesses the register selected in the first step. Because several of the SCC registers are shared between the two SCC channels and because code accessing them may not always be yours (i.e., AppleTalk), interrupts should be disabled during the two steps. The following code shows two quick subroutines to access the SCC's Read and Write registers while preventing interrupts between the register number set and the register read or write steps: longa off must be using 8-bit accumulator longi off and index registers ; ; Write to a SCC command register - channel A or B. ; Input: A = value to store ; X = SCC register number ($0-$F) ; Y = $01 channel A ; $00 channel B ; WriteSCC php save the current interrupt status sei disable interrupts pha save value to write txa get SCC register number from X sta $C038,y set the register number pla restore value to write sta $C038,y write the value plp restore the interrupt status rts ; ; Read from a SCC command register - channel A or B. ; Input: A = SCC register number ($0-$F) ; Y = $01 channel A ; $00 channel B ; Output: A = register value ; ReadSCC php save the current interrupt status sei disable interrupts sta $C038,y set the SCC register number lda $C038,y get the value from the SCC register xba look ahead 2 lines... plp restore the interrupt status xba set N and Z flags for exit rts Just to be complete, here's how RR0, WR0, the receive buffer, and the transmit buffer SCC registers are accessed on the Apple IIgs: longa off must be using 8-bit accumulator longi off and index registers ; ; Read RR0 - channel A or B ; Input: Y = $01 channel A ; $00 channel B ; Output: A = RR0 register value ; ReadRR0 lda $C038,y get the value from RR0 rts ; ; Write WR0 - channel A or B ; Input: A = value to store at WR0 ; Y = $01 channel A ; $00 channel B ; WriteWR0 sta $C038,y write the value to WR0 rts ; ; Read from SCC receive buffer - channel A or B ; Input: Y = $01 channel A ; $00 channel B ; Output: A = value of data received ; ReadData lda $C03A,y get the value from SCC data register rts ; ; Write to SCC transmit buffer - channel A or B ; Input: A = value of data to transmit ; Y = $01 channel A ; $00 channel B ; WriteData sta $C03A,y write the value to SCC data register rts Hint #3: All SCC channels are not created equal. In the IIgs, the SCC's receive and transmit clocks for both channels are driven by a single crystal oscillator circuit. This is accomplished by connecting a 3.6864 MHz crystal between the /RTxC and /SYNC pins of channel A. Channel B's /RTxC pin is connected to Channel A's /SYNC pin to drive channel B's clocks from channel A's oscillator circuit. Because of this single circuit, Write Register 11 (WR11) bit 7 must be set to 1 for SCC channel A and must be set to 0 for SCC channel B. Hint #4: RR3 is available only in SCC channel A. When your interrupt handler is checking to see if the interrupt condition was caused by your SCC channel, remember to always look at RR3 in SCC channel A. RR3 in channel A contains the interrupt pending bits for both SCC channels. RR3 in channel B always returns all zeros, which doesn't tell you a lot about what's happening. Don't be a Serial Killer How to Install and Remove your SCC Interrupt Handler If you're going to handle serial I/O and don't want your application to have to poll the SCC chip all the time to see if something has happened, you probably want to install an interrupt handling routine that is called every time a SCC chip condition you want to know about occurs. This section of the Note shows how to install and remove your own SCC interrupt handler. The steps for installing your SCC interrupt handler are: 1. Ensure the serial firmware's Input and Output buffering is disabled. The state of I/O buffering can be checked by looking at bit 14 of the ModeBitImage parameter returned by the GetModeBits extended interface call. I/O buffering can be disabled with the firmware's BD control command. 2. Disable the SCC Master Interrupt Enable (WR9, bit 3) briefly while performing the next six steps. The value you should write to WR9 is 00000010. 3. Get the address of the system interrupt flag byte, SerFlag. The ROM version determines the method of finding the address of SerFlag. In ROM version 01 and later, you can get the address with a call to the Miscellaneous Tools GetAddr using a reference number of $000E. With ROM version 00 (the original IIgs ROM), the address of SerFlag is $E10104. Refer to the Apple II Miscellaneous Technical Note #7, Apple II Family Identification for information on identifying Apple IIgs ROM versions. 4. Once you have the correct address of SerFlag, preserve the byte's current value, then turn on the bits in the byte which reflect the port from which you are handling interrupts. The bits for the different ports are as follows (note the relationship of the bits of RR3 to SerFlag): Port 1: ORA #%00111000 Port 2: ORA #%00000111 5. Initialize the SCC modes. The Z8530 Serial Communications Controller Technical Manual shows the order the SCC registers must be programmed. However, you must stray from the manual slightly due to the hardware implementation of the SCC in the IIgs. A typical initialization sequence to set the SCC up for asynchronous serial communications through channel B (the modem port) would look similar to the following: SCC Register Value Comment RR0 - ensure synchronization with SCC WR4 01000100 x16 clock, 1 stop, no parity WR3 11000000 8 bit receive data, auto enables off, receiver disabled WR5 01100010 DTR is active, 8 bit transmit data, no break, transmit disabled, RTS is inactive WR11 01010000 no Xtal on channel B, receive and transmit clock = baud rate generator output WR12 01011110 low byte of baud rate generator time constant = $5E - 1200 baud WR13 00000000 high byte of baud rate generator time constant = $00 - 1200 baud WR14 00000000 no local loopback or auto echo, /DTR follows inverted DTR bit in WR5, use /RTxC for baud rate generator clock, disable baud rate generator WR14 00000001 enable the baud rate generator WR3 11000001 receiver enabled WR5 01101010 transmit enabled WR15 00000000 no interrupts on this channel for now... 6. Tell the SCC which external and status conditions can cause an interrupt by setting the appropriate bits in WR15. This step is not needed unless you are setting bit 0 of WR1 (External/Status Master Interrupt Enable) in the next step. 7. Enable the interrupts modes you want by setting the appropriate bits in WR1 (00010011 for all SCC interrupt conditions). 8. Use ALLOC_INTERRUPT to add your interrupt handler to the operating system's interrupt vector table. The interrupt identification number returned by ALLOC_INTERRUPT is needed when you remove your interrupt handler. 9. Reenable the SCC Master Interrupt flag (WR9, bit 3). The value you should write to WR9 is 00001010. The interrupt handling routine must conform to the rules listed in the ProDOS 8 Technical Reference Manual and GS/OS Reference, Volume 2. When you get ready to shut down your application, you need to remove your interrupt handler. The steps for removing the SCC interrupt handler you installed are as follows: 1. Disable the SCC Master Interrupt Enable (WR9, bit 3) briefly while performing the next six steps. The value you should write to WR9 is 00000010. 2. Disable all interrupts modes for your port by writing a $00 to WR1. 3. Remove any character that might be left in the receive data register by reading it once. 4. Clear any pending transmit overrun and external and status interrupts by writing 11010000 to WR0. 5. Clear any pending transmit interrupt by writing 00101000 to WR0. 6. Use DEALLOC_INTERRUPT to remove your interrupt handler from the operating system's interrupt vector table. 7. Restore SerFlag to its original value. 8. Reenable the SCC Master Interrupt flag (WR9, bit 3). The value you should write to WR9 is 00001010. Further Reference _____________________________________________________________________________ o Apple IIgs Toolbox Reference Manual, Volume 1 o Apple IIgs Firmware Reference Manual o Apple IIgs Hardware Reference Manual, Second Edition o GS/OS Reference, Volumes 1 and 2 o ProDOS 8 Technical Reference Manual o Apple II Miscellaneous Technical Note #7, Apple II Family Identification o GS/OS Technical Note #9, Interrupt Handling Anomalies o Z8530 Serial Communications Controller Technical Manual (Zilog Corporation) o Z85C30 Serial Communications Controller Technical Manual (Advanced Micro Devices, Inc.)