Extending Commodore 64 BASIC

Back in the day, I used to write machine-language subroutines for the Commodore 64 that I would then call from a main program written in BASIC. I found it easier to use BASIC as the higher-order controller over a set of ML functions that usually did things for which CBM BASIC 2.0 was not well-suited.

In one case, I wrote an Xmodem file-transfer protocol handler. The routines to send data, receive data, read/write from/to files on disk, and computer checksums were all handled by machine-language routines.

I found it easier to experiment with the sometimes-touchy timing of the in-process handshakes using BASIC. ( Some Xmodem implementations on some BBS’s had personalities of their own. )

In my earliest programs, I would POKE addresses of data into predetermined spots in memory using BASIC and my ML code would know where to look to pick up those parameters.

Later, my friend Mike showed me a technique that allowed me to pass parameters right on the SYS command that invoked a particular machine-code routine.

While that technique worked well, I found myself memorizing numbers ( memory addresses or function indexes ) instead of some kind of mnemonic. The obvious answer was to change BASIC to incorporate my ML routines as new commands. There were a few different techniques for doing this sort of thing. I just had to pick one. I experimented with a few and never really got around to using one I liked until now … 30 years later.

I decided to use the “@” symbol to identify my new commands. At this time, I am only adding imperative commands. I may address extended functions later, but there’s an old Transactor article that really covers that topic well. Each of my command names will only be a single alphabetic letter in length.

I have not seen the technique I’m using employed in the way I am using it. The traditional approach to implementing a “@” sentinel character followed by a single-character involves “wedging” the CHRGET routine at $0073. This allows you to “wedge” CHRGET to allow your own code to examine the input stream as BASIC is poring over the input text. This technique causes extra time to be spent over the life of the BASIC program’s execution. I wanted to try to pare this down a bit more.

I’ve called this program “the Plug-in BASIC kernel” as it is meant for budding assembly-language coders to tinker with their own BASIC dialects. I’ve implemented only two commands to demonstrate how to use the kernel. The commands are “@C” ( clear the screen ) and “@B” ( change the border, background, and foreground text colors. )

The entire source is here:

; Plug-in BASIC Kernel
; Copyright (c) 2014 by
; Jim Lawless - jimbo@radiks.net
; MIT / X11 license
; See: http://www.mailsend-online.com/license2014.php

* = $c000

CHRGET = $0073
CHRGOT = $0079
SNERR = $AF08
NEWSTT = $a7ae
GONE = $a7e4
STROUT = $ab1e

   jsr intromsg

   lda #<newgone    sta $0308    lda #>newgone
   sta $0309
   rts

; table for commmands
table=*
   .word notimp-1 ; @a

   .word do_border-1 ; @b
   .word do_cls-1  ; @c

   .word notimp-1 ; @d
   .word notimp-1 ; @e
   .word notimp-1 ; @f
   .word notimp-1 ; @g
   .word notimp-1 ; @h
   .word notimp-1 ; @i
   .word notimp-1 ; @j
   .word notimp-1 ; @k
   .word notimp-1 ; @l
   .word notimp-1 ; @m
   .word notimp-1 ; @n
   .word notimp-1 ; @o
   .word notimp-1 ; @p
   .word notimp-1 ; @q
   .word notimp-1 ; @r
   .word notimp-1 ; @s
   .word notimp-1 ; @t
   .word notimp-1 ; @u
   .word notimp-1 ; @v
   .word notimp-1 ; @w
   .word notimp-1 ; @x
   .word notimp-1 ; @y
   .word notimp-1 ; @z

newgone = *
   jsr CHRGET
   php
   cmp #"@"
   beq newdispatch

; not our @ token ... jmp back
; into GONE
   plp
; jump past the JSR CHRGET call in GONE
   jmp GONE+3

newdispatch = *
   plp
   jsr dispatch
   jmp NEWSTT

dispatch = *
   jsr CHRGET
   cmp #'a'
   bcs contin1
   jmp SNERR
contin1 = *
   cmp #'z'
   bcc contin2
   jmp SNERR
contin2 =  *
   sec
   sbc #'a'
   cmp #'z'
   asl
   tax
   lda table+1,x
   pha
   lda table,x
   pha
   jmp CHRGET

msg .text "plug-in basic command not implemented..."
    .byte 0

notimp = *
   ldy #>msg
   lda #<msg    jmp STROUT intromsg = *    ldy #>imsg
   lda #<imsg
   jmp STROUT 

imsg .text "plug-in basic kernel v 0.01a"
   .byte $0d
   .text "by jim lawless"
; please add your vanity text here for any
; customizations you make

   .byte 0

; syntax
;  @c
; clear the screen
do_cls = *
   lda #147
   jmp $ffd2

; syntax
; @b border,backgnd,char
; set border, background, and
; character color

do_border = *
   jsr $b79e ; get byte into .x
   stx $d020 ; set border
   jsr $aefd ; skip comma

   jsr $b79e ; get byte into .x
   stx $d021 ; set background
   jsr $aefd ; skip comma

   jsr $b79e ; get byte into .x
   stx $286  ; set text color
   rts

   end

Here’s how the program works.

Instead of wedging CHRGET, I changed the vector to GONE ( the routine that executes the next BASIC program token ) at locations $308 and $309 so that my routine would first take a look at the next token.

   lda #<newgone    sta $0308    lda #>newgone
   sta $0309
   rts

If that next token happened to be an “@” character, I would invoke my own dispatch routine. Otherwise, I would branch three bytes past the original GONE routine, popping the processor status from the original CHRGET call.

newgone = *
   jsr CHRGET
   php
   cmp #"@"
   beq newdispatch

; not our @ token ... jmp back
; into GONE
   plp
; jump past the JSR CHRGET call in GONE
   jmp GONE+3

In the new dispatch routine, I would check to see if the next character was in the range of ‘a’ through ‘z’ inclusive. If so, the letter would be translated to a value in the range of 0 to 25 inclusive. It would then be doubled and used as an offset into a table of machine-language routine addresses.

newdispatch = *
   plp
   jsr dispatch
   jmp NEWSTT

dispatch = *
   jsr CHRGET
   cmp #'a'
   bcs contin1
   jmp SNERR
contin1 = *
   cmp #'z'
   bcc contin2
   jmp SNERR
contin2 =  *
   sec
   sbc #'a'
   cmp #'z'
   asl
   tax
   lda table+1,x
   pha
   lda table,x
   pha
   jmp CHRGET

In order to change the code to add your own commands, simply point the entry in this table to your own routine’s address:

; table for commmands
table=*
   .word notimp-1 ; @a

   .word do_border-1 ; @b
   .word do_cls-1  ; @c

   .word notimp-1 ; @d
   .word notimp-1 ; @e
   .word notimp-1 ; @f
   .word notimp-1 ; @g
   .word notimp-1 ; @h
   .word notimp-1 ; @i
   .word notimp-1 ; @j
   .word notimp-1 ; @k
   .word notimp-1 ; @l
   .word notimp-1 ; @m
   .word notimp-1 ; @n
   .word notimp-1 ; @o
   .word notimp-1 ; @p
   .word notimp-1 ; @q
   .word notimp-1 ; @r
   .word notimp-1 ; @s
   .word notimp-1 ; @t
   .word notimp-1 ; @u
   .word notimp-1 ; @v
   .word notimp-1 ; @w
   .word notimp-1 ; @x
   .word notimp-1 ; @y
   .word notimp-1 ; @z

I have assembled the reference code to the common address $c000 (49152). To load the demo kernal, type:

load “plugin”,8,1

and then, type

sys 49152

…to activate it.

c64_1

c64_2

Let’s change the colors with the new @B command. The @B command expects three numeric-expression parameters: the border color, the background color, and the text color.

c64_3

c64_4

Now, let’s exercise the “@C” command to clear the screen. ( I find this one to be very handy. )

c64_5

c64_6

If you try a command that is not implemented, you’ll get a warning message, but it won’t generate a syntax error unless you’ve followed it with other parameters.

c64_7

These commands can be incorporated into BASIC programs themselves. You can download the source and *.P00 (PRG) file here:

http://www.mailsend-online.com/wp2/pluginbasic.zip

I’ve provided the above as something for others to tinker with.

Advertisements

About Jim Lawless

I've been programming computers for about 36 years ... 30 of that professionally. I've been a teacher, I've worked as a consultant, and have written articles here and there for publications like Dr. Dobbs Journal, The C/C++ Users Journal, Nuts and Volts, and others.
This entry was posted in Programming and tagged , , , . Bookmark the permalink.

12 Responses to Extending Commodore 64 BASIC

  1. Jay Versluis says:

    Hi Jim,

    thanks for this post – I was thinking exactly that, “how can I extend BASIC with my own ML commands”, and “how do things like Simon’s Basic” do it. Great writeup! In fact, I was inspired by a Jim Butterfield demo on YouTube in which he re-wrote the READY prompt, and swapped SYNTAX ERROR with IDIOT ERROR. Very funny!

    I’m only just getting started with machine language and I’m following Jim’s book – 30 years after owning a C64 I’m now beginning to understand the basics of ML 😉 Perhaps I can put it to good use on my new Plus/4 and C128. Jim explains the mnemonics and how to assemble directly into memory. That’s great to get started, but I can already see the limitations (what if you want to shift things down a bit – it’ll overwrite all memory locations, what about “variables”, shifting addresses, etc). I’m guessing you’re using an assembler for this, which – I assume – compiles source code (as above) into the actual executable ML code, correct?

    This is something I know nothing about – could you point me in the right direction? Is this something you do on “today’s” computers, or are such programmes available for the “real” Commodore devices? What are they called and how do they work? I only know the built-in MONITOR command and equivalents on C64 (like HESMON).

    • Jim Lawless says:

      Thanks, Jay. There are a number of articles available that detail what more elaborate BASIC enhancement schemes use. I’ll post a set of links when I get a chance.

      I use C64ASM from the command-line, but I think there’s an assembler with an IDE that people are beginning to favor. I’ll see if I can find that info.

    • Stefan Isser says:

      I know this is an older post, but maybe Jay still wants to know, or other readers are interested in the topic. When I was a teenager I did all my assembler coding with a monitor, but the said limitations really are bitter. Laking of documentation or the internet I had little chance to learn macro assemblers back then.
      Later I digged into hypra-ass (one of the simpler, but widespread assemblers here) and what should I say – it really makes a huge advantage in coding. You absolutely want to be able to add or delete lines of code, use labels, variable names or constants instead of numbers and much more. Many of such programms (macro assemblers) were available on the real machines like the C64. Getting good documentation about it however was harder back then.
      If you have a X1541 cable to connect a 1541 to your PC or a sd2iec device, you can download an assembler an put it on a disk to use it on the bare metal =)
      Programming on modern computers happens a lot and is called cross-developing (or f.e. using a cross-assembler). This has many advantages (think about screen-resolution, copy and paste, data safety, versioning, less size restrictions while developing, modern keyboards and so on).
      To my surprise still many of the cross-assemblers are command line tools (that you use like dos-commands) but there are also some IDE versions out there. I just started cross-developing a few days ago and can recommend “cbm prg studio”. It offers a good IDE and you can write assembler and basic programs with it. There are other good solutions like Relaunch64 out there, but I have not tried them.
      After compiling you can (automatically) start emulators like vice and directly test your code. I also created a .prg file, wrote it to disk and loaded it on the real C64. Worked like a charm =)

      • Jay Versluis says:

        Excellent info, Stefan – thanks for sharing. I’ve head of CBM PRG Studio, I think it’s Windows only, but it sounds like it has it all. And more to the point, developed in this millennium for a computer made in the last – very cool. I’ll check it out.

        Fascinating to hear how you’d have to program back in the days of the C64. I can imagine getting updated documentation and other user’s input must have been the hardest of them all, if not impossible without the web back then.

  2. Darren Arnold says:

    Thanks, Jim,

    I’ve been trying to figure this out for ages but after a day of staring at your code then trying to implement it in C64Studio it’s finally rammed home. The program file when I tried to reverse engineer it had extra bytes and I don’t know what they were for so sorry if I stripped out any leader or header.

    C64Studio is very popular now and since I’ve figured out the syntax differences, I thought it fair to provide the C64Studio version (sorry I stripped most of the comments so that I could see the clean code to figure out why I was blowing out the memory, but I haven’t removed the credits. The furthest I got in the early ’90s was to hand code an interrupt and watch the border flash, so this has really been a godsend:

    ; Plug-in BASIC Kernel
    ; Copyright (c) 2014 by
    ; Jim Lawless – jimbo@radiks.net
    ; MIT / X11 license
    ; See: http://www.mailsend-online.com/license2014.php

    * = $c000
    CHRGET = $0073
    CHARGOT = $0079
    SNERR = $AF08
    NEWSTT = $A7AE
    GONE = $A7E4
    STROUT = $AB1E

    jsr intromsg
    lda #newgone
    sta $0309
    rts
    table
    !word notimp-1; @a
    !word do_border-1 ; @b
    !word do_cls-1 ; @c
    !word notimp-1 ; @d
    !word notimp-1 ; @e
    !word notimp-1 ; @f
    !word notimp-1 ; @g
    !word notimp-1 ; @h
    !word notimp-1 ; @i
    !word notimp-1 ; @j
    !word notimp-1 ; @k
    !word notimp-1 ; @l
    !word notimp-1 ; @m
    !word notimp-1 ; @n
    !word notimp-1 ; @o
    !word notimp-1 ; @p
    !word notimp-1 ; @q
    !word notimp-1 ; @r
    !word notimp-1 ; @s
    !word notimp-1 ; @t
    !word notimp-1 ; @u
    !word notimp-1 ; @v
    !word notimp-1 ; @w
    !word notimp-1 ; @x
    !word notimp-1 ; @y
    !word notimp-1 ; @z
    newgone
    jsr CHRGET
    php
    cmp #’@’
    beq newdispatch
    plp
    jmp GONE+3
    newdispatch
    plp
    jsr dispatch
    jmp NEWSTT
    dispatch
    jsr CHRGET
    cmp #”A”
    bcs contin1
    jmp SNERR
    contin1
    cmp #”Z”
    bcc contin2
    jmp SNERR
    contin2
    sec
    sbc #”A”
    cmp #”Z”
    asl
    tax
    lda table+1, x
    pha
    lda table, x
    pha
    jmp CHRGET
    msg
    !text “plug-in basic command not implemented…”
    !byte 0
    notimp
    ldy #>msg
    lda #msg
    lda #imsg
    lda #<imsg
    jmp STROUT
    imsg
    !text "plug-in basic kernel v 0.01a"
    !byte $0d
    !byte 0
    do_cls
    lda #147
    jmp $ffd2
    do_border
    jsr $b79e
    stx $d020
    jsr $aefd
    jsr $b79e
    stx $d021
    jsr $aefd
    jsr $b79e
    stx $0286
    rts
    end

      • Darren Arnold says:

        Actually I found the following didn’t quite work: @Z
        I then found that I had to change the “A” to “Z” comparison to be “A” to “[” in contin2.
        I’m uncertain why that is. I presume it’s a register/deduction thing. Any clues? Your use of asl to double the jump vector was cute and efficient. I didn’t realise that as I tried to debug @Z and was fiddling about with the low byte and high byte off the table before the penny dropped.

        Also the “end” at the end of the code flags as an error message even if it doesn’t affect the compiler, you can leave it off.

        II also found that if you didn’t use the precise syntax for @B,#,#,# it caused an out of memory error as it tried to grasp for non-existent characters, even though that wasn’t the main point of your coding example.

        Now to try and figure out how I can display how many memory bytes are free in the splash screen… it’s doing me an injury…

      • Jim Lawless says:

        Interesting stuff, Darren.

        Yes, bit-shifting for powers-of-two multiplication / division is an old practice.

        I don’t have my C64 dev environment together just yet, but I will take a look at the code you mention.

        When you speak of displaying bytes free in the splash screen … are you creating your own ROM?

      • Stefan Isser says:

        Thank you Jim for this useful source-code. Yesterday I wrote a useful routine (to use on the real hardware) that I’d love to use by command instead of SYS… I got this example running well, lets see how it works together with my routine… thanks anyway.

        Darren wrote:I then found that I had to change the “A” to “Z” comparison to be “A” to “[” in contin2. I’m uncertain why that is.

        cmp #value
        bcs label .. will only jump if a is smaller than value, but not if a is equal to value.
        that’s the reason why you have to add 1 to value here.

        Darren, about displaying the free memory bytes: maybe try to solve the task in a solo project that just prints out the info, get it running and add the working source in here later. I guess you will have to do a 16bit subtraction between the pointers “bottom string space” (zero page $33,34) and “end of basic arrays” (zero page $31,$32). This should yield the available memory (I think the basic command fre calculates it this way too, maybe doing a clr command before calculating). Then just call the kernal routine to print integers. Tipp: Maybe consult the book “Mapping the Commodore 64”. … Good luck and tell us if it worked.

        Something different: I think the cmp ‘z’ in contin2 does nothing and can be deleted. Can somebody confirm or disconfirm this?

  3. Darren Arnold says:

    … I also had to change the “Z” in contin1 to a “[” by the way as it still got confused for me

  4. Darrren Arnold says:

    Hi Jim, since I haven’t used the C64 since 1991 I’m just going to start slow, something along the lines of a Simon’s Basic type thing. Starting with the example you gave my next step is to try and create a command table beyond @A – @Z,using whole keywords but retaining the @ as the prefix. @CLS, @DIR @RUNDY. I’m rubbish with the syntax and what things do but structural stuff I can figure out having done Basic, Visual Basic and Pascal, so following flow isn’t too bad for me. I was inspired by my purchase of a C64 to replace the one I had that blackscreened and a wave of nostalgia rolled over me.

    • Jim Lawless says:

      You might want to take a look at the following electronic copies of The Transactor
      magazine articles ( they’re available in archives on the ‘net ):

      Transactor vol 5 issue 5 pp. 30-35 ( “Introducing TransBASIC” )

      Transactor vol 5 issue 6 pp. 19-21 ( “TransBASIC Installment #2” )

      ( Also from v5 issue 6, pp. 22-25 ( “A New Wedge for the Commodore 64
      … Add commands by trapping syntax errors.” Neat technique! )

      Transactor vol 6 issue 1 pp. 14-19 ( “TransBASIC Installment #3” )

      Transactor vol 7 issue 1 pp. 54-57 ( “Adding Functions to BASIC” )

      Transactor vol 7 issue 1 pp. 58-62 ( “Command Wedge … Modifying
      BASIC’s Commands” )

      There are many more installments to TransBASIC that you can look
      through as well.

      Also, you might check out Stephen Judd’s BLARG graphics command extensions for the C64. You can go to the Fridge site and do a find on the Word BLARG …(see C=Hacking e-mag issues #9 and #11 ).

      http://www.ffd2.com/fridge/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s