Category Archives: 6809 Assembly

Color BASIC Attract Screen – part 5

See also: part 1, part 2, part 3, part 4, unrelated, and part 5.

In part 4 of this series, Jason Pittman provided several variations of creating the attract screen:

Jason Pittman variation #1

If those four corners bother you, then my attempt will really kick in that OCD when you notice how wonky the colors are moving…

Jason Pittman
10 CLS0:C=143:PRINT@268,"ATTRACT!";
20 FOR ZZ=0TO1STEP0:FORX=0TO15:POKEX+1024,C:POKEX+1040,C:POKE1535-X,C:POKE1519-X,C:POKE1055+(32*X),C:POKE1472-(32*X),C:GOSUB50:NEXT:GOSUB50:NEXT
50 C=C+16:IF C>255 THEN C=143
60 RETURN

Jason Pittman variation #2

Also, another option using the substrings might be to fill the sides by printing two-character strings on the 32nd column so that a character spills over to the first column of the next line:

Jason Pittman
10 CLS 0:C=143:OF=1:CH$=""
20 FOR X=0TO40:CH$=CH$+CHR$(C):GOSUB 90:NEXT
30 FOR ST=0TO1STEP0
40 PRINT@0,MID$(CH$,OF,31):GOSUB 120
50 FORX=31TO480STEP32:PRINT@X,MID$(CH$,OF,2);:GOSUB 120:NEXT
60 PRINT@481,MID$(CH$,OF,30);:GOSUB120
70 NEXT
80 REM ADVANCE COLOR
90 C=C+16:IF C>255 THEN C=143
100 RETURN
110 REM ADVANCE OFFSET
120 OF=OF+2:IF OF>7 THEN OF=OF-8
130 RETURN

Jason Pittman variation #3

One more try at O.C.D-compliant “fast”:

Jason Pittman
10 DIM CL(24):FORX=0TO7:CL(X)=143+(X*16):CL(X+8)=CL(X):CL(X+16)=CL(X):NEXT
20 CLS0:FORXX=0TO1STEP0:FORYY=0TO7:FORZZ=1TO12:READPO,CT,ST,SR:FOR X=SRTOSR+CT-1:PO=PO+ST:POKE PO,CL(X+YY):NEXT:NEXT:RESTORE:NEXT:NEXT
180 REM POSITION,COUNT,STEP,START
190 DATA 1024,8,1,0,1032,8,1,0,1040,8,1,0,1048,6,1,0,1055,8,32,6,1311,6,32,6,1535,8,-1,4,1527,8,-1,4,1519,8,-1,4,1511,6,-1,4,1504,8,-32,2,1248,6,-32,2

The #3 variation using DATA statements is my favorite due to its speed. Great work!

The need for speed: Some assembly required.

It seems clear that even the fastest BASIC tricks presented so far are still not as fast as an attract screen really needs to be. When this happens, assembly code is the solution. There are also at least two C compilers for Color BASIC that I need to explore, since writing stuff in C would be much easier for me than 6809 assembly.

Shortly after part 4, I put out a plea for help with some assembly code that would rotate graphical color blocks on the 32 column screen. William “Lost Wizard” Astle answered that plea, so I’ll present the updated routine in his LWASM 6809 compiler format instead of as an EDTASM+ screen shot in the original article.

* lwasm attract32.asm -fbasic -oattract32.bas --map

    org $3f00

start
    ldx #1024   X points to top left of 32-col screen
loop
    lda ,x+     load A with what X points to and inc X
    bpl skip    if not >128, skip
    adda #16    add 16, changing to next color
    ora #$80    make sure high gfx bit is set
    sta -1,x    save at X-1
skip
    cmpx #1536  compare X with last byte of screen
    bne loop    if not there, repeat
    sync        wait for screen sync
    rts         done

    END

The code will scan all 512 bytes of the 32-column screen, and any byte that has the high bit set (indicating it is a graphics character) will be incremented to the next color. This would allow us to draw our attract screen border one time, then let assembly cycle through the colors.

How it works:

  • The X register is loaded with the address of the top left 32-column screen.
  • The A register is loaded with the byte that X points to, then X is incremented.
  • BPL is used to skip any bytes that do not have the high bit set. This optimization was suggested by William. An 8-bit value can be treated as an unsigned value from 0-255, or as a signed value of -127 to 128. A signed byte uses the high bit to indicate a negative. Thus, a positive number would not have the high bit set (and is therefor not in the 128-255 graphics character range).
  • If the high bit was set, then 16 is added to A.
  • ORA is used to set the high bit, in case it was in the final color range (240-255) and had 16 added to it, turning it in to a non-graphics block. Setting the high bit changes it from 0-16 to 128-143.
  • The modified value is stored back at one byte before where X now points. (This was another William optimization, since originally I was not incrementing X until after the store, using an additional instruction to do that.)
  • Finally, we compare X to see if it has passed the end of screen memory.
  • If it hasn’t, we do it all again.
  • Finally, we have a SYNC instruction, that waits for the next screen interrupt. This is not really necessary, but it prevents flickering of the screen if the routine is being called too fast. (I’m not 100% sure if this should be here, or at the start of the code.)

The LWASM compiler has an option to generate a BASIC program full of DATA statements containing the machine code. You can then type that program in and RUN it to get this routine in memory. The command line to do this is in the first comment of the source code above.

10 READ A,B
20 IF A=-1 THEN 70
30 FOR C = A TO B
40 READ D:POKE C,D
50 NEXT C
60 GOTO 10
70 END
80 DATA 16128,16147,142,4,0,166,128,42,6,139,16,138,128,167,31,140,6,0,38,241,19,57,-1,-1

The program loads at $3f00 (16128), meaning it would only work on a 16K+ system. There is no requirement for that much memory, and it could be loaded anywhere else (even on a 4K system). The machine code itself is only 20 bytes. Since the code was written to be position independent (using relate branch instructions instead of hard-coded jump instructions), you could change where it loads just by altering the first two numbers in the DATA statement (start address, end address).

For instance, on a 4K CoCo, memory is from 0 to 4095. Since the assembly code only uses 20 bytes, one could load it at 4076, and use CLEAR 200,4076 to make sure BASIC doesn’t try to overwrite it. However, I found that the SYNC instruction hangs the 4K CoCo, at least in the emulator I am using, so to run on a 4K system you would have to remove that.

Here is the BASIC program modified for 4K. I added a CLEAR to protect the code from being overwritten by BASIC, changed the start and end addresses in the data statements, and altered the SYNC code to be an RTS (changing SYNC code of 19 to a 57, which I knew was an RTS because it was the last byte of the program in the DATA statements). This means it is wasting a byte, but here it is:

5 CLEAR 200,4076
10 READ A,B
20 IF A=-1 THEN 70
30 FOR C = A TO B
40 READ D:POKE C,D
50 NEXT C
60 GOTO 10
70 END
80 DATA 4076,4095,142,4,0,166,128,42,6,139,16,138,128,167,31,140,6,0,38,241,57,57,-1,-1

Using the code

Lastly, here is an example that uses this routine. I’ll use the BASIC loader for the 32K version, then add Jason’s variation #1 to it, modified by renaming it to start at line 100, and removing the outer infinite FOR Z loop so it only draws once. I’ll then add a GOTO loop that just executes this assembly routine over and over.

5 CLEAR 200,16128
10 READ A,B
20 IF A=-1 THEN 70
30 FOR C = A TO B
40 READ D:POKE C,D
50 NEXT C
60 GOTO 10
70 GOTO 100
80 DATA 16128,16147,142,4,0,166,128,42,6,139,16,138,128,167,31,140,6,0,38,241,19,57,-1,-1
100 CLS0:C=143:PRINT@268,"ATTRACT!";
120 FORX=0TO15:POKEX+1024,C:POKEX+1040,C:POKE1535-X,C:POKE1519-X,C:POKE1055+(32*X),C:POKE1472-(32*X),C:GOSUB150:NEXT:GOSUB150
130 EXEC 16128:GOTO 130
150 C=C+16:IF C>255 THEN C=143
160 RETURN

And there you have it! An attract screen for BASIC that uses assembly so it’s really not a BASIC attract screen at all except for the code that draws it initially using BASIC.

I think that about covers it. And, this routine also looks cool on normal 32-column VDG graphics screens, too, causing the colors to flash as if there is palette switching in use. (You can actually palette switch the 32-column screen colors on a CoCo 3.)

Addendum: WAR by James Garon

On 7/2/2022, Robert Gault posted to the CoCo list a message titled “Special coding in WAR“. He mentioned some embedded data inside this BASIC program. You can download it as a cassette or disk image here:

https://colorcomputerarchive.com/search?q=War+%28Tandy%29&ww=1

You can even go to that link and click “Play Now” to see the game in action.

I found this particularly interesting because this BASIC program starts with one of the classic CoCo attract screens this article series is about. In the program, the author did two tricks: One was to embed graphics characters in a PRINT statement, and the other was to embedded a short assembly language routine in a string that would cycle through the screen colors, just like my approach! I feel my idea has been validated, since it was already used by this game in 1982. See it in action:

https://colorcomputerarchive.com/test/xroar-online/?machine=cocous&basic=RUN%22WAR%22%5cr&cart=rsdos&disk0=/unzip%3Ffile%3DDisks/Games/War%20(Tandy).zip/WAR.DSK

And if you are curious, the code in question starts at line 60000. I did a reply about this on the CoCo mailing list as I dug in to what it is doing. That sounds like it might make a part 6 of this series…

Until next time…

Robert Gault’s EDTASM+for native CoCo 6809/6309 assembling.

Radio Shack introduced the Color Computer in 1980. It came 4K or RAM, and Microsoft Color BASIC in the ROM. It could be expanded to 16K RAM, which allowed adding a second ROM for Extended BASIC. A plug-in disk interface cartridge came later, with it’s own ROM containing Disk BASIC.

I’ve often wondered what Microsoft used to write the CoCo BASIC ROMs in.

EDTASM+

Around 1982, Radio Shack released the EDTASM+ ROM-Pak for the Color Computer. It was a 6809 assembler for machine language, as well as a debugger. It could load and save files (source code and final binaries) to cassette tape.

There was also Disk EDTASM+, which added some extra features — though the most important one was probably that it could load and save to a disk drive, making that process far faster.

Someone put up a nice EDTAM+ information page on my CoCoPedia.com website.

Since Microsoft created EDTASM, I suspect it may have been (or was at least based on) the tool they used for writing the Color Computer ROMs.

If you want to see it in action, head over to the JS Mocha CoCo emulator where you will find it available from a menu:

http://www.haplessgenius.com/mocha/

The EDTASM+ ROM-Pak and Bill Barden’s Color Computer Assembly Language Programming book where how I learned 6809 assembly. I later used Disk EDTASM+.

EDTASM++?

While the CoCo 1 and 2 were basically the same machine, just with redesigned circuit boards and enclosures, the 1986 CoCo 3 was quite different. It could operate in a double speed more, and provided up to 80 columns of text versus the original CoCo’s 32 columns. It also came with 128K — double what the CoCo 1/2 could handle — and could be expanded to 512K (though third party folks figured out how to do 1 an 2 megabyte upgrades).

Unfortunately, Radio Shack never released an update to the EDTASM+ ROM-Pak or disk software. It was still limited to the small memory and screen size of the original 1980 CoCo hardware.

Folks came up with various patches. I had one that patched my Disk EDTASM+ to run in 80 columns on the CoCo 3, in double speed more (faster assembling!) while setting the disk drive step rate to 6ms. It was a much nicer experience coding with long lines!

After this I moved on to OS-9, and used the Microware assemblers (asm and rma) from OS-9 Level 1 and Level 2. I am not sure I touched EDTASM+ again until I played with it on JS Mocha, decades later.

Hitatchi 6309

Hitcachi made a clone of the 6809. This replacement chip had some undocumented features such as more registers and more op codes. EDTASM+ couldn’t help with that, but there were some OS-9 compilers that were updated to support it.

That’s when folks like Robert Gault came to our rescue with enhancements for the original EDTASM+. Robert added support for the 6309, and many new features — including CoCo 3 compatibility.

His EDTASM+ looks like this on a CoCo 3 in 80 column mode:

Robert Gault’s EDTASM+ update.

If you notice the copyright date, you’ll see he has continued to update and support it. Today he offers it in a variety of versions that run on the original CoCo 1/2, a CoCo 3, certain emulators, RGB-DOS support (for hard drive users), CoCoSDC (the modern SD card floppy replacement) as well as supporting things like DriveWire.

You can pick up your own copy for $35 as follows:

EDTASM6309 $35
Robert Gault
832 N.Renaud
Grosse Pointe Woods, MI 48236
USA
e-mail:	robert.gault@att.net

There are a number of new features added. Here is the list provided in the README.txt file:

CHANGES TO EDTASM (Tandy Version)
1) Tape is no longer supported; code has been removed.
2) Buffer size increased to over 42K bytes.
3) Directory obtainable from both Editor and ZBUG; V command.
4) Multiple FCB and FDB data per line.
5) FCS supported.
6) SET command now works properly.
7) Screen colors remain as set from Basic before starting EDTASM.
8) Symbol table printed in five names per line on Coco3.
9) On assembly with /NL option, actual errors are printed.
10) Warning error on long branch where short is possible.
11) ZBUG now defaults to numeric instead of symbolic mode.
12) RGB DOS users now have support for drive numbers higher than 3.
13) Hitachi 6309 opcodes are now supported for both assembly and disassembly
including latest discoveries.
14) HD6309 detection is included and if present incorporates a ZBUG error trap
for illegal opcodes and enables monitoring and changing the E,F,V registers
from ZBUG.
15) Coco 3 users can now safely exit to Basic and use their RESET button from
EDTASM.
16) Keyboard now has auto repeat keys when keys are held down.
17) Lower case is now supported for commands, opcodes, options, and symbols.
Take care when loading or saving files or using symbols, ex. NAME does not
equal name, \.A not= \.a, etc.
18) Local names are now supported. Format is A@-Z@ and a@-z@ for 52 local
symbols. New sets of locals are
started after each blank line in the source code. Local
symbols do not appear in or clutter symbol table.
19) Local symbols can only be accessed from ZBUG in expanded form:
ex. A@00023  not A@.
20) Now reads source code files that don't have line numbers. Writes normal
source files with line numbers ( W filename ) or without line numbers
( W# filename ).
21) Macro parameters now function correctly from INCLUDEd files.
22) While in the Editor, the U key will backup one screen in your source file.
23) DOS.BAS can be used to program the F1 and F2 keys on a Coco3. See below.
24) Coco3 WIDTH80 now uses 28 lines of text.

Coco 1&2 versions do require 64K RAM, the Coco 3 version will work with 128K
of RAM. You can assemble 6309 code even if your Coco has a 6809 cpu.

It also adds some new commands:

V - obtains a directory from either Editor or ZBUG modes.
U - scrolls backwards through source code.
FCS - is used exactly like FCC but automatically add $80 to the last character
in the string.
FCB, FDB - for multiple entries per line entries should be seperated by a
comma. Make sure that the comment field for that line DOES NOT CONTAIN ANY
COMMAS or an error will result.
New ‘V’ directory command in Robert Gault’s EDTASM+ update.

If you are wanting to do some CoCo assembly language programming, I highly recommend you sending $35 to Robert and pick up a copy of his version. EDTASM+ is tricky to learn, and his updates make it a bit less tricky.

And tell him Allen sent ya.

Until next time…

Online 6809 emulator with semi-MC6847 support

Awhile back, the Internet led me to a wondrous thing: an online 6809 emulator, complete with compiler, debugger, and text/graphical output!

http://6809.uk

This website, “designed and coded” by Gwilym Thomas, is amazing. If has a spot where you can enter 6809 assembly source code, then you can compile and run it!

http://6809.uk

It even has a few sample programs you can select and try out.

While it runs, you see the registers update, as well as a source-level debugger showing what op codes are currently executing. You can set break points, and memory watch points, too.

It also provides text output in the form of the MC6847 VDG chip (used by the CoCo, and a few other systems). The graphics mode is different VDG. While it supports some similar resolutions, it also adds a 16-color display.

The screen memory is mapped to $400 (1024) just like the CoCo, so you can run stuff like this:

start ldx #1024
loop inc ,x+
 cmpx #1536
 bne loop
 bra start

If you past that in to the Assembly language input window and then click Assemble source code, you will see the text characters in the Text screen preview window cycling through values. Neat!

The graphics screen starts just past the text screen at $600 (1536). I think that might be where it started on a non-Disk Extended Color BASIC system. (See my article about memory on the CoCo for more details.)

The documentation notes this about the modes:

The graphics screen is a memory-mapped display of 6144 bytes of RAM beginning at address $0600. There are 3 graphics colour modes, in which either 1, 2, or 4 bits represent a single pixel in 2, 4, or 16 colours respectively. Addresses increase left to right, top to bottom as for the text screen.

Columns and rows are zero-base with (0, 0) at the (left, top). Sequences of bits (1, 2, or 4) from high to low represent pixels from left to right. The 2 colour mode has 256 pixels by 192, the 4 colour 128 by 192, each line being 32 bytes. The 16 colour mode has 128 pixels by 96, each line being 64 bytes.

Example: in 4 colour (2 bit) mode pixel (93, 38) would be in byte $0600+(3832)+trunc (93/4), because there are 4 pixels in a byte. The colour value (0..3) would be stored in bits 5 & 4, ie. shifted left ((4-1)2)-((93 mod 4)*2 times).

http://6809.uk/doc/doku.php?id=interactive_6809_emulator

Changing screen modes is NOT done via simulated VDG registers. Instead, it has code that looks like this:

    ldd #$0204       ; select 4 colour graphics mode
    swi3

I have not been able to find details on what values represent what mode. Also, the documentation says there is keyboard input:

Click the text screen panel then start typing for the emulator to receive keyboard input. Remember that (due to limitations of the emulated hardware) when lower case characters are printed to the screen they will appear in inverse video.

http://6809.uk/doc/doku.php?id=interactive_6809_emulator

I have not figured out how this works, yet.

As far as the 6809 assembler goes, it does not parse all of the extensions that the LWTOOLS’ lwasm assembler supports, so I have been modifying my projects to be compatible with the emulator’s assembler. This has let me, with minor changes for things like ROM calls, test and debug my code in a way that is impossible on actual hardware.

Here is the documentation:

http://6809.uk/doc/doku.php?id=interactive_6809_emulator

If you create anything interesting in it, please let everyone know in the comments.

In an Internet full of so much garbage, it’s wonderful to find such a gem.

Until next time…

6809 assembly request: make this more better?

Updates:

  • 2021-9-17 – Added revisions suggested by William “Lost Wizard” Astle.

Hello, folks who actually know how to program 6809 assembly.

Can you make this more better?

The idea is to look at each byte on the 32-column VDG screen (from 1024 to 1535) and if the high bit is set (>128, thus a semigraphics block character), add 16 to it. That moves it to the next color. If it rolls over, adjust it back to the proper range.

i.e., if the block is 250, and it adds 16, you end up with 10. Setting the high bit again bumps it back to the 128-255 range. Initially, I did this using a compare to #128, then using ADDA of #128. Then I looked up the BIT test. Rather than compare to decide if I need to do something, I just do the OR to set that high bit back. Seems to work. Maybe faster than checking each byte?

Does this make sense?

Please and thank you.

William Astle Updates

William Astle immediately left these comments:

I’d use “BPL” instead of “BITA/BEQ”. (Bit 7 is the sign bit and LD and ST set flags.) Also, I’d do “LDA ,X+” and then if updating the screen, do “STA -1,X”. That removes the need for the “LEAX”.

William Astle

BPL is “Branch if PLus”. When a register value is used to represent values that can be negative (-127 to 128 versus 0 to 255), the high bit is used to indicate negative. I expect BPL is basically “branch if the high bit is not set.”

When I “LDA ,X”, I am loading register A with whatever is pointed to by X. Adding the plus “LDA ,X” will load A then increment X. Since STA allows an offset, so you can do things like “STA 5,X” to start the value in register A 5 locations past wherever X points. Doing a -1 stores it one byte earlier. That eliminates me needing to use “LEAX” to add one to X.

Here is the update:

References

References I used:

Benchmarking the CoCo keyboard – part 2

See also: part 1, part 2, part 3, part 4, part 5, part 6, part 7 and more (coming “soon”).

Previously, a code snippet shared by Jim McClellan on Facebook motivated me to benchmark various methods of reading the CoCo keyboard in BASIC:

10 KBD=PEEK(135) AND PEEK(65282)
20 PRINT KBD
30 GOTO 10

Unfortunately, as I started my stream of consciousness article, I discovered that this sample code did not work on the Xroar emulator I was using. I recalled there had been some differences to Color BASIC’s keyboard scanning code in later versions, and thought this might be why.

Indeed, the knowledgeable Lost Wizard William Astle chimed in via comments:

I think it would only work on Color Basic 1.2 or 1.3. The reason for that is Color Basic 1.2 introduced an optimization where it only does the full matrix scan if no keys (or joystick buttons!) are pressed. It does this by writing a 0 to FF02 to “strobe” all columns. That means there will be at least one 0 bit in FF00 representing a key pressed if anything is down at all. If it detects nothing, it just exits, leaving the column strobe set to 0.

Prior to Color Basic 1.2, the matrix scan was always run. In that case, if no keys are down, it will have strobed all columns in sequence (by writing a zero bit to successive bits of FF02). This zero bit is shifted over by a ROL instruction so it eventually shifts off into carry and the final result in FF02 is FF. Eventually, most versions will eventually strobe no columns by writing FF to FF02 which is used to detect joystick buttons. The debounce check also puts a nonzero, non FF value in FF02 corresponding with the column where a key was detected. Depending on the Color Basic version, the final results of KEYIN and FF02 are (assuming I read the code correctly):

1.0: FF if no key jor joystick button, non-FF if key down (or debounce fails)

1.1: FF if no key or joystick button, non-FF if key down (or debounce fails)

1.2: 0 if no key or joystick button, FF if key or joystick button, not 0 or FF if debounce fails

1.3: same as 1.2; no modifications to KEYIN

Coco3: always FF (the check for a key optimization is removed) except when debounce fails

Basically, this trick only works on Color Basic 1.2 and 1.3. And even then, it isn’t actually reliable since you won’t get FF if debounce fails. Instead, you’ll potentially get a value with a single zero bit which will corrupt your key code. The odds of the timing being just right for that to happen are really small. However, it is possible.

So my considered opinion based on the variances between ROM versions is that this trick must not be used.

As a side note: aside from the system initialization code, KEYIN has the most significant changes between Color Basic versions.

William Astle

With this note, I now have to change my original plan for this article. I was going to see how much faster this code would be with normal tricks (HEX versus decimal constants, variables instead of constants, etc.) to see which approach would make any game using this code faster.

But since this sample only works with specific versions of Color BASIC, I now need to come up with a more portable non-portable way to do this. This sent me back to some other Facebook posts from Ben Jimenez and Jim Gerrie back in April 2020:

10 CLS
20 POKE 341,255:POKE 342,255:POKE 343,255:POKE 344,255
30 I$=INKEY$:IF I$="" THEN GOTO 20
40 PRINT ASC(I$)
50 GOTO 20

When I run this under Xroar using Color BASIC 1.1, I see that it will print a repeating series of numbers as I hold down each arrow key. I also notice that it repeats for some other keyboard presses, but not all.

But why? A new mystery!

I suspect this is because those POKEs must have something to do with the row or column the four arrow keys are in the keyboard matrix:

      1     2     3     4     5     6     7     8
      |     |     |     |     |     |     |     |
1 --- @ --- A --- B --- C --- D --- E --- F --- G 
      |     |     |     |     |     |     |     | 
2 --- H --- I --- J --- K --- L --- M --- N --- O 
      |     |     |     |     |     |     |     | 
3 --- P --- Q --- R --- S --- T --- U --- V --- W 
      |     |     |     |     |     |     |     | 
4 --- X --- Y --- Z -- UP -- DWN - LFT - RGT - SPACE 
      |     |     |     |     |     |     |     | 
5 --- 0 -- 1! -- 2" -- 3# -- 4$ -- 5% -- 6& -- 7' 
      |     |     |     |     |     |     |     | 
6 -- 8( -- 9) -- :* -- ;+ -- ,< -- -= -- .> -- /? 
      |     |     |     |     |     |     |     | 
7 -- ENT - CLR - BRK - ALT - CTL - F1 -- F2 - SHIFT 

All four arrow keys are in row 4, and they repeat. I am betting that the other keys that repeat are the ones in the same columns UP, DOWN, LEFT and RIGHT are in (col 4-7). A quick test shows that @, A and B to not repeat (they use columns 1, 2 and 3), but C, D E and F do (columns 4-7) and G does not (column 8).

Mystery solved.

Whatever those POKEs are doing, they are resetting something involving columns 4, 5, 6 and 7. This sends me back into the Color BASIC Unraveled disassembly to see what memory locations 341 (&H155) to 344 (&H158) are:

0152  KEYBUF  RMB  8  KEYBOARD MEMORY BUFFER

Eight bytes, and we are setting four of them to 255 (all bits set to 1). No help so far. I start making notes:

KEYBUF - Keyboard memory buffer:

338 &H152
339 &H153
340 &H154
341 &H155 - POKEd to 255 (&HFF)
342 &H156 - POKEd to 255 (&HFF)
343 &H157 - POKEd to 255 (&HFF)
344 &H158 - POKEd to 255 (&HFF)
345 &H159

Next, I search the code to see where this KEYBUF is being used, and then things get confusing. The disassembly has multiple code listings that reference this — different code for different versions of the ROM. Great.

KEYIN konfusion

As William Astle referenced, this buffer is used by the KEYIN routine and there are multiple implementations. The code is described as:

KEYIN
SCAN THE KEYBOARD FOR A KEY DEPRESSION - Return zero
flag = 1 if no new key down. Return the ASCII value
of the key in ACCA if a new key is depressed.

Ah, this looks familiar. As soon as I read this, I remembered something from learning CoCo 6809 programming using the EDTASM cartridge.

POLCAT ROM routine digression

Microsoft, thinking ahead to potential changes in the ROM code, set aside a few “documented ROM calls” that assembly language programmers could use. If you wrote an assembly program for Color BASIC 1.0 and wanted to use the KEYIN routine in ROM by jumping directly to its address in the ROM, that program would not work in later Color BASIC 1.2 versions that had KEYIN at a different address.

Instead, Microsoft placed a few hooks in ROM starting at location &HA000. Each entry was the address of a routine elsewhere in the ROM. By using a “jump by reference” assembly instruction, you could end up at wherever those addresses point. POLCAT was the entry for getting a keystroke, and it points to KEYIN:

0002 A000 A1 CB  POLCAT  FDB  KEYIN   GET A KEYSTROKE
0003 A002 A2 82  CHROUT  FDB  PUTCHR  OUTPUT A CHARACTER
0004 A004 A7 7C  CSRDON  FDB  CASON   TURN ON CASSETTE MOTOR, START READING
0005 A006 A7 0B  BLKIN   FDB  GETBLK  READ A BLOCK FROM CASSETTE
0006 A008 A7 F4  BLKOUT  FDB  SNDBLK  WRITE A BLOCK TO CASSETTE
0007 A00A A9 DE  JOYIN   FDB  GETJOY  READ JOYSTICKS
0008 A00C A7 D8  WRTLDR  FDB  WRLDR   TURN ON MOTOR AND WRITE $55’S TO CASSETTE

That was the official documented way to see if a key was pressed. The implementation of what POLCAT points to changes between ROM versions, so while this compatible call would return a key, the way that it obtained that key changed.

Here’s a short assembly program using POLCAT (KEYIN).

Using the Color BASIC POLCAT ROM routine.

When I assemble that in EDTASM (“A/IM/WE/AO”) and execute from ZBUG (“Z”, “G START”), I can start pressing keys and see them POKEd to the top left corner of the screen, and moving forward as I press different keys. Holding down a key does not repeat. Thus, the KEYIN routine does not support keyboard repeat.

But I digress.

When Jim’s original code PEEKs two values from memory location 135 (last key pressed) and 65282 (&HFF02, the PIA chip that reads the eight keyboard columns of the keyboard matrix), it apparently relying on code in the Color BASIC ROM that is resetting things so it always updates. In the 1.1 ROM I am using, the ROM code is different so that method does not work.

However, the second bit of code (from either Jim or Ben) using the four POKEs and INKEY$ does appear to work on 1.1 as well as 1.2.

Dam this stream of consciousness!

Sorry to disappoint, but I’ll let someone else write an article explaining the differences in KEYIN. I can already see this is over my head without spending substantial time diffing through the various ROM versions…

At this point, I see WHAT works, but cannot explain WHY. In the next installment, I’ll move beyond the WHY and get back to the original goal of trying to make the WHAT work as fast as possible.

To be continued…

CoCo Notebook: mystery 6809 assembly

Welcome to the first installment of my new column, Allen’s Old CoCo Notebook. I will be sharing scans of pages from my original notebook I used during my earliest days with the Color Computer, and hopefully figuring out what all the stuff on them means.

Today’s installment deals with this page of hand written 6809 assembly:

CoCo Notebook: unknown 6809 disassembly

If memory serves, seeing the use of greater than “>” and less than “<” tells me this was a disassembly of some code, most likely done via Radio Shack’s EDTASM+ (either the cartridge that I started out with, or the disk version that I used when I got a disk drive).

But what does it do? First, let me try to transcribe it, hopefully without typos.

CLRA
COMB
NEG	<0
LDU	#16A
PULA	A,X
STA	>263C
STX	>263D
LDX	#261D
STX	>16B
LDD	<8A
STD	>2600
JMP	>AC7C
CLR	<70
STX	,S
LDX	>263F
LDA	,X+
STX	>263F
TSTA
BNE	3F3A
LDA	>263D
STA	>16A
STX	>16B
LDA	#0D
PULS	X,PC
NEG	<0
NEG	<27
…

The lack of memory addresses prevents me from knowing if this was a disassembly of a routine in the Color BASIC ROMs, but there are at least a few addresses that might offer some clues. I will consult the Color BASIC Unravelled book series and see what is at those locations.

CLRA
COMB
NEG	<0
LDU	#16A

Address $16a is indeed an addressed used by Color BASIC. It is an entry inside the RAM vector table At that location are three bytes which point to the “CONSOLE IN” input routine. Where it goes depends on what level and version of BASIC is installed. It seems to be loading that address into the U (user stack?) register of the 6809.

PULA	A,X
STA	>263C
STX	>263D

PULA A,X is pulling whatever is on the stack (pointed to by U) into the A and X register. I believe this would load the 8-bit value at $16A into A, and the following two bytes into the 16-bit X register. At the CONSOLE IN location should be a “JMP $xxxx” instruction, so this seems to be loading the JMP and address into registers.

It then stores them at $263c (JMP) and $263d. These locations are in the lower 32K address space so this is likely memory being used by some machine language program loaded into RAM — possibly its own dispatch table. That location should low look like:

$263c JMP
$263d $xx
$263e $yy

…where $xxyy is the address for CONSOLE IN in the ROM. This code is saving the current vector, likely so it can be restored when the program ends or, more likely, used for something else later.

LDX	#261D
STX	>16B

Next it loads the address $261d into register X. This is likely the address of some routine in the above-mentioned machine language program.

It stores that address at $16b, which is the address portion of the 3-byte CONSOLE IN vector entry. Thus, originally that looked like:

$16a JMP
$16b $xx
$16c $yy

…and it is replaced with:

$16a JMP
$16b $26
$16c $1d

This code is preserving where CONSOLE IN currently goes, then pointing it to a new routine. I am guessing that after this routine does whatever it does, it then calls the saved ROM CONSOLE IN code to complete the task.

I am now guessing that this may be part of a remote terminal driver — a program like REMOTE from a 1983 Rainbow magazine that would patch the input/output of BASIC to the serial port and allow for running a BBS.

LDD	<8A
STD	>2600
JMP	>AC7C

In the Color BASIC Unravelled book, the entry for memory location $8a says “PV DUMMY – THESE TWO BYTES ARE ALWAYS ZERO.” The same comment is found in Extended Color BASIC Unravelled, and Disk Extended Color BASIC Unravelled. But, for whatever reason, this code is loading these two bytes and saving them at location $2600.

It then jumps to $ac7c, which is in ROM space. That location says “THIS IS THE MAIN LOOP OF BASIC WHEN IN DIRECT MODE.” It looks like we have patched CONSOLE IN and then returned control to BASIC.

The next bit of code is an an unknown address, but since it is after a JMP it is probably the start of a new routine.

CLR	<70
STX	,S
LDX	>263F

This code sets the byte at $70 to zero. Or something. The comment for that location is “PV START OF STRING STORAGE (TOP OF FREE RAM)”.

It then saves X on the stack (or wherever the S stack pointer is pointing?), then loads X (indirect) with whatever two bytes are pointed to by $263f. Earlier we saved the CONSOLE IN entry at $263c to $263e, so this seems to be directly after those three bytes.

LDA	,X+
STX	>263F
TSTA
BNE	3F3A

Here it is loading A with a byte from X, which currently points to $263f. It then increments X by one. X must be a pointer to the next byte to be processed.

TSTA will see if A is zero. If it is not equal, it will branch (BNA) to $3f3a. This appears to be some kind of loop that goes through that memory until it finds a zero, updating the pointer along the way. I am betting this is some kind of buffer routine.

If A is zero, we continue to this next bit of code:

LDA	>263D
STA	>16A
STX	>16B
LDA	#0D
PULS	X,PC
NEG	<0
NEG	<27

A is loaded with an 8-bit value that $263d points to, which is the saved address of BASIC’s CONSOLE IN routine. It stores it at >16A, the first byte of the RAM vector for CONSOLE IN.

It then stores X at $16b and $16c. That restores the CONSOLE IN RAM vector back to what it was. This must be shutdown code.

The code that follows is probably cleanup code — though loading A with #0d (13, a carriage return) looks interesting.

…and then I stopped disassembling.

But why?

But why was I doing this? If it was code from Rainbow (REMOTE), I would have had a copy and not need to disassemble.

A quick check through the issue of Rainbow where REMOTE first appeared shows that it loaded at $3f00. The disassembly shows a branch to $3f3a, which is this in REMOTE:

REMOUT JSR RSOUT

Ah, this looks promising. And RSOUT is defined as:

RSOUT EQU $8E0C

And that address is somewhere in Extended Color BASIC. Turning to the Extended BASIC Unravelled book shows that to be “SEND CHAR IN ACCA OUT OVER RS232 OUTPUT.”

Bingo! The branch in my disassembly ends up at the CoCo ROM’s serial output routine.

It looks like I may have been disassembling REMOTE by Dan Downard. I believe it was the original version because of the date (1983) and because the next update (REMOTE2) moved its location to $7d00, requiring 32K.

Now I can look through the published source for REMOTE and find what they code was.

Moments later…

Unfortunately, I cannot find any matches for this code in the tiny ~130 byte REMOTE program. It looks like the branch location that matched up with REMOTE may have been a coincidence, or this code might have been a modified version of REMOTE. I recall at some point I customized one of the later versions (maybe after 1986) for some reason, but I had a printer by then and was not using this notebook.

Well, mystery not solved.

Do you recognize this code? Any clue what it goes to? Can you correct some of the things in my interpretation I did not understand?

Until next time…

Microsoft Visual Studio Code (PC/Mac/Linux) for CoCo cross development.

Microsoft Visual Studio Code for Mac (left) with extension to color code BASIC code for easy loading into Xroar emulator (right).

As mentioned elsewhere, there are some CoCo-related extensions available for the Microsoft Visual Studio Code editor. I did not realize that MS released this editor for Mac and Linux as well as Windows.

Here is the free editor:

https://code.visualstudio.com

Once installed, you can download (from within the editor) extensions.

Here are two extensions by Tandy UK — one for COLOUR BASIC and the other for 6309/6809 assembly:

https://marketplace.visualstudio.com/publishers/Tandy

There is another 6809 assembly extension available:

https://marketplace.visualstudio.com/items…

It colorizes files ending in .a or .asm. 

I don’t see many details about the Tandy UK ones, but I am indeed getting colorized BASIC keywords when I have a .bas file.

Interfacing assembly with BASIC via DEFUSR, part 6

See also: part 1, part 2, part 3, part 4, part 5 and part 6.

Previously, we finally got to do something semi-useful with assembly: we replaced a slow full-screen scrolling routine in BASIC with a turbo-charged assembly routine, all called via the DEFUSR command.

Today, let’s apply this concept a bit further with the shell of a Pac-Man style video game written in BASIC, but enhanced with assembly.

In my Optimizing Color BASIC, part 3 article, I set the groundwork for writing a game in BASIC that involved moving a character around the screen and detecting collisions with enemy characters. Today I will combine that with the previous maze demo and create the world’s easiest Pac-Man game (no enemies, and no bothersome dots to eat).

The Maze

A few years ago, I started toying with a video output project for the Arduino computers. I began by simply bouncing a circle around the screen and then, for some reason, turned that in to an animated Pac-Man. This led me to digging in to some wonderful websites that had reverse engineered the original Pac-Man source code to explain how everything worked. You can find the series here:

http://subethasoftware.com/2014/02/09/arduino-pac-man-project/

Although I have yet to finish the game, I learned quite a bit about how Pac-Man works, including how the ghosts behave. I don’t know if BASIC would be fast enough to handle the logic of four ghosts and all the other stuff, but it sure would be fun to try — it would be much easier to write it in BASIC than C, I think.

But I digress.

The reason I mention this series is so I can show this picture:

Pac-Man!

For the Arduino project, I started with a screen shot of the original game and downsized it to fit the low resolution, black and white Arduino TVOut graphics library. It ended up looking like this:

Arduino Pac-Man!

Pac-Man was designed on a tile system. The original game resolution was 224×288. The screen was made up of 8×8 tiles, 28 across and 36 down. Without the score lines at the top and the players left lines at the bottom, the playfield itself was 28×31. The maze tiles looked like this:

Pac-Man maze tiles.

…and since the CoCo’s screen is 32×16, if we used one character per tile, we could replicate the same horizontal dimensions, but we’d need to scroll up and down to get to all 31 lines of the maze.

I was initially working on this for a 4K programming challenge I started (and have yet to complete). Using ASCII, the make looks like this:

XXXXXXXXXXXXXXXXXXXXXXXXXXXX
X            XX            X
X XXXX XXXXX XX XXXXX XXXX X
X X  X X   X XX X   X X  X X
X XXXX XXXXX XX XXXXX XXXX X
X                          X
X XXXX XX XXXXXXXX XX XXXX X
X XXXX XX XXXXXXXX XX XXXX X
X      XX    XX    XX      X
XXXXXX XXXXX XX XXXXX XXXXXX
     X XXXXX XX XXXXX X     
     X XX          XX X     
     X XX XXX--XXX XX X     
XXXXXX XX X      X XX XXXXXX
          X      X          
XXXXXX XX X      X XX XXXXXX
     X XX XXXXXXXX XX X     
     X XX          XX X     
     X XX XXXXXXXX XX X     
XXXXXX XX XXXXXXXX XX XXXXXX
X            XX            X
X XXXX XXXXX XX XXXXX XXXX X
X XXXX XXXXX XX XXXXX XXXX X
X   XX                XX   X
XXX XX XX XXXXXXXX XX XX XXX
XXX XX XX XXXXXXXX XX XX XXX
X      XX    XX    XX      X
X XXXXXXXXXX XX XXXXXXXXXX X
X XXXXXXXXXX XX XXXXXXXXXX X
X                          X
XXXXXXXXXXXXXXXXXXXXXXXXXXXX

It may look odd presented as Xs. and the aspect ratio is different, but it’s the exact Pac-Man layout used in the arcade. Here is the full play field that will scroll on the CoCo’s 32×16 screen:

Pac-Man full maze.

Since the original Pac-Man played on a monitor that was turned sideways, it was taller than it was wider. Most home ports either shrink the screen down, or flatten it out. By scrolling, maybe we can keep the aspect ratio similar.

And this is how my ASCII Pac-Man maze came to be.

As I referenced at the top of this article, I have been covering ways to Optimize Color BASIC in another article series. A recent part discussed reading the keyboard and moving a character around the screen. I took some of this code and used it to place a character in the Pac-Man maze and move it around. I also added collision detection making sure the player could not run through any of the walls.

Today I would like to present my work-in-progress Pac-Man maze, entirely in BASIC, and the changes I made to integrate the screen moving assembly routines. The assembly calls (and all the DATA statements) are in this listing, but are commented out. The ‘commented-out ines in red are what lines to uncomment to see the assembly-enhanced version, and any line just in red is the BASIC version that would need to be commented out.

The Listing

Here is the current listing, with comments to follow explaining how it works. I have been writing this on my Mac in a text editor, then loading it in to the XRoar emulator for testing. Because of this, you will notice I put spaces between program sections to make them easier to see. When this loads in to an emulator as an ASCII program, those empty lines are ignored. It works out nice.

0 REM
1 REM      PAC-MAZE 1.00
2 REM   BY ALLEN C. HUFFMAN
3 REM WWW.SUBETHASOFTWARE.COM
4 REM
6 REM
7 REM
8 REM
9 'CLEAR200,&H3F00

10 DIM MZ$(30)

15 REM
16 REM READ MAZE IN TO ARRAY
17 REM
20 FOR A=0 TO 30:READ MZ$(A):NEXT
21 'GOSUB2000:DEFUSR0=&H3F00

25 REM
26 REM UP+DOWN+LEFT+RGHT CHARS
27 REM
30 KB$=CHR$(94)+CHR$(10)+CHR$(8)+CHR$(9)

35 REM
36 REM PLAYER/WALL/BG CHARS
37 REM
40 PC=159 'PAC-MAN CHAR
41 WC=ASC("X") 'WALL CHAR
42 BG=96 'BACKGRND CHAR

50 REM
51 REM INITIALIZATION
52 REM
60 ST=7 'SCRN START LINE
61 PM=1360 'PAC-MAN START LOC
62 DR=0 'CURRENT DIRECTION
63 DN=0 'NEXT DIRECTION

80 REM
81 REM DRAW INITIAL MAZE
82 REM
90 CLS:FOR A=0 TO 15:PRINT @A*32+2,MZ$(A+ST);:NEXT

100 REM
101 REM MAIN LOOP
102 REM
110 POKE PM,PC
120 A$=INKEY$:IF A$="" THEN 140
130 KB=INSTR(KB$,A$):IF KB=0 THEN 140 ELSE DN=KB
135 REM TRY NEXT DIRECTION
140 ON DN GOSUB 500,600,700,800
145 REM THEN TRY CURRENT DIR
150 IF DR<>DN THEN ON DR GOSUB 500,600,700,800
160 GOTO 100

500 REM
501 REM UP
502 REM
510 IF PEEK(PM-32)<>BG THEN RETURN
520 POKE PM,BG:DR=1
530 IF PM<1183 AND ST>0 THEN ST=ST-1:GOSUB 950 ELSE PM=PM-32
540 RETURN

600 REM
601 REM DOWN
602 REM
610 IF PEEK(PM+32)<>BG THEN RETURN
620 POKE PM,BG:DR=2
630 IF PM>1376 AND ST<15 THEN ST=ST+1:GOSUB 900 ELSE PM=PM+32
640 RETURN

700 REM
701 REM LEFT
702 REM
710 IF PEEK(PM-1)<>BG THEN RETURN
720 POKE PM,BG:DR=3
730 PM=PM-1:RETURN

800 REM
801 REM RIGHT
802 REM
810 IF PEEK(PM+1)<>BG THEN RETURN
820 POKE PM,BG:DR=4
830 PM=PM+1:RETURN

900 REM
901 REM SCROLL SCREEN UP
902 REM
910 FOR A=0 TO 15:PRINT @A*32+2,MZ$(A+ST);:NEXT
915 'Z=USR0(1):PRINT@482,MZ$(ST+15);
920 RETURN

950 REM
951 REM SCROLL SCREEN DOWN
952 REM
960 FOR A=0 TO 15:PRINT @A*32+2,MZ$(A+ST);:NEXT
965 'Z=USR0(2):PRINT@2,MZ$(ST);
970 RETURN

999 GOTO 999

1000 DATA "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"
1001 DATA "X            XX            X"
1002 DATA "X XXXX XXXXX XX XXXXX XXXX X"
1003 DATA "X X  X X   X XX X   X X  X X"
1004 DATA "X XXXX XXXXX XX XXXXX XXXX X"
1005 DATA "X                          X"
1006 DATA "X XXXX XX XXXXXXXX XX XXXX X"
1007 DATA "X XXXX XX XXXXXXXX XX XXXX X"
1008 DATA "X      XX    XX    XX      X"
1009 DATA "XXXXXX XXXXX XX XXXXX XXXXXX"
1010 DATA "     X XXXXX XX XXXXX X     "
1011 DATA "     X XX          XX X     "
1012 DATA "     X XX XXX--XXX XX X     "
1013 DATA "XXXXXX XX X      X XX XXXXXX"
1014 DATA "<         X      X         >"
1015 DATA "XXXXXX XX X      X XX XXXXXX"
1016 DATA "     X XX XXXXXXXX XX X     "
1017 DATA "     X XX          XX X     "
1018 DATA "     X XX XXXXXXXX XX X     "
1019 DATA "XXXXXX XX XXXXXXXX XX XXXXXX"
1020 DATA "X            XX            X"
1021 DATA "X XXXX XXXXX XX XXXXX XXXX X"
1022 DATA "X XXXX XXXXX XX XXXXX XXXX X"
1023 DATA "X   XX                XX   X"
1024 DATA "XXX XX XX XXXXXXXX XX XX XXX"
1025 DATA "XXX XX XX XXXXXXXX XX XX XXX"
1026 DATA "X      XX    XX    XX      X"
1027 DATA "X XXXXXXXXXX XX XXXXXXXXXX X"
1028 DATA "X XXXXXXXXXX XX XXXXXXXXXX X"
1029 DATA "X                          X"
1030 DATA "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"
1100 REM
1101 REM MAZE ARRAY TO GRAPHICS
1102 REM
1110 FOR R=0 TO 30
1120 DIM P,PL,PS,C:P=VARPTR(MZ$(R))
1130 PL=PEEK(P):PS=PEEK(P+2)*256+PEEK(P+3)
1140 FOR C=PS TO PS+PL-1
1150 PRINT CHR$(PEEK(C));
1155 IF PEEK(C)=ASC("X") THEN POKEC,175
1160 NEXT:PRINT
1170 NEXT

2000 REM
2001 REM LOAD ASSEMBLY ROUTINE
2002
2010 READ A,B
2020 IF A=-1 THEN 2070
2030 FOR C = A TO B
2040 READ D:POKE C,D
2050 NEXT C
2060 GOTO 2010
2070 RETURN 'END
2080 DATA 16128,16217,189,179,237,90,39,14,90,39,28,90,39,42,90,39,55,204,255,255,32,67,142,4,32,166,132,167,136,224,48,1,140,5,255,47,244,32,47,142,5,223,166,132,167,136,32,48,31,140,4,0,44,244,32,30,142,4,1,166,132,167,31,48,1,140,5,255,47,245,32,14
2090 DATA 142,5,254,166,132,167,1,48,31,140,4,0,44,245,204,0,0,126,180,244,-1,-1

As listed, this will do the game entirely in BASIC. Using the arrow keys, you can move around the yellow PAC-BLOCK and explore the maze. When you get near the top or bottom of the maze, the screen will sluggishly scroll so you can access the rest of the maze.

Give that a try and explore the top and bottom of the maze so you can get an idea of the speed BASIC scrolls at.

Then, to make it use the assembly language routines:

  1. Uncomment line 9. This protects memory beyond &H3F00 for the assembly language code.
  2. Uncomment line 21. This will GOSUB to the routine that reads in the assembly language and POKEs it in to memory starting at &H3F00.
  3. Comment line 910 (BASIC redraw/scroll up code).
  4. Uncomment line 915. This calls the assembly routine to scroll the screen up, then redraws a new line at the bottom.
  5. Comment line 960 (BASIC redraw/scroll down code).
  6. Uncomment line 965. This calls the assembly routine to scroll the screen down, then redraws a new line at the top.

Make those changes and re-run the program then move from top to bottom and see how much faster the scree “scrolls.”

Assembly!

And, the assembly could be made almost twice as fast, and the BASIC code could be optimized to be faster, too.

But before we do that, let’s dig in to how the code actually works.

Dissection

  • 20-21 read in all the maze lines in to an array called MZ$. The maze strings are in the DATA statements starting at line 1000.
  • 30 builds a string that contains the ASCII characters for Up, Down, Left and Right. It is much faster to use INSTR and parse through a string rather than have to build one with CHR$() inside the INSTR call every time.
  • 40-42 sets some default variables:
    • PC is the character of Pac-Man to POKE to the screen (159 is a yellow block).
    • WC is what character to use for wall detection (an ASCII “X” letter). The move code will PEEK screen memory, and not let you move in any direction that contains an “X”.
    • BG is the background character (a space) that will be used to erase Pac-Man before moving him.
  • 60-63 initialize some game play variables:
    • ST is which of the 31 lines of the maze should be the first line to display. Thus, ST=7 means we will initially draw lines 7-22 on the screen to display that middle section of the maze.
    • PM is the memory location where Pac-Man will be POKEd. The screen memory starts at 1024, so this default is somewhere in the middle of the screen under the ghost house.
    • DR is the direction Pac-Man is currently moving.
    • DN is the next direction the Pac-Man will try to move at an intersection. Like the arcade, this version will let you press UP while Pac-Man is moving left, and as soon as there is an opening in the wall, the direction will turn UP.
  • 90 draws the initial 16 maze lines that will fit on the screen.
  • 110 POKEs the Pac-Man character on to the screen (showing the yellow block).
  • 120-130 wait for one of the four keys in KB$ (up, down, left or right) to be pressed. If no key is pressed, it skips to line 140, else it sets DN (direction next) to match the key that was pressed.
  • 140 uses DN (next direction) to call a routine to try to move Pac-Man up, down, left or right.
  • 150 assumes that if DN and DR don’t match, a new direction has been pressed, so it will use DR (current direction) to call the up, down, left or right routine.
  • 160 goes back to 100 to keep doing this forever.
  • 510 is the UP routine. It will PEEK the memory location 32 bytes higher in memory (one line up from the current Pac-Man PM location) and if it is NOT the background character (ie, not some place we can move), it returns.
  • 520 POKEs the background character where Pac-Man is, erasing him, then sets DR (direction) to 1 for up.
  • 530 checks to see if the Pac-Man location is before a certain spot on the screen and that the screen is starting at a line later than the first one. If so, then the screen is allowed to scroll up (start line ST is decremented). A GOSUB to 950 will handle scrolling the screen. Otherwise, we don’t need to scroll and can just subtract 32 from the Pac-Man location, moving him up one line.
  • 540 returns us back to the main loop.
  • 610-640 is the same process for moving Pac-Man down, but we check for locations at the bottom of the screen and memory +32 from Pac-Man.
  • 710-730 is the same code for moving Pac-Man left. We never scroll left or right so we don’t have to do as much here.
  • 810-830 is the same code for moving Pac-Man right.
  • 910-920 is the routine to scroll the screen up:
    • 910 scrolls the screen up in BASIC by redrawing all 16 lines of the maze.
    • 915 uses the assembly language routine to move the screen up, then PRINTs the next line at the bottom that would be displayed.
  • 960-970 are the same thing for scrolling down.
  • 1000-1030 is the 31 line maze.
  • 2000-2090 is the assembly language loader generated by lwasm and renumbered to fix. It READs in the assembly from DATA statements and POKEs it in to memory.

Baby steps.

Next time, let’s improve this a bit.

Interfacing assembly with BASIC via DEFUSR, part 5

See also: part 1, part 2, part 3, part 4, part 5 and part 6.

Now that I’ve gotten my digressions with BASIC variable access speeds and input speeds and INKEY/INSTR and GOTO/GOSUB speeds and HEX versus DECimal speeds out of the way, I can finally get back to digressing on using assembly language to speed up BASIC.

Where were we?

Oh, right…

In part 4 of this article I presented an example of using BASIC to scroll a PAC-MAN style maze that was too tall to fit on 16 the line screen.

I also presented some assembly code that would scroll the screen much faster than BASIC could ever hope to.

Today, let’s combine these two items and try to create a fast-scrolling maze playfield.

Let’s get started!

START SHOUTING AT ME! (revisited)

But before we get started, let’s revisit the uppercase routine I presented in Part 3.

Simon Jonassen is well-known in the CoCo Community for doing some amazing things on the original CoCo 1 and 2 hardware (and, lately, the CoCo 3 as well). He is quite the master of optimization, and has created some stunning sound players that allow the original CoCo to have cool background music while doing other things (if only the game programmers of 1980 knew about this!). He also has a cool web-based CoCo semigraphics editor. He provided a few enhancements:

* UCASE.ASM v1.01
* by Allen C. Huffman of Sub-Etha Software
* www.subethasoftware.com / alsplace@pobox.com
*
* 1.01 a bit smaller per Simon Jonassen
*
* DEFUSRx() uppercase output function
*
* INPUT:   VARPTR of a string
* RETURNS: # chars processed
*
* EXAMPLE:
*   CLEAR 200,&H3F00
*   DEFUSR0=&H3F00
*   A$="Print this in uppercase."
*   PRINT A$
*   A=USR0(VARPTR(A$))
*
ORGADDR     EQU     $3f00

GIVABF      EQU     $B4F4   * 46324
INTCNV      EQU     $B3ED   * 46061
CHROUT      EQU     $A002

            opt	    6809    * 6809 instructions only
            opt	    cd      * cycle counting

            org     ORGADDR

start       jsr     INTCNV  * get passed in value in D
            tfr     d,x     * move value (varptr) to X
            ldy     2,x     * load string addr to Y
;           ldb     ,x      * load string len to B
            beq     null    * exit if strlen is 0
            ldb     ,x      * load string len to B
            ldx     #0      * clear X (count of chars conv)

loop        lda     ,y+	    * get next char, inc Y
;           lda     ,y      * load char in A
            cmpa    #'a     * compare to lowercase A
            blt     nextch  * if less, no conv needed
            cmpa    #'z     * compare to lowercase Z
            bgt     nextch  * if greater, no conv needed
lcase       suba    #32     * subtract 32 to make uppercase
            leax    1,x     * inc count of chars converted
nextch      jsr     [CHROUT] * call ROM output character routine
;           leay    1,y     * increment Y pointer
cont        decb            * decrement counter
            bne	    loop    * not done yet
;           beq     exit    * if 0, go to exit
;           bra     loop    * go to loop

exit        tfr     x,d     * move chars conv count to D
;           bra     return
            jmp     GIVABF  * return to caller

null        ldd     #-1     * load -2 as error
return      jmp     GIVABF  * return to caller

* lwasm --decb -o ucase2.bin ucase2.asm -l
* lwasm --decb -f basic -o ucase2.bas ucase2.asm -l
* lwasm --decb -f ihex -o ucase2.hex ucase2.asm -l
* decb copy -2 -r ucase2.bin ../Xroar/dsk/DRIVE0.DSK,UCASE2.BIN

This code is 46 bytes long, compared to my original which was 49 bytes. The changes are:

  1. Move the initial LDB with string length to after the string length check, since it’s only needed if we get past that check and have a string.
  2. Change my LDA ,Y to LDA ,Y+ to increment Y there and not need the LEAY 1,Y later.
  3. Changed my “characters left” check from BEQ EXIT and BRA LOOP to BNE LOOP since it can just fall through and continue otherwise.
  4. Change a BRA RETURN to JMP GIVABF, since the branch would just end up at a JMP, and doing a JMP is faster than branching to a JMP.

Minor changes, but every little bit helps.

Simon also pointed out an embarrassing oversight in my very first example shown in part 1:

ORGADDR EQU $3f00

GIVABF EQU $B4F4   * 46324
INTCNV EQU $B3ED   * 46061

       org  ORGADDR
start  jsr  INTCNV * get passed in value in D
       tfr  d,x    * transfer D to X so we can manipulate it
       leax 1,x    * add 1 to X
       tfr  x,d    * transfer X back to D
return jmp  GIVABF * return to caller

He reminded me about the “addd” instruction which can add to D. For some reason, I was thinking I needed to use LEA to add to a 16-bit register, and since “LEAD 1,D” wasn’t a thing, I did the whole transfer to X, add one to X, transfer back to D thing.

He said I should just do this:

* ADDONE.ASM v1.01
* by Allen C. Huffman of Sub-Etha Software
* www.subethasoftware.com / alsplace@pobox.com
*
* 1.01 made less stupid per Simon Jonassen
*
* DEFUSRx() add one routine
*
* INPUT:   integer to add one to
* RETURNS: value +1
*
* EXAMPLE:
*   CLEAR 200,&H3F00
*   DEFUSR0=&H3F00
*   A=USR0(42)
*   PRINT A
*
ORGADDR EQU     $3f00

INTCNV  EQU     $B3ED   * 46061
GIVABF  EQU     $B4F4   * 46324

        org     ORGADDR

start   jsr     INTCNV  * get passed in value in D
;       tfr     d,x     * transfer D to X so we can manipulate it
;       leax    1,x     * add 1 to X
;       tfr     x,d     * transfer X back to D
        addd    #1      * add 1 to D
return  jmp     GIVABF  * return to caller

* lwasm --decb -o -9 addone2.bin addone2.asm
* lwasm --decb -f basic -o addone2.bas
* decb copy -2 -r addone2.bin ../Xroar/dsk/DRIVE0.DSK,ADDONE2.BIN

See what happens when people who actually know 6809 assembly language look at my code? Thanks, Simon!

Moving Day

My simple examples have been building up to slight less-simple ones that do something more useful, like moving data that would take days to move in BASIC. Previously, I presented a PAC-MAN maze that could “scroll” up and down the screen by PRINTing the whole screen each time with just the lines of the maze that should be visible. I also presented some assembly code that could be used to move the screen up, down, left or right.

Today, the first thing I want to do is integrate that assembly routine in to the PAC-MAN maze code. Instead of redrawing the entire screen each time, BASIC will only need to redraw the top or bottom line depending on which was the screen just scrolled. If my math is correct, printing one line instead of sixteen lines should be at least twice faster.

First, let’s revisit the screen moving assembly code, which, thanks to comments from L. Curtis Boyle, now has a smarter routine for checking which direction the user passed in to scroll (though it could still be thrown off by values larger than 255):

* SCRNMOVE.ASM v1.01
* by Allen C. Huffman of Sub-Etha Software
* www.subethasoftware.com / alsplace@pobox.com
*
* DEFUSRx() screen moving function
*
* INPUT:   direction (1=up, 2=down, 3=left, 4=right)
* RETURNS: 0 on success
*         -1 if invalid direction
*
* 1.01 better param parsing per L. Curtis Boyle
*
* EXAMPLE:
*   CLEAR 200,&H3F00
*   DEFUSR0=&H3F00
*   A=USR0(1)
*
ORGADDR EQU     $3f00

INTCNV  EQU     $B3ED   * 46061
GIVABF  EQU     $B4F4   * 46324

UP      EQU     1
DOWN    EQU     2
LEFT    EQU     3
RIGHT   EQU     4
SCREEN  EQU     1024    * top left of screen
END     EQU     1535    * bottom right of screen

        org     ORGADDR

start   jsr     INTCNV  * get incoming param in D
;       cmpb    #UP
        decb            * decrement B
        beq     up      * if one DEC got us to zero
;       cmpb    #DOWN
        decb            * decrement B
        beq     down    * if two DECs...
;       cmpb    #LEFT
        decb            * decrement B
        beq     left    * if three DECs...
;       cmpb    #RIGHT
        decb            * decrement B
        beq     right   * if four DECs...
error   ldd     #-1     * load D with -1 for error code
        bra     exit

up      ldx     #SCREEN+32
loopup  lda     ,x
        sta     -32,x
        leax    1,x
        cmpx    #END
        ble     loopup
        bra     return

down    ldx     #END-32
loopdown lda    ,x
        sta     32,x
        leax    -1,x
        cmpx    #SCREEN
        bge     loopdown
        bra     return

left    ldx     #SCREEN+1
loopleft lda    ,x
        sta     -1,x
        leax    1,x
        cmpx    #END
        ble     loopleft
        bra     return

right   ldx     #END-1
loopright lda   ,x
        sta     1,x
        leax    -1,x
        cmpx    #SCREEN
        bge     loopright
    
return  ldd     #0      * return code (0=success)
exit    jmp     GIVABF  * return to BASIC

* lwasm --decb -9 -o scrnmove2.bin scrnmove2.asm
* lwasm --decb -f basic -o scrnmove2.bas scrnmove2.asm
* decb copy -2 -r scrnmove2.bin ../Xroar/dsk/DRIVE0.DSK,SCRNMOVE2.BIN

The generated BASIC program looks like:

10 READ A,B
20 IF A=-1 THEN 70
30 FOR C = A TO B
40 READ D:POKE C,D
50 NEXT C
60 GOTO 10
70 END
80 DATA 16128,16217,189,179,237,90,39,14,90,39,28,90,39,42,90,39,55,204,255,255,32,67,142,4,32,166,132,167,136,224,48,1,140,5,255,47,244,32,47,142,5,223,166,132,167,136,32,48,31,140,4,0,44,244,32,30,142,4,1,166,132,167,31,48,1,140,5,255,47,245,32,14
90 DATA 142,5,254,166,132,167,1,48,31,140,4,0,44,245,204,0,0,126,180,244,-1,-1

Let’s take the original maze program and modify it to use the assembly routines instead:

0 REM MAZETEST.BAS
10 DIM MZ$(31)
20 FOR A=0 TO 30:READ MZ$(A):NEXT
30 CLS
40 REM SCROLL MAZE DOWN
50 FOR ST=0 TO 15
60 FOR LN=0 TO 15
70 PRINT @LN*32,MZ$(LN+ST);
80 NEXT:NEXT
90 REM SCROLL MAZE UP
100 FOR ST=15 TO 0 STEP-1
110 FOR LN=0 TO 15
120 PRINT @LN*32,MZ$(LN+ST);
130 NEXT:NEXT
140 GOTO 40
999 GOTO 999
1000 DATA "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"    
1010 DATA "X            XX            X"    
1020 DATA "X XXXX XXXXX XX XXXXX XXXX X"    
1030 DATA "X XXXX XXXXX XX XXXXX XXXX X"    
1040 DATA "X XXXX XXXXX XX XXXXX XXXX X"    
1050 DATA "X                          X"
1060 DATA "X XXXX XX XXXXXXXX XX XXXX X"   
1070 DATA "X XXXX XX XXXXXXXX XX XXXX X"    
1080 DATA "X      XX    XX    XX      X"   
1090 DATA "XXXXXX XXXXX XX XXXXX XXXXXX"    
2100 DATA "     X XXXXX XX XXXXX X     "    
2110 DATA "     X XX          XX X     "    
2120 DATA "     X XX XXXXXXXX XX X     "   
2130 DATA "XXXXXX XX X      X XX XXXXXX"   
2140 DATA "          X      X          "   
2150 DATA "XXXXXX XX X      X XX XXXXXX"   
2160 DATA "     X XX XXXXXXXX XX X     "   
2170 DATA "     X XX          XX X     "   
2180 DATA "     X XX XXXXXXXX XX X     "   
2190 DATA "XXXXXX XX XXXXXXXX XX XXXXXX"   
3200 DATA "X            XX            X"   
3210 DATA "X XXXX XXXXX XX XXXXX XXXX X"   
3220 DATA "X XXXX XXXXX XX XXXXX XXXX X"   
3230 DATA "X   XX                XX   X"   
3240 DATA "XXX XX XX XXXXXXXX XX XX XXX"   
3250 DATA "XXX XX XX XXXXXXXX XX XX XXX"   
3260 DATA "X      XX    XX    XX      X"   
3270 DATA "X XXXXXXXXXX XX XXXXXXXXXX X"   
3280 DATA "X XXXXXXXXXX XX XXXXXXXXXX X"   
3290 DATA "X                          X"   
4200 DATA "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"

The maze is 31 lines tall. The fake scrolling is done by redrawing the entire screen line-by-line. The screen is 16 lines tall, so initially we draw maze lines 0-15. Then we redraw maze lines 1-16, giving the appearance that the screen is scrolling up and a line has scrolled off the top of the screen. This repeats for lines 2-17, 3-18 and so on until we’ve drawn the last 16 lines of 15-30.

After “scrolling” all the way to the bottom of the maze, a second block of FOR/NEXT loops reverses the process, starting with maze lines 15-30, then 15-30 and so on until it is back to displaying the top lines 0-15.

The scrolling is done by the FOR/NEXT loops using the LN variables in lines 60-80 and 110-130.

Rather than redrawing all sixteen lines each time, we could use the assembly routine to move the screen, and then we’d just draw one line – top or bottom, depending on which was the screen scrolled.

In effect, we’d replace this:

40 REM SCROLL MAZE DOWN
50 FOR ST=0 TO 15
60 FOR LN=0 TO 15
70 PRINT @LN*32,MZ$(LN+ST);
80 NEXT:NEXT
90 REM SCROLL MAZE UP
100 FOR ST=15 TO 0 STEP-1
110 FOR LN=0 TO 15
120 PRINT @LN*32,MZ$(LN+ST);
130 NEXT:NEXT

…with this:

35 FOR LN=0 TO 15:PRINT @LN*32,MZ$(LN+ST);:NEXT
40 REM SCROLL MAZE DOWN
50 FOR ST=0 TO 15
60 Z=USR0(1)
70 PRINT @480,MZ$(ST+15);
80 NEXT
90 REM SCROLL MAZE UP
100 FOR ST=15 TO 0 STEP-1
110 Z=USR0(2)
120 PRINT @0,MZ$(ST);
130 NEXT

Line 35 was added to initially draw the screen. After that, the assembly routine can move it up or down, and let BASIC redraw just the one line that needs to be drawn.

This, of course, requires the assembly routine to be loaded. We can take the BASIC loader of that and renumber it so we can call it from our test program. Here is the scrnmove2.asm updated code from the top of this article, renumbered and changed in to a subroutine:

5000 REM ASSEMBLY ROUTINE
5010 READ A,B
5020 IF A=-1 THEN 5070
5030 FOR C = A TO B
5040 READ D:POKE C,D
5050 NEXT C
5060 GOTO 5010
5070 RETURN
5080 DATA 16128,16217,189,179,237,90,39,14,90,39,28,90,39,42,90,39,55,204,255,255,32,67,142,4,32,166,132,167,136,224,48,1,140,5,255,47,244,32,47,142,5,223,166,132,167,136,32,48,31,140,4,0,44,244,32,30,142,4,1,166,132,167,31,48,1,140,5,255,47,245,32,14
5090 DATA 142,5,254,166,132,167,1,48,31,140,4,0,44,245,204,0,0,126,180,244,-1,-1

Now, I can add this to the end of the mazetest.bas program and set it up so the USR0() calls will work:

10 CLEAR 200,&H3F00:DIM MZ$(31)
25 GOSUB5000:DEFUSR0=&H3F00

Now the program will use the CLEAR command to protect memory starting at &H3F00 (where the assembly will load), then after it reads all the maze strings in to memory (those DATA statements appear first), it will GOSUB 5000 and that READs the assembly code statements and POKEs them in to memory starting at &H3F00. The DEFUSR call is then done to make USR0(x) work.

With just a few lines changed, and getting our assembly routine in memory, now the maze scrolling is very fast! And, if we optimized the BASIC code around it, it could be even faster since most of the time is spent processing the BASIC program.

Here is the full listing:

0 REM MAZETST2.BAS - W/ASM!
10 CLEAR 200,&H3F00:DIM MZ$(31)
20 FOR A=0 TO 30:READ MZ$(A):NEXT
25 GOSUB5000:DEFUSR0=&H3F00
30 CLS

40 REM SCROLL MAZE DOWN
50 FOR ST=0 TO 15
60 FOR LN=0 TO 15
70 PRINT @LN*32,MZ$(LN+ST);
80 NEXT:NEXT
90 REM SCROLL MAZE UP
100 FOR ST=15 TO 0 STEP-1
110 FOR LN=0 TO 15
120 PRINT @LN*32,MZ$(LN+ST);
130 NEXT:NEXT

35 FOR LN=0 TO 15:PRINT @LN*32,MZ$(LN+ST);:NEXT
40 REM SCROLL MAZE DOWN
50 FOR ST=0 TO 15
60 Z=USR0(1)
70 PRINT @480,MZ$(ST+15);
80 NEXT
90 REM SCROLL MAZE UP
100 FOR ST=15 TO 0 STEP-1
110 Z=USR0(2)
120 PRINT @0,MZ$(ST);
130 NEXT
140 GOTO 40
999 GOTO 999
1000 DATA "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"    
1010 DATA "X            XX            X"    
1020 DATA "X XXXX XXXXX XX XXXXX XXXX X"    
1030 DATA "X XXXX XXXXX XX XXXXX XXXX X"    
1040 DATA "X XXXX XXXXX XX XXXXX XXXX X"    
1050 DATA "X                          X"
1060 DATA "X XXXX XX XXXXXXXX XX XXXX X"   
1070 DATA "X XXXX XX XXXXXXXX XX XXXX X"    
1080 DATA "X      XX    XX    XX      X"   
1090 DATA "XXXXXX XXXXX XX XXXXX XXXXXX"    
2100 DATA "     X XXXXX XX XXXXX X     "    
2110 DATA "     X XX          XX X     "    
2120 DATA "     X XX XXXXXXXX XX X     "   
2130 DATA "XXXXXX XX X      X XX XXXXXX"   
2140 DATA "          X      X          "   
2150 DATA "XXXXXX XX X      X XX XXXXXX"   
2160 DATA "     X XX XXXXXXXX XX X     "   
2170 DATA "     X XX          XX X     "   
2180 DATA "     X XX XXXXXXXX XX X     "   
2190 DATA "XXXXXX XX XXXXXXXX XX XXXXXX"   
3200 DATA "X            XX            X"   
3210 DATA "X XXXX XXXXX XX XXXXX XXXX X"   
3220 DATA "X XXXX XXXXX XX XXXXX XXXX X"   
3230 DATA "X   XX                XX   X"   
3240 DATA "XXX XX XX XXXXXXXX XX XX XXX"   
3250 DATA "XXX XX XX XXXXXXXX XX XX XXX"   
3260 DATA "X      XX    XX    XX      X"   
3270 DATA "X XXXXXXXXXX XX XXXXXXXXXX X"   
3280 DATA "X XXXXXXXXXX XX XXXXXXXXXX X"   
3290 DATA "X                          X"   
4200 DATA "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"

5000 REM ASSEMBLY ROUTINE
5010 READ A,B
5020 IF A=-1 THEN 5070
5030 FOR C = A TO B
5040 READ D:POKE C,D
5050 NEXT C
5060 GOTO 5010
5070 RETURN
5080 DATA 16128,16217,189,179,237,90,39,14,90,39,28,90,39,42,90,39,55,204,255,255,32,67,142,4,32,166,132,167,136,224,48,1,140,5,255,47,244,32,47,142,5,223,166,132,167,136,32,48,31,140,4,0,44,244,32,30,142,4,1,166,132,167,31,48,1,140,5,255,47,245,32,14
5090 DATA 142,5,254,166,132,167,1,48,31,140,4,0,44,245,204,0,0,126,180,244,-1,-1

Try the original BASIC-only version and then this new assembly-enhanced version and see what you think.

Next time, I will share a version of this scrolling maze that has a character you can control and move through the maze.

Until then…

Interfacing assembly with BASIC via DEFUSR, part 4

See also: part 1, part 2, part 3, part 4, part 5 and part 6.

Before we get started, a few comments from the previous installment.

JohnStrong (StrongWare) chimed in on Facebook with another improvement to the screen clearing assembly code. He suggested using a 16-bit register to blast bytes to the screen instead of doing it 8-bits at a time. It looks like this:

* CLEARX.ASM v1.02
* by Allen C. Huffman of Sub-Etha Software
* www.subethasoftware.com / alsplace@pobox.com
*
* 1.01 use TSTA instead of CMPD per L. Curtis Boyle
* 1.02 use STDD for 16-bit copy per John Strong
*
* DEFUSRx() clear screen to character routine
*
* INPUT:   ASCII character to clear screen to
* RETURNS: 0 is successful
*         -1 if error
*
* EXAMPLE:
*   CLEAR 200,&H3F00
*   DEFUSR0=&H3F00
*   A=USR0(42)
*   PRINT A
*
ORGADDR EQU $3f00

INTCNV EQU $B3ED   * 46061
GIVABF EQU $B4F4   * 46324

       org  ORGADDR
start  jsr  INTCNV * get passed in value in D
                   * D is made up of A and B, so if
                   * A has anything in it, it must be
		   * greater than 255.
       tsta        * test for zero
       bne  error  * branch if it is not zero
       ldx  #$400  * load X with start of 
loop   stb  ,x+    * store B register at X and increment X
       tfr  b,a    * transfer B to A
loop   std  ,x++   * store D (A and B) then increment X twice
       cmpx #$600  * compare X to end of screen
       bne  loop   * if not there, keep looping
       bra  return * done
error  ldd  #-1    * load D with -1 for error code
return jmp  GIVABF * return to caller

* lwasm --decb -o clearx3.bin clearx3.asm
* lwasm --decb -f basic -o clearx3.bas clearx3.asm
* decb copy -2 -r clearx3.bin ../Xroar/dsk/DRIVE0.DSK,CLEARX3.BIN

This change takes the byte to clear the screen to (in register B) and duplicates it (to register A), which affects the 16-bit register D since it is made up of A and B combined. Thus, if the desired byte is $2A (42), register D ends up being $2A2A.

Then, instead of copying one byte at a time, the loop copies two bytes at a time. The end result should be a faster screen clear. This ends up being two bytes larger than the second version, but still one byte smaller than my original version:

clearx.hex:
:103F0000BDB3ED108300FF2E0C8E0400E7808C06FD
:0B3F10000026F92003CCFFFF7EB4F474

clearx2.hex:
:103F0000BDB3ED4D260C8E0400E7808C060026F92B
:083F10002003CCFFFF7EB4F496

clearx3.hex
:103F0000BDB3ED4D260E8E04001F98ED818C06008A
:0A3F100026F92003CCFFFF7EB4F475

Cool! Thanks, John.

Meanwhile, back at the article…

So far, we have looked at interfacing assembly language with BASIC to do some useless things (add one to a number), questionably useful things (clear screen to any given character), and actually useful things (high speed uppercasing of text).

In this installment, we will try to do something else actually useful: move the screen around.

But first, let me digress a bit.

The cross compiler I use, lwtools by Lost Wizard Enterprises, is able to compile code to run under COLOR BASIC or OS-9/NitrOS-9. It also has some other options I just learned about (thanks, William!) that I wanted to mention.

Previously, I shared a small bit of assembly that would clear the 32-column screen to any specified character:

ORGADDR EQU $3f00

GIVABF EQU  $B4F4  * 46324
INTCNV EQU  $B3ED  * 46061

       org  ORGADDR
start  jsr  INTCNV * get passed in value in D
                   * D is made up of A and B, so if
                   * A has anything in it, it must be
                   * greater than 255.
       tsta        * test for zero
       bne  error  * branch if it is not zero
       ldx  #$400  * load X with start of screen
loop   stb  ,x+    * store B register at X and increment X
       cmpx #$600  * compare X to end of screen
       bne  loop   * if not there, keep looping
       bra  return * done
error  ldd  #-1    * load D with -1 for error code
return jmp  GIVABF * return to caller

NOTE: This article is using version 2, from the previous article, and does not include John Strong’s updates.

I have been compiling these to .BIN files, copying them over to a disk image, and then loading them in the XRoar emulator. It turns out, the lwasm also has another output option: BASIC. It will actually generate a short BASIC program that will POKE that assembly code in to memory! You use the format (-f) option like this:

lwasm --decb -f basic -o clearx2.bas clearx2.asm

This would assemble clearx2.asm and output it as a BASIC program! It looks like this:

10 READ A,B
20 IF A=-1 THEN 70
30 FOR C = A TO B
40 READ D:POKE C,D
50 NEXT C
60 GOTO 10
70 END
80 DATA 16128,16151,189,179,237,77,38,12,142,4,0,231,128,140,6,0,38,249,32,3,204
,255,255,126,180,244,-1,-1

The assembly is turned in to data statements, and it appears this is even capable of handling programs with multiple ORG statements. The DATA begins with the start memory location and the end memory location for a block of code, and then the actual code bytes. Clever.

This would be an easy way to add assembly code to your BASIC program without needing to LOADM/CLOADM a separate .BIN file. It will also give us a simple way to test this code in the XRoar emulator without copying files to a disk image (more on this in a moment).

But I digress.

Scroll With It, Baby

In all the examples I have shown so far, any parameter passed in was used to do something — a value to add to, a character to clear the screen to, or a string to print in uppercase.

The USR command allows for up to 10 functions to be defined (USR0 through USR9). This lets you easily have ten different assembly routines to call. However, you can also just use the parameter passed in to handle multiple functions.

Suppose you wanted to write a simple maze game using the 32 column text screen. You could limit your maze to be 32×16 (the size of the screen), or you could try to have a much larger maze and scroll it within the viewable screen area.

Scrolling UP is easy  … you just print something at the bottom of the screen, and BASIC moves the whole screen up. Try this:

10 PRINT TAB(RND(30));".":GOTO 10

That code will tab over a random number of spaces (0 to 30) and print a period. Over and over and over. If you run this, you see a cheesy scrolling star field (if stars were black and space was nuclear green).

Scrolling stars!

There was a famous Commodore BASIC program that did something similar using the PETASCII slash characters to generate a maze. There has even been an entire book written about this one liner:

*** COMMODORE 64 CODE ***
10 PRINT CHR$(205.5+RND(1)); : GOTO 10

The CoCo does not have the Commodore character set, but we do have “/” and “\” so we could try this:

10 PRINT CHR$(47+(RND(2)-1)*45);:GOTO 10

This will print either CHR$(47) (a slash) or randomly add 45 to print CHR$(92) (a backslash). We get a similar endless maze that scrolls up, but doesn’t look nearly as nice as the one on the Commodore.

Scrolling maze… Sorta.

See? Easy.

I expect I wasn’t the only kid who wrote simple space games like this, with the ship at the top and objects traveling up the screen towards it.

I think I may be digressing again, so let me get back to the main point.

If we wanted to scroll in the other direction, we could try to do it in BASIC by copying every byte down one line. Here is an attempt to do that by using PEEK and POKE:

10 CLS
20 REM SCROLL UP
30 FOR A=1 TO 100
40 PRINT TAB(RND(30));"."
50 NEXT
60 REM SCROLL DOWN
70 FOR A=1 TO 100
80 PRINT@0,TAB(RND(30));"."
90 GOSUB 2000 'DOWN
100 NEXT
999 GOTO 999
2000 REM SCROLL DOWN
2010 FOR Z=1535-32 TO 1024 STEP-1
2020 POKE Z+32,PEEK(Z)
2030 NEXT
2040 RETURN

XROAR TIP: If you want to try this out in the XRoar emulator, save the above listing out as a text file with the extension of .asc (“scrolldown.asc”). If you do that, in XRoar you can do “File -> Load” and point it to this file. Then, that file will act like a cassette with an ASCII program on it! You can then type “CLOAD” and load the program, without needing to transfer it to a disk image.

This program will let the stars scroll up the screen (100 lines worth) using normal PRINT, then it will try to make them scroll down the screen (100 times) using a PEEK/POKE subroutine.

Scrolling down is painfully slow this way. You can see this would be no way to write a game.

Side Note: If I were trying to write a “space ship flying through space” game, I would just draw the individual stars and other objects, moving them each time, instead of redrawing the entire screen. But that’s not the point of this silly code.

And, if we wanted to also scroll the screen left and right, we’d need similar (and painfully slow) code. Here is a brute-force BASIC program that attempts to move the screen in each direction using POKE and PEEK:

10 CLS
20 FOR A=1 TO 14
30 PRINT @32*A+A,"SCROLLING IS HARD"
40 NEXT
50 GOSUB 1000 'UP
60 GOSUB 2000 'DOWN
70 GOSUB 3000 'LEFT
80 GOSUB 4000 'RIGHT
999 GOTO 999
1000 REM SCROLL UP
1010 FOR A=1024+32 TO 1535
1020 POKE A-32,PEEK(A)
1030 NEXT
1040 RETURN
2000 REM SCROLL DOWN
2010 FOR A=1535-32 TO 1024 STEP-1
2020 POKE A+32,PEEK(A)
2030 NEXT
2040 RETURN
3000 REM SCROLL LEFT
3010 FOR A=1024+1 TO 1535-1
3020 POKE A,PEEK(A+1)
3030 NEXT
3040 RETURN
4000 REM SCROLL RIGHT
4010 FOR A=1535-1 TO 1024 STEP-1
4020 POKE A+1,PEEK(A)
4030 NEXT
4040 RETURN

If you run this, you see it prints a message down the screen, then SLOWLY moves every byte up, then back down, then left, then right. It is very slow. It also leaves leftover characters on the edge of the screen, with the idea being you would be drawing new characters over there if you were making a maze or something scroll.

It’s not elegant, nor is it pretty. Or useful.

Obviously, doing this to scroll a screen is not practical. Clever programmers will try to make large strings and then just print them in the proper position. It’s mush faster letting the BASIC ROM do the work for you. Here’s an example that will scroll a PAC-MAN maze up and down the screen:

10 DIM MZ$(31)
20 FOR A=0 TO 30:READ MZ$(A):NEXT
30 CLS
40 REM SCROLL MAZE DOWN
50 FOR ST=0 TO 15
60 FOR LN=0 TO 15
70 PRINT @LN*32,MZ$(LN+ST);
80 NEXT:NEXT
90 REM SCROLL MAZE UP
100 FOR ST=15 TO 0 STEP-1
110 FOR LN=0 TO 15
120 PRINT @LN*32,MZ$(LN+ST);
130 NEXT:NEXT
140 GOTO 40
999 GOTO 999
1000 DATA "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"    
1010 DATA "X            XX            X"    
1020 DATA "X XXXX XXXXX XX XXXXX XXXX X"    
1030 DATA "X XXXX XXXXX XX XXXXX XXXX X"    
1040 DATA "X XXXX XXXXX XX XXXXX XXXX X"    
1050 DATA "X                          X"
1060 DATA "X XXXX XX XXXXXXXX XX XXXX X"   
1070 DATA "X XXXX XX XXXXXXXX XX XXXX X"    
1080 DATA "X      XX    XX    XX      X"   
1090 DATA "XXXXXX XXXXX XX XXXXX XXXXXX"    
2100 DATA "     X XXXXX XX XXXXX X     "    
2110 DATA "     X XX          XX X     "    
2120 DATA "     X XX XXXXXXXX XX X     "   
2130 DATA "XXXXXX XX X      X XX XXXXXX"   
2140 DATA "          X      X          "   
2150 DATA "XXXXXX XX X      X XX XXXXXX"   
2160 DATA "     X XX XXXXXXXX XX X     "   
2170 DATA "     X XX          XX X     "   
2180 DATA "     X XX XXXXXXXX XX X     "   
2190 DATA "XXXXXX XX XXXXXXXX XX XXXXXX"   
3200 DATA "X            XX            X"   
3210 DATA "X XXXX XXXXX XX XXXXX XXXX X"   
3220 DATA "X XXXX XXXXX XX XXXXX XXXX X"   
3230 DATA "X   XX                XX   X"   
3240 DATA "XXX XX XX XXXXXXXX XX XX XXX"   
3250 DATA "XXX XX XX XXXXXXXX XX XX XXX"   
3260 DATA "X      XX    XX    XX      X"   
3270 DATA "X XXXXXXXXXX XX XXXXXXXXXX X"   
3280 DATA "X XXXXXXXXXX XX XXXXXXXXXX X"   
3290 DATA "X                          X"   
4200 DATA "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"

If you run this, you will see an ASCII maze that is 31 lines tall get scrolled up and down the 16 line screen. Using PRINT to blast out a string of bytes is much faster than PEEK and POKE.

Fancy BASIC programmers would use this trick, storing all their characters in strings and printing them on the screen. If you want to add left and right scrolling, you could do that with longer strings and MID$ to just print the middle 32 characters of the string.

But I digress. Again.

While there are ways to do simulate screen scrolling somewhat fast in BASIC, assembly language will still be much faster. I present this simple code that has assembly versions of the BASIC code I presented earlier. Instead of having four different subroutines to GOSUB to, you can call it by using USR0(z) and giving it a direction code (1=up, 2=down, 3=left and 4=right).

It looks like this:

* SCRNMOVE.ASM v1.00
* by Allen C. Huffman of Sub-Etha Software
* www.subethasoftware.com / alsplace@pobox.com
*
* DEFUSRx() screen moving function
*
* INPUT:   direction (1=up, 2=down, 3=left, 4=right)
* RETURNS: 0 on success
*         -1 if invalid direction
*
* EXAMPLE:
*   CLEAR 200,&H3F00
*   DEFUSR0=&H3F00
*   A=USR0(1)
*
ORGADDR EQU $3f00

INTCNV EQU  $B3ED * 46061
GIVABF EQU  $B4F4 * 46324

UP     EQU  1
DOWN   EQU  2
LEFT   EQU  3
RIGHT  EQU  4
SCREEN EQU  1024 * top left of screen
END    EQU  1535 * bottom right of screen

     org   ORGADDR

start jsr  INTCNV * get incoming param in D
     cmpb  #UP
     beq   up
     cmpb  #DOWN
     beq   down
     cmpb  #LEFT
     beq   left
     cmpb  #RIGHT
     beq   right
     bra   error

up   ldx   #SCREEN+32
loopup lda  ,x
     sta    -32,x
     leax   1,x
     cmpx   #END
     ble    loopup
     bra    return

down ldx    #END-32
loopdown    lda ,x
     sta    32,x
     leax   -1,x
     cmpx   #SCREEN
     bge    loopdown
     bra    return

left ldx    #SCREEN+1
loopleft    lda ,x
     sta    -1,x
     leax   1,x
     cmpx   #END
     ble    loopleft
     bra    return

right ldx   #END-1
loopright   lda ,x
     sta    1,x
     leax   -1,x
     cmpx   #SCREEN
     bge    loopright
     bra    return

error ldd   #-1    * load D with -1 for error code
     bra    exit
    
return ldd  #0
exit jmp  GIVABF      

* lwasm --decb -9 -o scrnmove.bin scrnmove.asm
* lwasm --decb -f basic -o scrnmove.bas scrnmove.asm
* decb copy -2 -r scrnmove.bin ../Xroar/dsk/DRIVE0.DSK,SCRNMOVE.BIN

If I use the “-f basic” option, I can produce a BASIC loader with DATA statements that contain the assembly language routines. I then renumbered them and made them a subroutine so at the top of the example program I can GOSUB to it, then install and use the routine.

1 CLEAR 200,&H3F00
2 GOSUB 1000
3 DEFUSR0=&H3F00
10 CLS
20 FOR A=1 TO 14
30 PRINT @32*A+A,"SCROLLING IS HARD"
40 NEXT
50 Z=USR0(1) 'UP
60 Z=USR0(2) 'DOWN
70 Z=USR0(3) 'LEFT
80 Z=USR0(4) 'RIGHT
999 GOTO 999
1000 REM LOAD ASM ROUTINE
1010 READ A,B
1020 IF A=-1 THEN 1070
1030 FOR C = A TO B
1040 READ D:POKE C,D
1050 NEXT C
1060 GOTO 1000
1070 RETURN
1080 DATA 16128,16225,189,179,237,193,1,39,14,193,2,39,27,193,3,39,40,193,4,39,52,32,66,142,4,32,166,132,167,136,224,48,1,140,5,255,47,244,32,54,142,5,223,166,132,167,136,32,48,31,140,4,0,44,244,32,37,142,4,1,166,132,167,31,48,1,140,5,255,47,245,32,21
1090 DATA 142,5,254,166,132,167,1,48,31,140,4,0,44,245,32,5,204,255,255,32,3,204,0,0,126,180,244,-1,-1

If you run this, you will see the screen jump and then it will look like the original example looked…it just happens almost instantly instead of taking minutes.

Now let’s try the star scrolling example again. Instead of GOSUBing to slow BASIC routines, we will use the assembly scroll up and down routines:

1 CLEAR 200,&H3F00
2 GOSUB 1000
3 DEFUSR0=&H3F00
5 SP$=STRING$(31," ")
10 CLS
20 REM SCROLL UP
30 FOR A=1 TO 100
35 PRINT @32*15,SP$;
40 PRINT @32*15,TAB(RND(30));".";
45 Z=USR0(1) 'UP
50 NEXT
60 REM SCROLL DOWN
70 FOR A=1 TO 100
80 PRINT@0,TAB(RND(30));"."
90 Z=USR0(2) 'DOWN
100 NEXT
110 GOTO 20
999 GOTO 999
1000 REM LOAD ASM ROUTINE
1010 READ A,B
1020 IF A=-1 THEN 1070
1030 FOR C = A TO B
1040 READ D:POKE C,D
1050 NEXT C
1060 GOTO 1000
1070 RETURN
1080 DATA 16128,16225,189,179,237,193,1,39,14,193,2,39,27,193,3,39,40,193,4,39,52,32,66,142,4,32,166,132,167,136,224,48,1,140,5,255,47,244,32,54,142,5,223,166,132,167,136,32,48,31,140,4,0,44,244,32,37,142,4,1,166,132,167,31,48,1,140,5,255,47,245,32,21
1090 DATA 142,5,254,166,132,167,1,48,31,140,4,0,44,245,32,5,204,255,255,32,3,204,0,0,126,180,244,-1,-1

You will notice scrolling up and down now go at the same speed, but it is slightly slower than the normal BASIC PRINT scroll up. This is because of line 35 and 75 that use a PRINT statement to erase a line before the screen scrolls. This is because my simple assembly routines don’t bother to do this (neither did the BASIC version).

If the usage is known, the assembly can easily be made to clear out whichever roll of column is being moved. Doing it inside the routine will be much faster than using a PRINT command (and, PRINT doesn’t help us if the screen is scrolling left or right).

Can we do better? I think so.

Next time … let’s make another pass over this screen scrolling routine and see if we can make it do something more useful.