; ; atari-2600-example.oph ; Skeleton code for an Atari 2600 ROM, ; plus an example of reading the joystick. ; By Chris Pressey, November 2, 2012. ; ; SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain. ; For more information, please refer to ; SPDX-License-Identifier: Unlicense ; ; Based on Chris Cracknell's Atari 2600 clock (also in the public domain): ; http://everything2.com/title/An+example+of+Atari+2600+source+code ; ; to build and run in Stella: ; ophis atari-2600-example.oph -o example.bin ; stella example.bin ; ; More useful information can be found in the Stella Programmer's Guide: ; http://alienbill.com/2600/101/docs/stella.html ; ; ; Useful system addresses (TODO: briefly describe each of these.) ; .alias VSYNC $00 .alias VBLANK $01 .alias WSYNC $02 .alias NUSIZ0 $04 .alias NUSIZ1 $05 .alias COLUPF $08 .alias COLUBK $09 .alias PF0 $0D .alias PF1 $0E .alias PF2 $0F .alias SWCHA $280 .alias INTIM $284 .alias TIM64T $296 .alias CTRLPF $0A .alias COLUP0 $06 .alias COLUP1 $07 .alias GP0 $1B .alias GP1 $1C .alias HMOVE $2a .alias RESP0 $10 .alias RESP1 $11 ; ; Cartridge ROM occupies the top 4K of memory ($F000-$FFFF). ; Thus, typically, the program will occupy all that space too. ; ; Zero-page RAM we can use with impunity starts at $80 and goes ; upward (at least until $99, but probably further.) ; .alias colour $80 .alias luminosity $81 .alias joystick_delay $82 .org $F000 ; ; Standard prelude for Atari 2600 cartridge code. ; ; Get various parts of the machine into a known state: ; ; - Disable interrupts ; - Clear the Decimal flag ; - Initialize the Stack Pointer ; - Zero all bytes in Zero Page memory ; start: sei cld ldx #$FF txs lda #$00 zero_loop: sta $00, x dex bne zero_loop ; and fall through to... ; ; Initialization. ; ; - Clear the Playfield Control register. ; - Set the player (sprite) colour to light green (write to COLUP0.) ; - Set the player (sprite) size/repetion to normal (write to NUSIZ0.) ; lda #$00 sta CTRLPF lda #$0c sta colour lda #$0a sta luminosity lda #$00 sta NUSIZ0 ; and fall through to... ; ; Main loop. ; ; A typical main loop consists of: ; - Waiting for the frame to start (vertical blank period) ; - Displaying stuff on the screen (the _display kernel_) ; - Doing any processing you like (reading joysticks, updating program state, ; etc.), as long as you get it all done before the next frame starts! ; main: jsr vertical_blank jsr display_frame jsr read_joystick jmp main ; ; Vertical blank routine. ; ; In brief: wait until it is time for the next frame of video. ; TODO: describe this in more detail. ; vertical_blank: ldx #$00 lda #$02 sta WSYNC sta WSYNC sta WSYNC sta VSYNC sta WSYNC sta WSYNC lda #$2C sta TIM64T lda #$00 sta WSYNC sta VSYNC rts ; ; Display kernal. ; ; First, wait until it's time to display the frame. ; .scope display_frame: lda INTIM bne display_frame ; ; (After that loop finishes, we know the accumulator must contain 0.) ; Wait for the next scanline, zero HMOVE (for some reason; TODO discover ; this), then turn on the screen. ; sta WSYNC sta HMOVE sta VBLANK ; ; Actual work in the display kernal is done here. ; ; This is a pathological approach to writing a display kernal. ; This wouldn't be how you'd do things in a game. So be it. ; One day I may improve it. For now, be happy that it displays ; anything at all! ; ; ; Wait for $3f (plus one?) scan lines to pass, by waiting for ; WSYNC that many times. ; ldx #$3F _wsync_loop: sta WSYNC dex bpl _wsync_loop sta WSYNC ; ; Delay while the raster scans across the screen. The more ; we delay here, the more to the right the player will be when ; we draw it. ; nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop ; ; OK, *now* display the player. ; sta RESP0 ; ; Loop over the rows of the sprite data, drawing each to the screen ; over four scan lines. ; ; TODO understand this better and describe it! ; ldy #$07 _image_loop: lda image_data, y sta GP0 sta WSYNC sta WSYNC sta WSYNC sta WSYNC dey bpl _image_loop lda #$00 sta GP0 ; ; Turn off screen display and clear display registers. ; lda #$02 sta WSYNC sta VBLANK lda #$00 sta PF0 sta PF1 sta PF2 sta COLUPF sta COLUBK rts .scend ; ; Read the joystick and use it to modify the colour and luminosity ; of the player. ; .scope read_joystick: lda joystick_delay beq _continue dec joystick_delay rts _continue: lda SWCHA and #$f0 cmp #$e0 beq _up cmp #$d0 beq _down cmp #$b0 beq _left cmp #$70 beq _right jmp _tail _up: inc luminosity jmp _tail _down: dec luminosity jmp _tail _left: dec colour jmp _tail _right: inc colour ;jmp _tail _tail: lda colour and #$0f sta colour lda luminosity and #$0f sta luminosity lda colour clc rol rol rol rol ora luminosity sta COLUP0 lda #$06 sta joystick_delay rts .scend ; ; Player (sprite) data. ; ; Because we loop over these bytes with the Y register counting *down*, ; this image is stored "upside-down". ; image_data: .byte %01111110 .byte %10000001 .byte %10011001 .byte %10100101 .byte %10000001 .byte %10100101 .byte %10000001 .byte %01111110 ; ; Standard postlude for Atari 2600 cartridge code. ; Give BRK and boot vectors that point to the start of the code. ; .advance $FFFC .word start .word start