Zilog z80 to Motorola 6809 Transcode – Part 020 – Sound ideas

I’ve taken a break from optimizing the Pac Man code to do some experiments with getting the CoCo 3 to play audio.  This is something I’ve read about but have never coded myself before.  I guess mainly because it involved Interrupt Requests, at least if you want to make the CoCo make sounds and continue doing something else at the same time and as I’ve written before learning more about IRQs is one of the reasons I am doing this Pac Man transcode and of course to see if the CoCo 3 can do it.  🙂

Recently I posted a question asking for some code to do sample playback on the CoCo mailing list and got some really solid information about how the CoCo DAC is used.  Basically once the CoCo is configured properly simply feed samples to at a constant rate to the 6 bit DAC.  The best way is to use the FIRQ with the timer Interrupt, you set the timer to a certain value and when the timer counts down to zero the FIRQ is triggered sending a byte of sampled data to the DAC.  Since this is going to happen very often while your program is running it is very important that the FIRQ routine is optimized.  I looked around for some code examples for doing this using the FIRQ but I came up empty.  I decided to take a look at how John Kowalski did sound in his Donkey Kong game.  I hope he doesn’t mind since he did post on his website that he hoped his version of Donkey Kong would inspire others to convert other video games to work on the CoCo.  He is a master at CoCo programming and I figure his code would be very optimized.  I didn’t decode all of his audio routine but it looks like his FIRQ routine changes the CoCo3 memory bank from the normal bank 0 to bank 1 where his audio samples are already loaded in memory and from there it grabs a sample byte does some add to it and outputs it to the DAC.  Once his data pointer reaches $8000 (sample end location) then it stops or repeats (I can’t remember)…  His method gave me the idea to do something similar and if I use the top 32k for one sample and the bottom 32k for another sample that I can then have two samples in memory at the same time and then play them back together without too much CPU interacting at all.  I’ll explain how I’ve worked this out below…

First things first…  I used the original Pac Man code on Mame in debug mode and setting it up to output to a wav file.  I tweaked the original pacman code while running it in debug mode (since I have it all decoded now) so that I can disable certain sounds and enable others to isolate individual sounds to get the best sounding samples from Pacman as I can.  The mame command is an example of capturing the cutscene music.

mame -window -natural -nojoy -debug pacman -wavwrite cutscenes.wav

Then I used some audio tools to tweak the samples to the exact length I needed and converted them down to 6kHz Mono, 8 bit unsigned raw data.  In this format the data is in the correct format to send to the CoCo3’s DAC.  I found some code on the internet from Robert Gault that explains how to setup the CoCo’s DAC so that you can simply send 8 bit audio to the CoCo and it will ignore the data in bits 0 & 1, which means that the audio doesn’t need to be processed ahead of time (making bits 0 & 1 a zero value) or using the ANDA  #$FC instruction on your data before sending it to the DAC.  It turns out to save space though I ended up stripping those bits away in my Pac Man transcode.  But for other projects it will come in handy.

I’ll spare you the details about storing the 8 bit data as 6 bits and decompressing them.  But basically you take the high 6 bits of each sample and take 4 bytes of data and turn it into 3 bytes (8 bits * 4 bytes = 32 bits, 6 bits * 4 values = 24 bits or 3 bytes)

As I stated above the FIRQ is setup to be triggered when the Timer counts down from a specific value for Pac Man I’m currently using a value of $280 with the timer speed set to 279.365 nano seconds.  I have two FIRQ routines, one that plays a sample over and over (playing the constant siren sample while Pacman is running in game mode) and another FIRQ that plays the same sample the one just described but adds a second sample that is played only once.

There may be simpler and better ways to pull this off but this is the method I came up with:

In order to make the FIRQ as fast as possible I don’t want to use anymore registers or accumulators then I need to.  Unlike the IRQ which automatically pushes all the acumulators and registers to the stack and restores them with the RTI instruction the FIRQ only affects the CC register and the stack pointer so it know where to return from once it’s complete.  This makes it faster but you also have to manage the registers yourself.  I have the FIRQ working only using the A accumulator and no registers.  Here is the code I’m currently using for the looped audio playback:

* Play Sample in the background continuously in mem $FD00-$8000 when it hits $8000 it will loop back to it's start location
Sample1Start:
        FDB     $0000       * location where sample starts counting down from, used for looping
FIRQ_Interrupt1:
* Play the Siren sample in the background continuously in mem block $8000-$9FFF when it hits $7FFF it will loop back to it's start location
        STA FIRQRestoreA+1  * Save A
        LDA FIRQENR         * Re enable the FIRQ
        LDA #$21            * Set the MMU bank registers to
        STA INIT1_Register1 * Bank task 1 - alternate 64k bank is now the current one
LoadAudio1:
        LDA $9000
        STA PIA1_Byte_0_IRQ * OUTPUT sound to DAC
        DEC LoadAudio1+2    * Decrement the LSB of Sample 1 pointer
        BNE >               * check if we hit 00, if so decrement the MSB of the sample pointer
        DEC LoadAudio1+2    * Decrement the LSB of Sample 1 pointer (force it to go from xx00 to xxFF modified sample data to account for this while being decompressed)
        DEC LoadAudio1+1    * Decrement the MSB of Sample 1 pointer
        BMI >               * if negative then we are good still over $7FFF otherwise (end of sample 1, reset pointer)
Hit8000:                    * Restore the pointer to the start location of the sample
        LDA Sample1Start    * Point to the MSB of the sample Start location
        STA LoadAudio1+1    * Store the MSB of Sample 1 pointer
        LDA Sample1Start+1  * Point to the LSB of the sample Start location
        STA LoadAudio1+2    * Store the LSB of Sample 1 pointer
!       LDA     #$20        * Set the MMU bank registers to
        STA     INIT1_Register1 * Bank task 0 - Back to the normal 64k bank
FIRQRestoreA:
        LDA #$00            * STA at the start of the FIRQ stores A's value here and gets loaded before the RTI saves a cycle and a byte of RAM
        RTI * Return from the FIRQ

One extra process to the audio data was to reverse it since the FIRQ routine has to count downward to know where the end of the sample is.  This is not the way John Kowalski did it in Donkey Kong but the code and idea is similar.  The sample starts for example at $9355 and counts down until the MSB of the address pointer changes to a positive value which means it reach $7F then it resets the pointer to $9355 and starts again.

To summarize the FIRQ:

  • The Routine saves the A accumulator and stores it in the LDA instruction (kind of self modifying code) so that the LDA instruction just before it exits the routine restores A’s value.
  • Next it re-enables the FIRQ by doing an LDA FIRQENR ($FF93),
  • Then it switches MMU memory bank to bank 1 where I have already setup the sample in memory block 4 ($8000-9FFF).   
  • The program loads the A register at the current pointer location and sends it to the DAC.
  • Decrement the pointer to the sampled data and if it becomes a positive number then reset the pointer to the start of the data
  • Swap the bank MMU memory bank back to the normal bank 0
  • Restore A
  • Return from the Interrupt

There is one extra problem when decrementing the LSB and the MSB of the pointer.  When the LSB gets to 00 it then decrements the MSB which means it jumps from a number like $9201 to $9200 since the LSB is now 00 it makes the MSB $91, so the value is now $9100 then the next value would be $91FF since the decrement is done before the check for 00 value.  So to get around this problem when the LSB becomes 00 I decrement the LSB again and decrement the MSB so the value actually goes from $9201 to $9200 then $91FF in one step which skips the $9200 value.  I couldn’t think of a better way to do it and still keep the code tight.  So I had to modify the audio sample data to compensate for this byte skipping, which is done in my decompression code.  I could have left the code with the $9201, to $9200, $9100, $91FF, $91FE… and modified the sample data to account for this too and it would save me a few more cycles after every 255 bytes are sent tot he DAC.  But that is pretty negligible, but maybe in the future…

So why not just count upwards you are probably asking, it’s so that I can use the top 32k of the RAM for one sample and the bottom 32k of RAM for the other sample.  My FIRQ has to exist in memory at all times in both MMU bank configurations (bank 0 and bank 1) to process the sound and it is located in the memory at $E000-FFFF where the special CoCo configuration settings are located ($FF00-$FFFF).  So I can’t have sample data in this location or it would clobber all those settings and my FIRQ code too.  Counting downwards allows me to check to see if the routine changes from a negative ($FF to $80) to a positive ($7F to 00).  My other FIRQ routine does basically the same as the one above except it uses the sample data from lower memory $0000-$7FFF and that gets added to the sample data from the top 32k and divides that value in half and sends it to the DAC.  This routine also counts downwards and when the MSB get’s to $FF and becomes a negative then this 2nd FIRQ routine ends and changes the FIRQ pointer to point to the first FIRQ again.  This 2nd FIRQ plays a second sample once along with the original sample that is being looped at the same time.

Here is the FIRQ2 code that plays the two samples together:

* Playback Sample 1 continuously play Sample 2 once
* Siren will be at the normal location in mem block $FD00-$8000 when it hits $7FFF it will loop back to it's start location
* 2nd sound will be in mem block $7FFF-$0000 backwards so we can count down to $0000 then leave this FIRQ when the sample is finished playing
* and jump to the main FIRQ just playing Sample 1 routine
FIRQ_Interrupt2:
        STA FIRQRestore2A+1  * Save A
        LDA FIRQENR          * Re enable the FIRQ
        LDA #$21             * Set the MMU bank registers to
        STA INIT1_Register1  * Bank task 1 - alternate 64k bank is now the current one
LoadAudio2:
        LDA $0000            * Load Siren Sound sample same address as the normal FIRQ
AddAudio:
        ADDA $0000           * Add 2nd sample points to address ($0000-$1fff) counting downwards
        RORA                 * Divide the combined samples by two
        STA  PIA1_Byte_0_IRQ * OUTPUT sound to DAC
        DEC LoadAudio2+2     * Decrement the LSB of Sample 1 pointer
        BNE Decrement_Snd2   * check if we hit 00, if so decrement the MSB of the sample pointer
        DEC LoadAudio2+2     * Decrement the LSB of Sample 1 pointer (force it to go from xx00 to xxFF modified sample data to account for this while being decompressed)
        DEC LoadAudio2+1     * Decrement the MSB of Sample 1 pointer
        BMI Decrement_Snd2   * if negative then we are good still over $7FFF skip ahead, if not (end of sample, reset pointer)
Hit8000_2:                   * Restore the pointer to the start location of the sample
        LDA Sample1Start     * Point to the MSB of the sample Start location
        STA LoadAudio2+1     * Store the MSB of Sample 1 pointer
        LDA Sample1Start+1   * Point to the LSB of the sample Start location
        STA LoadAudio2+2     * Store the LSB of Sample 1 pointer
Decrement_Snd2:
        DEC AddAudio+2       * Decrement the LSB of the 2nd sample pointer
        BNE >                * If we LSB of 2nd Sample reached 00 then exit routine
        DEC AddAudio+2       * Decrement the LSB of the 2nd sample pointer (force it to go from xx00 to xxFF modified sample data to account for this while being decompressed)
        DEC AddAudio+1       * Decrement the MSB of the 2nd sample pointer
        BPL >                * If we haven't reached $0000 then we are a positive number so exit routine, exit if we are now at $FFFF
        LDA LoadAudio2+1     * copy the sample pointer position from this FIRQ
        STA LoadAudio1+1     * to the normal FIRQ sample pointer
        LDA LoadAudio2+2     * copy the sample pointer position from this FIRQ
        STA LoadAudio1+2     * to the normal FIRQ sample pointer
        LDA #FIRQ_Interrupt1/256 * The 2nd sample is finished so we can set the FIRQ back to the normal Just playing Siren Sound routine
        STA FIRQ_Start_Address   * Update FIRQ jump address MSB  ( in the future could use one digit if we use the Direct Page for the FIRQ)
        LDA #FIRQ_Interrupt1-((FIRQ_Interrupt1/256)*256) * Get LSB of other normal FIRQ routine
        STA FIRQ_Start_Address+1 * Update FIRQ jump address LSB
!       LDA #$20                 * Set the MMU bank registers to
        STA     INIT1_Register1  * Bank task 0 - Back to the normal 64k bank
FIRQRestore2A:
        LDA #$00                 * STA at the start of the FIRQ stores A's value here and gets loaded before the RTI saves a cycle and a byte of RAM
        RTI                      * Return from the FIRQ

Here is an example of setting up the FIRQ’s with the sample data:

FIRQ1 –

* Play Background Siren Audio Sample over and over
        LDD     Snd_07_Insert_Coin_Length
        STD     LoadAudio1+1            * to the new FIRQ pointer
        LDA     #$2D                    * 11_Siren_6khz_8bit_Mono_rev MEM Block $2D
        STA     MMU_Reg_Bank1_4         * Page $8000-$9FFF  Block #4 - Move the sample Mem BLK to the $8000-$9FFF location in the alternate MMU Bank which the FIRQ uses
        LDX     #Snd_11_Siren_Length    * Get End location of sample to play
        STX     Sample1Start            * Start location that will be used to count down from and loop from
        STX     LoadAudio1+1            * Store where to start playback from

FIRQ2 –

* Play Insert Coin Audio Sample
        LDD     LoadAudio1+1            * copy the sample pointer position from FIRQ1
        STD     LoadAudio2+1            * to the new FIRQ pointer (FIRQ2)
        LDD     Snd_07_Insert_Coin_Length
        STD     AddAudio+1              * Store the length of the 2nd sample which is also the pointer to start playing the sample from
        LDA     #$25                    * 07_Insert_Coin_6khz_8bit_Mono_rev MEM Block $25
        STA     MMU_Reg_Bank1_0         * Store the Mem BLK in MMU Bank 1 block 0 = $0000-$1FFF
        LDX     #FIRQ_Interrupt2        * Change the FIRQ to the one that handles playing two samples together
        STX     FIRQ_Start_Address      * Point to the FIRQ Jump Vector

It’s also a good idea to setup the Direct Page to point to the location where the FIRQ routines exits so they execute as fast as possible.

Just for completeness this is the code that I use to initialize the audio hardware on the CoCo:

*************************************
* Configure Audio settings
        LDA     PIA0_Byte_1_HSYNC       * SELECT SOUND OUT
        ANDA    #$F7                    * RESET LSB OF MUX BIT
        STA     PIA0_Byte_1_HSYNC       * STORE
        LDA     PIA0_Byte_3_VSYNC       * SELECT SOUND OUT
        ANDA    #$F7                    * RESET MSB OF MUX BIT
        STA     PIA0_Byte_3_VSYNC       * STORE
        LDA     PIA1_Byte_3_IRQ_Ct_Snd  * GET PIA
        ORA     #$8                     * SET 6-BIT SOUND ENABLE
        STA     PIA1_Byte_3_IRQ_Ct_Snd  * STORE
* From Robert Gault
* This code masks off the two low bits written to $FF20 - we wont need this since we had to compress the audio but it is a neat feature
* So you can send the PCM Unsigned 8 Bit sample as is, no masking needed
        LDA     PIA1_Byte_1_IRQ
        PSHS    A
        ANDA    #%00110011            * FORCE BIT2 LOW
        STA     PIA1_Byte_1_IRQ       * $FF20 NOW DATA DIRECTION REGISTER
        LDA     #%11111100            * OUTPUT ON DAC, INPUT ON RS-232 & CDI
        STA     PIA1_Byte_0_IRQ
        PULS    A
        STA     PIA1_Byte_1_IRQ
*************************************

I know this post was pretty technical but it needs to be if others want to use it in their projects in the future.  See you in the next post…

Glen

Posted in CoCo Programming | 5 Comments

Zilog z80 to Motorola 6809 Transcode – Part 019 – You can try it, if you want…

Hello,

First things first, just so I don’t get anyone telling me.  At this point Pac Man is slow about 1/3rd the speed of the original arcade machine.  The cutscenes need to be fixed and there is no sound…  Other then that it does play 100% true to the original arcade machine.

I’m at the point where I have to re-write a lot of the way the Pac Man code relates to it’s own screen hardware in order to speed the game up and hopefully get it working at 100% speed.  Maybe even add sound if I can get it running fast enough.  It’s more for education purposes then for game play at this point as it is too slow (about 1/3rd real speed) to actual play for fun in it’s current state (unless you run the CoCo3 and the game from a fast emulator).  After this point the video code will change a lot and probably won’t relate to the original hardware so I figure the commented source code that I have at this point will be more useful to anyone who wants to learn how these old arcade games worked.  Then a future final version that I’ll make available, if I get it up to speed.

To get it working and to make the copyright holders happy,  you must have the rights to the original ROMs just like using MAME to actually play this game, even on the CoCo 3.  It won’t play unless you copy the ROM file PACMAN.5E to the floppy disk.  

Here is what you need to do to try out the game:

1) – Copy the disk image Disk1.dsk file to a real floppy using your favourite disk image tool

2) – Copy the PACMAN.5E ROM file to the floppy

After copying the ROM file and you type DIR you the disk should look like this:

Screen Shot 2017-03-08 at 4.35.10 PM

3) – Type RUN”PACMAN and hit Enter

If you get an NE Error it’s because you didn’t copy the PACMAN.5E file to the disk.  I’m sure taking a look at the PACMAN.BAS file will help.

You should see the loading screen:Screen Shot 2017-03-08 at 4.52.14 PM

Then the Startup / Option selection screen:Screen Shot 2017-03-08 at 4.52.49 PMFrom this screen use the arrow keys to change the options, other then selecting RGB/Composite these are the same as changing the DIP switch settings on a real Pac Man arcade machine.

From here press the Space Bar to start the Game, then hit 5 to insert a coin.  Press 1 to start a one player game or 2 to start a two player game.  Use the arrow keys to move Pac Man around.  Sorry no joystick support, as I don’t have a joystick for my CoCo 3.

Special Keys: 

A – Force scrolling to the top of the game screen

Z – Force scrolling to the bottom of the game screen

L – Skip the current level (this is built into the original Pac Man code for testing purposes)

A few things about this software:

The resolution of the real Pac Man hardware is 256×288, with the screen rotated 90 degrees.  So the play field is 288×256, the CoCo 3 hardware can use a maximum of 320×225 with 16 colours so it can’t show the full screen at one time without scaling the screen pixels.  Since this is a translation of Pac Man and I wanted the experience to look as close as possible to the real machine I decided that scrolling the screen vertically is the best option.  The game shows most of the maze at all times and if Pac Man is on the bottom half of the screen then it scrolls down.  If Pac Man is on the top half it scrolls up.  It only scrolls as much as it needs to show the maze.  If you want to see the top of the screen where the points are shown then press the A key.  If you want to see the bottom of the screen to see what level you are on press the Z key.

Also included with the Disk1.dsk floppy image is my commented 6809 source code.  I’ve documented a lot of the code while transcoding the z80 code to the 6809 and also found a lot of commented code on the internet that is included.  So this is a really good resource if someone wants to learn how these old arcade games from the 80’s worked.

Here is the link

I hope others find it useful,

Glen Hewlett

Posted in Uncategorized | Leave a comment

Zilog z80 to Motorola 6809 Transcode – Part 018 – Transcode is complete and a look at level 256

Hello again,

I’ve transcoded all the Pac Man z80 code to the 6809 now, except for some audio related code which isn’t going to be used on the CoCo 3.  The game plays properly now.  The demo mode ghost movement matches the original Pac Man ghost movement so I think it should play the same as the real machine meaning you should be able to use Pac Man patterns on the CoCo 3 version just like the real hardware.

The cutscenes are coded in but they currently look pretty bad right now since the sprites are changed around for normal game mode and the cutscenes use different sprites that I had to remove for normal game play (space limitation).  So I have to work on cleaning up the cutscenes.  Other then that any other little graphic glitch is sprite related and I don’t know if I’m going to fix them up before working out a better way to render the sprites which may fix those little glitches…

I tested out the game and changed the level to 256 to see if it would be drawn crazy just like a real Pac Man machine does and I’m happy to say that it does.  The only difference is the colour of the text isn’t the same, this is because the palette info on Pac Man hardware is handled differently then the CoCo 3.

 

screen-shot-2017-02-26-at-9-33-45-am

So from this point to make Pac Man perfect I need to:

  • Fix sprites while playing cutscene animations
  • Add Joystick support (I don’t have a joystick for my CoCo 3)
  • Speed up the game
  • Add sound output

Because the screen is a little too tall to be shown on the CoCo 3 screen I have written some code that auto scrolls vertically showing as much of the maze as it can at all times.  You can press the A key to see the top of the screen where the scores are shown.  Or you can press Z to see the bottom of the screen where the level info and the number of lives is shown.

See you in the next post…

Posted in CoCo Programming | Leave a comment

Zilog z80 to Motorola 6809 Transcode – Part 017 – Ready Player 1

Just a little update on my progress.  Things are looking pretty good.  I now have all the main game routines coded and I’ve added the buttons to insert a coin, select 1 or 2 player game and control Pac Man using the arrow keys.

screen-shot-2017-02-19-at-11-49-27-pm

There are tons of things I still have to fix, but it is quite playable except it’s slow compared to the real Pac Man.  I’m still hopeful that optimizing it will get it running at the proper speed.

Screen Shot 2017-02-19 at 11.50.15 PM.png

I still have to code in the cutscenes, but you can play it until the 2nd level is done and the first cutscene starts.

This is what I have to do at this point:

  • If you get to 10,000 points you should get a free man, currently it resets the score to 0 and no free man
  • Two player game high score didn’t get set properly, player 1 had high score but when player 2 was playing with a lower score it was still being copied to the high score
  • Fix Pac man death animation (need to copy from original sprite data)
  • Fix autoscrolling, currently broken if the ghosts are Dark Blue, it seems to follow the red ghost
  • Add the rest of the game code
  • fix sprites are wrecking the 1 player 2 player start message screen
  • Text in the middle of the screen is not erased when game starts
  • Maze gets erased beside where “game over” is written (sprites, problem)
  • When you eat the fruit the score shows up but part of the fruit is still shown until the score disappears
  • Credits jumps to 99 after starting a game
  • Credits not going down after playing a game
  • Test if keyboard scanning routine could be improved by swapping the STA and LDA bytes this would allows for only one STA for all the buttons along the same line

Of course I have to speed it up and sound would be cool too…

 

Posted in Uncategorized | Leave a comment

Zilog z80 to Motorola 6809 Transcode – Part 016 – Attract modes are done

Just a little update to say that I’ve made some great progress on the Pac Man code, it runs through both game attract modes properly and loops endlessly, looking for someone to put a quarter in the machine.

I found the random logic Pac Man uses actually points to the ROM hardware for part of the routine so I had to do the same in order to make the ghost movement match the original 100%.  The attract mode now matches real Pac Man and if someone know some patterns for Pac Man they should work on the CoCo 3 version exactly the same.

Getting the Maze demo working meant adding a lot of the ghost logic and once I add the controls in it shouldn’t be too much to actually get it to the point where I can play a level on the CoCo 3.

screen-shot-2017-02-17-at-8-50-16-pm

See you in the next post…

Posted in CoCo Programming | Leave a comment

CoCo 3 Loader for big programs

While working on the Pac Man transcode I was feeling a little cramped for space since the CoCo 3 has 64k of addressable space for the 6809 but loading a program that wont fit in memory between $E00 to $7FFF is difficult because the CoCo 3 uses some calls from the Disk Basic and Extended Basic while loading and if your program is too big it will over write the Extended Basic and Disk Basic routines and will lock up the computer.

There is some great info on William Astle’s page specifically his pdf on different ways to load programs into the CoCo.  I used this info and most of his two stage loader programs to make a generic loader that you can simply add to the beginning of your program and no longer worry about loading a huge file onto the CoCo3.  It as simple as assemble your program as usual, use the cat command to join the bigloader and your program into a new program and use that new program on the CoCo3.  Easy…

The technique is a little hard to wrap your head around but here goes…  The first program is the main loader it is a small program loaded into RAM at $2000 and while it’s loaded it also overwrites the end of the close routine to make it run itself instead of writing OK on the screen.  So once the program is loaded it auto starts and it copies all the data within it’s own large file into memory pages 0 to 7 (or more if you use the tricks mentioned below).  The first bit of code that is loaded is actually a secondary program.  Since it was loaded in RAM page 0 we know exactly where it is and when we finish copying all of our big program to RAM pages 0 to 7 we then make Block 6 use page 0 and execute the code which is now in RAM at $C000.  This code then copies the rest of itself to address $FD00 and then executes it from there.  This second program goes uses block 6 which is $C000-$DFFF as the place to read your program in from.  While it loads the program it handles all the ORG statements from your assembly language program and all the loading locations and even handles code that would write to RAM $C000-$DFFF so that when the loading is done your code will be exactly where it should be.  Your program will be autoexecuted after it’s loaded.

The loader allows you to use memory from $0000 to $FCFF for your program, that’s 64768 bytes!  Since it handles ORG statements you could actually use other pages in the CoCo3’s RAM to load even more data from your program.  For example if you wanted to load some picture data into RAM pages $23-$25:

    ORG  $FFA0
    FCB  $23
    ORG  $FFA1
    FCB  $24
    ORG  $FFA2
    FCB  $25
     ORG $0000
 Your data for graphics or whatever from $0000 to $5FFF go here

Just remember to put the pages back to normal before your code is written to those memory Blocks, unless you are doing something fancy…

    ORG  $FFA0
    FCB  $38
    ORG  $FFA1
    FCB  $39
    ORG  $FFA2
    FCB  $3A

Also keep in mind that the loader uses blocks 5 & 6 so don’t use them if you are doing special loads like the above example.

Other things to keep in mind:

-This over writes Extended Basic, Color Basic, Disk Basic and Super Extended Basic so your program will not be able to use these ROMS once it’s loaded.

-The loader uses high speed mode after loading from disk/tape (yeah it should work with a 64k tape based program too) so your program will be executed in high speed mode.

-The Stack is set to $FE00 which is probably the best place for it, since you can’t use $FD00 to $FDFF for your code because the actual loader uses this space.  The  RAM at $FD00 to FDFF is usable after your program is loaded if you want or it can become a nice space for the stack if left alone.

-RAM used for your code must be between $0000 and $FCFF, if it is bigger the $FCFF it will overwrite the loader code, there is no checking for this in the loader program.

To use the loader you simply copy the BigLoader to the start of your program using a binary copy tool.  First assemble your huge program for example we call it myprogram.bin.  With OSX and Linux use (cygwin under windows or a similar program) the cat command as follows:

cat bigloader.bin myprogram.bin > bigprogram.bin

This simply adds the loader to the start of your program.  Under RSDOS all you need is load the new bigprogram.bin since it now contains the loader and your program code.

Below is the source code for the loader programs if you modify the code for Part 2 it will change the position of where it thinks your program is and you will need to adjust it accordingly also it must be created as a RAW file not a regular CoCo binary.

You can download the program ready to use from here

Now I have some more room I can get back to Pac Man…

Part1:

        ORG     $2000
LOADER  LDD     #$176   ; restore close routine to be polite
        STD     $A42E
        CLRB            ; initialize block counter
LOADER1 STB     $FFA2   ; set MMU
        LDX     #$4000  ; init load pointer
LOADER2 JSR     $A176   ; get byte from file
        TST     <$70    ; EOF?
        BNE     LOADER3 ; branch if so
        STA     ,X+     ; save byte in memory
        CMPX    #$6000  ; end of MMU block?
        BLO     LOADER2 ; continue if not
        INCB            ; next MMU block
        BRA     LOADER1 ; set MMU and continue loading
LOADER3 JSR     $A42D   ; close file to be polite
        LDA     #$3A    ; Put BLK 2 back to default
        STA     $FFA2   ; page of $3A
        ORCC    #$50    ; disable interrupts to prevent crash
        CLR     $FFA6   ; stage two which decodes the loaded program will run from $C000
                        ; set CPU memory block 5 to physical memory 0
        STA     $FFD9   ; go to turbo mode
        JMP     $C000   ; transfer control to payload (stage two)
; this installs the close trap
        ORG     $A42E
        FDB     LOADER
; "LOADER" not really required
        END     LOADER

Part 2:

        ORG     $C000
        LDX     #Pointer
        LDY     #$FD00
!       LDD     ,X++
        STD     ,Y++
        CMPY    #$FE00
        BNE     <
        JMP     $FD00
Pointer:
        ORG     $FD00
LOADER  LDX     #$C0EB ; point to start of payload in logical block 6
        CLRA                    ; initialize block counter
        STA     $FFA6           ; initialize MMU point to $C000 where we will decode from
        LDS     #$FE00          ; Stack location
LOADER0 BSR     GETBYTE         ; fetch block flag
        TSTB
        BNE     LOADER2         ; branch if postamble
        BSR     GETBYTE         ; fetch block length
        STB     TWORD
        BSR     GETBYTE
        STB     TWORD+1
        LDY     TWORD           ; fetch block count
        BSR     GETBYTE         ; fetch load address
        STB     TWORD2
        BSR     GETBYTE
        STB     TWORD2+1
        LDU     TWORD2          ; fetch destination address
        CMPU    #$E000
        BHS     LOADER1         ; If new block is going to load at or above $E000 then load as normal it wont effect $C000-$DFFF 
        PSHS    D
        LDD     TWORD
        ADDD    TWORD2          ; end location in RAM
        CMPD    #$C000
        BHS     LoadHi          ; if writing to mem is going to hit our block then handle it
        PULS    D
LOADER1 BSR     GETBYTE         ; read a byte to its destination
Loader1b:
        STB     ,U+
        LEAY    -1,Y            ; finished block?
        BNE     LOADER1         ; continue if not
        BRA     LOADER0         ; handle next block
LOADER2 BSR     GETBYTE         ; ignore two bytes
        BSR     GETBYTE
        BSR     GETBYTE         ; fetch execute address
        STB     TWORD
        BSR     GETBYTE
        STB     TWORD+1
        LDB     #$3E
        STB     $FFA6           ; Put $C000 back to normal it could have code in it now
        JMP     [TWORD]         ; transfer control to main program
GETBYTE LDB     ,X+             ; fetch byte
        CMPX    #$E000          ; end of block?
        BEQ     >
        RTS
!                               ; return if not
        LDX     #$C000          ; reset pointer to start of block
        INCA                    ; bump block number
        STA     $FFA6           ; set MMU
        RTS                     ; return
LoadHi:
        CLR     FlagBigE
        CMPD    #$E000
        PULS    D
        BLO     >               ; If it is going to hit $E000 then we have to check when we hit it
        INC     FlagBigE
                                ; Other wise we will be good copying the rest of this block
!       BSR     GETBYTE         ; read a byte to its destination
        CMPU    #$C000
        BHS     LoadOther
        STB     ,U+
        LEAY    -1,Y            ; finished block?
        BNE     <               ; continue if not
        LBRA     LOADER0         ; handle next block
* Swap writing blocks and pointer from $C000 to $DFFF range to $A000 to $BFFF
* when done then swap them back
LoadOther:
!       PSHS    B
        LDB     #$3E
        STB     $FFA5 ; set block 5 to block 4 position
        PULS    B
        LEAU    -$2000,U
        TST     FlagBigE
        BNE     Continue1 ; Go handle blocks that end at or over $E000
        BRA     Continue0
!       BSR     GETBYTE         ; read a byte to its destination
Continue0:
        STB     ,U+
        LEAY    -1,Y            ; finished block?
        BNE     <               ; continue if not
        LDB     #$3D            ; Put block 5 back to normal
        STB     $FFA5           ; page $3D
        LBRA    LOADER0         ; handle next block
* Swap writing blocks and pointer from $C000 to $DFFF range to $A000 to $BFFF
* when we reach $C000 (really $E000) then swap them back and jump back to normal routine above
OverE000:
!       BSR     GETBYTE         ; read a byte to its destination
        CMPU    #$C000
        BEQ     >
Continue1:
        STB     ,U+
        LEAY    -1,Y            ; finished block?
        BNE     <               ; continue if not
!       PSHS    B
        LDB     #$3D            ; Put block 5 back to normal
        STB     $FFA5           ; page $3D
        PULS    B
        LDU     #$E000
        LBRA    Loader1b        ; continue as per normal now...
FlagBigE:
        FCB     0
TWORD: ; scratch locations
        FDB     0               
TWORD2:
        FDB     0
; read byte from A,X and bump pointer
PAYLOAD EQU     *               ; payload starts here
Posted in CoCo Programming, Uncategorized | Leave a comment

Zilog z80 to Motorola 6809 Transcode – Part 015 – Sprites on the Demo Maze

Not much to report, just adding more routines… I got as far in the attract mode as the first scene finished (where Pac man eats the power pill and the Dark Blue ghosts) then the maze is drawn and Pac man goes to the left, but Pac Man and the ghosts go through some of the walls…  I think the PaletteRAM is where the game checks for walls and they aren’t being setup properly  or detected properly.  But at least there is some progress to see on the screen…

screen-shot-2017-02-05-at-2-54-27-pm

See you in the next post…

Posted in Uncategorized | 5 Comments