commit 67ec3c9a652a1ff64c6bf273aa6af4dbbd821a6e Author: Joppe Blondel Date: Mon Sep 28 13:17:35 2026 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bbae4b9 --- /dev/null +++ b/Makefile @@ -0,0 +1,56 @@ +TARGET := monitor +BUILDDIR := build + +METAL_C_SRCFILES := $(wildcard *.c) +METAL_ASM_SRCFILES := $(wildcard *.asm) +METAL_RELFILES := $(METAL_ASM_SRCFILES:%.asm=$(BUILDDIR)/metal/%.rel) \ + $(METAL_C_SRCFILES:%.c=$(BUILDDIR)/metal/%.rel) + +MUSIC_FILE := updraft_flight + +METAL_CC := sdcc +METAL_AS := sdasz80 +METAL_MAKEBIN := makebin +METAL_FLAGS := -mz80 --no-std-crt0 --code-loc 0x0080 --data-loc 0x8000 +METAL_CPPFLAGS := -I src -I $(BUILDDIR)/metal/generated --disable-warning 283 +METAL_BIN_SIZE := 32768 +METAL_RAM_SIZE := 32768 + +.DEFAULT_GOAL := metal + +.PHONY: all metal clean + +all: metal + +metal: $(BUILDDIR)/metal/$(TARGET).ihx $(BUILDDIR)/metal/$(TARGET).bin $(BUILDDIR)/metal/$(TARGET).prog.bin + @awk -v rom_capacity=$(METAL_BIN_SIZE) -v ram_capacity=$(METAL_RAM_SIZE) '\ + $$1 ~ /^_(CODE|HOME|GSINIT|GSFINAL|INITIALIZER|ITABLE[0-7]*)$$/ { rom += $$5 } \ + $$1 ~ /^_(DATA|INITIALIZED|BSEG|BSS|HEAP|STACK)$$/ { ram += $$5 } \ + END { \ + printf "\nMemory usage:\n"; \ + printf " ROM: %5u / %5u bytes (%5.1f%%), %5u bytes free\n", rom, rom_capacity, 100 * rom / rom_capacity, rom_capacity - rom; \ + printf " RAM: %5u / %5u bytes (%5.1f%%), %5u bytes free\n", ram, ram_capacity, 100 * ram / ram_capacity, ram_capacity - ram; \ + }' $(BUILDDIR)/metal/$(TARGET).map + +$(BUILDDIR)/metal/$(TARGET).ihx: $(METAL_RELFILES) + @mkdir -p $(@D) + $(METAL_CC) $(METAL_FLAGS) -o $@ $(METAL_RELFILES) + +$(BUILDDIR)/metal/$(TARGET).bin: $(BUILDDIR)/metal/$(TARGET).ihx + $(METAL_MAKEBIN) -s $(METAL_BIN_SIZE) $< $@ + +$(BUILDDIR)/metal/$(TARGET).prog.bin: $(BUILDDIR)/metal/$(TARGET).ihx + $(METAL_MAKEBIN) -p $< $@ + +$(BUILDDIR)/metal/%.rel: %.c + @mkdir -p $(@D) + $(METAL_CC) $(METAL_FLAGS) $(METAL_CPPFLAGS) -o $@ -c $< + +$(BUILDDIR)/metal/%.rel: %.asm + @mkdir -p $(@D) + $(METAL_AS) -plosgff -o $@ $< + +clean: + rm -rf $(BUILDDIR) + +-include $(EMU_DEPFILES) diff --git a/boot.asm b/boot.asm new file mode 100644 index 0000000..a5801d6 --- /dev/null +++ b/boot.asm @@ -0,0 +1,175 @@ +STACK_TOP = 0xFFFF +DELAY_LOOPS_PER_MS = 40 + + .globl _main + .globl _delay_ms + .globl _io_out + .globl _io_in + .globl _enable_interrupts + .globl _disable_interrupts + .globl s__DATA + .globl l__DATA + .globl s__INITIALIZER + .globl s__INITIALIZED + .globl l__INITIALIZER + +.area _ITABLE (ABS) + +.org 0x00 + jp init +.org 0x08 + reti +.org 0x10 + reti +.org 0x18 + reti +.org 0x20 + reti +.org 0x28 + reti +.org 0x30 + reti +.org 0x38 + reti + +; ITABLE +.org 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 + +.area _HOME +init: + di + ld sp, #STACK_TOP + + call gsinit + + ; Setup interrupt mode 2 + xor a + 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 + +.area _CODE + +.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 + +; 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 diff --git a/boot.h b/boot.h new file mode 100644 index 0000000..96c8c48 --- /dev/null +++ b/boot.h @@ -0,0 +1,10 @@ +#ifndef __H_BOOT +#include + +void io_out(uint8_t a, uint8_t d); +uint8_t io_in(uint8_t d); +void delay_ms(uint16_t t); +void enable_interrupts(); +void disable_interrupts(); + +#endif \ No newline at end of file diff --git a/circ_buf.c b/circ_buf.c new file mode 100644 index 0000000..4c0a739 --- /dev/null +++ b/circ_buf.c @@ -0,0 +1,33 @@ +#include "circ_buf.h" + +void buffer_init(volatile circular_buffer_t* b){ + b->count = 0; + b->head = 0; + b->tail = 0; +} + +uint8_t buffer_is_full(volatile circular_buffer_t* b){ + return b->count == BUFFER_SIZE; +} + +uint8_t buffer_is_empty(volatile circular_buffer_t* b){ + return b->count == 0; +} + +uint8_t buffer_enqueue(volatile circular_buffer_t* b, char c){ + if(buffer_is_full(b)) + return 0; + b->data[b->head] = c; + b->head = (b->head+1)%BUFFER_SIZE; + b->count++; + return 1; +} + +uint8_t buffer_dequeue(volatile circular_buffer_t* b, char* c){ + if(buffer_is_empty(b)) + return 0; + *c = b->data[b->tail]; + b->tail = (b->tail+1)%BUFFER_SIZE; + b->count--; + return 1; +} diff --git a/circ_buf.h b/circ_buf.h new file mode 100644 index 0000000..4e362d7 --- /dev/null +++ b/circ_buf.h @@ -0,0 +1,19 @@ +#ifndef __H_CIRC_BUF +#include + +#define BUFFER_SIZE 5 + +typedef struct { + char data[BUFFER_SIZE]; + uint8_t head; + uint8_t tail; + uint8_t count; +} circular_buffer_t; + +void buffer_init(volatile circular_buffer_t* b); +uint8_t buffer_is_full(volatile circular_buffer_t* b); +uint8_t buffer_is_empty(volatile circular_buffer_t* b); +uint8_t buffer_enqueue(volatile circular_buffer_t* b, char c); +uint8_t buffer_dequeue(volatile circular_buffer_t* b, char* c); + +#endif \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..86f66d5 --- /dev/null +++ b/main.c @@ -0,0 +1,109 @@ +#include +#include "boot.h" +#include "circ_buf.h" + +#define CPUREG 0x00 +#define CPUREG_ROM_DISABLE 0x01 +#define CPUREG_BANK_SELECT 0x02 +#define CPUREG_BANK_ENABLE 0x04 +#define CPUREG_GPO_0 0x08 +#define CPUREG_GPO_1 0x10 +#define CPUREG_GPO_2 0x20 +#define CPUREG_GPO_3 0x40 +#define CPUREG_GPO_4 0x80 +#define CPUREG_GPO_MASK 0xf8 + +#define UART_CTC_CARD_BASE 0x20 + +#define CTC_BASE (UART_CTC_CARD_BASE+0x00) +#define CTC_CHANNEL_0 (CTC_BASE+0) +#define CTC_CHANNEL_1 (CTC_BASE+1) +#define CTC_CHANNEL_2 (CTC_BASE+2) +#define CTC_CHANNEL_3 (CTC_BASE+3) + +#define UART_BASE (UART_CTC_CARD_BASE+0x04) +#define UART_CONTROL (UART_BASE+0) +#define UART_DATA (UART_BASE+1) + +volatile uint8_t v; +volatile uint8_t uart_tx_busy; +volatile circular_buffer_t rx_buf; +volatile circular_buffer_t tx_buf; + +void uart_puts(char * c){ + while(*c){ + // fill tx buffer till it is full + while(*c && buffer_enqueue(&tx_buf, *c)){ + c++; + } + // if uart not busy kick off + if(!uart_tx_busy){ + disable_interrupts(); + char cc; + if(buffer_dequeue(&tx_buf, &cc)){ + uart_tx_busy = 1; + io_out(UART_DATA, cc); + io_out(UART_CONTROL, 0b10110101); + } + enable_interrupts(); + } + } +} + +void uart_interrupt(){ + uint8_t status = io_in(UART_CONTROL); + + if((status&0x21)!=0){ + // RDRF or OVRN + char c = io_in(UART_DATA); + if((status&0x01)!=0){ + // RDRF + buffer_enqueue(&rx_buf, c); + } + } + + if((status&0x02)!=0 && uart_tx_busy){ + //TXRE + char c; + if(buffer_dequeue(&tx_buf, &c)){ + io_out(UART_DATA, c); + }else{ + io_out(UART_CONTROL, 0b10010101); + uart_tx_busy = 0; + } + } +} + +void ctc_interrupt(){ +} + +void main(){ + buffer_init(&rx_buf); + buffer_init(&tx_buf); + + io_out(CTC_CHANNEL_0, 0x57); // no int, counter @ rising, value follows, soft reset, control word + io_out(CTC_CHANNEL_0, 13); // 38.462 kHz out; + + io_out(CTC_CHANNEL_3, 0b11001111); // int, counter @ falling, ext trig, value follows, soft reset, control word + io_out(CTC_CHANNEL_3, 1); + + io_out(CTC_CHANNEL_0, 0x00); // vector follows + io_out(CTC_CHANNEL_0, 0x40); + + uart_tx_busy = 0; + io_out(UART_CONTROL, 0b00000011); // master reset + io_out(UART_CONTROL, 0b10010101); // recv int, 8N1, /16 -> 2403.85 baud + + enable_interrupts(); + + uart_puts("Z80 monitor - v1\r\n"); + uart_puts("----------------\r\n"); + + v = 0; + for(;;){ + char c[2] = {0, 0}; + while(buffer_dequeue(&rx_buf, c)){ + uart_puts(c); + } + } +}