Welcome back to another exciting episode of my ultimate Atari 2600 programming tutorial. :bigyello:
Now things are gonna get interesting because now you are gonna get to plot pixels on the screen! Just one problem though. This one is a pain in the neck because the Atari was designed as a Pong-like system with cutting costs in mind. Even though there are 160 color clocks per visible scanline, there are only 40 pixels across the screen, with every pixel being 4x1 pixels wide. So the dimensions of the draw field pixels are 40x192. Thats why everything in Atari 2600 games looks so wide.
As for the playfield itself, there are 3 registers. PF0, PF1, and PF2. They only cover half the screen and is mirrored on the other side. PF0 is only 4 pixels wide, PF1 is 8 pixels wide, and PF2 is 8 pixels wide. So if you do the math, 4+8+8 + 4+8+8 = 40 pixels. Even though PF0 is 8 bits, the lower 4 bits are completely ignored by the processor. Any bits that are set will plot the pixels on the screen and mirror it on the other side. But it gets even worse. PF0 and PF2's bits are reveresed! For example if PF0 is 00010000, with the lower 4 bits ignored, and the upper 4 bits reversed, it will draw a pixel on the left side of the screen using 1000. So you can imagine games with scrolling being extremely difficult to program on this beast with not only PF0 and PF2's bits being reversed, but also mirrored on the other side!
The mirroring can be turned on and off as well with the first bit of CTRLPF.
Mirroring On:
Mirroring Off:
What that does if the mirroring is on is that the left half of the screen becomes PF0, PF1, PF2 and the right half becomes PF2, PF1, PF0. If the mirroring is off, it becomes PF0, PF1, PF2, and PF0, PF1, PF2.
But the bits are more than for just mirroring. Take a look at what the other bits do!
You can also set the color of the playfield through the COLUPF. For example:
This will set the color for the entire playfield to maroon red.
Now that you got the basics of what a playfield is, it's time to do some code to draw a playfield! In this example we will be drawing a border all around the screen with a blue background:
Save the file as playfield.asm, and change the Compile.bat code to this:
Be sure its all in the same folder as the DASM.exe so it can compile! Run the batch file to compile the playfield.asm code. You should have a playfield.bin file created. Run it through Stella and it should look like this:
![]()
And that's how it's done! You can play around with where the pixels are plotted if you'd like, or even change the colors of everything.
Exercise: Remove the right wall while having the left wall remain!
Answer to Tutorial 2 exercise:
Next tutorial I'm gonna show you how to do real graphics through asymmetrical playfields :)
Now things are gonna get interesting because now you are gonna get to plot pixels on the screen! Just one problem though. This one is a pain in the neck because the Atari was designed as a Pong-like system with cutting costs in mind. Even though there are 160 color clocks per visible scanline, there are only 40 pixels across the screen, with every pixel being 4x1 pixels wide. So the dimensions of the draw field pixels are 40x192. Thats why everything in Atari 2600 games looks so wide.
As for the playfield itself, there are 3 registers. PF0, PF1, and PF2. They only cover half the screen and is mirrored on the other side. PF0 is only 4 pixels wide, PF1 is 8 pixels wide, and PF2 is 8 pixels wide. So if you do the math, 4+8+8 + 4+8+8 = 40 pixels. Even though PF0 is 8 bits, the lower 4 bits are completely ignored by the processor. Any bits that are set will plot the pixels on the screen and mirror it on the other side. But it gets even worse. PF0 and PF2's bits are reveresed! For example if PF0 is 00010000, with the lower 4 bits ignored, and the upper 4 bits reversed, it will draw a pixel on the left side of the screen using 1000. So you can imagine games with scrolling being extremely difficult to program on this beast with not only PF0 and PF2's bits being reversed, but also mirrored on the other side!
The mirroring can be turned on and off as well with the first bit of CTRLPF.
Mirroring On:
Code:
lda #%00000001
sta CTRLPF
Mirroring Off:
Code:
lda #%00000000
sta CTRLPF
But the bits are more than for just mirroring. Take a look at what the other bits do!
Code:
CTRLPF
This address is used to write into the playfield control
register (a logic 1 causes action as described below)
D0 = REF (reflect playfield)
D1 = SCORE (left half of playfield gets color of player 0,
right half gets color of player 1)
D2 = PFP (playfield gets priority over players so they can
move behind the playfield)
D4 & D5 = BALL SIZE
D5 D4 Width
0 0 1 clock
0 1 2 clocks
1 0 4 clocks
1 1 8 clocks
Code:
lda #$45
sta COLUPF
Now that you got the basics of what a playfield is, it's time to do some code to draw a playfield! In this example we will be drawing a border all around the screen with a blue background:
Code:
processor 6502
include "vcs.h"
include "macro.h"
BLUE = $9A
;------------------------------------------------------------------------------
SEG
ORG $F000
Reset
; Clear RAM and all TIA registers
ldx #0
lda #0
Clear
sta 0,x
inx
bne Clear
;------------------------------------------------
; Once-only initialization. . .
lda #BLUE
sta COLUBK ; set the background color
lda #$45
sta COLUPF
lda #%00000001
sta CTRLPF
;------------------------------------------------
StartOfFrame
; Start of new frame
; Start of vertical blank processing
lda #0
sta VBLANK
lda #2
sta VSYNC
sta WSYNC
sta WSYNC
sta WSYNC ; 3 scanlines of VSYNC signal
lda #0
sta VSYNC
;------------------------------------------------
; 37 scanlines of vertical blank. . .
ldx #0
VerticalBlank
sta WSYNC
inx
cpx #37
bne VerticalBlank
;------------------------------------------------
; Do 192 scanlines of color-changing (our picture)
ldx #0 ; this counts our scanline number
lda #%11111111
sta PF0
sta PF1
sta PF2
; We won't bother rewriting PF0-PF2 every scanline of the top 8 lines - they never change!
Top8Lines
sta WSYNC
inx
cpx #8 ; are we at line 8?
bne Top8Lines ; No, so do another
; Now we want 176 lines of "wall"
; Note: 176 (middle) + 8 (top) + 8 (bottom) = 192 lines
lda #%00010000 ; PF0 is mirrored <--- direction, low 4 bits ignored
sta PF0
lda #0
sta PF1
sta PF2
; again, we don't bother writing PF0-PF2 every scanline - they never change!
MiddleLines
sta WSYNC
inx
cpx #184
bne MiddleLines
; Finally, our bottom 8 scanlines - the same as the top 8
; AGAIN, we aren't going to bother writing PF0-PF2 mid scanline!
lda #%11111111
sta PF0
sta PF1
sta PF2
Bottom8Lines
sta WSYNC
inx
cpx #192
bne Bottom8Lines
;------------------------------------------------
lda #%01000010
sta VBLANK ; end of screen - enter blanking
;------------------------------------------------
; 30 scanlines of overscan. . .
ldx #0
Overscan
sta WSYNC
inx
cpx #30
bne Overscan
jmp StartOfFrame
;------------------------------------------------------------------------------
ORG $FFFA
InterruptVectors
.word Reset ; NMI
.word Reset ; RESET
.word Reset ; IRQ
END
Code:
@echo off
dasm playfield.asm -lkernel.txt -f3 -v5 -oplayfield.bin
And that's how it's done! You can play around with where the pixels are plotted if you'd like, or even change the colors of everything.
Exercise: Remove the right wall while having the left wall remain!
Answer to Tutorial 2 exercise:
Code:
processor 6502
include "vcs.h"
include "macro.h"
BLACK = $00
;------------------------------------------------------------------------------
SEG
ORG $F000
Reset
; Clear RAM and all TIA registers
ldx #0
lda #0
Clear
sta 0,x
inx
bne Clear
;------------------------------------------------
; Once-only initialization. . .
lda #BLACK
sta COLUBK ; set the background color
;------------------------------------------------
StartOfFrame
; Start of new frame
; Start of vertical blank processing
lda #0
sta VBLANK
lda #2
sta VSYNC
sta WSYNC
sta WSYNC
sta WSYNC ; 3 scanlines of VSYNC signal
lda #0
sta VSYNC
;------------------------------------------------
; 37 scanlines of vertical blank. . .
ldx #0
VerticalBlank
sta WSYNC
inx
cpx #37
bne VerticalBlank
;------------------------------------------------
;192 lines of drawfield
ldx #0
DrawField:
sta WSYNC
inx
stx COLUBK
cpx #192
bne DrawField
;------------------------------------------------
; end of screen - enter blanking
lda #%01000010
sta VBLANK
;------------------------------------------------
; 30 scanlines of overscan. . .
ldx #0
Overscan
sta WSYNC
inx
cpx #30
bne Overscan
jmp StartOfFrame
;------------------------------------------------
ORG $FFFA
InterruptVectors
.word Reset ; NMI
.word Reset ; RESET
.word Reset ; IRQ
END