Programming samples by Robert Elton Maas Assembly language (MOS 6502) These are excerpts from the file DM2SIM.3Z (35k bytes total): ; MOS 6502 owned by REM ; DM2SIM (Datamedia, or other smart terminal, emulator with line-map) ; Copyright (C) 1979 and 1980 by Robert Elton Maas ;This software was written to run on REM's personal 6502 system built ; by Clem Smith (son of Leland Smith, music professor at Stanford). ;It interfaces REM's personal Beehive 3A terminal on one ACIA RS-232 port to ; a second ACIA RS-232 port that connects to an acoustic coupler. ;There are 4k bytes of RAM and 1kb pROM. All addresses below are in octal. ;(Reconfiguration would be needed to make this software run on any other ; 6502 such as a KIM or PET or APPLE). Memory layout is as follows: ;0000:0377 -- Zero page (ODT locals, echoback downloader, globals; see below) ;0400:0777 -- Stack and some tables for DM2SIM ;1000:1007 -- Interrupt vectors initialized by power-up/reset and ODT bkpt ;1010:1017 -- Default NMI handler set up by power-up/reset, not used below ;1020:TABLEZ-1 (appx. 3615) -- DM2SIM program ;TABLEZ:4677 -- unused (available to load and run other programs) ;4700:7777 -- Beehive screen-refresh buffer (20. lines of 80. chars) ;010000:173777 -- (NO MEMORY AT THESE ADDRESSES, 58K AVAILABLE FOR MORE RAM) ;174000:175777 -- Partly-decoded virtual addresses for I/O devices ;176000:177777 -- pROM containing power-up/reset init, ODT, loader and ; " unloader, TTY (telnet, talkthru) mode, status-driven I/O ; " routines, other crufty subroutines not used here. ; " 177772:177777 (in pROM) are indirect vectors for hardware RESET/NMI/IRQ ;CPU and UART flags built-in in the hardware... XMTIRQ=^O040 BBIT =^O020 ;True in saved PSW if interrupt caused by BRK instr. XMTBRK=^O365 ;Command for transmitting a BREAK signal for awhile ;ACIA/UART-memory-mapped addresses... BEEC =^O174100 ;Control port for Beehive ACIA/UART BEED =^O174101 ;Beehive data port MODC =^O174120 ;Control port for modem ACIA/UART MODD =^O174121 ;Modem data port BEEBR =^O174140 ;Beehive bit rate (both directions on same clock) MODBRT=^O174141 ;Modem bit rate transmit MODBRR=^O174142 ;Modem bit rate receive ;Entry addresses for routines in PROM... BRKIN =^O176070 ;ODT entry address PCHR =^O176760 ;Status-driven output subroutine SETBEE=^O177024 ;Set up UART for Beehive, including speed INIMOD=^O177066 ;Set up UART for modem except keep speed as-is ;Character and special control signal definitions... BELL =^O007 TAB =^O011 LF =^O012 CR =^O015 ;Flags used in this program to indicate status of characters or lines... DONE =^O000 ;Nothing needs refresh on this line at all SOME =^O001 ;See '200 bits for which characters need refresh ALL =^O002 ;'200 bits not yet set, whole line needs mark for refresh CLEAR =^O003 ;Entire line needs to be erased and marked for refresh REF =^O200 ;Refresh bit on character ; DM2SIM (Datamedia, or other smart terminal, emulator with line-map) ;Interrupt handler (suitable for any 7-bit TTY-mode), assembly-code ;Temporarily use location SAVA=3 non-reentrantly to allow use of register A ; to fetch old processor status which contains BRK flag... BRKCHK: STA SAVA PLA ;Fetch saved-psw to discover PHA ; cause of interrupt... AND #BBIT ;was int. due to BRK? BEQ T1036 JMP BRKIN ;yes, PROM ODT T1036: LDA SAVA ;no, IRQ due to UART... ;Same state as above (register A restored there, stack restored above) ;During handling of interrupt, old psw automatically saved on stack above, ; registers saved on stack here... GOTIRQ: PHA TXA PHA TYA PHA ;Main loop looking for cause of interrupt. ;After finding a cause, restart search at top again to catch ; other pending interrupt-causes before returning from interrupt... INTL1: LDA MODC LSR A ;leftmost bit easiest to test BCC INTL2 ;If not modem-input data-ready, go to other tests ;Interrupt caused by character ready from modem... LDX MODD ;Fetch character immediately IRQMI1: LDA COMIHI ;CO-INTMI-HI PHA LDA COMILO ;CO-INTMI-LO PHA TXA ;Ok, now move char from X to A AND #^O177 RTS ;[continue process MI] ;[when process is ready for next char, it'll JSR here] INTMIZ: PLA STA COMILO ;CO-INTMI-LO PLA STA COMIHI ;CO-INTMI-HI JMP INTL1 ;Done handling INTMI, back to top of int-check loop INTL2: LSR A ;now this bit leftmost BCC L3 ;if modem-xmt not ready, didn't cause interrupt LDA MODCTL AND #XMTIRQ BEQ L3 ;if modem-xmt-int not enabled, didn't cause ; interrupt even if it is "ready" ;Interrupt caused by transmit-to-modem done... JMP INTMO ;[process MO] will return via JMP INTL1 L3: LDA BEEC LSR A ;leftmost bit again BCC L4 ;Interrupt caused by character typed on terminal... LDA BEED ;Fetch character typed AND #^O177 ;ignore parity bit JSR INTBI ;[process BI] JMP INTL1 L4: LSR A ;now this bit leftmost again BCC L5 ;As above, if ACIA-xmt not ready, didn't cause interrupt ;DM2SIM -- INTMI0 (Modem-Input interrupt handler) ; This is the process which actually interprets DataMedia 2500 ;commands. To emulate another terminal, merely change this code. MI1X1: CMP #^O002 ;^B = HOME BNE MI1X2 MIHOME: STY DMROW ;Y=0 and Zero-flag set from BNE-fallthru or jump! STY DMCOL JMP MI18 MI1X2: CMP #BELL ;^G = BELL BNE MI1X3 INC NBELL JMP MI18 MI1X5: CMP #TAB ;^I = TAB BNE MI1X6 LDA DMCOL ORA #^O007 STA DMCOL JMP MIRIGHT ;Insert line, bubbling later lines out of the way... INSLIN: LDX #^O023 ;19. INSL1: CPX DMROW BEQ INSL2 JSR XCHM1 DEX BPL INSL1 INSL2: LDA #CLEAR STA DMTOFLG,X JMP INTMI8 ;Delete line, bubbling later lines in to fill the gap... DELLIN: LDX DMROW DELL1: CPX #^O023 ;19. BCS INSL2 INX JSR XCHM1 CLC BCC DELL1 This is the complete file CLK821.M65: ;Alarm-clock for 6502 ;Copyright 1982 by Robert Elton Maas ;Memory available: ZP0=^O307 ;First loc on zero page unused by DM2SIM ZPZ=^O377 ;Last loc on zero page unused by DM2SIM MAIN0=^O4001 ;First loc of main memory unused by DM2SIM MAINZ=^O4677 ;Last loc of main memory unused by DM2SIM ;Hardware registers BEEC=^O174100 ;Beehive control BEED=^O174101 ;Beehive data ;PROM routines SETBEE=^O177024 ;Set up UART for Beehive, including speed ;ASCII constants HOME=^O5 CR=^O15 LF=^O12 CTRL=^O100 CTRLA='A-CTRL CTRLB='B-CTRL CTRLE='E-CTRL CTRLF='F-CTRL LEECH=^O034 OPENBR='[ CLOSBR='] ;Variables&pointers on zero page .=ZP0 JMP START CURSOR: .BLKB 1 ;Where edit point is, relative to TEXT0 DPYPTR: .BLKB 1 ;Where printing pointer is, relative to TEXT0 COUNT: .BLKB 1 ;Number of characters remaining in this second .BLKB ZPZ-. ;Make sure space not overrun ;Main program and data string .=MAIN0+20 ;Allow a few bytes for DM2SIM expansion TEXT: .BYTE HOME TIME0: .ASCII /00:00'00/ SEC1=.-1 .BYTE ^O177,CR,LF ALARM0: .ASCII /09:00'00/ TXZ: .ASCII / / .BYTE HOME,0 START: ;First, init the ACIA... JSR SETBEE STA0: JSR MAYBEL ;Set bell character in printing string if time ;Then reset to print updated string LDA #240. ;This many chars/sec STA COUNT LDA #0 ;Start at beginning of string STA DPYPTR STA1: JSR MAYIN ;If input pending, handle it JSR BRTOUT ;Output one char with possible brightening LDA COUNT BNE STA1 ;Loop until COUNT exhausted JSR UPDCLK ;Update clock one second JMP STA0 MAYBEL: ;Set BEL if time .GEQ. alarm, SPACE otherwise LDX #-1 MAYBE1: INX LDA TIME0,X CMP ALARM0,X BEQ MAYBE1 ;Loop until difference found BCC MAYBE2 ;If minus, not time yet LDA #7 ;Bell BNE MAYBE3 MAYBE2: LDA #^O40 ;Space MAYBE3: STA TXZ RTS MAYIN: ;Test for input, and handle if there is any LDA BEEC LSR A ;Test rightmost bit BCC MAYINZ ;If not, return immediately LDA BEED ;Fetch character AND #^O177 ;Use 7 bits only CMP #CTRLB BEQ INLEFT CMP #CTRLF BEQ INRITE CMP #CTRLA BEQ INSTRT CMP #CTRLE BEQ INEND CMP #LEECH BNE MAYIN2 .BYTE 0,0 ;Breakpoint MAYINZ: RTS MAYIN2: LDX CURSOR STA TEXT,X ;Store typed character at cursor INRITE: LDY CURSOR ;Fall into cursor-right INY BNE INCURS INLEFT: LDY CURSOR DEY BNE INCURS INSTRT: LDY #1 BNE INCURS INEND: LDY #TXZ-TEXT INCURS: STY CURSOR RTS BRTOUT: ;Output one char with possible brightening, increment DPYPTR, ; decrement COUNT, and leave status with result (zero iff exhausted) LDA CURSOR CMP DPYPTR BEQ BRTBRT ;If equal, do bright one JSR RAWPTR ;Else just do normal one CLC BCC BRT5 BRTBRT: LDA #OPENBR JSR RAWOUT JSR RAWPTR LDA #CLOSBR JSR RAWOUT BRT5: LDX DPYPTR LDA TEXT,X BEQ BRTZ ;If char was nul, skip the incrementing INX STX DPYPTR BRTZ: RTS RAWPTR: LDX DPYPTR LDA TEXT,X RAWOUT: PHA RAWOL: LDA BEEC AND #^O2 ;See if XMT ready BEQ RAWOL ;While not ready keep looping PLA STA BEED DEC COUNT ;Z/NZ status from this returned RTS UPDCLK: ;Add one second to clock time LDX #SEC1-TEXT UPDC1: INC TEXT,X LDA TEXT,X CMP #'9+1 BCC UPDZ LDA #'0 STA TEXT,X DEX INC TEXT,X LDA TEXT,X CMP #'5+1 BCC UPDZ LDA #'0 STA TEXT,X DEX ;Decr twice to skip punctuation character DEX BNE UPDC1 ;Go do same mod-60 increment on next group UPDZ: RTS .BYTE 0,0 ;Breakpoint .BLKB MAINZ-. ;Make sure space not overrun .END START These are excerpts from the file PCNMOS.M65 (43k bytes total): ;MOS 6502 originate-mode communication test program ; mostly following PC Net protocols in effect at the time of writing. ; -C- Copyright 1980 by Robert Elton Maas, all rights reserved ; ** OVERVIEW OF BUFFER ALLOCATION ** ; Transmit buffer either is empty or has one directed-block to send. ; XVB0 is start of buffer for validated-block ; XDB0 is start of buffer for directed-block ; XSB0 is start of buffer for stream-block ; PXDBZ contains 0 if empty or pointer to end of directed-block plus 1 ; PXVBZ contains 0 if empty, or pointer to end of validated-block plus 1 ; Note that the byte at PXVBZ is zero, which may or may not be ; used by the translating layer depending on odd/even length packet. ; Receive buffer is divided into the part that has been validated and ; the part that has been received but not yet validated. Only the part ; which has been validated is passed beyond the validating layer. Only ; the buffer after the end of all this data is available for new data, ; but each time an apparant packet is rejected the unvalidated data is ; emptied, leaving that space available for the next packet. ; RVB0 is the start of the validated-block, if any ; RDB0 is the start of the directed-block, if any ; RSB0 is the start of the stream-block, if any ; PRDBZ points to the end of the directed-block plus 1; ; if its value is RVB0, there was no validated-block at all, ; if its value is RDB0, there was an empty validated-block, ; if its value is RSB0, there was a directed-block with no data, ; if its value is greater than RSB0, there is stream data. ; Note that the checksum bytes from the validated-block, which would ; occur starting at PRDBZ, have been flushed. ; PRVBZ points to the next byte of empty buffer after the partial ; validated-block currently being received. If this isn't equal ; to PRDBZ, there is such a partial block. ;;Toplevel program starts here... RESTRT: ;Be sure CPU interrupts are disabled... SEI ;Init stack pointer... LDX #^O377 TXS ;Init ACIAs... JSR SETBEE LDA ^O13 ;User-settable modem speed STA MODBRR ;Set receive-from-modem speed JSR INIMOD LDA #^O025 ;Set not to use any ACIA interrupts ; (change to 21 to have 2 stop bits) STA BEEC STA MODC ;Arm NMI interrupt... JSR SETNMI ;Set interrupt vector&routine CLI ;Actually arm CPU interrupts ;Init zero-page globals... LDA #0 LDX #ZP0-ZP1 ;Negative count will be incremented RESZ1: STA ZP1,X ;Stores 0 in ZP0 ZP0+1 ... ZP1-1 INX BNE RESZ1 LDA #RVB0&$FF STA PRDBZ ;Mark receive-buffer empty ;; ** PCNET MODEM-HANDLER LAYER ** ;; (This code for manually-operated originate-only acoustic coupler) ;; Low-level routines to access UART/MODEM ;A <= R7WBT [X,Y,TMPA clobbered] Read char from modem w/ timeout ;Get 7-bit character from modem, or time out and return -1 ;This routine uses three loops. The inner (DEY) loop should be adjusted ; to make this full loop plus one iteration of the next outer loop total ; 1/1200 of a second. For example, on REM's 6502 the 5 machine cycles ; of the 2 instructions in the inner loop take about 1.4 microseconds, ; so making the LDY #^O167 turns out right. Faster CPUs will take a ; smaller number here. The next loop shouldn't be modified. It wil ; cycle exactly 240 times, yielding 1/5 of a second total. The ; outermost loop should be adjusted to determine the desired timeout, ; in steps of 1/5 second from 1/5 = 0.2 to 256/5 = 51.2 seconds. R7WBT: LDA #50 ;50/5 = 10 seconds, adequate for testing STA TMPA ;Need 3 counters, also need register A for ; testing modem status, thus must put one counter in RAM TIMM1: LDX #240 ;240/1200 = 1/5 second TIMM2: LDA MODC ;Modem control (status actually) register LSR A ;Low order bit of ACIA is rcv data ready, save ; one byte by shifting instead of doing AND #1 BCC TIMM3 ;If carry clear, i.e. rcv ready was zero, loop more LDA MODD ;Modem data register AND #^O177 ;Turn off sign bit (parity from UART) RTS ;GOT DATA -- Return character value between 0 and octal 177 ; and minus flag in cpu NOT set TIMM3: LDY #^O167 ;See above, for 1.4 microsecond clock TIMM4: DEY BNE TIMM4 ;1.4 * ^O167 + once each of TIMM2 thru BNE TIMM2 ; yield about 833 microseconds (1/1200 sec) DEX BNE TIMM2 ;See above, 1/5 second total above DEC TMPA BNE TIMM1 ;See above, desired time before dropping out LDA #-1 RTS ;TIMEOUT -- Exit with -1 in register A, minus cpu flag set ;Receive-pipeline side of half-duplex conversation, toplevel loop. ; RCVHDX -- Given PRDBZ pointing to the ; start of available buffer (after any preceding packets that have been ; validated but not yet passed to the directing layer), this routine ; processes incoming data until at least three turnaround characters ; are seen in close proximity. Each packet seen is de-translated from ; Radix-41 back to binary, and if it is valid then PRDBZ is updated ; to include it in queue of received packets and to avoid overwriting ; it with later received data. At all times (except at critical pieces ; of code) here and elsewhere, RVB0 thru (PRDBZ)-1 are the queue of ; received&validated data (header and data, but with checksums and zero-fill ; removed). Inside this routine, (PRDBZ) thru RVBZ-1 is the receive-buffer, ; of which the part from (PRDBZ) thru (PRVBZ)-1 contains received but ; not yet validated data while and (PRVBZ) thru RVBZ-1 is empty. ;Note that this routine does output in two cases. Upon seeing half-duplex ; turnaround it sends 5 atsigns immediately to acknowledge receipt of the ; turnaround. Upon not seeing ANY data whatsoever for about ten seconds ; it times out and sends five brackets to provoke the other node to speak up. ;This is the toplevel RCVHDX/RCVLTB loop only. It calls R7WBT to read ; data from the UART or time out on no data. It calls INITRN,COTRAN,FINTRN ; to process Radix-41 data (INITRN at start, COTRAN for each character, ; FINTRN upon seeing an atsign). It calls HDXXMB to transmit brackets if ; it times out. ;First initialize bracket-count and flush anything in ACIA/UART, typically ; the last two brackets of the transmission preceding this one, which were ; sitting in the ACIA/UART all during our last transmission... RCVHDX: LDA #0 STA HDXCNT ;Will need to see 3 brackets before exiting RCVHDX HDXR0: LDA MODC ;Flush data that arrived during or before our xmit... LSR A ;Data-ready is low-order bit, shift instead of mask BCC HDXR00 ;If no data arrived, finished this flushing loop LDA MODD ;Character already in ACIA, flush it BCS HDXR0 ;Once-only stuff done, now process one character at a time, searching for ; brackets and meanwhile passing anything else to framing layer. HDXR00: JSR INITRN ;Initialize phase and buffer for R41-->bin translator ;This will start writing at (PRDBZ) which is always ; immediately after any data already validated but ; overwriting any bad data received since then. RCVH1: JSR R7WBT ;Wait for one 7-bit character from modem BPL RCVH2 ;If got character, go handle it LDA #^O164 ;"tttt" (timeout) JSR JAMBEE JSR HDXXMB ;TIMEOUT -- Transmit just the brackets JMP RCVH1 ; then immediately resume listening RCVH2: JSR PUTBEE ;Echo to tty CMP RCVBRK BEQ RCVH3 ;If bracket, go handle it ;Not bracket, pass it up and discount bracket count fractionally... JSR RCVFRA LDA HDXCNT BEQ RCVH1 ;Already zero, stop there DEC HDXCNT ;Not yet zero, decrement toward zero JMP RCVH1 RCVH3: LDA HDXCNT CLC ADC #5 ;Add one full bracket STA HDXCNT CMP #3*5 ;See if reached 3 full brackets recently BCC RCVH1 ;If not 3 yet, keep receiving ;3 brackets seen in close proximity, done with receiving ACKHDX: LDA #ATSIGN+PARITY ;Acknowledge HDX turnaround immediately... JMP PU5TWO