processor 6502
; Set up TIA registers as constants
VSYNC = $00
VBLANK = $01
WSYNC = $02
COLUPF = $08
COLUBK = $09
PF0 = $0D
PF1 = $0E
PF2 = $0F
INTIM = $284
TIM64T = $296
As with the NES, things start out with basic housekeeping. Memory and TIA registers are zeroed out. The colours for the background and playfield are then set.
org $F000
Start
; Typical starting houskeeping
SEI ; Disable Interrupts
CLD ; Clear BCD mode.
LDX #$FF ; Set ...
TXS ; ... stack pointer
; lets take advantage of X to wipe memory and TIA registers
LDA #0
ClearMemory
STA 0,X
DEX
BNE ClearMemory
; set up background and playfield colors
LDA #$CE ; A light greenish color
STA COLUBK
LDA #$60 ; Purple!
STA COLUPF
Next we start the main loop. This is where things get really different. The 2600 does not have any type of graphics memory. Instead, the display is drawn by the program as the television is actually drawing the display. This means that the program has to manage the television display. The first thing for doing that is to perform a Vertical sync which synchronizes the television signal with the frame that we are sending. This is done by telling the TIA chip we are syncing then waiting for 3 scanlines to be drawn. The WSYNC register will halt the processor until the horizontal blank (when the TV's Cathode ray is turned off and moved to the left for the next scanline) starts. This is followed by a 37 scan line blank period before we start drawing the display. By setting a timer, we can do some work while we wait. The timer is set for 2752 cycles before it goes off. We have nothing to do, so we will waste this time.
MainLoop
; Vertical sync
; bit D1 of VSYNC needs to be set to turn on vsync
LDA #2
STA VSYNC
; now wait for 3 scanlines
STA WSYNC
STA WSYNC
STA WSYNC
; Vertical Blank
; set timer so we know when vertical blank nearly over
LDA #43 ;load 43 (decimal) in the accumulator
STA TIM64T ;and store that in the timer
; end VSync
LDA #0 ; Zero out bit 2 of VSYNC
STA VSYNC ; to indicate sync time is over
; some game logic can go here while vertical blank happening
; as long as less than 2752 cycles
WaitForEndOfVBlank
LDA INTIM ; load remaining time
BNE WaitForEndOfVBlank ; wait till timer done
TAX ; set x to 0 for holding current scanline
TAY ; set y to 0 for offset data
; end vblank period
STA WSYNC
STA VBLANK
Now we are ready to start drawing the message. The 2600 does not have any type of text capability so we are going to create the message using playfield graphics. This is only 40 blocks per scan line so rather rough looking but it is what we have. The thing is, the playfield registers only support 20 blocks with the other half of the display either copied or mirrored. In order to have an asymmetrical display like we need, the playfield registers need to be changed in the middle of drawing the line. Each line consists of 22 2/3 cycles in the horizontal blank period followed by 53 1/3 cycles of actual drawing time for a total of 76 cycles per scan line. These are 6502 cycles, as the TIA uses something it calls color-clocks which happen at 3 times the rate of processor cycles. This means that we need to set up the playfield registers by loading in our data from a data segment at the end of the program. Then we do other stuff until the beam is far enough along. As I am repeating the playfield data for 16 lines per data set, the checking if time to move to next line of data is done in the middle of the scan to let the beam catch up with us. Finally, we set up the other half of the display then finish our end-of-scan-line logic.
ScanLoop
; fill left playfield data from table
LDA PlayfieldData,Y
STA PF0
LDA PlayfieldData+1,Y
STA PF1
LDA PlayfieldData+2,Y
STA PF2
; end of scanline logic done here so beam is far enough to reload
; playfields. We are simply checking if on a line evenly divisible by
; 16 since when 16 (32,48,...) the lower bits will be zeros.
INX
TXA
AND #15
PHA
; replace playfield data with right side data from table
LDA PlayfieldData+3,Y
STA PF0
LDA PlayfieldData+4,Y
STA PF1
LDA PlayfieldData+5,Y
STA PF2
; durring mid line we calculated if time for next set of playfield bytes
; and pushed it onto stack. Pull results and check
PLA
BNE EndOfLine
; if time for new playfield data, increment y index by 6
TYA
CLC
ADC #6
TAY
EndOfLine
STA WSYNC
; are we finished rendering?
CPX #191
BNE ScanLoop
Once we have finished drawing the display, it is time for the over scan. This lasts 30 scan lines. Again, a timer is set up. Other things can be done while the timer is running, but we don't have anything to do so this will be wasted as well. Obviously, in a game this is where some of the game logic would be handled, and all the buttons handled. Did I mention that the buttons on the console, including the reset button, are the responsibility of the program? Thankfully we really don't need to worry about that with this program.
; Overscan
LDA #2 ; Set D1 bit for the VBLANK...
STA VBLANK ; Make TIA output invisible for the overscan,
; set timer for overscan
LDA #35
STA TIM64T
; could put more game logic here if we had any
; 2240 cycles set on timer
WaitForOverscan
LDA INTIM ; load remaining time
BNE WaitForOverscan ; wait till timer done
STA WSYNC
JMP MainLoop ; Loop forver!
And now we are done. Now the playfield data. Notice that the playfield registers are not logically set up. PF0 is only half a byte, with bits 4 through 7 used drawn in that order (backwards from a human perspective). PF1 is written from bits 7 to 0 so is logical from a human perspective. PF2 is written from bits 0 to 7 so again is backwards from a human perspective. This strangeness was most likely done to make the TIA chip easier and cheaper to produce. Still, it is simply a matter of making sure the data is in the correct order, so here is the display data.
; Game Data
PlayfieldData ; pf0-4..7 pf1-7..0 pf2 0..7 pf0-4..7 pf1-7..0 pf2-0..7
.byte 000000, 000000, 000000, 000000, 000000, 000000
.byte %01000000, %01011110, 100001, %10000000, %10000000, 000000
.byte %01000000, %01010000, 100001, %01000000, %01000000, 000000
.byte %11000000, %11011100, 100001, %01000000, %01000000, 000000
.byte %01000000, %01010000, 100001, %01000000, %01000000, 000000
.byte %01000000, %01011110, %11101111, %10010000, %10000000, 000000
.byte 000000, 000000, 000000, 000000, 000000, 000000
.byte 000000, 000010, %01100100, %11100000, 100001, 100011
.byte 000000, 000010, %10010100, 100000, %10100001, 100100
.byte 000000, 000010, %10010101, %11100000, 100001, 100100
.byte 000000, 000011, %10010110, 100000, %10100001, 000100
.byte 000000, 000010, %01100100, 100000, %10111101, 100011
.byte 000000, 000000, 000000, 000000, 000000, 000000
; Set pointers hardware uses to find start of program
org $FFFC
.word Start
.word Start
And we are finished. Clearly the NES is a nicer system but it is a lot newer. Considering what is involved in creating a 2600 game, you have to be impressed with many of the games that were created for the platform.
No comments:
Post a Comment