diff --git a/README.md b/README.md index cb21695..7788369 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Currently it supports these platforms: - [the Brother WP-2450DS typewriter (and probably others)](arch/brother/wp2450/README.md) - [the Brother PN-8510MDS SuperPowerNote laptop (and probably others)](arch/brother/pn8510/README.md) - [the Brother PN-8800FXB SuperPowerNote laptop (and probably others)](arch/brother/pn8800/README.md) + - [the nano-z80 SoC for the Tang Nano 20k FPGA board](arch/nano-z80/README.md) (Some of these are pretty stale due to difficulty of testing and may not work. Later entries are newer! If you have any problems, please report bugs.) diff --git a/arch/nano-z80/README.md b/arch/nano-z80/README.md new file mode 100644 index 0000000..734ed53 --- /dev/null +++ b/arch/nano-z80/README.md @@ -0,0 +1,60 @@ +Platform: the nano-z80 +====================== + +The [nano-z80](https://github.com/venomix666/nano-z80) is a Z80 based SoC for the [Tang Nano 20k FPGA Board](https://wiki.sipeed.com/hardware/en/tang/tang-nano-20k/nano-20k.html). It is specifically designed to run CP/M but on modern hardware - with HDMI output, USB keyboard, SD-card and the processor running at ~25 MHz. + +What you get with this port: + +- 14x4 MB drives on the SD-card +- Most of an ADM-3a / Kaypro II terminal emulator supporting 80x30 text +- A TPA of 57 kB +- Two serial ports, one on the built in USB UART and on a TTL UART header +- A crude but functional terminal program with X-modem file transfer support +- Utilities for changing baudrate and setting the display color +- A fully implemented IO-byte which allows using monitor/keyboard or any of serial ports for the console. + +How to use it +------------- +Write `nano-z80.img` to a micro-SD card using `dd` and press `B` in the nano-z80 monitor to boot from the SD-card. + +By default, only drive `A:` is formatted. To use the additional drives, run `mkfs B:` up to `mkfs N:` to format the other drives once you have booted to CP/M. + +UART B (the one on the TTL UART header) supports changing baudrates, this can be done either using the `baudrate.com` tool or from inside `nanoterm`. + +The USB keyboard arrow key configuration defaults to ADM3A key codes. This can be changed to WordStar or EMACS/MINCE key codes with the `arrowkey.com` tool. + +If you put the SD-card in a SD-card reader on a Linux machine, the diskdefs file in the +cpmish root allows you to read and write files to the A: drive, e.g.: + + cpmcp -f nanoz80 -i /dev/sdf 0:oncpm onlinux + +Modifying the IO byte at boot +-------------- +The IO is mapped in the following way: +- CRT: USB keyboard and HDMI output +- TTY/LPT: UART header on carrier board +- PTP/PTR/UC1: USB UART on Tang Nano 20k board + +The default value is 0x81, so that the HDMI output and USB keyboard is used for the console. It can be modified according to the table below before booting by first loading to image from the SD-card using the `L` command in the monitor and then writing to address 0xF833 by running for instance `WF633,83` to redirect the console to the USB UART, and then booting by running `JF600`. + +As the monitor is diplayed both on the USB UART and the HDMI-output/USB-keyboard by default, this can be useful when running with no external display connected. +``` + LIST PUNCH READER CONSOLE +0x81 - 10 00 00 01 - LPT: TTY: TTY: CRT: +0x95 - 10 01 01 01 - LPT: PTP: PTR: CRT: +0x80 - 10 00 00 00 - LPT: TTY: TTY: TTY: +0x83 - 10 00 00 11 - LTP: TTY: TTY: UC1: +0x97 - 10 01 01 11 - LTP: PTP: PTR: UC1: +``` + + +Who? +---- + +CP/Mish was written David Given, and is covered under the terms of the whole CP/Mish project. See the documentation in the project root for more information. This port and the nano-z80 project was made by [Henrik Löfgren](https://github.com/venomix666/). + + + + + + diff --git a/arch/nano-z80/bios.z80 b/arch/nano-z80/bios.z80 new file mode 100644 index 0000000..6f6a5dd --- /dev/null +++ b/arch/nano-z80/bios.z80 @@ -0,0 +1,715 @@ +; nano-z80 cpmish BIOS © 2025 Henrik Löfgren +; This file is distributable under the terms of the 2-clause BSD license. +; See COPYING.cpmish in the distribution root directory for more information. + + maclib cpm + maclib nano-z80 + maclib cpmish + maclib addresses + extern TTYPUTC + extern TTYINIT + cseg +;label BBASE + +; BIOS jump table. + + jp BOOTE + jp WBOOTE + jp CONSTE + jp CONINE + jp CONOUTE + jp LISTE + jp PUNCHE + jp READERE + jp HOMEE + jp SELDSKE + jp SETTRKE + jp SETSECE + jp SETDMAE + jp READE + jp WRITEE + jp LISTSTE + jp SECTRANE + +; Default IOBYTE +; The IO is mapped in the following way: +; CRT: USB keyboard and HDMI output +; TTY/LPT: UART header on carrier board +; PTP/PTR/UC1: USB UART on Tang Nano 20k board +; +; LIST PUNCH READER CONSOLE +; 0x81 - 10 00 00 01 - LPT: TTY: TTY: CRT: +; 0x95 - 10 01 01 01 - LPT: PTP: PTR: CRT: +; 0x80 - 10 00 00 00 - LPT: TTY: TTY: TTY: +; 0x83 - 10 00 00 11 - LTP: TTY: TTY: UC1: +; 0x97 - 10 01 01 11 - LTP: PTP: PTR: UC1: +ioconfig: db 0x81 + +; Actual BIOS entrypoints. +; +; The BIOS calls typically use a simple calling convention where the parameter +; is in BC and the result is returned in A and HL. The docs don't mention +; anything about other registers so we'll assume they can be corrupted. In +; addition, our syscall stuff assumes that on return A = L, just like in the +; BDOS, so we have a single parameter and a single response --- all very simple. + +; Cold boot on system startup. +BOOTE: + di + ld a, 0x01 + out (ROM_DISABLE), a ; disable ROM + ld sp, 0x0100 ; ephemeral startup stack + ei + + ld a, (ioconfig) ; set default IOBYTE + ld (IOBYTE), a + + xor a + ld (CDISK), a ; clear current disk / user + + call TTYINIT + call print + db 0x1a ; clear screen + cpmish_banner 'nanoZ80' + db 0 + ; fall through +call_ccp: + ld a, 0xc3 + ld hl, BBASE + 3 ; init BIOS entrypoint + ld (0x0000), a + ld (0x0001), hl + + ld hl, FBASE + 6 ; init BDOS entrypoint + ld (0x0005), a + ld (0x0006), hl + + ld a, (CDISK) ; current selected disk + ld c, a + jp CBASE ; pass control to CCP + +; Warm boot on application exit. +WBOOTE: + ld sp, 0x0100 ; ephemeral user stack + + ld c, 0 ; select drive 0 + call SELDSKE + ld bc, 0 + call SETTRKE ; select track 0 + + ld hl, CBASE ; location to load + ld bc, 4 ; first sector to load + + ; Reload OS from disk +boot_loop: + push bc + push hl + + call SETSECE ; set sector to load + + pop bc ; DMA address into BC + push bc + call SETDMAE ; set address + call READE ; actually load the sector + + pop hl ; DMA address back into HL + ld bc, 128 + add hl, bc + + pop bc ; current sector back into bc + ld a, c + inc c + cp 56 ; end of track 0 + jr nz, boot_loop + jr call_ccp + +; CONST +CONSTE: + ld a, (IOBYTE) + and 0x03 ; Mask console bits + cp 0 + jr nz, CONST_2 + ; UART B + ld a, IO_SELECT_UART + out (IO_BANK), a + in a, (UART_B_RX_AVAIL) + or a + jr nz, CONST_AVAIL + ret +CONST_2: + cp 0x03 + jr nz, CONST_3 + ; UART A + in a, (UART_RX_AVAIL) + or a + jr nz, CONST_AVAIL + ret +CONST_3: + ; CRT + in a, (KB_AVAIL) + or a + ret z + +CONST_AVAIL: + ld a, 0xff + + ret + +; CONIN +CONINE: + ld a, (IOBYTE) + and 0x03 ; Mask console bits + cp 0 + jr nz, CONIN_2 +UART_B_CONIN: + ; UART B + ld a, IO_SELECT_UART + out (IO_BANK), a + in a, (UART_B_RX_AVAIL) + or a + jr z, UART_B_CONIN + in a, (UART_B_RX_DATA) + ret +CONIN_2: + cp 0x03 + jr nz, CONINEUSB +UART_A_CONIN: + ; UART A + in a, (UART_RX_AVAIL) + or a + jr z, UART_A_CONIN + + in a, (UART_RX_DATA) + ret + ; Default to CRT: +CONINEUSB: + in a, (KB_AVAIL) + or a + jr z, CONINEUSB + in a, (KB_CHAR) + ret + +; CONOUT +CONOUTE: + ld a, (IOBYTE) + and 0x03 ; Mask console bits + cp 0 + jr nz, CONOUT_2 +UART_B_CONOUT: + ; UART B + ld a, IO_SELECT_UART + out (IO_BANK), a + in a, (UART_B_TX_READY) + bit 0, a + jr z, UART_B_CONOUT + ld a, c + and 0x7f ; Remove highest bit + out (UART_B_TX_DATA), a + ret +CONOUT_2: + cp 0x03 + jr nz, CONOUT_3 +UART_A_CONOUT: + ; UART A + in a, (UART_TX_READY) + bit 0, a + jr z, UART_A_CONOUT + ld a,c + and 0x7f ; Remove highest bit + out (UART_TX_DATA),a + ret +CONOUT_3: + ; Default to CRT + ld a,c + and 0x7f ; Remove highest bit + ;out (VID_WRITE_X),a + jp TTYPUTC + ret + +; LIST +LISTE: + ld a,(IOBYTE) + and 0xc0 ; Mask list bits + ; Send to UART B unless CRT: is selected + cp 0x40 + jr nz, LIST_UART_B + ld a, c + out (VID_WRITE_X),a + ret +LIST_UART_B: + ld a,IO_SELECT_UART + out (IO_BANK), a + in a, (UART_B_TX_READY) + or a + jr z, LIST_UART_B + ld a, c + out (UART_B_TX_DATA), a + ret + +; PUNCH +PUNCHE: + ld a, (IOBYTE) + and 0x30 ; Mask punch bits + or a + jr nz, PUNCH_UART_A + ld a, IO_SELECT_UART + out (IO_BANK), a +PUNCH_UART_B: + ; UART B + in a, (UART_B_TX_READY) + or a + jr z, PUNCH_UART_B + + ld a, c + out (UART_B_TX_DATA), a + ret +PUNCH_UART_A: + ; UART A + in a, (UART_TX_READY) + or a + jr z, PUNCH_UART_A + + ld a, c + out (UART_TX_DATA), a + ret + +; LIST +LISTSTE: + ld a, (IOBYTE) + and 0xc0 ; Mask list bits + cp 0x40 + jr nz, LISTST_UART_B + ; CRT is always ready + ld a, 0xff + ret +LISTST_UART_B: + ld a, IO_SELECT_UART + out (IO_BANK), a + in a, (UART_B_TX_READY) + or a + ret z + ld a, 0xff + ret + +; READER +READERE: + ld a, (IOBYTE) + and 0x0c ; Mask reader bits + or a + jr nz, READER_UART_A + ld a, IO_SELECT_UART + out (IO_BANK), a +READER_UART_B: + ; UART B + in a, (UART_B_RX_AVAIL) + or a + jr z, READER_UART_B + + in a, (UART_B_RX_DATA) + ret +READER_UART_A: + ; UART A + in a, (UART_RX_AVAIL) + or a + jr z, READER_UART_A + + in a, (UART_RX_DATA) + ret +; Selects a drive, returning the address of the DPH in HL (or 0x0000 on +; error). +SELDSKE: + ld a, (BDISK) + ld b, a + ld a, c + ld (BDISK), a + + ld hl, drive_a_dph + or a ; Test for 0 + ret z + + ld hl, drive_b_dph + dec a ; Test for 1 + ret z + + ld hl, drive_c_dph + dec a ; Test for 2 + ret z + + ld hl, drive_d_dph + dec a ; Test for 3 + ret z + + ld hl, drive_e_dph + dec a ; Test for 4 + ret z + + ld hl, drive_f_dph + dec a ; Test for 5 + ret z + + ld hl, drive_g_dph + dec a ; Test for 6 + ret z + + ld hl, drive_h_dph + dec a ; Test for 7 + ret z + + ld hl, drive_i_dph + dec a ; Test for 8 + ret z + + ld hl, drive_j_dph + dec a ; Test for 9 + ret z + + ld hl, drive_k_dph + dec a ; Test for 10 + ret z + + ld hl, drive_l_dph + dec a ; Test for 11 + ret z + + ld hl, drive_m_dph + dec a ; Test for 12 + ret z + + ld hl, drive_n_dph + dec a ; Test for 13 + ret z + + ld a, b ; Don't change disk + ld (BDISK), a + ld hl, 0 + ret + +HOMEE: + ld bc, 0 + ; fall through + +SETTRKE: + ld (BTRACK), bc + ret + +SETSECE: + ld (BSECTOR), bc + ret + +SETDMAE: + ld (BDMA), bc + ret + +READE: + call sd_init + call calc_sd_addr + ; Read strobe + ld a,0 + out (SD_READ),a + call sd_wait + ; Copy data to DMA + ld hl, (BDMA) + ld a, (BSDPAGE) + out (SD_PAGE), a + ld c, SD_DATA +read_copy_loop: + in a, (c) + ld (hl), a + inc hl + inc c + ld a, c + cp a, 0 + jr nz, read_copy_loop + + call sd_init + ret + +WRITEE: + call sd_init + call calc_sd_addr + + ; Read full page first + ld a, 0 + out (SD_READ),a + call sd_wait + + ; Copy data from DMA + ld hl, (BDMA) + ld a, (BSDPAGE) + out (SD_PAGE), a + ld c, SD_DATA +write_copy_loop: + ld a, (hl) + out (c), a + inc hl + inc c + ld a, c + cp a, 0 + jr nz, write_copy_loop + + ; Write strobe + ld a,0 + out (SD_WRITE),a + call sd_wait + + ret + +SECTRANE: + ld h, b + ld l, c + ret + +call_hl: + jp (hl) + +; Prints the text immediately following the call to print. +print: + pop hl ; return address points to text to print + ld a, (hl) + inc hl + push hl ; save address after current char + + or a + ret z ; if byte was zero, return + ld c, a + call CONOUTE + jr print + +; Calculate the SD-card sector address +calc_sd_addr: + ; Store current disk in E register for later + ld a, (BDISK) + ld e, a + ; Load the current track + ld hl, (BTRACK) + ld d, 0 + ; Multiply by 16 + ld b,4 + +shift_loop: + add hl,hl + rl d + djnz shift_loop + + ; Add the sector divided by 4 + ld bc, (BSECTOR) + ld a,c + and a,0x03 + ld (BSDPAGE),a + sra b + rr c + sra b + rr c + add hl,bc + jp nc, no_carry_sector + inc d +no_carry_sector: + ; Add SD-card address offset and store + ld a, SD_OFFSET_3 + out (SD_ADDR_3),a + + ld bc, SD_OFFSET_0+SD_OFFSET_1*256 + add hl, bc + jp nc, no_carry_add + inc d +no_carry_add: + ld a, d + add a, e ; Add disk number + add a, SD_OFFSET_2 + out (SD_ADDR_2),a + ld a,h + out (SD_ADDR_1),a + ld a,l + out (SD_ADDR_0),a + + ret + +sd_init: + ld a, IO_SELECT_SD + out (IO_BANK), a + ; Fall through +sd_wait: + in a, (SD_BUSY) + cp a, 0 + jr nz, sd_wait + ret + +drive_a_dph: + dw 0 ; Sector translation vector + dw 0, 0, 0 ; BDOS scratchpad + dw dirbuf ; Directory scratchpad + dw DRVADPB ; Drive parameter block + dw 0 ; Disk change check vector + dw drive_a_bitmap ; Allocation bitmap + +drive_b_dph: + dw 0 ; Sector translation vector + dw 0, 0, 0 ; BDOS scratchpad + dw dirbuf ; Directory scratchpad + dw DRVBDPB ; Drive parameter block + dw 0 ; Disk change check vector + dw drive_b_bitmap ; Allocation bitmap + +drive_c_dph: + dw 0 ; Sector translation vector + dw 0, 0, 0 ; BDOS scratchpad + dw dirbuf ; Directory scratchpad + dw DRVBDPB ; Drive parameter block + dw 0 ; Disk change check vector + dw drive_c_bitmap ; Allocation bitmap + +drive_d_dph: + dw 0 ; Sector translation vector + dw 0, 0, 0 ; BDOS scratchpad + dw dirbuf ; Directory scratchpad + dw DRVBDPB ; Drive parameter block + dw 0 ; Disk change check vector + dw drive_d_bitmap ; Allocation bitmap + +drive_e_dph: + dw 0 ; Sector translation vector + dw 0, 0, 0 ; BDOS scratchpad + dw dirbuf ; Directory scratchpad + dw DRVBDPB ; Drive parameter block + dw 0 ; Disk change check vector + dw drive_e_bitmap ; Allocation bitmap + +drive_f_dph: + dw 0 ; Sector translation vector + dw 0, 0, 0 ; BDOS scratchpad + dw dirbuf ; Directory scratchpad + dw DRVBDPB ; Drive parameter block + dw 0 ; Disk change check vector + dw drive_f_bitmap ; Allocation bitmap + +drive_g_dph: + dw 0 ; Sector translation vector + dw 0, 0, 0 ; BDOS scratchpad + dw dirbuf ; Directory scratchpad + dw DRVBDPB ; Drive parameter block + dw 0 ; Disk change check vector + dw drive_g_bitmap ; Allocation bitmap + +drive_h_dph: + dw 0 ; Sector translation vector + dw 0, 0, 0 ; BDOS scratchpad + dw dirbuf ; Directory scratchpad + dw DRVBDPB ; Drive parameter block + dw 0 ; Disk change check vector + dw drive_h_bitmap ; Allocation bitmap + +drive_i_dph: + dw 0 ; Sector translation vector + dw 0, 0, 0 ; BDOS scratchpad + dw dirbuf ; Directory scratchpad + dw DRVBDPB ; Drive parameter block + dw 0 ; Disk change check vector + dw drive_i_bitmap ; Allocation bitmap + +drive_j_dph: + dw 0 ; Sector translation vector + dw 0, 0, 0 ; BDOS scratchpad + dw dirbuf ; Directory scratchpad + dw DRVBDPB ; Drive parameter block + dw 0 ; Disk change check vector + dw drive_j_bitmap ; Allocation bitmap + +drive_k_dph: + dw 0 ; Sector translation vector + dw 0, 0, 0 ; BDOS scratchpad + dw dirbuf ; Directory scratchpad + dw DRVBDPB ; Drive parameter block + dw 0 ; Disk change check vector + dw drive_k_bitmap ; Allocation bitmap + +drive_l_dph: + dw 0 ; Sector translation vector + dw 0, 0, 0 ; BDOS scratchpad + dw dirbuf ; Directory scratchpad + dw DRVBDPB ; Drive parameter block + dw 0 ; Disk change check vector + dw drive_l_bitmap ; Allocation bitmap + +drive_m_dph: + dw 0 ; Sector translation vector + dw 0, 0, 0 ; BDOS scratchpad + dw dirbuf ; Directory scratchpad + dw DRVBDPB ; Drive parameter block + dw 0 ; Disk change check vector + dw drive_m_bitmap ; Allocation bitmap + +drive_n_dph: + dw 0 ; Sector translation vector + dw 0, 0, 0 ; BDOS scratchpad + dw dirbuf ; Directory scratchpad + dw DRVBDPB ; Drive parameter block + dw 0 ; Disk change check vector + dw drive_n_bitmap ; Allocation bitmap + +; Boot disk +label DRVADPB + dw 64 ; Number of CP/M sectors per track + db 7, 127 ; BSH/BLM for 16384-byte blocks + db 7 ; EXM for 16384-byte allocation units and >255 blocks + dw DRIVE_A_BLOCKS-1 ; DSM + dw 511 ; DRM, one fewer than the number of directory entries + db 0x80, 0x00 ; Initial allocation vector for one directory block + dw 0 ; Size of disk change check vector: zero as this is a fixed disk + dw 1 ; Number of reserved tracks + +; Other disks +label DRVBDPB + dw 64 + db 7, 127 + db 7 + dw DRIVE_B_BLOCKS-1 + dw 511 + db 0x80, 0x00 + dw 0 + dw 0 + + +drive_a_bitmap: + ds (DRIVE_A_BLOCKS+7) / 8 +drive_b_bitmap: + ds (DRIVE_B_BLOCKS+7) / 8 +drive_c_bitmap: + ds (DRIVE_B_BLOCKS+7) / 8 +drive_d_bitmap: + ds (DRIVE_B_BLOCKS+7) / 8 +drive_e_bitmap: + ds (DRIVE_B_BLOCKS+7) / 8 +drive_f_bitmap: + ds (DRIVE_B_BLOCKS+7) / 8 +drive_g_bitmap: + ds (DRIVE_B_BLOCKS+7) / 8 +drive_h_bitmap: + ds (DRIVE_B_BLOCKS+7) / 8 +drive_i_bitmap: + ds (DRIVE_B_BLOCKS+7) / 8 +drive_j_bitmap: + ds (DRIVE_B_BLOCKS+7) / 8 +drive_k_bitmap: + ds (DRIVE_B_BLOCKS+7) / 8 +drive_l_bitmap: + ds (DRIVE_B_BLOCKS+7) / 8 +drive_m_bitmap: + ds (DRIVE_B_BLOCKS+7) / 8 +drive_n_bitmap: + ds (DRIVE_B_BLOCKS+7) / 8 + +dirbuf: + ds 128 + +label BDISK + db 0 +label BTRACK + dw 0 +label BSECTOR + dw 0 +label BSDPAGE + dw 0 +label BDMA + dw 0 + + + diff --git a/arch/nano-z80/boot.z80 b/arch/nano-z80/boot.z80 new file mode 100644 index 0000000..839b41a --- /dev/null +++ b/arch/nano-z80/boot.z80 @@ -0,0 +1,28 @@ +; nano-z80 cpmish BIOS © 2025 Henrik Löfgren +; This file is distributable under the terms of the 2-clause BSD license. +; See COPYING.cpmish in the distribution root directory for more information. + +; This is the boot sector setting where to load from the SD card and where in +; RAM to place the image + + ; Magic numbers + db 0x6e + db 0x61 + db 0x6e + db 0x6f + + ; Image SD card sector + db 0x00 + db 0x00 + db 0x00 + db 0x01 + + ; Image length + db 0x0f + + ; RAM page to load to + db 0xe0 + + ; RAM page to boot from + db 0xf6 + diff --git a/arch/nano-z80/build.py b/arch/nano-z80/build.py new file mode 100644 index 0000000..e219fc6 --- /dev/null +++ b/arch/nano-z80/build.py @@ -0,0 +1,84 @@ +from build.ab import simplerule +from build.cpm import cpm_addresses, diskimage +from utils.build import unix2cpm +from third_party.zmac.build import zmac +from third_party.ld80.build import ld80 + +(cbase, fbase, bbase) = cpm_addresses(name="addresses", bios_size=0x0a00) + +zmac(name="boot", src="./boot.z80", relocatable=False) + +zmac( + name="bios", + src="./bios.z80", + deps=[ + "arch/nano-z80/include/nano-z80.lib", + "include/cpm.lib", + "include/cpmish.lib", + ".+addresses", + ], +) + +zmac( + name="tty", + src="./tty.z80", + deps=[ + "arch/nano-z80/include/nano-z80.lib", + "include/cpm.lib", + "include/cpmish.lib", + "arch/common/utils/tty.lib", + "arch/common/utils/print.lib", + ], +) + + +# Builds the memory image. +ld80( + name="bootfile_mem", + objs={ + cbase: ["third_party/zcpr1"], + fbase: ["third_party/zsdos"], + bbase: [".+bios",".+tty",], + }, +) + +# Repackages the memory image as a boot track. This doesn't include the extra +# section of boot image which exists above the directory. +simplerule( + name="bootfile", + ins=[".+boot", ".+bootfile_mem"], + outs=["=bootfile.img"], + commands=[ + "dd if={ins[0]} of={outs[0]} bs=128 count=1 2> /dev/null", + "dd if={ins[1]} of={outs[0]} bs=128 seek=4 skip=446 count=64 2> /dev/null", + ], + label="nanoZ80", +) + +unix2cpm(name="readme", src="README.md") + +diskimage( + name="diskimage", + format="nanoz80", + bootfile=".+bootfile", + map={ + "-readme.txt": ".+readme", + "dump.com": "cpmtools+dump", + "stat.com": "cpmtools+stat", + "asm.com": "cpmtools+asm", + "copy.com": "cpmtools+copy", + "submit.com": "cpmtools+submit", + "bbcbasic.com": "third_party/bbcbasic+bbcbasic_ADM3A", + "camel80.com": "third_party/camelforth", + "qe.com": "cpmtools+qe_NANOZ80", + "rawdisk.com": "cpmtools+rawdisk", + "mkfs.com": "cpmtools+mkfs", + "baudrate.com": "arch/nano-z80/tools+baudrate", + "nanoterm.com": "arch/nano-z80/tools+nanoterm", + "colorfg.com": "arch/nano-z80/tools+colorfg", + "colorbg.com": "arch/nano-z80/tools+colorbg", + "cls.com" : "arch/nano-z80/tools+cls", + "arrowkey.com" : "arch/nano-z80/tools+arrowkey", + }, +) + diff --git a/arch/nano-z80/include/nano-z80.lib b/arch/nano-z80/include/nano-z80.lib new file mode 100644 index 0000000..fa96c1d --- /dev/null +++ b/arch/nano-z80/include/nano-z80.lib @@ -0,0 +1,109 @@ +; nano-Z80 defines + +; Global addresses +RAM_END equ $FFFF ; Top of RAM +INPUT_BUF equ $FF00 ; Monitor input buffer +PAGE_CNT equ $FFFE ; SD card page counter +BOOT_PAGE equ $FFFF ; RAM page to boot from + +; UART IO ports +UART_TX_DATA equ $70 ; UART transmit data +UART_TX_READY equ $71 ; UART transmit ready +UART_RX_DATA equ $72 ; UART receive data +UART_RX_AVAIL equ $73 ; UART receive data available +UART_B_TX_DATA equ $04 ; UART B transmit data, paged +UART_B_TX_READY equ $05 ; UART B transmit ready, paged +UART_B_RX_DATA equ $06 ; UART B receive data +UART_B_RX_AVAIL equ $07 ; UART B receive data available +UART_B_BAUD equ $08 ; UART B baud rate setting register + +; IO mux ports and constants +ROM_DISABLE equ $7e ; ROM disable register, 1=ROM disabled +IO_BANK equ $7f ; IO bank selection register + +IO_SELECT_LEDS equ $00 ; IO bank value for LEDs +IO_SELECT_GPIO equ $01 ; IO bank value for GPIO +IO_SELECT_USB equ $02 ; IO bank value for USB HID +IO_SELECT_SD equ $03 ; IO bank value for SD Card +IO_SELECT_VID equ $04 ; IO bank for video controller +IO_SELECT_UART equ $05 ; IO bank value for UART B +; LEDs IO ports +LEDS_REG equ $00 ; LED registers, bits 0-6 control one LED each +WS2812_R equ $01 ; WS2812 RED +WS2812_G equ $02 ; WS2812 GREEN +WS2812_B equ $03 ; WS2812 BLUE + +; GPIO IO ports +GPIO_DATA1 equ $00 ; GPIO data register 1 +GPIO_DATA2 equ $01 ; GPIO data register 2 +GPIO_DIR1 equ $02 ; GPIO direction register 1 +GPIO_DIR2 equ $03 ; GPIO direction register 2 + +; USB HID IO ports +USB_KEY_AVAIL equ $00 ; USB keyboard input available +USB_KEY_CHAR equ $01 ; USB keyboard data register +USB_KEY_MOD equ $02 ; USB keyboard modifier keys +USB_MOUSE_BUT equ $03 ; USB mouse buttons +USB_MOUSE_DX equ $04 ; USB mouse dx +USB_MOUSE_DY equ $05 ; USB mouse dy +USB_GAME_BUT1 equ $06 ; USB gamepad buttons reg 1 +USB_GAME_BUT2 equ $07 ; USB gamepad buttons reg 2 +USB_NEW_REP equ $08 ; USB new report available +USB_DEV_TYPE equ $09 ; USB device type +USB_ERROR equ $0a ; USB error +USB_ARROWKEY equ $0b ; USB keyboard arrow key configuration +KB_AVAIL equ $74 ; Keyboard character available, non-banked +KB_CHAR equ $75 ; Keyboard character, non-banked + +; SD Card interface IO ports +SD_ADDR_0 equ $00 ; SD card address (LSB) +SD_ADDR_1 equ $01 ; .. +SD_ADDR_2 equ $02 ; .. +SD_ADDR_3 equ $03 ; SD card address (MSB) +SD_BUSY equ $04 ; SD card busy flag +SD_READ equ $05 ; SD card read strobe +SD_WRITE equ $06 ; SD card write stribe +SD_PAGE equ $07 ; Select data page (0-3) +SD_STATUS equ $08 ; SD card status +SD_TYPE equ $09 ; SD card type +SD_DATA equ $80 ; Start of data page ($80-$FF) + +; Disk definitions +DRIVE_A_SIZE = 4*1024 ; kB +DRIVE_A_BLOCKS = DRIVE_A_SIZE / 16 ; 16 kB blocks + +DRIVE_B_SIZE = 4*1024 ; kB +DRIVE_B_BLOCKS = DRIVE_B_SIZE / 16 ; 16 kB blocks + +SD_OFFSET_3 = 0x00 +SD_OFFSET_2 = 0x00 +SD_OFFSET_1 = 0x00 +SD_OFFSET_0 = 0x00 + +; TTY/Video interface IO ports +VID_LINE equ $00 ; Line avaialble for editing +VID_CURSOR_X equ $01 ; Cursor X position +VID_CURSOR_Y equ $02 ; Cursor Y position +VID_CURSOR_VIS equ $03 ; Cursor visible +VID_SCROLL_UP equ $04 ; Scroll up strobe +VID_SCROLL_DOWN equ $05 ; Scroll down strobe +VID_WRITE_CHAR equ $06 ; Write character to TTY +VID_BUSY equ $07 ; TTY busy flag +VID_CLEAR_TO_EOL equ $08 ; Clear to EOL strobe +VID_CLEAR_SCREEN equ $09 ; Clear screen strobe +VID_CLEAR_TO_EOS equ $0a ; Clear to EOS strobe +VID_DELETE_LINE equ $0b ; Delete line strobe +VID_INSERT_LINE equ $0c ; Insert line strobe +VID_ENABLED equ $0d ; Enably tty functions +VID_AUTOSCROLL equ $0e ; Enable autoscroll +VID_FG_RED equ $10 ; Foreground red +VID_FG_GREEN equ $11 ; Foreground green +VID_FG_BLUE equ $12 ; Foreground blue +VID_BG_RED equ $13 ; Background red +VID_BG_GREEN equ $14 ; Background green +VID_BG_BLUE equ $15 ; Background blue +VID_LINE_DATA equ $80 ; Data on current line ($80-$CF) +VID_WRITE_X equ $76 ; Always available TTY write +VID_BUSY_X equ $77 ; Always avaialble TTY busy + + diff --git a/arch/nano-z80/tools/arrowkey.z80 b/arch/nano-z80/tools/arrowkey.z80 new file mode 100644 index 0000000..b9e1660 --- /dev/null +++ b/arch/nano-z80/tools/arrowkey.z80 @@ -0,0 +1,96 @@ + maclib addresses + +IOBANK = 0x7f +IO_SELECT_USB = 0x02 +USB_ARROWKEY = 0x0b + + + cseg + org 0x100 + + call print_string_inline + db "nano-z80 arrow key configuration tool" + db 13,10 + db 13,10 + db "Current arrow key setting: " + db 0 + ; Print the current setting + ld a, IO_SELECT_USB + out (IOBANK), a + in a,(USB_ARROWKEY) + + cp 0 + jr nz, checkws + call print_string_inline + db "ADM3A" + db 0 + jr check_done +checkws: + cp 1 + jr nz, checkemacs + call print_string_inline + db "WordStar" + db 0 + jr check_done +checkemacs: + cp 2 + jr nz, check_3 + call print_string_inline + db "EMACS/MINCE" + db 0 + jr check_done +check_3: + ; No check needed + call print_string_inline + db "ADM3A (redundant)" + db 0 +check_done: + call print_string_inline + db 13,10,13,10 + db "Please select new configuration:" + db 13,10 + db "1: ADM3A" + db 13,10 + db "2: WordStar" + db 13,10 + db "3: EMACS/MINCE" + db 13,10,0 + + ; Get input + call BBASE+0x09 ; CONIN + cp '1' + jr c, illegal_input + cp '4' + jr c, accepted_input + jr illegal_input +accepted_input: + dec a + ld b, a + ld a, IO_SELECT_USB + out (IOBANK), a + ld a, b + out (USB_ARROWKEY), a + call print_string_inline + db 13,10 + db "Configuration updated" + db 0 + ret + +illegal_input: + call print_string_inline + db 13,10 + db "Illegal selection" + db 0 + ret +print_string_inline: + pop hl + ld a,(hl) + inc hl + push hl + cp 0 + ret z + ld c, a + call BBASE+0x0c ; CONOUT + jr print_string_inline + + diff --git a/arch/nano-z80/tools/baudrate.z80 b/arch/nano-z80/tools/baudrate.z80 new file mode 100644 index 0000000..8a2b3f0 --- /dev/null +++ b/arch/nano-z80/tools/baudrate.z80 @@ -0,0 +1,116 @@ + maclib addresses + +IOBANK = 0x7f +IO_SELECT_UART = 0x05 +UART_B_BAUD = 0x08 + + + cseg + org 0x100 + + call print_string_inline + db "nano-z80 baud rate tool" + db 13,10 + db 13,10 + db "Current baudrate setting: " + db 0 + ; Print the current setting + ld a, IO_SELECT_UART + out (IOBANK), a + in a,(UART_B_BAUD) + + cp 0 + jr nz, check9600 + call print_string_inline + db "4800" + db 0 + jr check_done +check9600: + cp 1 + jr nz, check19200 + call print_string_inline + db "9600" + db 0 + jr check_done +check19200: + cp 2 + jr nz, check38400 + call print_string_inline + db "19200" + db 0 + jr check_done +check38400: + cp 3 + jr nz, check57600 + call print_string_inline + db "38400" + db 0 + jr check_done +check57600: + cp 4 + jr nz, check115200 + call print_string_inline + db "57600" + db 0 + jr check_done +check115200: + ; No check needed + call print_string_inline + db "115200" + db 0 +check_done: + call print_string_inline + db 13,10,13,10 + db "Please select new baudrate:" + db 13,10 + db "1: 4800" + db 13,10 + db "2: 9600" + db 13,10 + db "3: 19200" + db 13,10 + db "4: 38400" + db 13,10 + db "5: 57600" + db 13,10 + db "6: 115200" + db 13,10,0 + + ; Get input + call BBASE+0x09 ; CONIN + cp '1' + jr c, illegal_input + cp '7' + jr c, accepted_input + jr illegal_input +accepted_input: + dec a + ld b, a + ld a, IO_SELECT_UART + out (IOBANK), a + ld a, b + out (UART_B_BAUD), a + call print_string_inline + db 13,10 + db "Baudrate updated" + db 0 + ret + +illegal_input: + call print_string_inline + db 13,10 + db "Illegal selection" + db 0 + ret +print_string_inline: + pop hl + ld a,(hl) + inc hl + push hl + cp 0 + ret z + ld c, a + call BBASE+0x0c ; CONOUT + jr print_string_inline + + diff --git a/arch/nano-z80/tools/build.py b/arch/nano-z80/tools/build.py new file mode 100644 index 0000000..f3e843a --- /dev/null +++ b/arch/nano-z80/tools/build.py @@ -0,0 +1,52 @@ +from third_party.zmac.build import zmac +from build.ack import ackprogram + +zmac( + name="baudrate", + relocatable=False, + src="./baudrate.z80", + deps=[ + "arch/nano-z80+addresses", + ], +) + +zmac( + name="colorfg", + relocatable=False, + src="./colorfg.z80", + deps=[ + "arch/nano-z80+addresses", + ], +) + +zmac( + name="colorbg", + relocatable=False, + src="./colorbg.z80", + deps=[ + "arch/nano-z80+addresses", + ], +) + +zmac( + name="cls", + relocatable=False, + src="./cls.z80", + deps=[ + "arch/nano-z80+addresses", + ], +) + +zmac( + name="arrowkey", + relocatable=False, + src="./arrowkey.z80", + deps=[ + "arch/nano-z80+addresses", + ], +) + +ackprogram( + name="nanoterm", + srcs=["./nanoterm.c", "./uart.s"], +) diff --git a/arch/nano-z80/tools/cls.z80 b/arch/nano-z80/tools/cls.z80 new file mode 100644 index 0000000..32d5c46 --- /dev/null +++ b/arch/nano-z80/tools/cls.z80 @@ -0,0 +1,10 @@ + maclib addresses + + cseg + org 0x100 + + ld a, 0x1a + ld c, a + call BBASE+0x0c ; CONOUT + ret + diff --git a/arch/nano-z80/tools/colorbg.z80 b/arch/nano-z80/tools/colorbg.z80 new file mode 100644 index 0000000..0e252f8 --- /dev/null +++ b/arch/nano-z80/tools/colorbg.z80 @@ -0,0 +1,109 @@ + maclib addresses + +IOBANK = 0x7f +IO_SELECT_VID = 0x04 +VID_BG_R = 0x13 +VID_BG_G = 0x14 +VID_BG_B = 0x15 + +CPM_FCB = 0x005c + + cseg + org 0x100 + + ; Read hex color values from command line + ld hl, CPM_FCB + inc hl ; Skip drive letter + ld de, bg_color + ld b,0 +read_col: + ld a, (hl) + call hex_conv + ret c ; Exit if failed + + ; Shift to high nibble + sla a + sla a + sla a + sla a + ; Store in RAM + ld (de), a + + ; Read second digit + inc hl + ld a, (hl) + call hex_conv + ret c + ld c, a + ld a, (de) + or a, c + ld (de), a + inc de + inc hl + + inc b + ld a,b + cp 3 + jr nz, read_col + + ; Set color + ld de, bg_color + ld a, IO_SELECT_VID + out (IOBANK), a + + ld a, (de) + out (VID_BG_R), a + inc de + ld a, (de) + out (VID_BG_G), a + inc de + ld a, (de) + out (VID_BG_B), a + + ret + +; Convert ASCII hex digit to number, print usage if not valid +hex_conv: + cp '0' + jr c, print_usage +hex_conv_a: + cp '9' + jr c, hex_conv_b + jr z, hex_conv_b + jr hex_conv_c +hex_conv_b: + sub 0x30 + or a ; Clear carry flag + ret +hex_conv_c: + cp 'F' + jr c, hex_conv_d + jr z, hex_conv_d + jr print_usage +hex_conv_d: + sub 0x37 + or a ; Clear carry flag + ret + +print_usage: + call print_string_inline + db "colorbg - set background color on the nano-z80 computer" + db 13,10 + db "Usage: colorbg RRGGBB. Eg. colorfg FFFFFF for white background" + db 13,10, 0 + scf + ret + +print_string_inline: + pop hl + ld a,(hl) + inc hl + push hl + cp 0 + ret z + ld c, a + call BBASE+0x0c ; CONOUT + jr print_string_inline + +bg_color: + db 0,0,0 diff --git a/arch/nano-z80/tools/colorfg.z80 b/arch/nano-z80/tools/colorfg.z80 new file mode 100644 index 0000000..af8575d --- /dev/null +++ b/arch/nano-z80/tools/colorfg.z80 @@ -0,0 +1,109 @@ + maclib addresses + +IOBANK = 0x7f +IO_SELECT_VID = 0x04 +VID_FG_R = 0x10 +VID_FG_G = 0x11 +VID_FG_B = 0x12 + +CPM_FCB = 0x005c + + cseg + org 0x100 + + ; Read hex color values from command line + ld hl, CPM_FCB + inc hl ; Skip drive letter + ld de, fg_color + ld b,0 +read_col: + ld a, (hl) + call hex_conv + ret c ; Exit if failed + + ; Shift to high nibble + sla a + sla a + sla a + sla a + ; Store in RAM + ld (de), a + + ; Read second digit + inc hl + ld a, (hl) + call hex_conv + ret c + ld c, a + ld a, (de) + or a, c + ld (de), a + inc de + inc hl + + inc b + ld a,b + cp 3 + jr nz, read_col + + ; Set color + ld de, fg_color + ld a, IO_SELECT_VID + out (IOBANK), a + + ld a, (de) + out (VID_FG_R), a + inc de + ld a, (de) + out (VID_FG_G), a + inc de + ld a, (de) + out (VID_FG_B), a + + ret + +; Convert ASCII hex digit to number, print usage if not valid +hex_conv: + cp '0' + jr c, print_usage +hex_conv_a: + cp '9' + jr c, hex_conv_b + jr z, hex_conv_b + jr hex_conv_c +hex_conv_b: + sub 0x30 + or a ; Clear carry flag + ret +hex_conv_c: + cp 'F' + jr c, hex_conv_d + jr z, hex_conv_d + jr print_usage +hex_conv_d: + sub 0x37 + or a ; Clear carry flag + ret + +print_usage: + call print_string_inline + db "colorfg - set foreground color on the nano-z80 computer" + db 13,10 + db "Usage: colorfg RRGGBB. Eg. colorfg FFFFFF for white text" + db 13,10, 0 + scf + ret + +print_string_inline: + pop hl + ld a,(hl) + inc hl + push hl + cp 0 + ret z + ld c, a + call BBASE+0x0c ; CONOUT + jr print_string_inline + +fg_color: + db 0,0,0 diff --git a/arch/nano-z80/tools/nanoterm.c b/arch/nano-z80/tools/nanoterm.c new file mode 100644 index 0000000..855725b --- /dev/null +++ b/arch/nano-z80/tools/nanoterm.c @@ -0,0 +1,447 @@ +#include +#include +#include + +#define ESC 0x1b +#define BELL 0x07 +#define BACKSPACE 0x08 +#define TAB 0x09 +#define CR 0x0d +#define LF 0x0a +#define VT 0x0b +#define FF 0x0c +#define LOCAL_CMD 0x11 // ctrl+q +#define SOH 0x01 +#define EOT 0x04 +#define ACK 0x06 +#define DLE 0x10 +#define XON 0x11 +#define XOFF 0x13 +#define NAK 0x15 +#define SYN 0x16 +#define CAN 0x18 +#define SUB 0x1a + +// UART functions written in assembly +extern uint8_t uart_rx_avail(void); +extern uint8_t uart_getc(void); +extern void uart_putc_raw(uint8_t c); +extern uint8_t uart_getbaud(void); +extern void uart_setbaud_raw(uint8_t b); + +uint8_t local_echo; +uint8_t baudrate; + +static FCB xmodem_file; +static uint8_t xmodem_buffer[128]; + +void print(const char* s) +{ + for(;;) { + uint8_t b = *s++; + if(!b) return; + cpm_conout(b); + } +} + + +void crlf(void) +{ + print("\r\n"); +} + +void printx(const char* s) { + print(s); + crlf(); +} + +void printhex4(uint8_t nibble) +{ + nibble &= 0x0f; + if(nibble < 10) + nibble += '0'; + else + nibble += 'a'-10; + cpm_conout(nibble); +} + +void printhex8(uint8_t b) +{ + printhex4(b>>4); + printhex4(b); +} +uint8_t dummy(uint8_t b) { + uint8_t x; + x=b; + return x; +} +// Wrapper which is only used since I for the life of me can't figure out +// the correct calling convention +void uart_putc(uint8_t b) { + dummy(b); + uart_putc_raw(b); +} + +void uart_setbaud(uint8_t b) { + if(b>5) b=5; + + dummy(b); + uart_setbaud_raw(b); +} + + +void print_settings(void) { + printx("Current settings"); + print("Local echo: "); + if(!local_echo) { + printx("OFF"); + } else { + printx("ON"); + } + + print("Baudrate: "); + switch(baudrate) { + case 0: + print("4800"); + break; + case 1: + print("9600"); + break; + case 2: + print("19200") ; + break; + case 3: + print("38400"); + break; + case 4: + print("57600"); + break; + case 5: + print("115200"); + break; + default: + print("Undefined"); + break; + } + crlf(); + crlf(); +} + +void set_baudrate(void) { + uint8_t b; + crlf(); + printx("Select new baudrate"); + printx("1: 4800"); + printx("2: 9600"); + printx("3: 19200"); + printx("4: 38400"); + printx("5: 57600"); + printx("6: 115200"); + b = cpm_conin(); + + b=b-'1'; + if(b>5) { + printx("Invalid selection"); + return; + } + uart_setbaud(b); + baudrate = uart_getbaud(); + crlf(); + print_settings(); +} + +static uint8_t getblockchar(uint8_t *data) { + uint8_t data_available; + uint16_t i; + for(i=0; i<30000; i++) { + data_available = uart_rx_avail(); + + if(data_available) { + *data = uart_getc(); + break; + } + } + return data_available; +} + +static void xmodem_receive(void) { + char filename_input[14]; + uint8_t block_cnt; + uint8_t block_exp = 1; + uint8_t pos = 0; + uint8_t checksum; + uint8_t inp; + uint8_t outp; + uint8_t data_available; + + print("X modem receive"); + crlf(); + print("Enter filename: "); + + filename_input[0]=13; + filename_input[1]=0; + cpm_readline((uint8_t *)filename_input); + crlf(); + + // Reset FCB + memset(&xmodem_file, 0, sizeof(xmodem_file)); + + // Parse filename + cpm_parse_filename(&xmodem_file,&filename_input[2]); + + // Create file + cpm_make_file(&xmodem_file); + + print("Waiting for sender"); + crlf(); + print("Press any key to cancel"); + crlf(); + outp = NAK; + // Transmission + while(1) { + uart_putc(outp); + if(getblockchar(&inp)) { + if(inp == EOT) { + crlf(); + print("Transmission done"); + crlf(); + cpm_close_file(&xmodem_file); + return; + } + if(inp == CAN) { + crlf(); + print("Transmission cancelled"); + crlf(); + cpm_close_file(&xmodem_file); + return; + } + if(inp == SOH) { + // Got header, get package + outp = NAK; + checksum = 0; + getblockchar(&inp); + block_cnt = inp; + getblockchar(&inp); + if((block_cnt == (inp ^ 0xFF)) && (block_cnt == block_exp)) { + // Get block, otherwise retry + for(pos=0; pos<128; pos++) { + getblockchar(&inp); + xmodem_buffer[pos]=inp; + checksum += inp; + } + // Verify checksum + getblockchar(&inp); + if(checksum == inp) { + outp = ACK; + cpm_set_dma(&xmodem_buffer); + cpm_write_sequential(&xmodem_file); + //printi(block_cnt); + } + block_exp++; + } + } + } + cpm_conout('.'); + if(cpm_const()) { + cpm_close_file(&xmodem_file); + return; + } + } + +} + +static void xmodem_send_block(uint8_t block_cnt) { + uint8_t i; + uint8_t checksum; + uint8_t data; + + // Print block number + //printi(block_cnt); + print("."); + + // Send header + uart_putc(SOH); + uart_putc(block_cnt); + uart_putc(block_cnt ^ 0xFF); + + checksum = 0; + // Send data + for(i=0; i<128; i++) { + data = xmodem_buffer[i]; + checksum += data; + uart_putc(data); + } + + // Send checksum + uart_putc(checksum); +} + +static void xmodem_send(void) { + char filename_input[14]; + uint8_t block_cnt = 1; + uint8_t pos = 0; + uint8_t delay; + uint8_t inp; + uint8_t outp; + uint8_t nak_cnt = 0; + + print("X modem send"); + crlf(); + print("Enter filename: "); + + filename_input[0]=13; + filename_input[1]=0; + cpm_readline((uint8_t *)filename_input); + crlf(); + + // Reset FCB + memset(&xmodem_file, 0, sizeof(xmodem_file)); + + // Parse filename + cpm_parse_filename(&xmodem_file,&filename_input[2]); + + // Open file + if(cpm_open_file( &xmodem_file)) { + print("Error opening file\r\n"); + return; + } + + // Load first block + cpm_set_dma(&xmodem_buffer); + cpm_read_sequential(&xmodem_file); + + print("Waiting for receiver..."); + crlf(); + print("Press any key to cancel"); + crlf(); + + while(1) { + if(getblockchar(&inp)) { + if(inp == NAK) { + // Resend block + xmodem_send_block(block_cnt); + nak_cnt++; + if(nak_cnt == 11) { + print("Too many NAKs, aborting"); + crlf(); + uart_putc(CAN); + cpm_close_file(&xmodem_file); + return; + } + } + if(inp == ACK) { + cpm_conout('.'); + // Load next block + cpm_set_dma(&xmodem_buffer); + if(cpm_read_sequential(&xmodem_file)) { + crlf(); + print("Transmission done"); + crlf(); + uart_putc(EOT); + cpm_close_file(&xmodem_file); + return; + } + block_cnt++; + // Send next block + xmodem_send_block(block_cnt); + } + } + if(cpm_const()) { + // Cancel due to keypress + cpm_close_file(&xmodem_file); + uart_putc(CAN); + return; + } + } +} + + +int main(void) { + uint8_t run=1; + uint8_t console_data; + uint8_t uart_data; + + local_echo = 0; + baudrate = uart_getbaud(); + + printx("Nanoterm for the nano-z80"); + printx("Press ctrl-q + h for help"); + crlf(); + print_settings(); + + while(run) { + if(cpm_bios_const()) { + console_data = cpm_bios_conin(); // No echo... + if(console_data == LOCAL_CMD) { + console_data = cpm_conin(); + switch(console_data) { + case 'q': + case 'Q': + // Quit + crlf(); + printx("Goodbye!"); + cpm_warmboot(); + break; + case 'e': + case 'E': + // Toggle local echo + crlf(); + print("Local echo "); + if(!local_echo) { + local_echo = 1; + printx("ON"); + } else { + local_echo = 0; + printx("OFF"); + } + break; + case 'b': + case 'B': + set_baudrate(); + break; + case 'p': + case 'P': + print_settings(); + break; + case 's': + case 'S': + xmodem_send(); + break; + case 'r': + case 'R': + xmodem_receive(); + break; + case LOCAL_CMD: + uart_putc(LOCAL_CMD); + break; + case 'h': + case 'H': + // Print help + crlf(); + printx("Available commands:"); + printx("Ctrl-q + q: Quit"); + printx("Ctrl-q + e: Toggle local echo"); + printx("Ctrl-q + b: Set baudrate"); + printx("Ctrl-q + p: Print current settings"); + printx("Ctrl-q + s: Send file with X-modem"); + printx("Ctrl-q + r: Receive file with X-modem"); + printx("Ctrl-q + ctrl+q: Send ctrl-q "); + break; + default: + break; + } + } else { + if(local_echo == 1) { + cpm_conout(console_data); + } + + uart_putc(console_data); + } + } + if(uart_rx_avail()) { + uart_data = uart_getc(); + cpm_conout(uart_data); + } + + } +} diff --git a/arch/nano-z80/tools/uart.s b/arch/nano-z80/tools/uart.s new file mode 100644 index 0000000..d577868 --- /dev/null +++ b/arch/nano-z80/tools/uart.s @@ -0,0 +1,84 @@ +.sect .text +.sect .rom +.sect .data +.sect .bss + + +IOBANK = 0x7f +IO_SELECT_UART = 0x05 +UART_B_TX_DATA = 0x04 +UART_B_TX_READY = 0x05 +UART_B_RX_DATA = 0x06 +UART_B_RX_AVAIL = 0x07 +UART_B_BAUD = 0x08 + +! Check if there is RX data available, return 1 if there is, 0 if not +.sect .text +.define _uart_rx_avail +_uart_rx_avail: + mvi a, IO_SELECT_UART + out IOBANK + in UART_B_RX_AVAIL + mvi d, 0 + mov e, a + ret + +! Get a byte from the serial port +.sect .text +.define _uart_getc +_uart_getc: + mvi a, IO_SELECT_UART + out IOBANK + in UART_B_RX_DATA + mvi d,0 + mov e,a + ret + +! Send a byte to the serial port, blocking +.sect .text +.define _uart_putc_raw +_uart_putc_raw: + pop d ! pop return address + pop h ! pop character to send + + xthl ! L = character to send + push h + push d + mvi a, IO_SELECT_UART + out IOBANK +uart_send_loop: + in UART_B_TX_READY + cpi 0 + jz uart_send_loop + mov a,l + out UART_B_TX_DATA + ret + +! Get the current baudrate +.sect .text +.define _uart_getbaud +_uart_getbaud: + mvi a, IO_SELECT_UART + out IOBANK + in UART_B_BAUD + mvi d,0 + mov e,a + ret + + ! Set the baudrate +.sect .text +.define _uart_setbaud_raw +_uart_setbaud_raw: + pop d ! pop return address + pop h ! pop setting + + xthl ! L = setting + push h + push d + mvi a, IO_SELECT_UART + out IOBANK + mov a,l + out UART_B_BAUD + ret + + diff --git a/arch/nano-z80/tty.z80 b/arch/nano-z80/tty.z80 new file mode 100644 index 0000000..aedab20 --- /dev/null +++ b/arch/nano-z80/tty.z80 @@ -0,0 +1,101 @@ +; nano-z80 cpmish BIOS © 2025 Henrik Löfgren +; This file is distributable under the terms of the 2-clause BSD license. +; See COPYING.cpmish in the distribution root directory for more information. + + maclib cpm + maclib cpmish + maclib nano-z80 + + extrn SYSIN + extrn SYSOUT + extrn ADDAHL + + public TTYINIT + public TTYPUTC + public TTYPUT8 + public TTYPUT16 + public TTYPUTSI + + cseg + +SCREEN_WIDTH = 80 +SCREEN_HEIGHT = 30 + +CURSOR_UPDATES = 1 +CLEAR_SCREEN_ON_INIT = 1 +EMULATE_CLEAR_TO_EOL = 0 +EMULATE_CLEAR_TO_EOS = 0 + maclib tty + maclib print + +TTYINIT equ tty_init +TTYPUTC equ tty_putc +TTYPUT8 equ tty_puthex8 +TTYPUT16 equ tty_puthex16 +TTYPUTSI equ tty_putsi + +tty_rawwrite: + push af + call video_init + call tty_update_cursor + ; Check if output should be inverted + ld a, (tty_attributes) + bit 0, a + jr z, vid_write + pop af + or a, $80 + push af +vid_write: + pop af + out (VID_WRITE_X), a + ret + +; Moves the cursor to the current location. +tty_update_cursor: + call video_init + ld a, (tty_cursorx) + out (VID_CURSOR_X), a + ld a, (tty_cursory) + out (VID_CURSOR_Y), a + ret + +tty_delete_line: + call tty_update_cursor + call video_init + out (VID_DELETE_LINE), a + call tty_update_cursor + ret + ;jp tty_update_cursor + +tty_insert_line: + call video_init + out (VID_INSERT_LINE), a + ret + ;jp tty_update_cursor + +tty_clear_to_eol: + call video_init + out (VID_CLEAR_TO_EOL), a + ret + ;jp tty_update_cursor + +tty_clear_to_eos: + call video_init + out (VID_CLEAR_TO_EOS), a + ret ;jp tty_update_cursor + +; Add a timeout as just checking the busy flag gets stuck sometimes +; for unknows reasons +video_init: + ld a, IO_SELECT_VID + out (IO_BANK), a + ld b,37 +video_init_delay: + in a, (VID_BUSY) + ret z + djnz video_init_delay + ret + + +; vim: ts=4 sw=4 et ft=asm + diff --git a/build.py b/build.py index 5110c9e..ca2f13d 100644 --- a/build.py +++ b/build.py @@ -9,6 +9,7 @@ "lw30.img": "arch/brother/lw30+diskimage", "wp1.img": "arch/brother/wp1+diskimage", "kayproii.img": "arch/kayproii+diskimage", - "nc200.img": "arch/nc200+diskimage" + "nc200.img": "arch/nc200+diskimage", + "nano-z80.img": "arch/nano-z80+diskimage" }, ) diff --git a/cpmtools/libcuss/build.py b/cpmtools/libcuss/build.py index ce5eb3e..a0c34dd 100644 --- a/cpmtools/libcuss/build.py +++ b/cpmtools/libcuss/build.py @@ -10,6 +10,7 @@ "BROTHER_POWERNOTE", "SPECTRUM_PLUS_THREE", "SPECTRUM_NEXT", + "NANOZ80", ] for terminal in libcuss_terminals: @@ -28,4 +29,4 @@ def libcuss_ackprogram(name, deps=[], cflags=[], **kwargs): deps=deps + ["cpmtools/libcuss+libcuss_" + terminal], cflags=["-DLIBCUSS_"+terminal] + cflags, **kwargs - ) + ) diff --git a/cpmtools/libcuss/libcuss.h b/cpmtools/libcuss/libcuss.h index 1fbafc5..08aaf9b 100644 --- a/cpmtools/libcuss/libcuss.h +++ b/cpmtools/libcuss/libcuss.h @@ -57,6 +57,10 @@ extern void con_revoff(void); #define SCREENWIDTH 80 #define SCREENHEIGHT 24 #define LIBCUSS_ROBOTRON +#elif defined LIBCUSS_NANOZ80 + #define SCREENWIDTH 80 + #define SCREENHEIGHT 30 + #define LIBCUSS_ADM3 #else #error "No libcuss configuration specified." #endif diff --git a/diskdefs b/diskdefs index 913ab9b..ef5d053 100644 --- a/diskdefs +++ b/diskdefs @@ -91,5 +91,17 @@ diskdef brother-powernote os 2.2 end +# 4 Meg format used for the nanoZ80 +diskdef nanoz80 + seclen 512 + tracks 512 + sectrk 16 + blocksize 16384 + maxdir 512 + boottrk 1 + os 2.2 +end + + # vim: set ts=4 sw=4 expandtab