STACK_TOP = 0xFFFF DELAY_LOOPS_PER_MS = 40 MONITOR_BASE = 0xC000 MONITOR_ROM_SOURCE = 0x0080 TINYBASIC_BASE = 0xD000 TINYBASIC_ROM_SOURCE = 0x1000 .include "tinybasic.inc" .globl _main .globl _delay_ms .globl _io_out .globl _io_in .globl _enable_interrupts .globl _disable_interrupts .globl _jump_to .globl _monitor_uart_putc .globl _monitor_uart_getc .globl monitor_image_end .globl s__DATA .globl l__DATA .globl s__INITIALIZER .globl s__INITIALIZED .globl l__INITIALIZER .area _BOOT (ABS) .org 0x00 jp boot_copy .org 0x08 boot_copy: di ld sp, #STACK_TOP ld hl, #MONITOR_ROM_SOURCE ld de, #MONITOR_BASE ld bc, #(monitor_image_end - MONITOR_BASE) ldir ld hl, #TINYBASIC_ROM_SOURCE ld de, #TINYBASIC_BASE ld bc, #TINYBASIC_SIZE ldir jp init .area _ITABLE (ABS) ; The executable vector page is part of the image copied to high RAM. .org MONITOR_BASE jp init .org MONITOR_BASE + 0x08 reti .org MONITOR_BASE + 0x10 reti .org MONITOR_BASE + 0x18 reti .org MONITOR_BASE + 0x20 reti .org MONITOR_BASE + 0x28 reti .org MONITOR_BASE + 0x30 reti .org MONITOR_BASE + 0x38 reti ; IM 2 table: the CTC supplies vectors 40, 42, 44, and 46. .org MONITOR_BASE + 0x40 .dw ctc_isr ; CTC channel 0: vector 0x40 .dw ctc_isr ; CTC channel 1: vector 0x42 .dw ctc_isr ; CTC channel 2: vector 0x44 .dw uart_isr ; CTC channel 3: vector 0x46 ; Stable entry points for programs living alongside the monitor. ; C050: write the character in A. C053: return a character in A, or 0. .org MONITOR_BASE + 0x50 jp monitor_service_putc .org MONITOR_BASE + 0x53 jp monitor_service_getc .area _HOME init: di ld sp, #STACK_TOP call gsinit ; Setup interrupt mode 2 ld a, #0xC0 ld i, a im 2 call _main hang: jr hang .area _GSINIT gsinit: ld bc, #l__INITIALIZER ld a, b or c jr z, zero_data ld de, #s__INITIALIZED ld hl, #s__INITIALIZER ldir zero_data: ld bc, #l__DATA ld a, b or c jr z, gsinit_done ld hl, #s__DATA xor a clear_loop: xor a ; BC loop test below changes A ld (hl), a inc hl dec bc ld a, b or c jr nz, clear_loop gsinit_done: .area _GSFINAL ret monitor_image_end: .area _CODE ; Monitor ABI services preserve all working registers. PUTC also preserves AF; ; GETC returns the received byte (or zero) in A. monitor_service_putc: push af push bc push de push hl push ix push iy call _monitor_uart_putc pop iy pop ix pop hl pop de pop bc pop af ret monitor_service_getc: push bc push de push hl push ix push iy call _monitor_uart_getc pop iy pop ix pop hl pop de pop bc ret .macro MAKE_ISR entry, handler .globl handler entry: push af push bc push de push hl push ix push iy ex af, af' push af exx push bc push de push hl call handler pop hl pop de pop bc exx pop af ex af, af' pop iy pop ix pop hl pop de pop bc pop af ei reti .endm MAKE_ISR ctc_isr, _ctc_interrupt MAKE_ISR uart_isr, _uart_interrupt _enable_interrupts:: ei ret _disable_interrupts:: di ret ; Write a byte to an I/O port. ; Input: A = port address, L = data byte. _io_out:: ld c, a ld a, l out (c), a ret ; Read a byte from an I/O port. ; Input: A = port address. ; Return: A = data byte. _io_in:: ld c, a in a, (c) ret ; Execute code at HL. Because this routine is entered with CALL, a RET in the ; target returns to the monitor immediately after jump_to(). _jump_to:: jp (hl) ; Approximate millisecond delay. ; Input: HL = delay in milliseconds. _delay_ms:: ld a, h or l ret z delay_ms_outer$: push hl ld hl, #DELAY_LOOPS_PER_MS delay_ms_inner$: dec hl ld a, h or l jr nz, delay_ms_inner$ pop hl dec hl ld a, h or l jr nz, delay_ms_outer$ ret