{ File: RS232CInterrupts.asm Modification History: Chuck Fay : 17-Nov-82 18:10:24: Fix FifoOverflow handling; move all stack POPs before EIs to avoid potential stack overflow memory smash; change RxIllegalInt and TxIllegalInt to press on rather than crash with MP 515. Jim Frandeen : September 17, 1982 8:51 AM: Allow any block size in Async mode. Jim Frandeen : May 25, 1982 3:56 PM Jim Frandeen : April 13, 1982 11:32 AM: Created file. } Get "SysDefs" Get "RS232CDefs" Get "CommonDefs" IMP AsyncTimerCounter ;From SIOSubs IMP AsyncTimerStoppedFlag ;From SIOSubs IMP GetByteCount ;From RS232CGet IMP ErrorReport ;From Common IMP RxMaxFrameSize ;From BisyncInput IMP StatusChangeFlag ;From RS232CGet IMP TxBisyncStateSwitch ;From BisyncInterrupts IMP TxUnderrunDetectedFlag ;From RS232CPut EXP AsyncRS232CInterrupt EXP AsyncTimerValue EXP CharactersLeftInClientBuffer EXP CharactersUntilBoundary EXP CharLengthMask EXP CharLengthMask1 EXP PrevAsyncDCD EXP PrevCTS EXP PrevSdlcDCD EXP CurrentFrameToFillPtr EXP CurrentFrameToSendPtr EXP EndOfFrame EXP ExitInterrupt EXP FifoStateSwitch EXP FirstFrameToFillPtr EXP FirstRxCharPtr EXP FirstMaxFrameBoundary EXP LastNewFrameAddressHigh EXP LastNewFrameAddressLow EXP LastTxBufferAddressHigh EXP LastTxBufferAddressHigh1 EXP LastTxBufferAddressLow EXP LastTxBufferAddressLow1 EXP MaxFrameSize EXP NoMoreBytesToSend EXP PutCompletedFlag EXP PutFinishedSwitch EXP RxAsyncDataInt EXP RxAsyncExternalInt EXP RxAsyncSpecialInt EXP RxIllegalInt EXP RxSdlcDataInt EXP RxSdlcExternalInt EXP RxSdlcSpecialInt EXP StoreFrameHeader EXP TxAsyncDataInt EXP TxAsyncExternalInt EXP TxBisyncExternalInt EXP TxBufferPointer EXP TxSdlcExternalInt EXP SdlcFrameFlag EXP SdlcRS232CInterrupt EXP TerminateFrame EXP TxIllegalInt ; {This is what a frame looks like:} FrameKey: EQU 0 ;# FrameValid => frame has been stored over FrameReadyFlag: EQU 1 ;#0 => frame is ready to send FrameLastCharLow: EQU 2 FrameLastCharHigh: EQU 3 ;stored by interrupt routine FrameCPAddrLow: EQU 4 ;low byte of CP address FrameCPAddrMid: EQU 5 ;middle byte of CP address FrameCPAddrHigh: EQU 6 ;high byte of CP address FrameSizeLow: EQU 7 ;number of data bytes in frame FrameSizeHigh: EQU 8 FrameCommand: EQU 9 ;WriteBlock command for CP micrrocode FrameData: EQU 10 ;First character of data FrameValid EQU 7EH ; AsyncRS232CInterrupt: { Come here in Async mode from Common when we get a RST 6.5 Interrupt from the Zilog SIO chip. Save state } PUSH PSW PUSH H {Read the interruptVector to determine the cause of the interrupt. Multiply the vector by 2. This gives us a number which we can OR into the low bits of the jump table address. The jump table is in the Common so that we can assure its exact address and guarantee that the low 5 bits of the address are zero. } MVI A,PointToWR2 OUT TxCont IN TxCont ; Read Interrupt Vector RLC ;Interupt Vector * 2 ORI 60H ;Low address is 60H MOV L,A MVI H,20H ;High address is 20H PCHL ;Jump to JumpTable {AsyncJumpTable: @2060H JMP TxAsyncDataInt ; When Vecter =0 Tx Buffer Empty DB 0 JMP TxAsyncExternalInt ; When Vecter =2 Ex Stat DB 0 JMP TxIllegalInt ; When Vecter =4 Rx Char DB 0 JMP ExitInterrupt ; When Vecter =6 Sp Rx Cond DB 0 JMP RxIllegalInt ; When Vecter =8 Tx Buffer Empty DB 0 JMP RxAsyncExternalInt ; When Vecter =A Ex Stat DB 0 JMP RxAsyncDataInt ; When Vecter =C Rx Char DB 0 JMP RxAsyncSpecialInt ; When Vecter =E Sp Rx Cond } {The following interrupts should never happen, but occasionally they do, perhaps because of crosstalk on the DLion RS232 cable. Therefore, we just ignore them, rather than crash with an MP code. The one that seems to happen is RxIllegalInt. The counters are for debugging use, for instance of new cables.} RxIllegalInt: LXI H,RxIllegalIntCount INR M ;Increment illegal interrupt counter JMP ExitInterrupt TxIllegalInt: LXI H,TxIllegalIntCount INR M ;Increment illegal interrupt counter JMP ExitInterrupt RxIllegalIntCount: DB 0 TxIllegalIntCount: DB 0 ; TxAsyncDataInt: {Come here when we get an interrupt for Tx Buffer Empty. See if the last character has already been sent.} LHLD TxBufferPointer DCX H LDA LastTxBufferAddressLow CMP L JNZ SendNextAsyncCharacter LDA LastTxBufferAddressHigh CMP H JNZ SendNextAsyncCharacter {We already sent the last character.} MVI A,ResetTxIntPending OUT TxCont STA PutCompletedFlag POP H POP PSW EI ;Enable interrupts RET SendNextAsyncCharacter: INX H MOV A,M ;Get byte from buffer OUT TxData ;Send byte INX H ;Point to next character SHLD TxBufferPointer ;Update buffer pointer POP H POP PSW EI ;Enable interrupts RET ; RxAsyncDataInt: { Come here when the SIO chip has another character of data ready. Reset the Async timer.} DB opLXIH ;HL ← AsyncTimerValue AsyncTimerValue: DW 0 SHLD AsyncTimerCounter ;Set iteration counter MVI A,0B0H ;timer channel 2, mode 0, binary OUT TimerMode LXI H,TimerCount2+8000H MVI M,0FEH ;Send low byte of 1840 decimal MVI M,47H ;Send high byte XRA A STA AsyncTimerStoppedFlag {Fetch the next character and mask it, depending on the length of the character. Store the character, increment the pointer to the next character position.} IN RxData ;A ← RxData DB opANI ;A ← RxData AND CharLengthMask CharLengthMask: DB 0 STAX B ;Store character in Fifo INX B {See if we are have filled the Iocb.} DB opLXIH ;HL ← CharactersUntilBoundary CharactersLeftInClientBuffer: DW 0 DCX H ;Decrement count MOV A,H ORA L JZ ClientBufferFull SHLD CharactersLeftInClientBuffer {See if we are at the end of a block.} DB opLXIH ;HL ← CharactersUntilBoundary CharactersUntilBoundary: DW 0 DCX H ;Decrement boundary count MOV A,H ORA L JZ BlockFull SHLD CharactersUntilBoundary POP H POP PSW EI ;Enable interrupts RET ClientBufferFull: {Come here if this character has filled the client's buffer. End this frame, mark it EOF, and start a new frame. This is a normal completion for Asynchronous mode. In asynchronous mode, a frame ends either when it fills the IOCB or when we get a timeout, whichever comes first.} PUSH D ;Save DE MVI E,80H ;Set EndOfFrame JMP TerminateFrame BlockFull: {Come here if this character has filled the block. This happens when a client's buffer is larger than our input buffer. End this frame, mark it end of block, and start a new frame. The Get routine will send this data to the CP.} PUSH D ;Save DE MVI E,4 ;Set EndOfBlock JMP TerminateBlock ; RxAsyncExternalInt: { Come here when we get an External/Status interrupt. Register RR0 indicates the cause of the interrupt: 80: 10000000 Break Asynchronous 08: 00001000 Data Carrier Detect (DCD)} IN RxCont ;Read RR0 MOV H,A ;Save RR0 in H ANI DCD DB opCPI ;Compare to previous DCD PrevAsyncDCD: DB 0 STA PrevAsyncDCD ;Save current DCD MVI A,ResetExternalStatusInterrupts OUT RxCont JZ TestRxAsyncBreak {Continue if DataCarrierDetect has changed state. Notify the Get loop so that it will do a NakedNotify. We store ResetExternalStatusInterrupts in StatusChangeFlag.} STA StatusChangeFlag TestRxAsyncBreak: ORA H ;RR0 will be negative if Abort bit is set JM AsyncBreakAbortDetected POP H POP PSW EI ;Exit Interrupt RET AsyncBreakAbortDetected: {We got a Break/Abort. We end the current frame and return it to the CP with aborted status in the Iocb. Set FrameCompletionCode to Aborted. NOTE: If this frame does not contain any characters, we will not send any. We check for this at StartFrameTransfer.} PUSH D ;Save DE MVI E,1 ;Set CompletionCode to Aborted TerminateFrame: {We get called from Get when we get a timeout or an abort. E = CompletionCode: 1 for Aborted or 80H for Timeout. End this frame and start a new one.} LHLD GetByteCount SHLD CharactersLeftInClientBuffer TerminateBlock: {RxMaxFrameSize is the number of characters until our next boundary.} LHLD RxMaxFrameSize SHLD CharactersUntilBoundary JMP EndOfFrame ; RxAsyncSpecialInt: { Come here when we get a Special interrupt. Register RR1 indicates the cause of the interrupt: 40: 01000000 Framing error in Async 20: 00100000 RxOverrun 10: 00010000 Parity error A Framing Error occurs if the character is assembled without any stop bits. This bit is set only for the character on which it occurred. RxOverrun means data is being overwritten because the channel's three-byte receiver buffer is full and a new character is being received. A Parity Error occurs when the parity bit of the character does not match with the programmed parity. Once this bit is set, it remains set until the Error Reset Command is given.} PUSH D ;Save DE {Read the character that caused the interrupt and place it in the RxFifo as we would a normal character.} IN RxData ;A ← RxData DB opANI ;A ← RxData AND CharLengthMask CharLengthMask1: DB 0 STAX B ;Store character in Fifo INX B LXI H,8000H+RxCont ;Point to RxCont register MVI M,PointToWR1 MOV A,M ;Read CompletionCode from RR1 ANI 0F0H MOV E,A ;Save CompletionCode in E MVI M,ErrorReset {RxMaxFrameSize is the number of characters until our next boundary.} LHLD RxMaxFrameSize SHLD CharactersUntilBoundary JMP EndOfFrame ; SdlcRS232CInterrupt: { Come here in Sdlc mode from the EXEC when we get a RST 6.5 Interrupt from the Zilog SIO chip. Save state } PUSH PSW PUSH H {Read the interruptVector to determine the cause of the interrupt. Multiply the interrupt vector by 2. This gives us a number which we can OR into the low bits of the jump table address. The jump table is in the Common so that we can assure its exact address and guarantee that the low 5 bits of the address are zero.} MVI A,PointToWR2 OUT TxCont IN TxCont ; Read Interrupt Vector ORA A PutFinishedSwitch: {This opcode will be changed to a JMP when the last character has been sent. The jump table will then pass control to NoMoreBytesToSend.} DB opJNZ DW BranchOnSdlcInterruptVector TxSdlcDataInt: {Come here when we get an interrupt for Tx Buffer Empty. Send another character to the SIO chip.} DB opLXIH ;HL ← TxBufferPointer TxBufferPointer: DW 0 {We want to send the character and then test for last so as to service the interrupt as fast as possible.} MOV A,M ; get byte from buffer OUT TxData ; send byte DB opMVIA ;A ← LastTxBufferAddressLow LastTxBufferAddressLow: DB 0 CMP L JZ LastTxBufferAddressLowEqual INX H ; increment buffer pointer SHLD TxBufferPointer ; update buffer pointer ExitInterrupt: {Come here to restore state and exit. } POP H POP PSW EI ;Enable interrupts RET LastTxBufferAddressLowEqual: DB opMVIA ;A ← LastTxBufferAddressHigh LastTxBufferAddressHigh: DB 0 CMP H JZ LastByteToSend INX H ; increment buffer pointer SHLD TxBufferPointer ; update buffer pointer POP H POP PSW EI ;Enable interrupts RET LastByteToSend: {We have just sent the last byte. The next time we get a TxData interrupt, we will JMP to NoMoreBytesToSend.} MVI A,opJMP STA PutFinishedSwitch STA PutCompletedFlag POP H POP PSW EI ;Enable interrupts RET NoMoreBytesToSend: MVI A,ResetTxIntPending OUT TxCont ; disable this interrupt & exit POP H POP PSW EI ;Enable interrupts RET ; BranchOnSdlcInterruptVector: RLC ;Interupt Vector * 2 MVI H,20H ;High address is 20H ORA H ;Low address is 20H MOV L,A PCHL ;Jump to JumpTable {SdlcJumpTable: @2060H JMP NoMoreBytesToSend ; When Vecter =0 Tx Buffer Empty DB 0 JMP TxSdlcExternalInt ; When Vecter =2 Ex Stat DB 0 JMP TxIllegalInt ; When Vecter =4 Rx Char DB 0 JMP ExitInterrupt ; When Vecter =6 Sp Rx Cond DB 0 JMP RxIllegalInt ; When Vecter =8 Tx Buffer Empty DB 0 JMP RxSdlcExternalInt ; When Vecter =A Ex Stat DB 0 JMP RxSdlcDataInt ; When Vecter =C Rx Char DB 0 JMP RxSdlcSpecialInt ; When Vecter =E Sp Rx Cond } ; TxSdlcExternalInt: TxAsyncExternalInt: TxBisyncExternalInt: {Tx Sdlc External Interrupt processing routine Come here if we get Break, Clear To Send, Data Carrier Detect, or Tx Underrun. Notify the Sdlc Put Loop if we get underrun.} IN TxCont MOV H,A ;Save RR0 in H ANI TxUnderrun STA TxUnderrunDetectedFlag {Test for change in Clear To Send.} MOV A,H ANI CTS DB opCPI ;Compare to previous CTS PrevCTS: DB 0 STA PrevCTS ;Save current CTS MVI A,ResetExtStatusInterrupts OUT TxCont JZ NoChangeInCTS STA StatusChangeFlag NoChangeInCTS: POP H POP PSW EI ;Enable interrupts RET ; RxSdlcDataInt: {Well here it is folks! This is what we have all been waiting for. This is why Domino never uses registers B and C. They are reserved for NextRxCharPtr so that SDLC will run at 56KB in interrupt mode. Fetch the next character and store the character, increment the pointer to the next character position. Note that we do not check for any boundary. We depend on the EndOfFrame interrupt to stop the receiver. At 56KB, we don't have time to check the boundary.} {Set SdlcFrameFlag so we know we're in a frame if we get an abort. A = 18H from the InterruptVector*2. This seems to waste 13 cycles, but Abort is the longest interrupt, and it will cause Overruns if we try a longer method of finding out if we are in a frame.} STA SdlcFrameFlag IN RxData ;A ← RxData STAX B ;Store character in Fifo INX B POP H POP PSW ; Restore Registers EI RET ; RxSdlcExternalInt: { Come here when we get an External/Status interrupt. Register RR0 indicates the cause of the interrupt: 80: 10000000 Break 08: 00001000 Data Carrier Detect (DCD)} IN RxCont ;Read RR0 MOV H,A ;Save RR0 in H ORA H JP TestSdlcStateChange {We got a Break/Abort. Check to see if we are processing a frame. Breaks detected when the SIO is not synchronized to the data stream are not interesting.} DB opMVIA ;A ← SdlcFrameFlag SdlcFrameFlag: DB 0 ORA A JZ TestSdlcStateChange {If we are in a frame, we end the current frame and return it to the CP with aborted status in the Iocb. Set FrameCompletionCode to Aborted. NOTE: If this frame does not contain any characters, we will not send any. We check for this at StartFrameTransfer.} PUSH D ;Save DE MVI E,1 ;Set CompletionCode to Aborted JMP EndOfFrame TestSdlcStateChange: MOV A,H ;A ← RR0 ANI DCD DB opCPI ;Compare to previous DCD PrevSdlcDCD: DB 0 STA PrevSdlcDCD ;Save current DCD MVI A,ResetExternalStatusInterrupts OUT RxCont JZ ExitSdlcExternalInt {DataCarrierDetect has changed state. Notify the Get loop so that it will do a NakedNotify. We store ResetExternalStatusInterrupts in StatusChangeFlag.} STA StatusChangeFlag ExitSdlcExternalInt: POP H POP PSW EI ;Exit Interrupt RET ; RxSdlcSpecialInt: { Come here when we get a Special interrupt. Register RR1 indicates the cause of the interrupt: 80: 10000000 End Of Frame 40: 01000000 CRC error 20: 00100000 RxOverrun 10: 00010000 Parity error RxOverrun means data is being overwritten because the channel's three-byte receiver buffer is full and a new character is being received. A Parity Error occurs when the parity bit of the character does not match with the programmed parity. Once this bit is set, it remains set until the Error Reset Command is given.} PUSH D ;Save DE {Read the character that caused the interrupt and place it in the RxFifo as we would a normal character.} IN RxData ;A ← RxData STAX B ;Store character in Fifo INX B LXI H,8000H+RxCont ;Point to RxCont register MVI M,PointToWR1 MOV A,M ;Read CompletionCode from RR1 ANI 0F0H ;Mask off CRC residue MOV E,A ;Save CompletionCode in E MVI M,ErrorReset ; {This is the Get Fifo processing system. The interrupt routine puts characters into the Fifo, and the Get routine removes one frame at a time. Each time a frame is ready, the Fifo routine notifies the Get routine by posting a completion code in the frame header. The interrupt routine fills a frame and sets the FrameCompletionFlag # 0 when a frame is ready to send. The interrupt routine stores the address of the last character stored + 1 in FrameLastChar. We never start a new frame unless there is room in the Fifo for the maximum frame size + the size of the frame header. RxMaxFrameSize contains the maximum frame size. We get an overflow when there is no room in the Fifo for a new frame. The first frame begins at RxFifo. The next frame begins at the next address following the first frame. If the following calculation is negative, we need to start a new frame at the beginning of the RxFifo: LastNewFrameAddress - NextFrameToFillPtr.} {The Fifo has two states: Open and Closed. The Fifo is in the Open state when its boundary is the end of the Fifo. The Fifo is in the Closed state when its boundary is the next frame to send. In the Closed state, we have to be careful not to run into the next frame to send. It is in the Closed state that we are in danger of overflow. An overflow condition occurs when there is no room for a maximum size frame. The Fifo starts in the Open state. We pass from the Open state to the Closed state when the interrupt routine wraps around and begins a new frame to fill at the beginning of the Fifo. We pass from the Closed state to the Open state when the Get routine wraps around and begins a new frame to send at the beginning of the Fifo.} EndOfFrame: {Come here when a frame has terminated. E = CompletionCode. BC points to the last character stored + 1. We have satisfied the Rx interrupt condition. Since this is EndOfFrame, we won't have to worry about another input character for a while, but we do have to worry about the transmit buffer becoming empty. This is a good time to check for it.} MVI A,PointToWR2 OUT TxCont IN TxCont ; Read Interrupt Vector ORA A JZ HandleTxDataInEOF StoreFrameHeader: {Enter here for Bisync end of frame.} DB opLXIH ;HL ← CurrentFrameToFillPtr CurrentFrameToFillPtr: DW 0 ;HL points to CompletionCode {The CompletionCode is in E. OR this with the fifo OverflowFlag in case this frame already had a fifo overflow.} DB opMVIA ;A ← OverflowFlag OverflowFlag: DB 0 ;0 => no overflow, 2 => overflow ORA E MOV M,A ;Store CompletionCode XRA A ;WARNING! Note side effect on FifoStateSwitch below STA OverflowFlag ;Turn off fifo overflow flag for next time INX H ;Point to count field of Frame MOV M,C ;Store low pointer to last char INX H ;Point to count field high of frame MOV M,B ;Store high pointer to last char {Now initialize the NextFrameToFill depending on the state of the Fifo: Open or Closed. The following op code will be JMP if the Fifo State is Closed, or JNZ if the Fifo State is Open. Because of the XRA instruction above, the ConditionCode will always be = 0 when we come here. When the Fifo wraps around in the Get routine, we set the Switch to opJNZ for Open. When the Fifo wraps around in the interrupt routine, we set the Switch to opJMP for Closed.} FifoStateSwitch: DB opJNZ DW FifoClosed FifoOpen: {JNZ will always fall through when the State is Open. Come here if the next frame to send is behind us. This means we have until the end of the Fifo for another frame. See if we have enough room in the Fifo to start another frame. If the following calculation is negative, we have to start the next frame at the beginning of the Fifo: LastNewFrameAddress - NextFrameToFillPtr.} DB opMVIA ;A ← LastNewFrameAddressLow LastNewFrameAddressLow: DB 0 SUB C DB opMVIA ;A ← LastNewFrameAddressHigh LastNewFrameAddressHigh: DB 0 SBB B JM StartFirstAsyncFrame {Continue if we have room for another frame. Now we can initialize CurrentFrameToFillPtr and BC (NextRxCharPtr). Set HL to point to new frame.} MOV D,B MOV E,C ;DE ← CurrentFrameToFillPtr LXI H,FrameData DAD D ;HL ← new NextRxCharPtr XCHG ;HL ← CurrentFrameToFillPtr MOV B,D MOV C,E ;BC ← NextRxCharPtr MVI M,FrameValid ;Set FrameValid INX H SHLD CurrentFrameToFillPtr XRA A MOV M,A ;Turn off CompletionCode STA SdlcFrameFlag POP D ;Restore DE POP H POP PSW EI ;Enable interrupts RET StartFirstAsyncFrame: {Come here to begin a new frame at the beginning of the Fifo. First, be sure we have room to start a new frame at the beginning of the Fifo. We calculate: CurrentFrameToSendPtr - (FirstFrameToFillPtr + MaxFrameSize). If this is negative, we have an overflow condition.} LHLD CurrentFrameToSendPtr DB opLXID ;DE ← FirstMaxFrameBoundary FirstMaxFrameBoundary: DW 0 MOV A,L SBB E MOV A,H SBB D JM FifoOverflow {Continue if we have room to start a new frame. Now we can initialize the FifoState to Closed.} MVI A,opJMP STA FifoStateSwitch DB opLXIB ;BC ← FirstRxCharPtr FirstRxCharPtr: DW 0 DB opLXIH ;HL ← FirstFrameToFillPtr FirstFrameToFillPtr: DW 0 MVI M,FrameValid ;Set FrameValid INX H SHLD CurrentFrameToFillPtr XRA A MOV M,A ;Turn off CompletionCode STA SdlcFrameFlag POP D POP H POP PSW EI ;Enable interrupts RET FifoClosed: {BC = NextFrameToFillPtr. Test to see that we do not overflow the Fifo. We calculate: CurrentFrameToSendPtr - (NextFrameToFillPtr + MaxFrameSize) If this is negative, we have an overflow condition. MaxFrameSize is the size specified in the client's IOCB + FrameData.} DB opLXID ;DE ← CurrentFrameToSendPtr CurrentFrameToSendPtr: DW 0 DB opLXIH ;DE ← MaxFrameSize MaxFrameSize: DW 0 DAD B ;HL ← MaxFrameSize+NextFrameToFillPtr MOV A,E SUB L ;Subtract from CurrentFrameToSendPtr MOV A,D SBB H JM FifoOverflow {Continue if we have room to start a new frame. Now we can initialize CurrentFrameToFillPtr.} MOV D,B MOV E,C ;DE ← CurrentFrameToFillPtr LXI H,FrameData DAD D ;HL ← new NextRxCharPtr XCHG ;HL ← CurrentFrameToFillPtr MOV B,D MOV C,E ;BC ← NextRxCharPtr MVI M,FrameValid ;Set FrameValid INX H SHLD CurrentFrameToFillPtr XRA A MOV M,A ;Turn off CompletionCode STA SdlcFrameFlag POP D POP H POP PSW EI ;Enable interrupts RET ; FifoOverflow: {We don't have room to start a new frame. We will store the next frame right over the current frame. Set CompletionCode back to empty so the GetLoop won't think this frame is ready. When the next frame completes, we will OR the CompletionCode with OverflowFlag to signal the loss of this frame to the GetLoop.} MVI A,FifoOverflowed STA OverflowFlag ;Note overflow for next time through EndOfFrame LHLD CurrentFrameToFillPtr XRA A MOV M,A ;Turn off CompletionCode STA SdlcFrameFlag LXI D,FrameData-1 DAD D MOV B,H MOV C,L POP D POP H POP PSW EI ;Enable interrupts RET ; HandleTxDataInEOF: {See if the last character has been sent. If the last byte has already been sent, we don't have to worry about clearing the interrupt.} DB opMVIA ;A ← PutCompletedFlag PutCompletedFlag: DB 0 ORA A JNZ StoreFrameHeader LHLD TxBufferPointer MOV A,M ;Get character from buffer OUT TxData ;send byte DB opMVIA ;A ← LastTxBufferAddressLow LastTxBufferAddressLow1: DB 0 CMP L JNZ UpdateTxBufferPointer DB opMVIA ;A ← LastTxBufferAddressHigh LastTxBufferAddressHigh1: DB 0 CMP H JNZ UpdateTxBufferPointer {We have just sent the last byte. The next time we get a TxData interrupt, we will JMP to NoMoreBytesToSend.} MVI A,opJMP STA PutFinishedSwitch STA PutCompletedFlag JMP StoreFrameHeader UpdateTxBufferPointer: INX H ; increment buffer pointer SHLD TxBufferPointer ; update buffer pointer JMP StoreFrameHeader END