Bernd Ulmann
Published © CC BY

A tiny Z80 based computer

A powerful Z80 based computer with an IDE disk interface and a simple operating system capable of reading FAT32 filesystems.

AdvancedShowcase (no instructions)Over 2 days2,512
A tiny Z80 based computer

Story

Read more

Code

Code snippet #2

Plain text
;******************************************************************************
;
;  Small monitor for the Z80 single board computer consisting of 32 kB ROM 
; ($0000 to $ffff), 32 kB RAM ($8000 to $ffff) and a 16c550 UART.
;
; B. Ulmann, 28-SEP-2011, 29-SEP-2011, 01-OCT-2011, 02-OCT-2011, 30-OCT-2011,
;            01-NOV-2011, 02-NOV-2011, 03-NOV-2011, 06/07/08-JAN-2012
; I. Kloeckl, 06/07/08-JAN-2012 (FAT implementation for reading files)
; B. Ulmann, 14-JAN-2011, 
;
; Version 0.8
;
;******************************************************************************
;
; TODO: 
;       Read and print IDE error status codes in case of error!
;
; Known issues:
;       Memory Dump has a problem when the end address is >= FF00
;
;******************************************************************************
;
;  RST $00 will enter the monitor (do not care about the return address pushed
; onto the stack - the stack pointer will be reinitialized during cold as well
; as during warm starts.
;
;  Monitor routines will generally called by placing the necessary parameters
; into some processor registers and then issuing RST $08. More about this later.
;
;  Memory layout is as follows:
;
;  +-------+
;  ! $FFFF !    General purpose 512 byte buffer
;  !  ---  !
;  ! $FE00 !
;  +-------+
;  ! $DFFF !    FAT control block
;  !  ---  !
;  ! $FDDC !
;  +-------+
;  ! $FDDB !    File control block
;  !  ---  !
;  ! $FBBE !
;  +-------+
;  ! $FBBD !    81 byte string buffer
;  !  ---  !
;  ! $FB6D !
;  +-------+
;  ! $FB6C !    12 byte string buffer
;  !  ---  !
;  ! $FB61 !
;  +-------+
;  ! $FB60 !    Buffers for various routines
;  !  ---  !
;  ! $FB4D !
;  +-------+
;  ! $FB4C !    Cold/warm start control (1 byte)
;  +-------+
;  ! $FBBD !    Stack
;  !  ...  !
;  ! $8000 !    Begin of RAM
;  +-------+
;  ! $7FFF !    ROM area
;  !  ---  !    RST $08 calls a system routine
;  ! $0000 !    RST $00 restarts the monitor
;  +-------+
;
;
monitor_start   equ     $0000           ; $0000 -> ROM, $8000 -> Test image
;
                org     monitor_start
;
rom_start       equ     $0
rom_end         equ     $7fff
ram_start       equ     $8000
ram_end         equ     $ffff
buffer          equ     ram_end - $1ff  ; 512 byte IDE general purpose buffer
;
; Define the FAT control block memory addresses:
;
datastart       equ     buffer - 4      ; Data area start vector
rootstart       equ     datastart - 4   ; Root directory start vector
fat1start       equ     rootstart - 4   ; Start vector to first FAT
psiz            equ     fat1start - 4   ; Size of partition (in sectors)
pstart          equ     psiz - 4        ; First sector of partition
rootlen         equ     pstart - 2      ; Maximum number of entries in directory
fatsec          equ     rootlen - 2     ; FAT size in sectors
ressec          equ     fatsec - 2      ; Number of reserved sectors
clusiz          equ     ressec - 1      ; Size of a cluster (in sectors)
fatname         equ     clusiz - 9      ; Name of the FAT (null terminated)
fatcb           equ     fatname         ; Start of the FATCB
;
; Define a file control block (FCB) memory addresses and displacements:
;
file_buffer     equ     fatcb - $200            ; 512 byte sector buffer
cluster_sector  equ     file_buffer - 1         ; Current sector in cluster
current_sector  equ     cluster_sector - 4      ; Current sector address
current_cluster equ     current_sector - 2      ; Current cluster number
file_pointer    equ     current_cluster - 4     ; Pointer for file position
file_type       equ     file_pointer - 1        ; 0 -> not found, else OK
first_cluster   equ     file_type - 2           ; First cluster of file
file_size       equ     first_cluster - 4       ; Size of file
file_name       equ     file_size - 12          ; Canonical name of file
fcb             equ     file_name               ; Start of the FCB
;
fcb_filename            equ     0
fcb_file_size           equ     $c
fcb_first_cluster       equ     $10
fcb_file_type           equ     $12
fcb_file_pointer        equ     $13
fcb_current_cluster     equ     $17
fcb_current_sector      equ     $19
fcb_cluster_sector      equ     $1d
fcb_file_buffer         equ     $1e
;
; We also need some general purpose string buffers:
;
string_81_bfr   equ     fcb - 81
string_12_bfr   equ     string_81_bfr - 12
;
;  A number of routines need a bit of scratch RAM, too. Since these are 
; sometimes interdependent, each routine gets its own memory cells (only
; possible since the routines are not recursive).
;
load_file_scrat equ     string_12_bfr - 2       ; Two bytes for load_file
str2filename_de equ     load_file_scrat - 2     ; Two bytes for str2filename
fopen_eob       equ     str2filename_de - 2     ; Eight bytes for fopen
fopen_rsc       equ     fopen_eob - 4
fopen_scr       equ     fopen_rsc - 2
dirlist_scratch equ     fopen_scr - 2           ; Eight bytes for fopen
dirlist_eob     equ     dirlist_scratch - 2
dirlist_rootsec equ     dirlist_eob - 4
;
start_type      equ     dirlist_rootsec  - $1   ; Distinguish cold/warm start
;
uart_base       equ     $0
ide_base        equ     $10
;
uart_register_0 equ     uart_base + 0
uart_register_1 equ     uart_base + 1
uart_register_2 equ     uart_base + 2
uart_register_3 equ     uart_base + 3
uart_register_4 equ     uart_base + 4
uart_register_5 equ     uart_base + 5
uart_register_6 equ     uart_base + 6
uart_register_7 equ     uart_base + 7
;
eos             equ     $00             ; End of string
cr              equ     $0d             ; Carriage return
lf              equ     $0a             ; Line feed
space           equ     $20             ; Space
tab             equ     $09             ; Tabulator
;
; Main entry point (RST 00H):
;
rst_00          di                      ; Disable interrupts 
                jr      initialize      ; Jump over the RST-area
;
;  RST-area - here is the main entry point into the monitor. The calling 
; standard looks like this:
;
; 1) Set register IX to the number of the system routine to be called.
; 2) Set the remaining registers according to the routine's documentation.
; 3) Execute RST $08 to actually call the system routine.
; 4) Evaluate the values returned in the registers as described by the 
;    Routine's documentation. 
;
;  (Currently there are no plans to use more RST entry points, so this routine
; just runs as long as necessary in memory. If more RSTs will be used, this
; routine should to be moved to the end of the used ROM area with only a 
; simple jump at the RST $08-location.)
;
;  This technique of calling system routines can be used as the following
; example program that just echos characters read from the serial line
; demonstrates:
;
;         org     $8000           ; Start in lower RAM
; loop    ld      ix, 5           ; Prepare call to getc
;         rst     08              ; Execute getc
;         cp      3               ; CTRL-C pressed?
;         jr      z, exit         ; Yes - exit
;         ld      ix, 6           ; Prepare call to putc
;         rst     08              ; Execute putx
;         jr      loop            ; Process next character
; exit    ld      ix, 4           ; Exit - print a CR/LF pair
;         rst     08              ; Call CRLF
;         ld      hl, msg         ; Pointer to exit message
;         ld      ix, 7           ; Prepare calling puts
;         rst     08              ; Call puts
;         rst     00              ; Restart monitor (warm start)
; msg     defb    "That's all folks.", $d, $a, 0
;
;  Currently the following functions are available (a more detailed description
; can be found in the dispatch table itself):
;
;       0:      cold_start
;       1:      is_hex
;       2:      is_print
;       3:      to_upper
;       4:      crlf
;       5:      getc
;       6:      putc
;       7:      puts
;       8:      strcmp
;       9:      gets
;       A:      fgetc
;       B:      dump_fcb
;       C:      fopen
;       D:      dirlist
;       E:      fatmount
;       F:      fatunmount
;
;                org     monitor_start + $08
                nop                     ; Beware: zasm is buggy concerning
                nop                     ; the org pseudo-statement. Therefore
                nop                     ; The displacement to the RST $08
                nop                     ; entry point is generated by this
                nop                     ; NOP-sequence.
rst_08          push    bc              ; Save bc and hl
                push    hl
                push    ix              ; Copy the contents of ix
                pop     hl              ; into hl
                add     hl, hl          ; Double to get displacement in table
                ld      bc, dispatch_table
                add     hl, bc          ; Calculate displacement in table
                ld      bc, (hl)        ; Load bc with the destination address
                push    bc
                pop     ix              ; Load ix with the destination address
                pop     hl              ; Restore hl
                pop     bc              ; and bc
                jp      (ix)            ; Jump to the destination routine
dispatch_table  defw    cold_start      ; $00 = clear etc.
                ; Parameters:    N/A
                ; Action:        Performs a cold start (memory is cleared!)
                ; Return values: N/A
                ;
                defw    is_hex
                ; Parameters:    A contains a character code
                ; Action:        Tests ('0' 0
                ;
                defw    gets
                ; Parameters:    HL contains a buffer address, B contains the
                ;                buffer length (including the terminating
                ;                null byte!)
                ; Action:        Reads a string from STDIN. Terminates when
                ;                either the buffer is full or the string is
                ;                terminated by CR/LF.
                ; Return values: N/A
                ;
                defw    fgetc
                ; Parameters:    IY (pointer to a valid FCB)
                ; Action:        Reads a character from a FAT file
                ; Return values: Character in A, if EOF has been encountered,
                ;                the carry flag will be set
                ;
                defw    dump_fcb
                ; Parameters:    IY (pointer to a valid FCB)
                ; Action:        Prints the contents of the FCB in human
                ;                readable format to STDOUT
                ; Return values: N/A
                ;
                defw    fopen
                ; Parameters:    HL (points to a buffer containing the file
                ;                file name), IY (points to an empty FCB)
                ; Action:        Opens a file for reading
                ; Return values: N/A (All information is contained in the FCB)
                ;
                defw    dirlist
                ; Parameters:    N/A (relies on a valid FAT control block)
                ; Action:        Writes a directory listing to STDOUT
                ; Return values: N/A
                ;
                defw    fatmount
                ; Parameters:    N/A (needs the global FAT control block)
                ; Action:        Mounts a disk (populates the FAT CB)
                ; Return values: N/A
                ;
                defw    fatunmount
                ; Parameters:    N/A (needs the global FAT control block)
                ; Action:        Invalidates the global FAT control block
                ; Return values; N/A
;
;  The stackpointer will be predecremented by a push instruction. Since we need
; a 512 byte buffer for data transfers to and from the IDE disk, the stack 
; pointer is initialized to start at the beginning of this buffer space.
;
initialize      ld      sp, start_type - $1
;
; Initialize UART to 9600,8N1:
;
                ld      a, $80
                out     (uart_register_3), a
                ld      a, $c           ; 1843200 / (16 * 9600)
                out     (uart_register_0), a
                xor     a
                out     (uart_register_1), a
                ld      a, $3           ; 8N1
                out     (uart_register_3), a
;
; Print welcome message:
;
                ld      hl, hello_msg
                call    puts
;
;  If this is a cold start (the location start_type does not contain $aa)
; all available RAM will be reset to $00 and a message will be printed.
;
                ld      a, (start_type)
                cp      $aa             ; Warm start?
                jr      z, main_loop    ; Yes - enter command loop
                ld      hl, cold_start_msg
                call    puts            ; Print cold start message
                ld      hl, ram_start   ; Start of block to be filled with $00
                ld      de, hl          ; End address of block
                inc     de              ; plus 1 (for ldir)
                ld      bc, ram_end - ram_start
                ld      (hl), $00       ; Load first memory location
                ldir                    ; And copy this value down
                ld      hl, start_type
                ld      (hl), $aa       ; Cold start done, remember this
;
; Read characters from the serial line and send them just back:
;
main_loop       ld      hl, monitor_prompt
                call    puts
; The monitor is rather simple: All commands are just one or two letters. 
; The first character selects a command group, the second the desired command
; out of that group. When a command is recognized, it will be spelled out 
; automatically and the user will be prompted for arguments if applicable. 
                call    monitor_key     ; Read a key
; Which group did we get?
                cp      'C'             ; Control group?
                jr      nz, disk_group  ; No - test next group
                ld      hl, cg_msg      ; Print group prompt
                call    puts
                call    monitor_key     ; Get command key
                cp      'C'             ; Cold start?
                jp      z, cold_start
                cp      'W'             ; Warm start?
                jp      z, warm_start
                cp      'S'             ; Start?
                jp      z, start
                cp      'I'             ; Info?
                call    z, info
                jr      z, main_loop
                jp      cmd_error       ; Unknown control-group-command
disk_group      cp      'D'             ; Disk group?
                jr      nz, file_group  ; No - file group?
                ld      hl, dg_msg      ; Print group prompt
                call    puts
                call    monitor_key     ; Get command
                cp      'I'             ; Info?
                call    z, disk_info
                jr      z, main_loop
                cp      'M'             ; Mount?
                call    z, mount
                jr      z, main_loop
                cp      'T'             ; Read from disk?
                call    z, disk_transfer
                jr      z, main_loop
                cp      'U'             ; Unmount?
                call    z, unmount
                jr      z, main_loop
                jr      cmd_error       ; Unknown disk-group-command
file_group      cp      'F'             ; File group?
                jr      nz, help_group  ; No - help group?
                ld      hl, fg_msg      ; Print group prompt
                call    puts
                call    monitor_key     ; Get command
                cp      'C'             ; Cat?
                call    z, cat_file
                jr      z, main_loop
                cp      'D'             ; Directory?
                call    z, directory
                jr      z, main_loop
                cp      'L'             ; Load?
                call    z, load_file
                jr      z, main_loop
                jr      cmd_error       ; Unknown file-group-command
help_group      cp      'H'             ; Help? (No further level expected.)
                call    z, help         ; Yes :-)
                jp      z, main_loop
memory_group    cp      'M'             ; Memory group?
                jp      nz, group_error ; No - print an error message
                ld      hl, mg_msg      ; Print group prompt
                call    puts
                call    monitor_key     ; Get command key
                cp      'D'             ; Dump?
                call    z, dump
                jp      z, main_loop
                cp      'E'             ; Examine?
                call    z, examine
                jp      z, main_loop
                cp      'F'             ; Fill?
                call    z, fill
                jp      z, main_loop
                cp      'I'             ; INTEL-Hex load?
                call    z, ih_load
                jp      z, main_loop
                cp      'L'             ; Load?
                call    z, load
                jp      z, main_loop
                cp      'M'             ; Move?
                call    z, move
                jp      z, main_loop
                cp      'R'             ; Register dump?
                call    z, rdump
                jp      z, main_loop
                jr      cmd_error       ; Unknown memory-group-command
group_error     ld      hl, group_err_msg
                jr      print_error
cmd_error       ld      hl, command_err_msg
print_error     call    putc            ; Echo the illegal character
                call    puts            ; and print the error message
                jp      main_loop
;
; Some constants for the monitor:
;
hello_msg       defb    cr, lf, cr, lf, "Simple Z80-monitor - V 0.8 "
                defb    "(B. Ulmann, Sep. 2011 - Jan. 2012)", cr, lf, eos
monitor_prompt  defb    cr, lf, "Z> ", eos
cg_msg          defb    "CONTROL/", eos
dg_msg          defb    "DISK/", eos
fg_msg          defb    "FILE/", eos
mg_msg          defb    "MEMORY/", eos
command_err_msg defb    ": Syntax error - command not found!", cr, lf, eos
group_err_msg   defb    ": Syntax error - group not found!", cr, lf, eos
cold_start_msg  defb    "Cold start, clearing memory.", cr, lf, eos
;
; Read a key for command group and command:
;
monitor_key     call    getc
                cp      lf              ; Ignore LF
                jr      z, monitor_key  ; Just get the next character
                call    to_upper
                cp      cr              ; A CR will return to the prompt
                ret     nz              ; No - just return
                inc     sp              ; Correct SP to and avoid ret!
                jp      main_loop
;
;******************************************************************************
;***
;*** The following routines are used in the interactive part of the monitor
;***
;******************************************************************************
;
; Print a file's contents to STDOUT:
;
cat_file        push    bc
                push    de
                push    hl
                push    iy
                ld      hl, cat_file_prompt
                call    puts
                ld      hl, string_81_bfr
                ld      b, 81
                call    gets            ; Read the filename into buffer
                ld      iy, fcb         ; Prepare fopen (only one FCB currently)
                ld      de, string_12_bfr
                call    fopen
cat_file_loop   call    fgetc           ; Get a single character
                jr      c, cat_file_exit
                call    putc            ; Print character if not EOF
                jr      cat_file_loop   ; Next character
cat_file_exit   pop     iy
                pop     hl
                pop     de
                pop     bc
                ret
cat_file_prompt defb    "CAT: FILENAME=", eos
;
;  directory - a simple wrapper for dirlist (necessary for printing the command
; name)
;
directory       push    hl
                ld      hl, directory_msg
                call    puts
                call    dirlist
                pop     hl
                ret
directory_msg   defb    "DIRECTORY", cr, lf, eos
;
; Get and print disk info:
;
disk_info       push    af
                push    hl
                ld      hl, disk_info_msg
                call    puts
                call    ide_get_id      ; Read the disk info into the IDE buffer
                ld      hl, buffer + $13
                ld      (hl), tab
                call    puts            ; Print vendor information
                call    crlf
                ld      hl, buffer + $2d
                ld      (hl), tab
                call    puts
                call    crlf
                pop     hl
                pop     af
                ret
disk_info_msg   defb    "INFO:", cr, lf, eos
;
; Read data from disk to memory
;
disk_transfer   push    af
                push    bc
                push    de
                push    hl
                push    ix
                ld      hl, disk_trx_msg_0
                call    puts            ; Print Read/Write prompt
disk_trx_rwlp   call    getc
                call    to_upper
                cp      'R'             ; Read?
                jr      nz, disk_trx_nr ; No
                ld      ix, ide_rs      ; Yes, we will call ide_rs later
                ld      hl, disk_trx_msg_1r
                jr      disk_trx_main   ; Prompt the user for parameters
disk_trx_nr     cp      'W'             ; Write?
                jr      nz, disk_trx_rwlp
                ld      ix, ide_ws      ; Yes, we will call ide_ws later
                ld      hl, disk_trx_msg_1w
disk_trx_main   call    puts            ; Print start address prompt
                call    get_word        ; Get memory start address
                push    hl
                ld      hl, disk_trx_msg_2
                call    puts            ; Prompt for number of blocks
                call    get_byte        ; There are only 128 block of memory!
                cp      0               ; Did the user ask for 00 blocks?
                jr      nz, disk_trx_1  ; No, continue prompting
                ld      hl, disk_trx_msg_4
                call    puts
                jr      disk_trx_exit
disk_trx_1      ld      hl, disk_trx_msg_3
                call    puts            ; Prompt for disk start sector
                call    get_word        ; This is a four byte address!
                ld      bc, hl
                call    get_word
                ld      de, hl
                pop     hl              ; Restore memory start address
                ; Register contents:
                ;       A:  Number of blocks
                ;       BC: LBA3/2
                ;       DE: LBA1/0
                ;       HL: Memory start address
disk_trx_loop   push    af              ; Save number of sectors
                call    disk_trampoline ; Read/write one sector (F is changed!)
                push    hl              ; Save memory address
                push    bc              ; Save LBA3/2
                ld      hl, de          ; Increment DE (LBA1/0)
                ld      bc, $0001       ; by one and
                add     hl, bc          ; generate a carry if necessary
                ld      de, hl          ; Save new LBA1/0
                pop     hl              ; Restore LBA3/2 into HL (!)
                jr      nc, disk_trx_skip
                add     hl, bc          ; Increment BC if there was a carry
disk_trx_skip   ld      bc, hl          ; Write new LBA3/2 into BC
                pop     hl              ; Restore memory address
                push    bc              ; Save LBA3/2
                ld      bc, $200        ; 512 byte per block
                add     hl, bc          ; Set pointer to next memory block
                pop     bc              ; Restore LBA3/2
                pop     af
                dec     a               ; One block already done
                jr      nz, disk_trx_loop
disk_trx_exit   pop     ix
                pop     hl
                pop     de
                pop     bc
                pop     af
                ret
disk_trampoline jp      (ix)
disk_trx_msg_0  defb    "TRANSFER/", eos
disk_trx_msg_1r defb    "READ: ", cr, lf, "    MEMORY START=", eos
disk_trx_msg_1w defb    "WRITE: ", cr, lf, "    MEMORY START=", eos
disk_trx_msg_2  defb    " NUMBER OF BLOCKS (512 BYTE)=", eos
disk_trx_msg_3  defb    " START SECTOR=", eos
disk_trx_msg_4  defb    " Nothing to do for zero blocks.", cr, lf, eos
;
; Dump a memory area
;
dump            push    af
                push    bc
                push    de
                push    hl
                ld      hl, dump_msg_1
                call    puts            ; Print prompt
                call    get_word        ; Read start address
                push    hl              ; Save start address
                ld      hl, dump_msg_2  ; Prompt for end address
                call    puts
                call    get_word        ; Get end address
                call    crlf
                inc     hl              ; Increment stop address for comparison
                ld      de, hl          ; DE now contains the stop address
                pop     hl              ; HL is the start address again
                ; This loop will dump 16 memory locations at once - even
                ; if this turns out to be more than requested.
dump_line       ld      b, $10          ; This loop will process 16 bytes
                push    hl              ; Save HL again
                call    print_word      ; Print address
                ld      hl, dump_msg_3  ; and a colon
                call    puts
                pop hl                  ; Restore address
                push    hl              ; We will need HL for the ASCII dump
dump_loop       ld      a, (hl)         ; Get the memory content
                call    print_byte      ; and print it
                ld      a, ' '          ; Print a space
                call    putc
                inc     hl              ; Increment address counter
                djnz    dump_loop       ; Continue with this line
                ; This loop will dump the very same 16 memory locations - but
                ; this time printable ASCII characters will be written.
                ld      b, $10          ; 16 characters at a time
                ld      a, ' '          ; We need some spaces
                call    putc            ; to print
                call    putc
                pop     hl              ; Restore the start address
dump_ascii_loop ld      a, (hl)         ; Get byte
                call    is_print        ; Is it printable?
                jr      c, dump_al_1    ; Yes
                ld      a, '.'          ; No - print a dot
dump_al_1       call    putc            ; Print the character
                inc     hl              ; Increment address to read from
                djnz    dump_ascii_loop
                ; Now we are finished with printing one line of dump output.
                call    crlf            ; CR/LF for next line on terminal
                push    hl              ; Save the current address for later
                and     a               ; Clear carry
                sbc     hl, de          ; Have we reached the last address?
                pop     hl              ; restore the address
                jr      c, dump_line    ; Dump next line of 16 bytes
                pop     hl
                pop     de
                pop     bc
                pop     af
                ret
dump_msg_1      defb    "DUMP: START=", eos
dump_msg_2      defb    " END=", eos
dump_msg_3      defb    ": ", eos
;
; Examine a memory location:
;
examine         push    af
                push    hl
                ld      hl, examine_msg_1
                call    puts
                call    get_word        ; Wait for a four-nibble address
                push    hl              ; Save address for later
                ld      hl, examine_msg_2
                call    puts
examine_loop    pop     hl              ; Restore address
                ld      a, (hl)         ; Get content of address
                inc     hl              ; Prepare for next examination
                push    hl              ; Save hl again for later use
                call    print_byte      ; Print the byte
                call    getc            ; Get a character
                cp      ' '             ; A blank?
                jr      nz, examine_exit; No - exit
                ld      a, ' '          ; Print a blank character
                call    putc
                jr      examine_loop
examine_exit    pop     hl              ; Get rid of save hl value
                call    crlf            ; Print CR/LF
                pop     hl
                pop     af
                ret
examine_msg_1   defb    "EXAMINE (type ' '/RET): ADDR=", eos
examine_msg_2   defb    " DATA=", eos
;
; Fill a block of memory with a single byte - the user is prompted for the
; start address, the length of the block and the fill value.
;
fill            push    af              ; We will need nearly all registers
                push    bc
                push    de
                push    hl
                ld      hl, fill_msg_1  ; Prompt for start address
                call    puts
                call    get_word        ; Get the start address
                push    hl              ; Store the start address
                and     a               ; Clear carry
                ld      bc, ram_start
                sbc     hl, bc          ; Is the address in the RAM area?
                jr      nc, fill_get_length
                ld      hl, fill_msg_4  ; No!
                call    puts            ; Print error message
                pop     hl              ; Clean up the stack
                jr      fill_exit       ; Leave routine
fill_get_length ld      hl, fill_msg_2  ; Prompt for length information
                call    puts
                call    get_word        ; Get the length of the block
                ; Now make sure that start + length is still in RAM:
                ld      bc, hl          ; BC contains the length
                pop     hl              ; HL now contains the start address
                push    hl              ; Save the start address again
                push    bc              ; Save the length
                add     hl, bc          ; Start + length
                and     a               ; Clear carry
                ld      bc, ram_start
                sbc     hl, bc          ; Compare with ram_start
                jr      nc, fill_get_value
                ld      hl, fill_msg_5  ; Print error message
                call    puts
                pop     bc              ; Clean up the stack
                pop     hl
                jr      fill_exit       ; Leave the routine
fill_get_value  ld      hl, fill_msg_3  ; Prompt for fill value
                call    puts
                call    get_byte        ; Get the fill value
                pop     bc              ; Get the length from the stack
                pop     hl              ; Get the start address again
                ld      de, hl          ; DE = HL + 1
                inc     de
                dec     bc
                ; HL = start address
                ; DE = destination address = HL + 1
                ;      Please note that this is necessary - LDIR does not
                ;      work with DE == HL. :-)
                ; A  = fill value
                ld      (hl), a         ; Store A into first memory location
                ldir                    ; Fill the memory
                call    crlf
fill_exit       pop     hl              ; Restore the register contents
                pop     de
                pop     bc
                pop     af
                ret
fill_msg_1      defb    "FILL: START=", eos
fill_msg_2      defb    " LENGTH=", eos
fill_msg_3      defb    " VALUE=", eos
fill_msg_4      defb    " Illegal address!", cr, lf, eos
fill_msg_5      defb    " Block exceeds RAM area!", cr, lf, eos
;
; Help
;
help            push    hl
                ld      hl, help_msg
                call    puts
                pop     hl
                ret
help_msg        defb    "HELP: Known command groups and commands:", cr, lf
                defb    "         C(ontrol group):", cr, lf
                defb    "             C(old start), I(nfo), S(tart), "
                defb    "W(arm start)", cr, lf
                defb    "         D(isk group):", cr, lf
                defb    "             I(nfo), M(ount), T(ransfer),"
                defb    "         U(nmount)", cr, lf
                defb    "                                  R(ead), W(rite)"
                defb    cr, lf
                defb    "         F(ile group):", cr, lf
                defb    "             C(at), D(irectory), L(oad)", cr, lf
                defb    "         H(elp)", cr, lf
                defb    "         M(emory group):", cr, lf
                defb    "             D(ump), E(xamine), F(ill), "
                defb    "I(ntel Hex Load), L(oad), R(egister dump)"
                defb    cr, lf, eos
;
; Load an INTEL-Hex file (a ROM image) into memory. This routine has been 
; more or less stolen from a boot program written by Andrew Lynch and adapted
; to this simple Z80 based machine.
;
; The INTEL-Hex format looks a bit awkward - a single line contains these 
; parts:
; ':', Record length (2 hex characters), load address field (4 hex characters),
; record type field (2 characters), data field (2 * n hex characters),
; checksum field. Valid record types are 0 (data) and 1 (end of file).
;
; Please note that this routine will not echo what it read from stdin but
; what it "understood". :-)
; 
ih_load         push    af
                push    de
                push    hl
                ld      hl, ih_load_msg_1
                call    puts
ih_load_loop    call    getc            ; Get a single character
                cp      cr              ; Don't care about CR
                jr      z, ih_load_loop
                cp      lf              ; ...or LF
                jr      z, ih_load_loop
                cp      space           ; ...or a space
                jr      z, ih_load_loop
                call    to_upper        ; Convert to upper case
                call    putc            ; Echo character
                cp      ':'             ; Is it a colon?                
                jr      nz, ih_load_error
                call    get_byte        ; Get record length into A
                ld      d, a            ; Length is now in D
                ld      e, $0           ; Clear checksum
                call    ih_load_chk     ; Compute checksum
                call    get_word        ; Get load address into HL
                ld      a, h            ; Update checksum by this address
                call    ih_load_chk
                ld      a, l
                call    ih_load_chk
                call    get_byte        ; Get the record type
                call    ih_load_chk     ; Update checksum
                cp      $1              ; Have we reached the EOF marker?
                jr      nz, ih_load_data; No - get some data
                call    get_byte        ; Yes - EOF, read checksum data
                call    ih_load_chk     ; Update our own checksum
                ld      a, e
                and     a               ; Is our checksum zero (as expected)?
                jr      z, ih_load_exit ; Yes - exit this routine
ih_load_chk_err ld      hl, ih_load_msg_3
                call    puts            ; No - print an error message
                jr      ih_load_exit    ; and exit
ih_load_data    ld      a, d            ; Record length is now in A
                and     a               ; Did we process all bytes?
                jr      z, ih_load_eol  ; Yes - process end of line
                call    get_byte        ; Read two hex digits into A
                call    ih_load_chk     ; Update checksum
                ld      (hl), a         ; Store byte into memory
                inc     hl              ; Increment pointer
                dec     d               ; Decrement remaining record length
                jr      ih_load_data    ; Get next byte
ih_load_eol     call    get_byte        ; Read the last byte in the line
                call    ih_load_chk     ; Update checksum
                ld      a, e
                and     a               ; Is the checksum zero (as expected)?
                jr      nz, ih_load_chk_err
                call    crlf
                jr      ih_load_loop    ; Yes - read next line
ih_load_error   ld      hl, ih_load_msg_2
                call    puts            ; Print error message
ih_load_exit    call    crlf
                pop     hl              ; Restore registers
                pop     de
                pop     af
                ret
;
ih_load_chk     ld      c, a            ; All in all compute E = E - A
                ld      a, e
                sub     c
                ld      e, a
                ld      a, c
                ret
ih_load_msg_1   defb    "INTEL HEX LOAD: ", eos
ih_load_msg_2   defb    " Syntax error!", eos
ih_load_msg_3   defb    " Checksum error!", eos
;
; Print version information etc.
;
info            push    hl
                ld      hl, info_msg
                call    puts
                ld      hl, hello_msg
                call    puts
                pop     hl
                ret
info_msg        defb    "INFO: ", eos
;
; Load data into memory. The user is prompted for a 16 bit start address. Then
; a sequence of bytes in hexadecimal notation may be entered until a character
; that is not 0-9 or a-f is encountered.
;
load            push    af
                push    bc
                push    de
                push    hl
                ld      hl, load_msg_1  ; Print command name
                call    puts
                call    get_word        ; Wait for the start address (2 bytes)
                push    hl              ; Remember address
                and     a               ; Clear carry
                ld      bc, ram_start   ; Check if the address is valid
                sbc     hl, bc          ; by subtracting the RAM start address
                pop     hl              ; Restore address
                ld      de, 0           ; Counter for bytes loaded
                jr      nc, load_loop   ; OK - start reading hex characters
                ld      hl, load_msg_3  ; Print error message
                call    puts
                jr      load_exit
                ; All in all we need two hex nibbles per byte. If two characters
                ; in a row are valid hexadecimal digits we will convert them 
                ; to a byte and store this in memory. If one character is 
                ; illegal, the load routine terminates and returns to the 
                ; monitor.
load_loop       ld      a, ' '
                call    putc            ; Write a space as byte delimiter
                call    getc            ; Read first character
                call    to_upper        ; Convert to upper case
                call    is_hex          ; Is it a hex digit?
                jr      nc, load_exit   ; No - exit the load routine
                call    nibble2val      ; Convert character to value
                call    print_nibble    ; Echo hex digit
                rlc     a
                rlc     a
                rlc     a
                rlc     a
                ld      b, a            ; Save the upper four bits for later
                call    getc            ; Read second character and proceed...
                call    to_upper        ; Convert to upper case
                call    is_hex
                jr      nc, load_exit
                call    nibble2val
                call    print_nibble
                or      b               ; Combine lower 4 bits with upper
                ld      (hl), a         ; Save value to memory
                inc     hl
                inc     de
                jr      load_loop       ; Get next byte (or at least try to)
load_exit       call    crlf            ; Finished...
                ld      hl, de          ; Print number of bytes loaded
                call    print_word
                ld      hl, load_msg_2
                call    puts
                pop     hl
                pop     de
                pop     bc
                pop     af
                ret
load_msg_1      defb    "LOAD (xx or else to end): ADDR=", eos
load_msg_2      defb    " bytes loaded.", cr, lf, eos
load_msg_3      defb    " Illegal address!", eos
;
; Load a file's contents into memory:
;
load_file       push    af
                push    bc
                push    de
                push    hl
                push    iy
                ld      hl, load_file_msg_1
                call    puts            ; Print first prompt (start address)
                call    get_word        ; Wait for the start address (2 bytes)
                ld      (load_file_scrat), hl
                and     a               ; Clear carry
                ld      bc, ram_start   ; Check if the address is valid
                sbc     hl, bc          ; by subtracting the RAM start address
                jr      nc, load_file_1
                ld      hl, load_file_msg_2
                call    puts
                jr      load_file_exit  ; Illegal address - exit routine
load_file_1     ld      hl, load_file_msg_4
                call    puts            ; Prompt for filename
                ld      hl, string_81_bfr
                ld      b, 81           ; Buffer length
                call    gets            ; Read file name into bfr
                ld      iy, fcb         ; Prepare open (only one FCB currently)
                ld      de, string_12_bfr
                call    fopen           ; Open the file (if possible)
                ld      hl, (load_file_scrat)
                ld      de, 0           ; Counter for bytes loaded
load_file_loop  call    fgetc           ; Get one byte from the file
                jr      c, load_file_exit
                ld      (hl), a         ; Store byte and
                inc     hl              ; increment pointer
                inc     de
                jr      load_file_loop  ; Process next byte
load_file_exit  call    crlf
                ld      hl, de          ; Print number of bytes loaded
                call    print_word
                ld      hl, load_file_msg_3
                call    puts
                pop     iy
                pop     hl
                pop     de
                pop     bc
                pop     af
                ret
load_file_msg_1 defb    "LOAD FILE: ADDR=", eos
load_file_msg_2 defb    " Illegal address!", eos
load_file_msg_3 defb    " bytes loaded.", cr, lf, eos
load_file_msg_4 defb    " FILENAME=", eos
;
; mount - a wrapper for fatmount (necessary for printing the command's name)
;
mount           push    hl
                ld      hl, mount_msg
                call    puts
                call    fatmount
                pop     hl
                ret
mount_msg       defb    "MOUNT", cr, lf, cr, lf, eos
;
; Move a memory block - the user is prompted for all necessary data:
;
move            push    af              ; We won't even destroy the flags!
                push    bc
                push    de
                push    hl
                ld      hl, move_msg_1
                call    puts
                call    get_word        ; Get address of block to be moved
                push    hl              ; Push this address
                ld      hl, move_msg_2
                call    puts
                call    get_word        ; Get destination start address
                ld      de, hl          ; LDIR requires this in DE
                ; Is the destination address in RAM area?
                and     a               ; Clear carry
                ld      bc, ram_start
                sbc     hl, bc          ; Is the destination in RAM?
                jr      nc, move_get_length
                ld      hl, move_msg_4  ; No - print error message
                call    puts
                pop     hl              ; Clean up stack
...

This file has been truncated, please download it to see its full contents.

Code snippet #1

Plain text
Simple Z80-monitor - V 0.8 (B. Ulmann, Sep. 2011 - Jan. 2012)
Cold start, clearing memory.

Z> DISK/MOUNT

        FATNAME:        MSDOS5.0
        CLUSIZ: 40
        RESSEC: 0008
        FATSEC: 00F0
        ROOTLEN:        0200
        PSIZ:           003C0030
        PSTART: 00001F80
        FAT1START:      00001F88
        ROOTSTART:      00002168
        DATASTART:      00002188

Z> FILE/DIRECTORY
Directory contents:
-------------------------------------------
FILENAME.EXT  DIR?   SIZE (BYTES)  1ST SECT
-------------------------------------------
_~1     .TRA            00001000        000021C8
TRASHE~1.       DIR     00000000        00002188
SPOTLI~1.       DIR     00000000        00002208
FSEVEN~1.       DIR     00000000        00002408
TEST    .TXT            00000013        00002688
FAT_1   .ASM            0000552C        000026C8
MEMO    .TXT            00000098        00002748
TEST210 .TXT            00000210        00002788
TEST1F0 .TXT            000001F0        00002DC8
TEST200 .TXT            00000200        00002E08
GROSS   .TXT            0000CB5A        00003488

Z> FILE/CAT: FILENAME=TEST.TXT
123 ich bin so frei
Z> FILE/LOAD FILE: ADDR=C000 FILENAME=TEST.TXT

0013 bytes loaded.

Z> MEMORY/DUMP: START=C000 END=C01F
C000: 31 32 33 20 69 63 68 20 62 69 6E 20 73 6F 20 66   123 ich bin so f
C010: 72 65 69 00 00 00 00 00 00 00 00 00 00 00 00 00   rei.............

Z> 
       

Credits

Bernd Ulmann

Bernd Ulmann

9 projects • 9 followers
I love all things that can compute, especially analog computers.

Comments