CoCo 3 – Game Collision Detection Routine (6809 assembly)

I’ve been recently toying with the idea of writing a game using the CoCo 3 256 x 200 x 16 colour resolution and I got far enough along to require collision detection of the game sprites. I wrote my own code but it wasn’t working properly and after fighting with it I decided I’d look at ROBOTRON’s source code (which is freely available online) and see if I can use it’s collision detection code in my game. I converted the ROBOTRON code to work with my game but it wasn’t working either. I fought with it for awhile knowing that the code works perfect for ROBOTRON. I finally realized that my game was using bytes for the width of the sprites and both my original collision detection code and the ROBOTRON code is using pixels for the width. Now that I’ve got the ROBOTRON version documented line by line (the original source wasn’t commented very much) and in a format that can be used easily I thought I’d share it and explain the basics of how it works.

I’m super impressed with the ROBOTRON source code, the programmers did a wickedly clever and optimized version of collision detection for the 6809. I’ll explain a few of the things they did that really impressed me down below as I get more into the guts of the code.

First an overview of how the collision detection of two sprites works:
1 – Test to see if the bounding boxes of each sprite are overlapping on screen
2 – Figure out what section of each sprite is on top of the other sprite and test if the overlapping pixels of the sprite are on top of each other. If so then you have a collision.

If you are writing an arcade game for the CoCo you are probably using compiled sprites. Which means you need to also have a bitmap copy of your sprite to test for collisions.

For example, if you have the Galaga ship as a sprite that ends up on screen as this:

; Image Width: 18           Image Height: 16 
; 000 ........0.........
; 001 ........0.........
; 002 ........0.........
; 003 .......000........
; 004 .......000........
; 005 ....1..000..1.....
; 006 ....1..000..1.....
; 007 ....0.00000.0.....
; 008 .1..0.00100.0..1..
; 009 .1...0011100...1..
; 010 .0..000101000..0..
; 011 .0.00000000000.0..
; 012 .000001000100000..
; 013 .000.1100011.000..
; 014 .00..11.0.11..00..
; 015 .0......0......0..

You wound need a bitmap of this sprite so the collision detection routine can use it to test with, like this:

        FCB     $00,$00,$00,$00,$F0,$00,$00,$00,$00
FCB $00,$00,$00,$00,$F0,$00,$00,$00,$00
FCB $00,$00,$00,$00,$F0,$00,$00,$00,$00
FCB $00,$00,$00,$0F,$FF,$00,$00,$00,$00
FCB $00,$00,$00,$0F,$FF,$00,$00,$00,$00
FCB $00,$00,$F0,$0F,$FF,$00,$F0,$00,$00
FCB $00,$00,$F0,$0F,$FF,$00,$F0,$00,$00
FCB $00,$00,$F0,$FF,$FF,$F0,$F0,$00,$00
FCB $0F,$00,$F0,$FF,$FF,$F0,$F0,$0F,$00
FCB $0F,$00,$0F,$FF,$FF,$FF,$00,$0F,$00
FCB $0F,$00,$FF,$FF,$FF,$FF,$F0,$0F,$00
FCB $0F,$0F,$FF,$FF,$FF,$FF,$FF,$0F,$00
FCB $0F,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$00
FCB $0F,$FF,$0F,$FF,$FF,$FF,$0F,$FF,$00
FCB $0F,$F0,$0F,$F0,$F0,$FF,$00,$FF,$00
FCB $0F,$00,$00,$00,$F0,$00,$00,$0F,$00

Once you have your bitmap for Sprite1 and Sprite2 you’re ready to test them to see if they’ve collided.

Before you use the code below you need to setup the variables for Sprite1 and Sprite2. The comments in the code explain what you need to set them up as before calling the DoCollision routine. All the variables below should be in direct page memory.

* Variables for collision detection, these should all be in Direct Page memory
* Sprite 1 related variables
Sprite1X RMB 1 * X co-ordinate
Sprite1Y RMB 1 * Y co-ordinate
Sprite1Width RMB 1 * Sprite 1 width in bytes
Sprite1Height RMB 1 * Sprite 1 height
Sprite1PixelsWide RMB 2 * Sprite 1 width in Pixels, 2nd byte will be a copy of Sprite1Height
Sprite1Bitmap RMB 2 * Address where Sprite1's bitmap starts
* Sprite 2 related variables
Sprite2X_Co_ordinate RMB 1 * Sprite2 X co-ordinate on screen (top left corner of sprite)
Sprite2Y_Co_ordinate RMB 1 * Sprite2 Y co-ordinate on screen (top left corner of sprite)
Sprite2Width RMB 1 * Width of Sprite2 in bytes
Sprite2Hieght RMB 1 * Height of Sprite2 (number of rows)
Sprite2PixelsWide RMB 1 * Width of Sprite2 in pixels (bytes * 2)
Sprite2HieghtAgain RMB 1 * Height of Sprite2 (number of rows) will also be the same as Sprite2Hieght
Sprite2Bitmap RMB 2 * Address in memory where Sprite2 bitmap is located (to test for collision)

*COLLISION DETECTION ROUTINE PARAMETERS
* Do not change the order of these, some of them get loaded as a 16 bit value, so they need to be in the correct order
*
XCNT RMB 1 * SCAN COUNTS
YCNT RMB 1
OX1 RMB 1 * OFFSETS Object X Value
OY1 RMB 1 * Object Y Value
PX1 RMB 1 * Player X Value
PY1 RMB 1 * Player Y Value
PLW RMB 1 * Width TEMPS
OBW RMB 1
ULX RMB 1 * UPPER LEFT X COORD
ULY RMB 1 * Upper Left Y Coord
LRX RMB 1 * Lower Right X Coord
LRY RMB 1 * Lower Right Y Coord
CTEMP RMB 2 * INDEX TEMP
CTEMP2 RMB 2
XTEMP RMB 2

Once you’ve setup Sprite1 and Sprite2’s variables you can call the routine below called DoCollisionDetection. If there is a collision then the carry bit will be set. If it returns and the carry bit is clear then there was no collision. Here’s an example of setting up Sprite1 and Sprite2’s variables then calling the DoCollisionDetection routine and how to react to it when it returns:

* Setup Sprite1
LDA ShotX * Players Shot X location
LDB ShotY * Players Shot Y location
STD Sprite1X * Save Sprite1 X & Y
LDA #1 * Shot width in bytes
LDB #1 * Shot height in rows
STD Sprite1Width * Save Sprite1's width in bytes and height
LDA #1 * Shot width in pixels
STD Sprite1PixelsWide * Save Sprite1's width in pixels and height
LDD #ShotBitmap * Address in memory where the shot bitmap is
STD Sprite1Bitmap * Save the location where the bitmap is in memory for Sprite1
* Setup Sprite2
LDA EnemyX * Enemy Ship X location
LDB EnemyY * Enemy Ship Y location
STD Sprite2X_Co_ordinate * Save Sprite2 X & Y
LDA #10 * Enemy Ship width in bytes
LDB #20 * Enemy Ship height in rows
STD Sprite2Width * Save Sprite2's width in bytes and height
LSLA * Enemy Ship width in pixels
STD Sprite2PixelsWide * Save Sprite2's width in pixels and height
LDD #EnemyBitmap * Address in memory where the Enemy Ship bitmap is
STD Sprite2Bitmap * Save the location where the bitmap is in memory for Sprite2
* Go see if the sprites collided
JSR DoCollisionDetection * See if Sprite1 and Sprite2 have collided
BCC NoCollision * If the carry flag is clear then no collision is detected
* If we get here then the two sprites have collided
* deal with the collision
...
...
RTS
* If we get here then No collision has happenned
NoCollision:
...
...
RTS

Here is the code that does the magic:

* Enter with Sprite1 and Sprite2 variables already setup
DoCollisionDetection:
LDD Sprite1X * A = Sprite1 X Pixel, B = Sprite1 Row Y (screen location)
STD ULX * Save Sprite1 Upper Left X & Y Co-ordinate
* CALC LOWER RT
ADDD Sprite1PixelsWide * Sprite1 Width = Sprite1Width + Object width, Sprite1Height = Sprite1Height + Object Height
STD LRX * Save the Lower right corner of Sprite1 X & Y co-ordinate
* Test to see if the two sprites overlap
LDD Sprite2X_Co_ordinate * A = where Sprite2 X starts upper left corner, B = where Sprite2 Y starts (upper row)
CMPA LRX * If Sprite2 upper left X >= Lower right corner of Sprite1 X
BHS NoCollision@ * Then return with no collision
CMPB LRY * If Sprite2 upper row Y >= Lower right corner of Sprite1 Y
BHS NoCollision@ * Then return with no collision
ADDD Sprite2Width * D = D + sprite2's Width & Height, A = Sprite2's bottom right X co-ordinate, B = Sprite2's bottom right Y Co-oridnate
CMPA ULX * If sprite2's bottom right X <= sprite1's Upper Left X
BLS NoCollision@ * Then return with no collision
CMPB ULY * If sprite2's bottom right Y > sprite1's Upper Left Y
BHI CheckBoundingBoxes * If B is greater, then collision is possible, go check where the bounding boxes overlap
NoCollision@
CLRA * Clear the carry flag to indicate no collision
RTS * Return
*
*COMPARE FOR PICTURE INTERSECTION
CheckBoundingBoxes:
* D = D + sprite2's Width & Height, A = Sprite2's bottom right X co-ordinate, B = Sprite2's bottom right Y Co-oridnate
SUBD Sprite2PixelsWide * D = sprite2's Width & Height - sprite1's Width & Height = size of the over lapping box
* D = D - (Width and Height of Sprite2)

STD XTEMP * Save D where A = difference from sprite2's bottom right X co-ordinate and sprite2's width
* B = difference from sprite2's bottom right y co-ordinate and sprite2's height
LDD #$0000 * CLEAR OFFSETS * D = 0
STD OX1 * GET OBJECT * OX1 = Sprite2 = X,Y = 0
STD PX1 * PLAYER * PX1 = Sprite1 = X,Y = 0

LDD XTEMP * Restore D = sprite2's Width & Height - sprite2's Width & Height
* FIND UPPERMOST
SUBB ULY * FIND UPPERMOST * B = B - Sprite1 Upper Left Y Co-ordinate
BHI IC10 * PLAYER UPPERMOST * If it's higher then use Sprite1 is the uppermost
NEGB * Otherwise make B a positive number and use Sprite2 is the uppermost
STB OY1 * OBJECT UPPERMOST * Save it as Sprite2 is uppermost
BRA IC2 * Skip ahead
IC10 STB PY1 * Save B sprite1 Y is uppermost

*FIND LEFTMOST
IC2 SUBA ULX * UPPER LEFT X COORD * A = A - Sprite1 Upper Left X Co-ordinate
BHI IC20 * PLAYER LEFTMOST * If it's higher then Sprite1 it is the left most
NEGA * Otherwise make A positive
STA OX1 * OBJECT LEFTMOST * save the value as Sprite2 is the left most
BRA IC3 * Skip ahead
IC20 STA PX1 * Save A sprite1 X is the left most
*FIND LOWERMOST
*CALC OY2
IC3 LDD XTEMP * GET OBJX * D = sprite2's Width & Height - sprite1's Width & Height
ADDD Sprite2PixelsWide * A = A + Sprite2 width, B = B + Sprite2 Height
SUBB LRY * Lower Right Y Coord * B = B - Lower right corner of Sprite1 Y
BHI IC4 * OBJECT LOWEST * If B was > LRY then Object is the lowest, skip ahead
CLRB * PLAYER LOWEST * B=0, Sprite1 is the lowest
*FIND RIGHTMOST
*CALC OX2
IC4 SUBA LRX * Lower Right X Coord A = A - lower right corner of sprite1 X
BHI IC40 * OBJECT RIGHTMOST * A was > LRX then Sprite2 is rightmost, skip ahead
CLRA * PLAYER RIGHTMOST * A =0, Sprtie1 is the rightmost
*CALC X,Y COUNT
IC40 STD CTEMP * SAVE * Save OX2 & OY2 as D in CTEMP
* GET WIDTH,HEIGHT
LDD Sprite2PixelsWide * D = Sprite2 width in pixels & Height
SUBD OX1 * OX1,OY1 * A = A - OX1, B = B - OY1
SUBD CTEMP * OX2,OY2 * A = A - OX2, B = B - OY2
LSRA * Divide X count by 2 so we have # of bytes to check per row of overlapping sprites
DECA * We test the bitmaps using LDA B,U (if we need to test 2 bytes), this will make itso we test LDA 1,U and LDA 0,U
* Otherwise we will be testing LDA 2,U and LDA 1,U which will be wrong (also do a LDA B,Y)
* Original code had a DECB down below that is commented out, no need to do it each row, just do it here once
STD XCNT * XCNT,YCNT * Save the X count & Y count in XCNT as the columns and rows in the bitmaps to check
*INIT SCAN PARAMETERS
*Y=OBJECT ADDR
*U=PLAYER ADDR
LDA Sprite1Width * A = Sprite1's width in bytes
STA PLW * Save it as Sprite1's width, so we can move to the next row to check against (below)
LDB PY1 * B = PY1 Height = Height of sprite1
MUL * D = A * B = starting row to test the bitmap from
LDX Sprite1Bitmap * X = Sprite1 bitmap start location
LEAX D,X * INIT PLAYER INDEXSH * X now points at the row to start testing Sprite1's bitmap at in memory
LDB PX1 * B = PX1 = pointer to start checking sprite1's bitmp at
ABX * X = X + B (column), X is now ready to test the bitplane for Sprite1 (ABX uses unsigned values in B for big sprites)
TFR X,U * U = X, U is now ready to test the bitplane for Sprite1

LDA Sprite2Width * A = Sprite2's width in bytes
STA OBW * Save it as Sprite2's width, so we can move to the next row to check against (below)
LDB OY1 * B = OY1 = Height of sprite2
MUL * D = A * B = starting row to test the bitmap from
LDX Sprite2Bitmap * X = Sprite2 bitmap start location
LEAX D,X * INIT PLAYER INDEXSH * X now points at the row to start testing Sprite2's bitmap at in memory
LDB OX1 * B = OX1 = pointer to start checking sprite2's bitmap at
ABX * X = X + B (column), X is now ready to test the bitplane for Sprite2

IC5 LDB XCNT * B = count of bytes to test per row (X direction)
; DECB * B = B - 1, test each column (make it zero based), not needed as we now do this above before saving this value
IC50 LDA B,U * A = byte in Sprite1's bitmap to test
BEQ NoCollisionYet * If = 0 then this pixel is transparent no need to compare, skip checking Sprite2
LDA B,X * CHECK * A = byte in Sprite2
BEQ NoCollisionYet * If = 0 then no need to compare, skip checking

* Test nibbles for collisions
* If we get here we know both bytes have a value (we don't know which nibble though)
* Test the left nibble of each, A already has value at B,Y from above
ANDA #$F0
BEQ CheckOther * If the left nible is zero then we know the right nibble must have a value or we wouldn't be here, go check Sprite2's right nibble
* If we get here then Sprite2 has a value in the left nibble, check Sprite1
LDA B,U * Get Sprite1 left nibble value
ANDA #$F0
BNE CollisionDetected * If the left nible is Not zero then there is a collision
* If we get here then the left nibble of Sprite2 is used but Sprite1's left nibble is zero
CheckRight:
LDA B,X * Get Sprite2 right nibble value
ANDA #$0F
BEQ NoCollisionYet * If the right nible is zero then skip ahead, no collision
CheckOther:
* If we get here then Sprite2 has a value in the right nibble, check Sprite1's
LDA B,U * Get Sprite1's right nibble value
ANDA #$0F
BNE CollisionDetected * If the right nible is Not zero then there is a collision

* If we get here then no collision detected so far
NoCollisionYet:
DECB * decreemnt the position to check the column in the bitmap
BPL IC50 * NOT FINISHED COLUMN * keep looping until we've checked all columns (B is not -1 yet the loop)
LDD PLW * A=PLW=Sprite1's width, B=OBW=Sprite2's width
LEAU A,U * Move to the next row of the bitmap to test from for Sprite1
ABX * Move to the next row of the bitmap to test from for Sprite2
DEC YCNT * Decrement the number of rows to test
BNE IC5 * Loop again if we haven't gone through all the rows yet
CLRA * Clear the carry flag to indicate no collision
RTS * Return
CollisionDetected:
COMA * Set the carry flag to indicate a collision
RTS * Return

I haven’t done extensive testing of the code above, most of it is a conversion of the same code in ROBOTRON with only a few tweaks of my own to speed it up a little so it should be pretty solid. Plus I added code to detect collision at the nibble level.

As for the really cool things I like about the code above, is it’s way smaller than the code I wrote. I really like the way it figures out which sprite is the upper/left and which is the lower/right. This is done where it does the comparison and if the value is a negative then do a NEG instruction and use the other sprite with the NEG value which then makes it a positive and uses that value.

When you write code that does a compare command like CMPA the processor does a SUBA but doesn’t change the value of the A instruction. It just sets the condition code flags accordingly. There are a couple lines of code above where it does the SUBA or SUBB instruction then does a BHI where it actually uses the results of the SUBA/SUBB instruction instead of doing a CMPA/CMPB and then doing the actions afterwards. This is VERY COOL, in my opinion.

I hope this code is helpful for other 6809 programmers to use for their games. If you do use another screen size like 320 x 224 x 16 colours you could change the code above to work on bytes only and ignore the pixel values. You would have a width of 160 bytes instead of 320 pixels which would keep the values in a range of under 256 so the code would work the same.

See you in the next post,
Glen

Posted in CoCo Programming | Tagged , , , , , , , , | 2 Comments

Introducing Advanced Text Screen Scrolling Solutions for CoCo BASIC Programmers

Introducing Advanced Text Screen Scrolling Solutions for CoCo BASIC Programmers

I’m excited to present a versatile tool designed specifically for BASIC programmers working with the CoCo platform. This software facilitates efficient scrolling in both the conventional 32×16 text window and the extended 80-column text screen for CoCo 3.

Whether you’re aiming to develop immersive scrolling-based games or streamline on-screen navigation, this tool promises to elevate the user experience.

Key Features:

  • Directional scrolling: Navigate upwards, downwards, left, or right.
  • Customized scrolling regions: Designate specific screen areas for scrolling.
  • Adjustable scrolling dimensions: Define the width and height of your scrolling section.
  • Hands-on guidance: Each program is bundled with sample BASIC code to ensure seamless setup and integration.

Access the Tools:

Empower your programming journey with these enhanced scrolling capabilities, tailored for the CoCo ecosystem.

Cheers,

Glen

For fun after I wrote this blog post I asked ChatGPT to re-write the article and this was the result 🙂

Posted in Uncategorized | 6 Comments

CoCo 3 – 256 x 200 x 16 colours is a great resolution

Since CoCo’s don’t have dedicated hardware sprites, I started writing my own library to handle this task. So in the future I won’t have to re-write this code again and again. I recently started reverse engineering the Amiga version of Rick Dangerous. This was a game my friends and I had such a blast playing when I was younger. I found out awhile ago that someone had disassembled the IBM and Atari ST version and made their own open source version called XRick. It’s written in C and will probably help me while I go through the Amiga version.

After the CoCo 3 my next computer was an Amiga 500 then an Amiga 3000 and an Amiga 1200. I never did do any programming on the Amiga, I used it for multimedia stuff and especially 3D modelling and of course for gaming. I thought it would be really cool to have Rick Dangerous on the CoCo 3 but not until I got my 2 Meg upgrade for my CoCo 3 did I think it would be possible. I still don’t know what I’ll end up with but I’m having a blast learning motorola 68000 assembly and exactly how the powerful chips inside the Amiga work. The 68000 CPU really is a lot like the 6809 but with more registers and accumulators and of course it’s all 32 bits. The processor runs at 7Mhz but a lot of the instructions take many cycles to perform. I think a good optimized version on a CoCo 3 with a 6309 CPU and 2 Megs of RAM could keep up…

So far, I’ve figured out how backgrounds in Rick Dangerous are drawn on the Amiga and also where and how the sprites are drawn on screen. I have all the audio samples located as well. Although it turns out Rick Dangerous has its own MOD player code built in for music. This will be difficult to emulate, but maybe a long sample will do.

If I want to make a version for the CoCo 3 I have most of the elements I need, some of the other things I need to do is figure out how enemies and traps are placed in each scene. The XRick source code might help here, but I’m not that great with ‘C’ source code. For now I’m writing some code to handle moving Rick Dangerous around the CoCo 3 screen.

For a fun comparison here’s the Amiga version (running on MAME):

Here’s my CoCo 3 version (so far, also running on MAME):

Now my point for writing the blog entry wasn’t to go into details about a game I’m disassembling but why the CoCo 3 screen with 256 x 200 x 16 colours is so awesome. The reason is, I figured out a really quick way to translate the X & Y co-ordinates of this specific screen dimensions to a memory location.
Rick Dangerous on the Amiga uses only even bytes for the X location of the sprites and even though it uses a 320 x 200 screen it only uses 256 pixels and has a black border around the screen. I think most games and demos stick to using only even bytes, this makes it easier for programming and takes half the graphics memory (since you don’t need one copy of the sprite at an even pixel and another at an odd pixel).

Sorry, I keep going off on tangents… Back to the CoCo 3 screen, I will be accessing the screen in RAM starting at $0000 to $63FF. A screen with 256 pixels across means each row is 128 bytes (2 pixels per byte). Therefore to get the memory location of an X co-ordinate of 28 and Y co-ordinate of 87 we need to do the following math:

Mem location = (Y *128) + (X / 2)

From our example = (87 * 128) + (28 / 2)

= 11,136 + 14

= 11,150 (Memory location to draw the sprite)

To do this in 6809/6309 assembly you need to do the following (takes 23 CPU cycles on a 6309):

LDU #SpriteXY * Stored as two bytes X, Y
CLRA
LDB 1,U * B = Y value
LSLB
ROLA
LSLB
ROLA
LSLB
ROLA
LSLB
ROLA
LSLB
ROLA
LSLB
ROLA
LSLB
ROLA * Rotate left 7 times to get D = D * 128


But we have a 6809/6309 CPU and have a multiply so let’s use that instead (takes 20 CPU cycles on a 6309):

LDU #SpriteXY * Stored as two bytes X, Y
LDA #128 * Multiply B with 128
LDB 1,U * B = Y value
MUL * D now has the Y value on screen calculated


Either way we still need code to take the X co-ordinate and Logical shift the value and add it to D. (takes 15 CPU cycles on a 6309)
Something like:
PSHS B * Save it on the stack
LDB ,U
LSRB * X co-ordinate / 2
ORB ,S+ * D now has the screen location


So at best using the above MUL method and then getting the X co-ordinate is 35 CPU cycles.

But what if instead of shifting left 7 times we load the value in A and shift D to the right once. That would save a lot of shifting. To do this you need to simply swap the X & Y values in your program so they’re always stored as Y,X in RAM. This way when loaded into the D register the Y value gets shifted right once instead of left 7 times and the X value gets shifted as well at the same time to change from a value in the range of 0 to 255 to 0 to 127, which is the byte location we need for our screen memory location. If you really want to handle even and odd pixels you still can, the carry bit at this point would indicate an odd pixel if it’s set, so a simple ( BCS DrawOddSprite) could handle it.

Since I’m using a 6309 and it has the LSRD instruction the code will be (takes 10 CPU cycles):
LDU #SpriteYX
LDD ,U * Get Y & X value in D
LSRD * D now has the value of Y = Y * 128 + X=X/2 (we now have our screen location)

It could be even faster if we use the same address for the Y&X value in direct page ram and load it from that location, that would only require 6 cycles. That’s why a resolution of 256 x 200 x 16 colours is so nice on the CoCo 3.

The important thing is to think differently and maybe you can get some nice speed increases in your code. See you in the next post,

Glen

Posted in Uncategorized | 5 Comments

CoCo 3 Memory Test

I upgraded my CoCo 3 with a 2 Meg upgrade kit from Cloud9 that I picked up at CoCoFest this year. Mark Marlette makes fantastic hardware for the CoCo 3 and has been doing it forever. If you want great service and hardware for your CoCo I highly recommend his services.

What does a programmer do once they get a shiny new 2 Meg upgrade for their CoCo 3? Write a program to test my new RAM, of course!

My first method was Stack blasting values 0 to 255 through each byte of RAM figuring this would be a great test. Once that was completed, I thought I’d look a little into what the program MemTest86+ does and I learned there is more to memory testing then I thought.

The first thing MemTest86+ does is detect the hardware of the computer it’s running on. So I wrote some code to mimic the look of MemTest86+ and called it MemTest2023+. My code detects if you have a 6809 or a 6309, the amount of RAM your computer has up to 2 Megs (I don’t have 8 Megs and I’m still not 100% clear how that all works). Next I hacked together some code that takes advantage of the differences in the TIMER FIRQ to detect if your machine has an 86′ GIME or a 87′ GIME. Sockmaster documented this difference on his website that is now graciously being hosted by Ciaran Anscomb.

My MemTest2023+ does the following upon startup:

Goes into High Speed and if you have a 6309 go into 6309 native mode to make the RAM access as fast as possible. Detect and show the GIME version (for fun) then performs the following RAM tests:

1 – Blast bytes – Stack blast values of 0 to 255 in every byte of RAM and compare it by pulling the values off the stack as fast as possible

2 – Walking Ones – Tests all address bits in all memory banks by using a walking ones pattern.

3 – Moving Inversions – Clear all RAM, then set bit 0 of each byte, then set bit 1 and so on, write the byte, then invert it, then invert it again back to original.

4 – Random Patterns – Set all RAM, then fill a block with random values, Read back random values to see if they match.

5 – Surround Read Disturb – Clear all RAM, then each cell in turn becomes the test cell. The test cell is complemented and the previous byte and the next byte are repeatedly read 32 times then the test cell is read to determine if it has been affected by the reading of its neighbour’s. The operation is then repeated for a background of 1s. The intent is to find disturbances caused by adjacent cell operations.

6 – Bit Fade – writes a byte to the memory and then repeatedly reads it back (256 times). This test is designed to detect any “bit fade” issues, where the voltage levels in the memory chips can gradually decay over time, causing errors.

It keeps track of how many times it has completed all the tests and will run until the CoCo is reset or powered off.

If it detects some bad RAM it will halt the test and indicate which 8K block is failing and the byte number inside that 8k block.

The program hasn’t been thoroughly tested, but it works for me on my two CoCo 3’s. Both have 6309’s in them curtesy of Frank and Retro Rewind another great CoCo company. One has an 87′ GIME the other has a ’86 GIME and one has 512k RAM the other has 2 Megs.

MemTest2023+ is available on my GitHub page as a .DSK image along with the source code. I’ve also uploaded it to the Colour Computer Archive.

I hope somebody has some fun testing their CoCo 3’s with it….

See you in the next post,

Glen

Posted in Uncategorized | 8 Comments

MOS 6502 to Motorola 6809 Assembly Language Transcoding

I’ve started looking at some MOS 6502 assembly code and I’m in the process of transcoding it to Motorola 6809 assembly. I’ve found an excellent PDF online describing the process of translating programs from the 6502 to the 6809 starting on page 77 of the July, 1982 edition of MICRO – The 6502/6809 Journal magazine there is a similar PDF on the Color Computer archive found here, but it’s not laid out as nicely as the magazine version. The article discusses the theory of doing a 6502 to 6809 translation and possible work arounds. I found that actually doing the translation in the real world that there are a few more things that need to be handled and some things that can be done in the process that may help speed up the 6809 code without actually re-writing the routine.

I won’t go over the same info from the PDF linked above such as the 6502 is little-endian and the 6809 is big-endian and tons more.

Once you managed to go through the 6502 disassembled code and generally converted it to a 6809 version the first thing I would suggest doing (especially if it’s a big program) would be to figure out in each routine if the accumulator and registers are used & changed and put that info in the comment for each JSR to that routine in the entire program. That way when you are doing your translation you know if the A,X or Y are used or changed in a routine so you know you can or can’t modify them while translating it to 6809 code. Also you can see if you can get away with using the B accumulator instead of X or Y in the code before the JSR.

Below are some things I’ve learned so far in my transcoding journey. The first thing that will bite you in the butt is that after a compare (CMP) on the 6502 and a compare (CMP) on the 6809 the carry bit will be the opposite.

Example of 6502 code:

CMP  #$41
BCC  Jumpahead

Example of 6809 version to execute the same way:

CMPA  #$41
BCS   Jumpahead

Similarly, 6502 code that does:

CMP  #$41
BCS  Jumpsomeplace

Example of 6809 version to execute the same way:

CMPA #$41
BCC  Jumpsomeplace

I’ve also found there are times in 6502 code where a CMP is done then immediately after an ADC is done, for example the 6502 code:

CMP  RamAddress
ADC  #$34

You will have to work around this with a few more lines of code on the 6809 as:

  CMPA  RamAddress
  BCS   >            ; carry is the opposite on 6809 so we don't add carry bit
  INCA
! ADDA  #$34

Sometimes the CMPA is used before a ROL or ROR command, 6502 code:

    CMP #$80             * test and set/clear the carry bit
    ROR A                * Use the carry bit in the ROR instruction

6809 version (kills accumulator B’s value):

    CMPA    #$80         * Carry bit will be reversed on the 6809
    RORB                 * Move the carry bit into bit 7 of B
    COMB                 * Flip the bits
    ROLB                 * Move bit 7 of B to the carry bit

    RORA                 * Use the carry bit in the ROR instruction

I’ve found that the carry bit is very important in 6502 code since the 6502 doesn’t have an ADDA instruction but only has an ADC (Add with Carry) instruction that it’s very important to follow the code and make sure the carry after a compare instruction is what you expect since it will be added to the accumulator.

I’ve found many places in 6502 code where the carry is cleared with the SEC instruction just before the ADC instruction.

6502 example:

CLC          ; Clear the carry flag
ADC  #$20    ; A = A + $20 + carry (carry is zero)

The 6809 version can skip the clearing of the carry flag and simply use the ADDA instruction:

ADDA  #$20   ; A = A + $20

If the carry flag is set before and ADC instruction on the 6502:

SEC           ; Set the carry flag
ADC   #$20    ; A = A + $20 + carry (carry is 1)

You can do this on the 6809 without setting the carry flag with:

ADDA  #$20+1   ; A = A + $20 + 1

The 6502 SBC instruction is a little different than the 6809. The 6502 does a subtraction of a memory location (or direct value) – the complement of the carry bit. The 6809 SBCA instruction does a subtraction from a memory location (or direct value) – the value of the carry bit. Here’s an example and a 6809 workaround.

6502 code:

 SBC  #$52     ; A = A - $52 - the complement of the Carry bit value

6809 code version:

  BCS   >            * if the carry is set skip ahead
  DECA               * otherwise if the carry is clear then A = A - 1
! SUBA  #$52         * A = A - $52

6809 version of the same code (this kills the B accumulator value):

 RORB                 * Move the carry bit into bit 7 of B
 COMB                 * Flip the bits
 ROLB                 * Move bit 7 of B to the carry bit
 SBCA   #$52          * A = A - $52 - the carry bit value

Darren Atkinson posted a comment below with an even better way to handle the 6502 SBC (Thanks Darren) translation as:

  ADCA  #$00        * If the Carry is set A=A+1, which will cancel the +1 below 
SUBA #$52+1 * A = A - $53

A little cleaner way to do the same thing and leave the SUBA be the same as the SBC you could:

  ADCA  #$FF        * If the Carry is set A=A+(-1)+1, if carry is clear the A=A+(-1)+0 
  SUBA  #$52        * A = A - $52

To do subtraction the 6502 only has the SBC (Subtract with carry) instruction, the carry flag afterwards is the opposite of the 6809. Just as a compare is done using subtraction but the resulting values are thrown away and only the condition code flags are kept.

Another thing the 6502 has is the SED instruction that puts the 6502 in decimal mode so that ADC and SBC are done with binary coded decimal versions of numbers. The 6809 does it a little differently as you use the DAA instruction after the ADDA or ADCA instruction.

A 6502 example of decimal addition:

CLC          ; Clear the carry flag
SED          ; Set Decimal Mode - put the processor in decimal mode
LDA   #$25   ; A = $25
ADCA  #$27   ; A = $25 + $27 resulting in $52
CLD          ; Clear Decimal Mode - put it in binary mode

On the 6809 the same could be done with:

LDA   #$25
ADDA  #$27   ; Produces binary result of $4C
DAA          ; Adjusts A to $52 (BCD result of $25 + $27)

Since the 6502 X & Y registers are 8 bits there are many times when a transfer command from A to Y or A to X or X to A or Y to A are used. Since the X & Y registers on the 6809 are 16 bit registers the TFR command will store the 8 bit accumulator to the LSB of the X or Y registers but the MSB of X & Y will always be $FF. To get around this we can add $100 to the register after the TFR command to fix the value.

A transfer from A to X on the 6502:

LDA  #$23  ; A = $23
TAX        ; X = $23  [2 cycles]

A similar transfer from A to X on the 6809:

LDA  #$23      ; A = $23
TFR  A,X       ; 8 bit accumulator to 16 bit register, X = $FF23  [6 cycles]
LEAX $0100,X   ; X=$23 [8 cycles]

Or

LDA  #$23
TFR  A,B       ; B = $23 [6 cycles]
CLRA           ; A = 0, D=$0023  [2 cycles]
TFR  D,X       ; X = $0023 [6 cycles]

It is faster and simpler to do a CLRA at the start of your 6809 translation of the program and use B as the accumulator in place of A throughout your entire 6809 translated code. Then you could simply do a TFR D,X or TFR D,Y as a direct substitute for 6502 TAX and TAY instructions. A TFR D,X or TFR D,Y is 6 cycles as the other method is 6 cycles + 8 cycles (for the LEAX. $100,X) = 14 cycles.

Example of what I mean would be:

CLRA        ; A = 0  [2 cycles]
...
LDB  #$23   ; B = $23
TFR  D,X    ; X = $0023  [6 cycles]

One other thing you could do (under certain circumstances) to speed up your translation but keep using A as the main accumulator would be change 6502 code such as this:

6 cycles:
   TAX                     ; Transfer A to X
   LDA  VelocityTable,X    ; Get velocity value from lookup table

Versions of the 6809 code:

27 Cycle version:
   TFR      A,X       ; Transfer A to X, X = $FF00 + unsigned value of A
   LEAX $0100,X       ; Fix X so now X = A
   LDA  VelocityTable,X    ; Get velocity value from lookup table

23 Cycle version:
   TFR      A,X       ; Transfer A to X, X = $FF00 + unsigned value of A
   LEAX $0100+VelocityTable,X ; Fix X and add the pointer table vale to X
                      ; X = VelocityTable + unsigned value of A
   LDA  ,X            ; Get velocity value from lookup table

16 or 13 cycle version:
    LDX    #VelocityTable       ; X = VelocityTable
    TSTA
    BPL    >
    LDX    #VelocityTable+$100  ; X = VelocityTable+$100
!   LDA    A,X                  ;Get velocity value from lookup table

If you know the value of A is always going to be between 0 and 127 then use    8 cycles as:
    LDX    #VelocityTable       ; X = VelocityTable
    LDA    A,X                  ;Get velocity value from lookup table

I’m getting a little into optimizations here but as usual you can make it even faster if you start dealing with the game logic yourself and use B instead of A for this routine. We can use the super fast ABX instruction which adds the unsigned value of B to X as:

10 Cycle version:
If B was used in the code instead of A (no TFR is needed)
   LDX   #VelocityTable  ; X = VelocityTable
   ABX                   ; X = X + unsigned value of B
   LDA   ,X              ; Get velocity value from lookup table

6502 – Indirect Indexed (I found this excellent description from here)

This mode is only used with the Y register. It differs in the order that Y is applied to the indirectly fetched address. An example instruction that uses indirect index addressing is LDA ($86),Y . To calculate the target address, the CPU will first fetch the address stored at zero page location $86. That address will be added to register Y to get the final target address. For LDA ($86),Y, if the address stored at $86 is $4028 (memory is 0086: 28 40, remember little endian) and register Y contains $10, then the final target address would be $4038. Register A will be loaded with the contents of memory at $4038.

Indirect Indexed instructions are 2 bytes – the second byte is the zero-page address – $86 in the example. (So the fetched address has to be stored in the zero page.)

While indexed indirect addressing will only generate a zero-page address, this mode’s target address is not wrapped – it can be anywhere in the 16-bit address space.

6502 instruction:

 STA (VecRamPtr),y

6809 versions:

Without changing any of the original 6502 source code:
 PSHS  A		; Save A
 LDD   VecRamPtr	; Get the address in D
 EXG   A,B		; change 6502 little endian to 6809 big endian
 LEAU  D,Y		; U = D + Y
 PULS  A		; Restore A
 STA   ,U		; Store A at U

If you fix the little endian of the 6502 source code a head of time:
 PSHS  A
 LDD   VecRamPtr
 LEAU  D,Y
 PULS  A
 STA   ,U

Another way with self mod code:
 LDU  VecRamPtr
 STU  Fix+1
Fix:
 LEAU $FFFF,Y
 STA  ,U

Fastest way, if you fixed little endian ahead of time and re-arranged the code so that you use B instead of Y in the 6502 source code you could then use:
 LDX  VecRamPtr
 ABX
 STA  ,X

6502 – Indexed Indirect (I found this excellent description from here)

This mode is only used with the X register. Consider a situation where the instruction is LDA ($20,X), X contains $04, and memory at $24 contains 0024: 74 20, First, X is added to $20 to get $24. The target address will be fetched from $24 resulting in a target address of $2074. Register A will be loaded with the contents of memory at $2074.

If X + the immediate byte will wrap around to a zero-page address. So you could code that like targetAddress = (X + opcode[1]) & 0xFF.

Indexed Indirect instructions are 2 bytes – the second byte is the zero-page address – $20 in the example. Obviously the fetched address has to be stored in the zero page.

6502 version:

LDA ($20,X)

6809 version:

LDA [$20,X]

That’s about all I’ve come across so far, if I find more as I look at more code I’ll add it above. If you know of anything else that the 6502 does that might bite me in the butt please comment below and I’ll add it to this list.

See you in the next post,

Glen

Posted in CoCo Programming | 7 Comments

Streaming large files on the CoCoSDC

Back in 2017, Ed Snider released a cool video and audio player for the CoCoSDC on a CoCo 1. I thought it was wicked and at that time I remember fighting with the CoCoSDC to try and get some of my own files streaming on the CoCo. Ed was very helpful and I did manage to get it working. Next I used his player and had some fun making colour videos that could play on the coco 1 but I haven’t really looked at it since.

Last week I was watching a CoCoTalk video and Tim Lindner was discussing a new version of the manual for the CoCoSDC that he made. He was discussing a few programs he included that would make it easier to work with the CoCoSDC from an assembly language program.

I tried to interface the CoCoSDC using the updated info from his latest manual and using the programs included but I ran into problems. I fought with it for awhile and figured out exactly what I needed to do. Part of the frustration is there is a minimum file size of at least 79,360 bytes. The other limitation is the file must be padded to 512 byte boundaries.

To make it easier for myself and others in the future I created a library that is easy to use specifically for opening files for streaming and closing files on the SD card. The library has an explanation how to use it with your own assembly language programs.

I put the library and an example program to use the library on my GitHub page. I’ll give an explanation how to use it below.

In your own 6809 assembly program for the CoCo 1, 2 or 3 you must include the library from the my GitHub page called SDC_Stream_File_Library.asm. To use it you load X with the address of your filename a dot and the extension is 3 characters and end the filename with a zero/null byte then do a JSR OpenSDC_File_X_At_Start like this:

        LDX     #MountFile          * Memory location of Filename, terminated with a zero
        JSR     OpenSDC_File_X_At_Start   * Open a file on the SDC for streaming, 512 bytes at a time

If it opened the file ok then the carry flag will be clear, if there was a problem then it will return with the carry flag set. So you should deal with a file opening error in your program.

Also note that if the file isn’t at least 79,360 bytes and padded to 512 byte boundaries the CoCoSDC will lock up while checking to make sure it’s ok. The Library won’t return and will get stuck in an endless loop. So if your program is locked up it could be a problem with the size or the 512 byte padding of the file you’re openning.

Once you’ve successfully gotten your file open, next you read the two bytes from address $FF4A, such as LDU $FF4A, to read the next two bytes from the file on the SD card you do a LDU $FF4A. The CoCoSDC automatically gets the next two bytes and makes them available at the same address $FF4A. Continue reading words at $FF4A until you’ve read 512 bytes. Then you have to make sure the CoCoSDC has filled its buffer and is ready to continue feeding you more of the file at address $FF4A. The Following code will work to check the CoCoSDC is ready to continue giving you the rest of the file:

* After reading a 512 byte sector we check if we are done and also make sure the SDC's buffer has been fully loaded again
PollSDC:
        LDB     ,Y                  * Poll status, get status Bits 000000xx:
                                    * Bit 1 set = SDC Buffer has been loaded, Bit 1 clear = SDC Buffer is still being loaded
                                    * Bit 0 set = Not at End of File yet,     Bit 0 clear = Reached the End of the File
        ASRB                        * BUSY --> carry
        BCC     StreamDone          * exit if BUSY cleared (we reached the End of the File)
        BEQ     PollSDC             * continue polling if the Buffer is not completely loaded

        DECA                        * Decrement the sector counter
        BNE     LoopAgain           * keep reading if we haven't counted down to zero

StreamDone:

Then keep reading another 512 bytes and so on…

Once you are done reading from the file, you must close it. To do this with the library you do the following command:

StreamDone:
        JSR     Close_SD_File       * Close file and put Controller back into Emulation Mode

That’s all you need to open a file from the beginning and close it once you’re done. The other thing you can do with the library is open the file at a specific 512 byte block. Instead of having to read through the file to get to the part you want you can use the following code (example of starting from the 6th logical sector):

* Open a file for streaming starting at Logical Sector Number, each sector is 512 bytes.  The LSN is in A & U where
* the Logical Sector Block, 24 bit block where A=MSB (bits 23 to 16), U=Least significant Word (bits 15 to 0)
*
    LDA  #$00
    LDU  #$0006          * Start at 6 * 512 bytes from the beginning of the file
    JSR  OpenSDC_File_X_At_AU  * Open File pointed by X starting at (A and U)

One last thing to note I’ve tried the code on a CoCo 1 and a CoCo 3 (even at double speed) and it works well. My CoCoSDC is currently running MCU version 121 and SDC DOS version 1.6

See you in the next post,

Glen

Posted in Uncategorized | 10 Comments

Code golf on the CoCo

Hello, I came across a website https://codegolf.stackexchange.com where people ask others to write programs in any programming language they wish and to make the program as short as possible.

I thought this would be a cool 6809 programming exercise since I’ve mainly been working on optimizing my programs for speed which almost always increases the size of the program, see my blog entries about 6809 optimization routines.

I came across what I thought was a cool request to draw the Mona Lisa on screen using the same method as a programmer named Jakub ‘Ilmenit’ Debski wrote. He dedicating the program to his wife Iza, who pasted away February 15th, 2014. This link describes the method the program needs to work.

The original program by Ilmenit was created for an Atari XL computer with a 6502 processor and is only 250 bytes long. It uses two system calls, one to setup the graphics mode and the other to draw pixels on the screen. My version for the CoCo 3 and 6809 processor is 283 bytes, but I had to write my own drawing routine and a few bytes to put the CoCo 3 in graphics mode so I’m ok with it being 33 bytes longer than the original.

On the Internet Archive wayback machine website I was able to find some info from the original post https://www.pouet.net/prod.php?which=62917 and I thought I’d reproduce them here, just so that others can find them easier:

“There are 64 pseudo random brush strokes of cycling color. Each stroke is shorter than previous to increase details. Intro code generates set of random numbers starting from initial 32bit seed with Galois LFSRs as a PRNG. This set is used to control direction of brush movement and and starting position. Each stroke has initial 16bit value that influences the seed. Therefore data is 4 bytes (initial seed) + 64*2 bytes of stroke PRNG seeds. Picture of Mona Lisa (3072 bytes) was compressed to those random brush strokes by external optimization program (a few days of work for modern CPU/GPU combo). The process can be seen as lossy compression (23x) of picture into random brush movements.

added on the 2014-04-13 18:59:08 by ilmenit

“The seeds were found with iterative brute-force approach. Each brush stroke is a layer which covers the previous one, therefore is mostly independent in calculations. For each layer there is a 16bit seed to be found that generates the best picture possible. Therefore there are 64 layers * 65536 possible values per layer and the program iterates through all of them (4 million of combinations – acceptable). This should be done for each initial 32bit seed (means *4 billion combinations) but can be performed in parallel. I’ve checked only a few thousands of randomly chosen initial seeds to find a nice looking picture. Details mask had to be used to multiply difference error on critical parts of the picture (eyes, nose, mouth). Otherwise too many details were put in the background.
Thank you all for the kind words.

added on the 2014-04-14 11:26:07 by ilmenit

I uploaded a video of the program running on a CoCo 3:

I included the source code below and I’d love to hear if anyone has any ideas to shrink the code even more. Hopefully it might inspire others to program their CoCo’s.

Updated code: I managed to get the code to 283 bytes, it is not relocatable now as I used the TFR PC,D to set the values for the A and B registers near the start and tweaked a few more things… I also managed to get a version that uses the BASIC ROM to draw the pixel but it only saves 2 bytes (281 bytes total for that version) since I had to prep the code before the draw pixel routine.

The version below is using all my code (no ROM calls) and is 283 bytes when assembled into a Machine language program loadable from BASIC.

I uploaded a zip file to the Color Computer Archive that contains the binary, a .DSK file and the source code below along with the original Atari XL 6502 version that inspired the code golf article.

See you in the next post, Glen

* Assemble with LWASM

 pragma noforwardrefmax   * Forces LWASM to do multi-pass refrences so it can use reuslts that are smaller than 16 bits

Testing       EQU   0  * 0 = no high speed poke, 1 = faster high speed poke & program autostarts
PIXELS_PER_LEN equ 32

  ORG   $FFB0        * Palette_0

CoCo3_Colours:
  FCB   %00000000    * Colour 0
  FCB   %00100010    * Colour 1
  FCB   %00110101    * Colour 2
  FCB   %00111110    * Colour 3

	ORG     $7B96      * starts here so we can use $7C as direct page for variables
* Mona Lisa Brush movement table
* BRUSH = [
Brush:
  FDB   $030A,$37BE,$2F9B,$072B,$0E3C,$F59B,$8A91,$1B0B
  FDB   $0EBD,$9378,$B83E,$B05A,$70B5,$0280,$D0B1,$9CD2
  FDB   $2093,$209C,$3D11,$26D6,$DF19,$97F5,$90A3,$A347
  FDB   $8AF7,$0859,$29AD,$A32C,$7DFC,$0D7D,$D57A,$3051
  FDB   $D431,$542B,$B242,$B114,$8A96,$2914,$B0F1,$532C
  FDB   $0413,$0A09,$3EBB,$E916,$1877,$B8E2,$AC72,$80C7
  FDB   $5240,$8D3C,$3EAF,$AD63,$1E14,$B23D,$238F,$C07B
  FDB   $AF9D,$312E,$96CE,$25A7,$9E37,$2C44,$2BB9,$2139
* ];

CoCo_START:
***********************************************************
        ORCC    #$50                    * Disable interrupts
        TFR     PC,D                    * A=$7C, B=$1A
        LDX     <$8A                    * X=0, in BASIC memory location $008A and $008B are always zero
;        LDA     #%01111100              * A = $7C
        STA     $FF90                   * CoCo 3 Mode, MMU Enabled, GIME IRQ Enabled, GIME FIRQ Enabled, Vector RAM at FEXX enabled, Standard SCS Normal, ROM Map 16k Int, 16k Ext
        TFR     A,DP                    * DP = $7C
        SETDP   $7C                     * Let the assembler know the DP value

  IF Testing
        STB     $FFD9                  	* High Speed mode enabled
  ENDIF

* Clear the screen
!
        CLR     ,X+
        CMPX    #Brush
        BNE     <                       * All clear
* X now points to the Brush table

* CoCo Prep
***********************************************************
* Set Hires Screen Resolution and the number of Colours
;        LDA     #%10000000              * = $80, Graphics mode, Colour output, 60 hz, max vertical res
;        LDB     #%00011010            	 * = $1A, 256 x 192 x 16 Colours requires 24,576 bytes = $6000 RAM
;        LDD     #$801A                  * Graphics mode, Colour output, 60 hz, max vertical res & 256 x 192 x 16 Colours requires 24,576 bytes = $6000 RAM
        LDA     #$80
        STD     $FF98                   * Graphics mode, Colour output, 60 hz, max vertical res
***********************************************************
      LEAU    cursor_y,PCR  * U = #cursor_y

next_part:
init_part:
      LDB   part		     ; part 0-63, from the end
      STB   length       ;

; (word) crc_seed = word_seed;
      LDD   ,X++         ; get the next value in A and B
      STD   crc_seed+2   ; STA crc_seed LSB & STB crc_seed LLSB
      STD   ,U           ;	STA cursor_y & STB cursor_x
init_part_end

next_length:
      LDA   #PIXELS_PER_LEN-1
      STA   pixel


next_pixel:
* inlined_rnd           * crc_seed
      ASL   cursor_y-1  * LLSB
      ROL   cursor_y-2  * LSB
      ROL   cursor_y-3  * MSB
      ROL   cursor_y-4  * MMSB
      BCC   nofeedback

      LDB   #4          * Set to 4 above
      LEAY  -4,U        * U=crc_seed   ;  LDU   #crc_seed   * U=crc_seed
xor_seed_loop:
      LDA   ,Y          * Get the crc_seed
      EORA  -4,Y        * XOR the value
      STA   ,Y+         * save the new value + move pointer
      DECB              * decrement the counter
      BNE   xor_seed_loop ;   bpl	xor_seed_loop

      STA   direction	   * a=direction=crc_seed[0]  crc_seed LLSB

nofeedback:
move_cursor:
      LDB   direction  ; B=direction
      ANDB  #$02       ; and #2		; random value 0 or 2 for cursor_y/cursor_x
      LSRB             ; make it 0 or 1
      LDA   B,U        ; B=0 then LDA  cursor_y else B=1 then LDA cursor_x
      TST   direction
      BMI   >
      INCA
      INCA
!
      DECA
      ANDA  #$7F       ; and #$7F	; limit value to 0-127
      STA   B,U        ; sta cursor_y(or cursor_x)

drawpoint:
* drawPixel(bx, by, COLOR[part AND 3])
* bx is a range from (0 to 127)
* by is a range from (0 to 95)
* Coco 3 Screen resolution is 256 x 192 x 16 colours, starting at $0000
* each row is 128 bytes

      LDD   cursor_y          * D = address, A = Y - co-ordinate, B= x - co-ordinate
      CMPA  #96
      BHS   DoneDrawPoint
      ANDB  #$7F
      LSRA
      RORB              * 1/2 and move bit zero to the carry
      TFR   D,Y
      LDD   #$F003      * Right Nibble and B has the right nibble colour
      ANDB  part        * Make colour a range of 0 to 3
      BCS   >           * If carry is set then it is an odd number (right nibble used)
      LDA   #$10
      MUL               * Set high nibble with the colour (left nibble)
      LDA   #$0F
!
      ANDA  ,Y          * A = the original pixel value for the old nibble
      STB   SelfModA_OR_B+1  * Self mod to do an OR of A & B
SelfModA_OR_B:
      ORA   #$FF        * A = A logical OR B
      STA   ,Y          * WRite the new value to the screen

DoneDrawPoint:
;;;;;;;; loop

      DEC   pixel     ;      DEC pixel
      BPL   next_pixel
      DEC   length     ;      DEC length
      BPL   next_length
      DEC   part     ;     DEC part
      BPL   next_part

      BRA  *      * Loop forever

part  FCB   63
xor_const:
;*      LLSB,LSB,MSB,MMSB
; 	FCB   $B7,$1D,$C1,$04  * Little Endian
  FCB   $04,$C1,$1D,$B7  * Big Endian
crc_seed:
;*      LLSB,LSB,MSB,MMSB
;  FCB   $E5,$54,$C8,$7E  * Little Endian
  FCB   $7E,$C8  * Big Endian
; next two bytes will be set when the program starts the crc_seed is 4 bytes so we have to make sure we make space for it
cursor_y  EQU   crc_seed+4
cursor_x  EQU   cursor_y+1
direction EQU   cursor_x+1		; this gets zeroed by setting graphics mode
pixel   EQU   direction+1
length  EQU   pixel+1

  IF Testing
      ORG   $A42E       * Make it autostart
      FDB   CoCo_START
  ENDIF

  END  CoCo_START
Posted in Uncategorized | 1 Comment

ROBOTRON last update…

Hello, it’s been quite awhile since my last blog post, I got tired/burnt out of working on ROBOTRON and I’ve also been doing a lot of home renovations and other life events have kept me from my CoCo hobby.

I recently started looking at CoCo programming again and even took a look at the ROBOTRON code but I don’t have any motivation to work on it at this point. It is just about complete, it has some audio samples working but needs some tweaking.

The main thing I was fighting with the game was some slow downs that occur in the game when there are tons of enemies firing at you. It doesn’t exactly seem like it’s a problem with the CoCo 3 drawing the sprites, as it seems to slow down and then speed up even when the same amount of shots are on screen. It might be some code that is looping more at certain times when the enemies are firing or something else. I’ve done many things to try to figure it out but I decided it’s going to have to be like that.

ROBOTRON was never my favourite game. My hope when starting to work on the game was that since the hardware is exactly the same as Joust that I could get the game going and move on in a relatively quick time frame. Unfortunately that wasn’t the case. I’ve always enjoyed the game and I have very fond memories of it. When I was young my best friend and I would go to the arcade and my friend would play ROBOTRON and I would play Defender. So it was never MY game. I don’t have the drive to continue working on the game for the CoCo. This is a hobby and it’s supposed to be fun. But ROBOTRON at this point just feels like frustrating work so I’ve decided I’m not going to work on it anymore.

Since it is pretty much playable and the CoCo community might enjoy playing it the way it is, I’ve decided I might as well put the game on the internet and let people have some fun with it. Plus CoCoFest is happening in another week and a half so why not…

The game load screen says it’s version 0.5 but that’s based on my source code I’m up to file number 52. Really the game should be version 0.9 as it’s completely playable with just a few graphic glitches and the slow downs I mentioned above and the audio routines needed tweaking and more samples need to be added. The DSK image of the game is available on my GitHub page and I’ll upload it to the CoCo Archive.

To play the game just type RUN”ROBOTRON

Press 5 to insert Coin, 1 or 2 to start a one or two player game. It does require two joysticks.

Hopefully my CoCo friends have some fun with the game,

Glen

p.s.

I’ve had requests for the source code as the game currently is so I’ve uploaded it on my GitHub page here: https://github.com/nowhereman999/ROBOTRON_CoCo3

Posted in Uncategorized | 2 Comments

CoCo 3 3D pixel animation and how to see how much CPU usage is being used per frame

Last week on the CoCo discord assembly channel Simon Jonassen posted some code to draw and animate 128 pixels that drew a radar or fan blade type animation on the CoCo 3 screen using some very optimized 6809 assembly code. I was intrigued by his code and the idea of how he was drawing and erasing the pixels. I came up with another way of doing it. He suggested the only way to know if it will work is by actually doing it. “The proof is in the pudding.” I needed a little break from Robotron anyways… While chatting a little about this with Simon he mentioned how cool it would be to use this effect to draw a 3D face. I’m an old 3D modelling guy who’s been modelling on computers since I got my Amiga 500 back in the late 80’s so once Simon suggested this all kinds of ideas popped into my head. All I needed was a way to get the 3D point info from 3D objects and do a little math with the numbers so they show up with some depth on the CoCo 3 screen and animate them.

I used blender’s animation tool and took a 3D object (in this case the Death Star) and did a 360 degree animation over 55 frames. I was able to write a tiny script to capture the output of the rotated object for each frame and export each frame to a file. I then wrote a program to get the vertices from each file and convert them into some data the CoCo 3 can display.

I start by preprocessing the data (which Simon also does with his program) by copying the background image of each pixel to be drawn on screen into a data table which holds the original value of the pixel from the previous frame along with the location to be drawn and also generate the new pixel for the current frame to draw. The table entries end up in a format that can be quickly pulled off the user stack as:

PULU A,B,X,Y where A is the original (background) value of the previous frame. B is the value of the new pixel to be drawn on the current frame. X is the screen address of the pixel from the previous dot and Y is the screen address of the pixel for the current frame. All that’s needed is to STA ,X and the old pixel from the previous frame is erased and a STB ,Y to draw the new pixel for the current frame. Here’s the small main loop of code that does the magic restore a pixel and draw a new one in 27 CPU cycles:

 LDU   #$C000  * Position U at the start of the block
!
 PULU  D,X,Y   * A = byte to use to put back previous frame to normal @ X
               * B = new coloured Dot to use on the current frame @ Y
 STA   ,X      * Erase old coloured pixel from previous frame on screen
 STB   ,Y      * Display new pixel
 CMPU  #$C000+NumberOfPoints*6-18 * Have we gone through all the pixels?
 BNE   <       * Keep looping until we've done them all

I threw together a little demo video to show the code in action. See the video here:

Pulling the data off the user stack let’s the CoCo 3 erase 727 pixels and draw another 727 pixels each frame and I’d say just under 60% CPU time per 60 hz frame. The blue colour at the top and sides of the frame is CPU timing info. It’s another cool thing I learned from Simon this week. Apparently this technique has been used since the 80’s but it’s new to me.

The way it works is you set the colour of the border to anything you like, in my example video I used blue for the border. Right at the start of your drawing routine you set the border colour and as soon as your drawing routine is done you turn off the border colour. What you get is the colour down the screen. The further down it goes the more time it took to draw your screen.

Just before you start drawing to the screen you use this code:

LDA     #%00001111     * xxRGBrgb - light blue colour
STA     $FF9A          * Setup Border Colour (Speed check)

At the end of your screen drawing code you use this code:

CLRA
STA     $FF9A           * Clear Border colour (Speed Check)

I think this is a pretty cool trick. Thanks Simon for sharing your code and ideas with the CoCo community to help push this retro computer to new limits.

Cheers,

Glen

Posted in Uncategorized | Leave a comment

Robotron for the CoCo 3 progress update

I’ve been asked for an update on the progress of Robotron and since I made some excellent progress today I thought I’d make a video and show it on YouTube.

I’ve got most of the sprites converted to compiled sprites but there are still some bits of the code that are using my blitter emulation code. There aren’t tons of sprites on the attract screens so I still don’t know if the CoCo 3 version is going to be fast enough to keep the game going at the same speed as the arcade game but I’ll keep working at it…

Next things to do are get the joysticks working and start a game. I still have to work on the code that draws the player as they beam on the screen from horizontal, vertical and diagonal lines. I’m sure there will be other things that running the actual game will show up as other things to fix that I haven’t come across yet. One will be to add the text messages/wave numbers that show up on the bottom of the screen. I should have room to fit them along the top with the players scores. We’ll see…

Cheers,

Glen

Posted in Uncategorized | 4 Comments