Developing on CP/M

This is a short “getting started” guide for development on CP/M. I’ll assume that you’ve got a CP/M system running; if not, have a look at my guide to setting up an emulated CP/M development system, and that you’re familiar with at least the basics of assembly language programming.

So you’ll need to have at least a text editor, an assembler, and a debugger. Also, I assume that you’re using CP/M 3.x; personally, I find it a bit nicer to use than CP/M 2.2, even though CP/M 2.2 is much more common. Another assumption is that we’re using a Z80; I realise that the majority of CP/M systems likely were 8080-based, but later in CP/M’s life the Z80 became the dominant processor.

The best reference for this area is the “CP/M Plus Programmer’s Guide”; this document covers pretty much all you’ll need to know. Quite often, the documentation from 30 years ago was better than that produced today!

To arguably oversimplify things, the two key concepts in writing assembly language programs for CP/M are:

  • Your program should start at address 0100h; this is the start address of the Transient Program Area (TPA).
  • You can call system functions via BDOS (Basic Disk Operating System) at address 0005h, after placing the function code in register C. A list of these functions and their input/output parameters is in section 3 of the Programmer’s Guide.

The following trivial program illustrates the above; it simply outputs a ‘q’ to the console, using the BDOS “console output” function.

        org     0100h
        ld      c,2     ; BDOS "Console Output"
        ld      e,'q'   ; The character to display
        call    5       ; Call BDOS ret

Using your favourite text editor, create a file called char.z80 with the above content. Using that, you can create char.com with Z80ASM (which is a native Z80 assembler by SLR Systems) and then run it simply like this:

7J>z80asm char
7J>char

(The “7J>” is the command prompt; in this example I’m using user 7 and have a default disk of J. But you can use whatever suits you.)

Alternatively, if you prefer to use the standard Digital Research tools you’ll need to use the MAC macro assembler plus the standard linker, as well macros from Z80.LIB, and you’d need to use different mnemonics in your source code. I’ll leave that as an exercise for the reader. Back when CP/M was still current, that’s how I used to use it, but nowadays I’d much rather use Z80ASM.

Another interesting BDOS function is function #6 for “direct I/O”. This allows you to read raw bytes from the console as well as polling for available input without blocking, depending on the value of the E register. The program listed below is a simple tool that uses this BDOS function to read the keyboard and display the key codes in ASCII. This keeps running until the space bar is pressed. This is a handy way to work out what ASCII sequences that (for example) an arrow key generates for your current terminal type, so you can start to develop some smarter keyboard handling functions in your own programs.

BDOS    equ     0005h

CONOUT  equ     02h
DIRIO   equ     06h

        org     0100h

; Output a '?' via direct I/O
        ld      c,DIRIO ; BDOS "Direct Console I/O""
        ld      e,'?'   ; The character to display
        call    BDOS    ; Call BDOS

; In a loop, keep reading via direct I/O and displaying via
; direct I/O until we read a space.
LOOP:   ld      c,DIRIO ; print a space
        ld      e,' '
        call    BDOS
        ld      c,DIRIO
        ld      e,0FDh  ; a blocking character read
        call    BDOS
        call    ASCII   ; display the ASCII result
        cp      ' '     ; did we get a space?
        jr      nz,LOOP ; nope, so try again
; yep, we got a space

; Output a '.' via direct I/O
        ld      c,DIRIO
        ld      e,'.'
        call    BDOS

        ret
ASCII:  push    af      ; save input value from clobbering
        srl     a       ; Move the top nibble into the bottom nibble
        srl     a
        srl     a
        srl     a
        call    DIGIT   ; Display the top nibble
        pop     af
        push    af
        and     0Fh
        call    DIGIT   ; Display the bottom nibble
        pop     af
        ret

; Display the byte in A as a pair of hex digits
DIGIT:  cp      0Ah     ; Is it 0-9 or A-F?
        jr      nc,LETTER
        add     '0'     ; Is a digit; Convert it to an ASCII value
        jr      DIGIT2
LETTER: add     'A'-10  ; Is a letter; Convert it to an ASCII value
DIGIT2: ld      c,CONOUT
        ld      e,a
        call    BDOS
        ret

The program will display a ‘?’ when it first starts, and it will keep reading characters and displaying ASCII codes until the space bar is pressed. Use it to explore what escape sequences various keys on your system produce.

Hopefully this article has provided you with enough information to get started with writing fantastic software for the thriving CP/M marketplace 🙂