; ; This code allows a 6502 BBC BASIC to run ; as if it were in a second processor box ; i.e. no BBC micro hardware present. ; ; It is a second-processor MOS with ; only the console I/O calls implemented. ; So it doesn't need a BBC Micro ; servicing anything like graphics, ; files, sound, and so on. ; ; You can expand this code to include such things, ; if your target board has suitable hardware. ; ; This code will fit inside 512 bytes, ; and some of that is padding. ; Enable one of the following lines ; to get the ROM size you want. ; 512 bytes to create code exactly matching know-good binary. ; 2K bytes maximum if using a common Hi-BASIC (at $B800), or ; 16K bytes maximum if using a standard BASIC (at $C000). ; ; ROM_START equ $FE00 ; for last 512 bytes ; ROM_START equ $F800 ; for last 2K ; ROM_START equ $C000 ; for last 16K ; ; Conditional assembly to wrap hardware-specific code: ; target_6850 equ 1 target_6850_address equ $FEF0 ; ; NB: ; ; The 6850 has no reception FIFO, ; the FTDI serial-to-USB modules send up to three bytes after RTS asserted. ; This causes problems talking to a PC over USB. ; ; The NMOS 6551 needs baud rate generation ; in the initialisation routine. ; ; The CMOS W65C51 handshaking does not work properly, ; and never will because they are not going to fix it. ; ; ; .binfrom ROM_START ; ASM80 command for size of binary file output ; ; ; 2016-11-11 Created by J.G. Harston ; All the heavy thinking done by him. ; 2021-11-17 Converted to a more conventional assembler syntax by Keith. ; Assembles online at asm80.com ; ; 2021-11-22 Verified against a known-good version ; ; ; A few changes: ; ; Names that were the same but differed in letter case have been renamed. ; To make them different, the mixed-case names had an underscore appended. ; ; A few ASCII code names: ; ASCII_CR equ $0D ASCII_LF equ $0A ASCII_ESC equ $1B ASCII_SP equ $20 vector_table equ $200 vector_table_size equ $35 tube_entry_table equ $FF95 ; ; Serial Tube system values ; ========================= TUBE_ESC equ $9B ; explicitly different from the ASCII escape code ; Mini65.bas by J.G. Harston ; > Mini65/src ; Source for Tiny 6502 Single-port Tube Client ; ============================================ ; This code may be freely reused, with acknowledgements ; ; Tiny 6502 Tube Client in 512 bytes ; Implements OSWRCH and OSRDCH via a single I/O port. ; ; Code to enter after RESET ; ========================= MAIN equ OSQUIT; Replace with address to jump to ; ; The line above creates an endless loop. ; ; ; Name and version ; ================ ; ver$="0.10" ; date$=" (11 Nov 2016)" ; load%=$FE00 ; name$="Mini65" ; ; ; System vectors ; ============== USERV equ $200 BRKV equ $202 IRQ1V equ $204 IRQ2V equ $206 CLIV equ $208 BYTEV equ $20A WORDV equ $20C WRCHV equ $20E RDCHV equ $210 FILEV equ $212 ARGSV equ $214 BGetV equ $216 BPutV equ $218 GBPBV equ $21A FINDV equ $21C FSCV equ $21E EVNTV equ $220 TEXT equ $FA IRQA equ $FC FAULT equ $FD ESCFLG equ $FF ; ; I/O values, these are suitable for a 6850 ; ========================================= if target_6850 TxRxControl equ target_6850_address ; write-only TxStatus equ target_6850_address ; read-only TxData equ target_6850_address+1 TxRDY equ 2 TxInit equ $13 RxStop equ $55 ; ; $13=Reset ACIA, $55=8N1, clock/16, RTS high ; RxStatus equ target_6850_address RxData equ target_6850_address+1 RxRDY equ 1 RxInit equ $55 RxCont equ $15 ; ; $55=RTS high, $15=RTS low ; .endif org ROM_START Code_starts_here: JMP RESET ; PrBanner: JSR PrText_ BANNER: db ASCII_CR db "Tiny 6502 Kernel " db "0.10" ; ver$ db ASCII_SP db ASCII_CR db 0 RTS ; ; Tube Client Startup Code ; ======================== RESET: SEI ; Disable interrupts LDX #default_vector_table_end-default_vector_table-1 ; 0x35 is last-byte offset RESETlp: LDA default_vector_table,X STA vector_table,X ; Copy default vectors DEX BPL RESETlp TXS ; Clear stack if target_6850 LDA #TxInit STA TxRxControl ; Initialise I/O port LDA #RxInit STA RxStatus LDA #0 STA ESCFLG else ; alternative code here endif CLI JSR PrBanner JSR OSNEWL LDA #1 JMP OSCOLD ; Enter main program ; ; ; Default error handler ; ===================== ErrorHandler: JSR OSNEWL LDX FAULT+0 LDY FAULT+1 INX BNE skip_inc_y INY ; XY=>error string skip_inc_y: ; JSR PRSTRNG ; Print error string HALT: JMP HALT ; And stop ; ; Interrupt handlers ; ================== IRQHandler: STA IRQA PLA PHA ; Save A, get flags from stack AND #$10 BNE BRKHandler ; If BRK, jump to BRK handler JMP (IRQ1V) ; Continue via IRQ1V handler IRQ1Handler: JMP (IRQ2V) ; Pass on to IRQ2V BRKHandler: TXA PHA ; Save X TSX LDA $0103,X CLD ; Get address from stack SEC SBC #1 STA FAULT+0 LDA $0104,X SBC #0 STA FAULT+1 ; 0xFD/E=>after BRK opcode PLA TAX LDA IRQA ; Restore X, get saved A CLI JMP (BRKV) ; Restore IRQs, jump to Error Handler IRQ2Handler: LDA IRQA ; Restore A NMIHandler: RTI ; Return from interupt ; ; OSRDCH - Wait for character from input stream ; ============================================= ; On exit, A =char, Cy=Escape flag ; osRDCH_: osRDCH_: JSR ReadData BCC osRDCH_ ; Wait for character BNE osRDCH_2 osRDCH_1: JSR ReadData BCC osRDCH_1 ; Wait for character osRDCH_2: CMP #ASCII_ESC BEQ osRDCH_3 ; Exit with CS if Escape ReadDataNone: CLC ; CC if not Escape osRDCH_3: RTS ; ; OSWRCH - Send character to output stream ; ======================================== ; On entry, A =character osWRCH_: ; WRCH is simply SendByte SendByte: JSR SendData ; Send byte CMP #TUBE_ESC BNE SendByteDone ; If not TUBE_ESC, done ; Fall through to send again ; ; ; *** LOW-LEVEL I/O CODE *** ; ========================== ; ; Send a raw byte of data ; ----------------------- SendData: .if target_6850 PHA SendWait: LDA TxStatus ; Get Status AND #TxRDY BEQ SendWait ; Wait until data can be sent PLA STA TxData ; Send data .else ; alternative code here .endif SendByteDone: RTS ; ; Read raw data ; ------------- ; On exit, P =CC, no data ReadData: .if target_6850 LDA RxStatus AND #RxRDY ; Get RxStatus BNE ReadDataNone ; Exit if no data present LDA RxData CMP #TUBE_ESC SEC .else .endif RTS; CS=Data present, EQ/NE=TUBE_ESC ; ;;; db STRING$((default_vector_table-$)AND255,CHR$255) ; ; Fill unused space with FF ; fill $FF, ($FF00-$) ; ; DEFAULT VECTOR TABLE ; ==================== org $FF00 default_vector_table: dw NullReturn ; 0x200 - USERV dw ErrorHandler ; 0x202 - BRKV dw IRQ1Handler ; 0x204 - IRQ1V dw IRQ2Handler ; 0x206 - IRQ2V dw NullReturn ; 0x208 - CLIV dw NullReturn ; 0x20A - BYTEV dw NullReturn ; 0x20C - WORDV dw osWRCH_ ; 0x20E - WRCHV dw osRDCH_ ; 0x210 - RDCHV dw NullReturn ; 0x212 - FILEV dw NullReturn ; 0x214 - ARGSV dw NullReturn ; 0x216 - BGetV dw NullReturn ; 0x218 - BPutV dw NullReturn ; 0x21A - GBPBV dw NullReturn ; 0x21C - FINDV dw Unsupported ; 0x21E - FSCV dw NullReturn ; 0x220 - EVNTV dw Unsupported ; 0x222 - UPTV dw Unsupported ; 0x224 - NETV dw Unsupported ; 0x226 - VduV dw Unsupported ; 0x228 - KEYV dw Unsupported ; 0x22A - INSV dw Unsupported ; 0x22C - ;V dw Unsupported ; 0x22E - CNPV dw NullReturn ; 0x230 - IND1V dw NullReturn ; 0x232 - IND2V dw NullReturn ; 0x234 - IND3V default_vector_table_end: ; PrHex_XY: TYA JSR PrHex_A TXA PrHex_A: PHA LSR A LSR A LSR A LSR A JSR PrNybble PLA PrNybble: AND #15 CMP #10 BCC PrDigit ADC #6 PrDigit: ADC #"0" JMP OSWRCH ; PrText_: PLA STA TEXT+0 PLA STA TEXT+1 ; TEXT=>embedded string JSR PrString_2 JMP (TEXT) ; Print string and jump back to code ; PrString_: STX TEXT+0 STY TEXT+1 ; TEXT=>string at YX PrString_Lp: LDY #$00 LDA (TEXT),Y ; Get character BEQ PrString_2 JSR OSASCI ; Print character if not $00 PrString_2: INC TEXT+0 BNE LFEA6 INC TEXT+1 ; Increment address LFEA6: TYA BNE PrString_Lp ; Loop back if not $00 ScanHex_: InitError: Unsupported: NullReturn: RTS ; ; Standard Tube entry points ; ========================== ;;; db STRING$(($FF95-$)AND255,CHR$255) ; ; Fill unused space with FF ; fill $FF, (tube_entry_table-$) org tube_entry_table LFF95: JMP NullReturn ; 0xFF95 OSCOLD: JMP OSQUIT ; 0xFF98 PRSTRNG: JMP PrString_ ; 0xFF9B Print zero-terminated text at YX, returns A=0, Y corrupted LFF9E: JMP NullReturn ; 0xFF9E SCANHEX: JMP ScanHex_ ; 0xFFA1 Scan hex string at ($F8), returned in $F0/1 DISKACC: JMP NullReturn ; 0xFFA4 DISKCCP: JMP NullReturn ; 0xFFA7 PRHEX: JMP PrHex_A ; 0xFFAA Print A in hex, A corrupted PR2HEX: JMP PrHex_XY ; 0xFFAD Print YX in hex, A corrupted USERINT: JMP NullReturn ; 0xFFB0 PRTEXT: JMP PrText_ ; 0xFFB3 Print zero-terminated inline text, returns A=0, Y corrupted VECDEF: db default_vector_table_end-default_vector_table ; 0xFFB6 $36 dw default_vector_table ; 0xFFB7 OSQUIT: JMP MAIN ; 0xFFB9 Quit current program OSERROR: JMP NullReturn ; 0xFFBC OSINIT: JMP InitError ; 0xFFBF Initialise MOS error handler, A corrupted DISKRST: JMP NullReturn ; 0xFFC2 LFFC5: JMP NullReturn ; 0xFFC5 NVRDCH: JMP osRDCH_ ; 0xFFC8 NVWRCH: JMP osWRCH_ ; 0xFFCB ; OSFIND: JMP (FINDV) ; 0xFFCE OSGBPB: JMP (GBPBV) ; 0xFFD1 OSBPUT: JMP (BPutV) ; 0xFFD4 OSBGET: JMP (BGetV) ; 0xFFD7 OSARGS: JMP (ARGSV) ; 0xFFDA OSFILE: JMP (FILEV) ; 0xFFDD ; OSRDCH: JMP (RDCHV) ; 0xFFE0 OSASCI: CMP #ASCII_CR BNE OSWRCH ; 0xFFE3 OSNEWL: LDA #ASCII_LF JSR OSWRCH ; 0xFFE7 OSWRCR: LDA #ASCII_CR ; 0xFFEC OSWRCH: JMP (WRCHV) ; 0xFFEE OSWORD: JMP (WORDV) ; 0xFFF1 OSBYTE: JMP (BYTEV) ; 0xFFF4 OS_CLI: JMP (CLIV) ; 0xFFF7 ; NMIV: dw NMIHandler ; 0xFFFA NMI Vector RESETV: dw RESET ; 0xFFFC RESET Vector IRQV: dw IRQHandler ; 0xFFFE IRQ Vector end