Jump to content

Part 3 - Beginnings of Collect 3


Recommended Posts

Source Code


Download and unzip this in your shared directory.



ROM for reference



Main files to look at:

  • cdfj.j - constants for CDFJ used by the 6507 assembly code
  • collect3.asm - 6507 assembly code
  • main/defines_cdfj.h - constants and functions for CDFJ used by the C code
  • main/main.c - C code




To build the program open the Konsole and:

  • use cd command to change into the Collect 3 source directory
  • type make





Test program


Launch Stella and run the collect3.bin.  You see a splash screen for a couple seconds:





Followed by a Menu screen:





Hit GAME RESET to start a game. Both players are moveable. hit GAME SELECT to return to menu.






Main Loop


  • Overscan 
    6507 saves state joysticks and console switches in Display Data RAM
    6507 code calls ARM code requesting the appropriate OverScan routine to run based on MODE (0=Splash, 1=Menu, 128=Game)
    ARM retrieves values from Display Data RAM and takes appropriate action
    ARM returns updated MODE value
  • Vertical Sync
    6507 triggers vertical sync
    ARM - nothing
  • Vertical Blank
    6507 code calls ARM code requesting the appropriate Vertical Blank routine to run based on MODE (0=Splash, 1=Menu, 128=Game) 
    ARM preps datastreams with what to display for the current frame
    ARM returns initial X positions for the players, missiles, and ball
    6507 positions the players, missiles, and ball
    6507 does additional prep based on value in MODE
  • Kernel
    6507 runs appropriate kernel routine based on value in MODE
    ARM acts as a coprocessor, feeding values to the 6507 via the Data Streams
  • back to Overscan



Display Data RAM


The 4K of Display Data is used to transfer information between the 6507 and ARM chip.  The usage is defined in the 6507 assembly file in the same way that Zero Page RAM is defined:

    ORG $0000

_RUN_FUNC:  ds 1        ; function to run
_SWCHA:     ds 1        ; joystick directions to ARM code
_SWCHB:     ds 1        ; console switches to ARM code
_INPT4:     ds 1        ; left firebutton state to ARM code
_INPT5:     ds 1        ; right firebutton state to ARM code

_DS_FROM_ARM:           ; ARM OverScan routines return value for MODE
_MODE:                  ; $00 = splash, $01 = menu, $80 = game 
_BALL_X:    ds 1        ; ARM VerticalBLank routines do not return MODE, instead
_M1_X:      ds 1        ; theyreturn values for the 5 X positions
_M0_X:      ds 1
_P1_X:      ds 1
_P0_X:      ds 1

; Splash screen datastreams
_SPLASH0:   ds 192
_SPLASH1:   ds 192
_SPLASH2:   ds 192
_SPLASH3:   ds 192

; Menu datastreams
_MENU0:     ds 192
_MENU1:     ds 192

; Game datastreams
_PLAYER0:   ds 192
_PLAYER1:   ds 192
_COLOR0:    ds 192
_COLOR1:    ds 192


The make process will automatically transfer labels prefixed with _ to the C code.  In the C code these values are accessed using the variable RAM. As an example this bit of code from SplashVerticalBlank() sets the X positions of the players:

    // set the X positions of the players
    RAM[_P0_X] = 64;
    RAM[_P1_X] = 72;


6507 sending data to ARM


The ARM chip does not have access to the hardware within the Atari, so the 6507 must obtain things like the state of the controllers and save it in Display Data RAM.  This is done by pointing the Communication Data Stream to _DS_TO_ARM, then writing those values to the stream.


This bit of code sets the pointer and populates _RUN_FUNC, _SWCHA, _SWCHB, _INPT4, and _INPT5.  

        ldx #<_DS_TO_ARM
        stx DSPTR
        ldx #>_DS_TO_ARM    ; NOTE: _DS_TO_ARM = 0, so we could leave this LDX out
        stx DSPTR
        sty DSWRITE         ; save in _RUN_FUNC, Y holds which function to call
        ldx SWCHA           ; read state of both joysticks
        stx DSWRITE         ; save in _SWCHA 
        ldx SWCHB           ; read state of console switches
        stx DSWRITE         ; save in _SWCHB
        ldx INPT4           ; read state of left joystick firebutton
        stx DSWRITE         ; save in _INPT4 
        ldx INPT5           ; read state of right joystick firebutton
        stx DSWRITE         ; save in _INPT5

NOTE: In collect3.asm the "save in" comments use incorrect names such as ARMswcha instead of _SWCHA. Those were left over from before we figured out how to use _ to automatically transfer values from dasm to the C code.



Run ARM code


After the information has been saved to Display Data, the ARM code needs to run. This is done by writing $FF to CALLFN.

        ldx #$FF            ; FF = Run ARM code w/out digital audio interrupts
        stx CALLFN          ; runs main() in the C code

at this point function main() in the C code will run. It uses the value stored in _RUN_FUNC to determine which C routine to run:


int main()
    // main() is called when the 6507 code writes to CALLFUNCTION
        case _FN_INIT:      Initialize();           break;
        case _FN_GAME_OS:   GameOverScan();         break;
        case _FN_GAME_VB:   GameVerticalBlank();    break;
        case _FN_MENU_OS:   MenuOverScan();         break;
        case _FN_MENU_VB:   MenuVerticalBlank();    break;
        case _FN_SPLASH_OS: SplashOverScan();       break;
        case _FN_SPLASH_VB: SplashVerticalBlank();  break;
    return 0;



Prepare datastreams for 6507


Before the ARM returns control to the 6507 it needs to prepare the datastream pointers and increments for the 6507. This is done by using these functions:


  • setIncrement(stream, whole, frac)
  • setPointer(stream, offset)
  • setPointer(stream, offset, frac)


the parameters are:


  • stream - 0-31 for the 32 datastreams, 32 for the Communication Datastream, or 33-34 for the Jump Datastreams
  • whole - the whole value of the increment. If you're incrementing 1.5 the whole value is 1
  • frac - the fraction part of the increment.  value is in a byte so for .25 = 64, .5 = 128, .75 = 192
  • offset - location within the 4K Display Data.



6507 reading data from ARM


Once the ARM code has finished, control will be returned to the 6507. The 6507 will use a data stream to access the information generated by the ARM routines.  Reading from the datastreams makes use of Fast Fetch mode, which overrides the LDA # immediate mode instruction.  Fast Fetch mode is turned on by storing FASTON to SETMODE:

        ldx #FASTON
        stx SETMODE

In Collect this is turned on during InitSystem and never turned off. If you ever need to turn it off store FASTOFF to SETMODE.


Collect3 uses this bit of code to read from DSCOMM, the communication datastream, to set the initial X position of TIA's 5 moveable objects:

        ; ARM VB routines send back the initial positions of the 5 objects      
        ldx #4
        lda #DSCOMM         ; will get _BALL_X, _M1_X, _M0_X, _P1_X, and _P0_X
        jsr PosObject
        bpl vbSetInitialX
        sta WSYNC
        sta HMOVE


The other datastreams are named DS0DATA thru DS31DATA, though I prefer to define constants to make the code easier to read:


; datastream usage for Game
_DS_GRP0        = DS0DATA
_DS_GRP1        = DS1DATA
_DS_COLUP1      = DS3DATA       


        ldy #192
        sta WSYNC
        lda #_DS_GRP0   ; 2  2 values from datastream pointing at _PLAYER0
        sta GRP0        ; 3  5
        lda #_DS_GRP1   ; 2  7 values from datastream pointing at _PLAYER1
        sta GRP1        ; 3 10
        lda #_DS_COLUP0 ; 2 12 values from datastream pointing at _COLOR0
        sta COLUP0      ; 3 15
        lda #_DS_COLUP1 ; 2 17 values from datastream pointing at _COLOR1
        sta COLUP1      ; 3 20
        bne GameKernel



  • Like 1
Link to comment
Share on other sites

On 12/15/2019 at 1:47 PM, Dionoid said:

when (re)setting the pointers for the data streams, why is the offset shifted by 20?

Great question!

For ARM performance reasons the various CDFJ registers are stored as 32 bit values.


The ARM has an inline barrel shifter which works efficiently with a number of operations.


Operand 2 is sent to the ALU by barrel shifter

Amount to shift is contained in 5 bit instruction field

No overhead, shift is done free in one cycle

Datastreams can be set to any address within the 4K Display Data block, or 2^12 address.


Since the barrel shifters are so efficient, the address is stored in the upper 12 bits of the value and the next 8 bits are the fractional part.  The 12 bits below that could be used as fractional as well, but the C functions aren't set up for that so you'd have to set the values yourself.


Likewise with the increments, uppermost 12 bits are the whole number, next 8 for fraction.


What this does is provide automatic wrapping from 4095 to 0 when the increment gets added to the address.  Without the shifting we'd need to use an AND instruction to force the wrap, which would use additional CPU time.

  • Like 2
Link to comment
Share on other sites

  • 6 months later...

Darrell, I'm a bit confused on how the 6502 passes data to the ARM processor.


CDFJ seems to recognize registers such as SWCHA and INPUT4 -  etc. when identified like so:

_SWCHA:     ds 1        ; joystick directions to ARM code

.. but what if I want to define a variable in 6507 ASM code, increment the variable value throughout the Kernel, and then make the final value available to the ARM processor in overscan? With the understanding that I would still need to include it in the "CallArmCode:" routine in order to pass it to the ARM, would still I declare the variable in the same location as _SWCHA in the same manner like so:

_MYVAR: ds 1 ;a variable that is not a register

...could I then use that variable as I normally would in a pure ASM program, like so:

ldx #5
stx _MYVAR
inc _MYVAR


Link to comment
Share on other sites

8 hours ago, flickertail said:

Darrell, I'm a bit confused on how the 6502 passes data to the ARM processor.


You're getting ahead of the tutorial ;) 


Display Data RAM is not mapped into 6507 address space so you cannot directly load, store, increment, decrement, etc. the value. You would:

  • put MyVar: ds 1 as part of the Zero Page RAM
  • put _MYVAR: ds 1 as part of Display Data RAM
  • update CallArmCode to copy MyVar into _MYVAR

Then your 6507 code would use MyVar.


In Part 8 the 6502 variables TimeLeftOS and TimeLeftVB are added and get passed to the ARM code. 



  • Like 1
Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Create New...