Improved Atari 2600 example programs

Add the color test program as a sample program. Also update
the hi_stella example so that it runs properly when run in a Harmony
cartridge.
This commit is contained in:
Michael Martin 2015-01-07 00:35:21 -08:00
parent c3d48da59d
commit 778fbf7e2c
5 changed files with 439 additions and 31 deletions

View File

@ -1,7 +0,0 @@
"Hi Stella" is a simple "Hello World" program for the "Stella" chip,
more famously known as the Atari 2600. Simply running
ophis hi_stella.oph
should produce hi_stella.bin, a 256-byte file that prints "HI" on
the screen with some rolling color bars.

View File

@ -0,0 +1,13 @@
"Hi Stella" is a simple "Hello World" program for the "Stella" chip,
more famously known as the Atari 2600. Simply running
ophis hi_stella.oph
should produce hi_stella.bin, a 256-byte file that prints "HI" on
the screen with some rolling color bars.
A more sophisticated program is colortest, which lets the user
explore the 128 colors provided by the system. Use up and down
to move the color value by 2, and left and right to move it
by 16. (The lowest bit in the color value byte is ignored, for
a total of 128 colors available.)

View File

@ -0,0 +1,391 @@
;;; ---------- COLOR TEST ----------
;;; Michael Martin, 2014
;;;
;;; This is a sample program for the Atari 2600 VCS that lets you
;;; explore the 128-color palette the system provides. This is
;;; presented mainly as a more sophisticated example program to
;;; supplement "hi_stella".
;;;
;;; It makes use of every graphical element but the Ball, and also
;;; makes use of multicolor asymmetric playfields.
.require "../../platform/stella.oph"
.outfile "colortest.bin"
.data
.org $0080
.space startcol 1 ; Starting color for the striped playfield
.space subrow 1 ; Counting lines per "tall pixel"
.space curcol 1 ; The color number we are focusing on at the moment
.space high_nybble 2 ; Pointer to graphic data for 16s hexit
.space low_nybble 2 ; Pointer to graphic data for ones hexit
.space input_allowed 1 ; Flag for whether or not to ignore input.
.text
;; Start at $f800 - 2KB ROMs are the smallest size available.
.org $f800
reset: `clean'start
;; We offer 1c as the initial color. It's a nice yellow shade.
lda #$1c
sta curcol
frame: `vertical'sync ; Beginning of the frame. Set up the timer
lda #43 ; to count out the length of VBLANK while
sta TIM64T ; we do the processing for the display.
;; Place the player and missile graphics appropriately. We
;; count cycles and write the missile and player reset registers
;; at the closest times we can manage. Due to the way the TIA
;; timing works, the formula for the pixel they will show up at
;; is N*3-55+P, where N is the number of cycles from the end of
;; the latest STA WSYNC and the end of the STA RES* instruction,
;; and P is 1 for player sprites and 0 for missiles and the ball.
;;
;; The line after that we can strobe HMOVE to adjust them the
;; rest of the way into place.
;;
;; We will be using the missiles to draw the left and right
;; sides of a largish square and the player sprites to display a
;; byte value (the current color) as two hex digits (one per
;; player). All the rest of our graphics will be done via the
;; playfield registers.
;;
;; The missile graphics are being targeted to pixels 40 and 116
;; and will be 4 pixels wide each. The player graphics will be
;; 8 pixels wide and are targeting pixels 72 and 80.
sta WSYNC
sta WSYNC ; = 0
ldy #$06 ; +2 = 2
* dey ; +2 = 4- 9-14-19-24-29
bne - ; +3 = 7-12-17-22-27-31
sta RESM0 ; +3 = 34 (31*3-55 = 38. Needs to move 2 pixels right.)
lda #$E0 ; +2 = 36
sta HMCLR ; +3 = 39 - reset the fine-move registers
sta HMM0 ; +3 = 42 - set M0 to move 2 right
sta RESP0 ; +3 = 45 (42*3-54 = 72. Placed perfectly.)
sta RESP1 ; +3 = 48 (45*3-54 = 81. Needs to move 1 pixel left.)
lda #$10 ; +2 = 50
sta HMP1 ; +3 = 53 - and set P1 to move 1 left.
nop ; +2 = 55
nop ; +2 = 57
sta RESM1 ; +3 = 60 (57*3-55 = 116. Placed perfectly.)
sta WSYNC
sta HMOVE ; Next scanline, execute the fine moves.
lda #$20
sta NUSIZ0 ; Quad-size missiles, single copy of single player
sta NUSIZ1 ; M1 and P1 are the same
;; Read the input
lda #$00
sta SWACNT
lda SWCHA
bit input_allowed
bmi true_input_read
;; Wait for neutral stick so we can re-enable input.
and #$f0
cmp #$f0
bne input_done
;; Bits are set if the direction isn't active, so we only get
;; here if the stick was neutral. this also means the accumulator
;; has #$f0 in it now, which means we can store it directly and
;; the BIT/BMI above will start succeeding next frame.
sta input_allowed
beq input_done
true_input_read:
;; Now we rotate it through the carry bit to see what
;; direction was pushed. We advance the color 2 or 16 at a time,
;; depending. (The least significant bit in the color register is
;; the one ignored, so we are not missing anything here.)
ror ; Skip P2 input
ror
ror
ror
ror ; Carry clear if up
bcs +
inc curcol ; If up, increase color by 2
inc curcol
jmp input_found
* ror ; Carry clear if down
bcs +
dec curcol ; If down, decrease color by 2
dec curcol
jmp input_found
* ror ; Carry clear if left
bcs +
lda curcol
sec
sbc #$10 ; Left decreases color by 16
sta curcol
jmp input_found
* ror ; Carry clear if right
bcs input_done
lda curcol
adc #$10 ; Right increases color by 16
sta curcol
input_found:
lda #$00
sta input_allowed
input_done:
;; Clear the playfield while we wait, and make it asymmetric
lda #$00
sta PF0
sta PF1
sta PF2
sta CTRLPF
;; alter playfield color so we get a rotating effect
dec startcol
;; prepare numeric sprite values
lda curcol
lsr
lsr
lsr
lsr
tay
lda digits_low, y
sta high_nybble
lda curcol
and #$0f
tay
lda digits_low, y
sta low_nybble
lda #$ff
sta low_nybble+1
sta high_nybble+1
;; Wait for VBLANK to finish, then turn off the VBLANK signal.
* lda INTIM
bne -
sta WSYNC
sta VBLANK
;; Display kernel.
;; Top blank: 4 lines
ldx #4
stx subrow
* sta WSYNC
dex
bne -
;; Header graphics: 20 lines
ldy #5
ldx startcol
header_loop:
sta WSYNC
stx COLUPF ; +3 = 3
lda pf0_left-1,y ; +4 = 7
sta PF0 ; +3 = 10
lda pf1_left-1,y ; +4 = 14
sta PF1 ; +3 = 17
lda pf2_left-1,y ; +4 = 21
sta PF2 ; +3 = 24
cmp $80 ; +3 = 27 (3-cycle no-op)
lda pf0_right-1,y ; +4 = 31
sta PF0 ; +3 = 34
lda pf1_right-1,y ; +4 = 38
sta PF1 ; +3 = 41
lda pf2_right-1,y ; +4 = 45
sta PF2 ; +3 = 48 ** MUST STORE PF2 2ND TIME ON EXACTLY CYCLE 48 **
inx ; +2 = 50
inx ; +2 = 52
dec subrow ; +5 = 57
bne header_loop ; +2 = 59
dey ; +2 = 61
beq header_done ; +2 = 63
lda #4 ; +2 = 65
sta subrow ; +3 = 68
bne header_loop ; +3 = 71
;; We've cut it very fine here! We only have 76 cycles per
;; scanline and we use nearly all of them.
header_done:
;; Ruled split between title and data (8 lines)
ldy #$00 ; Clear playfield now that we're done (+2 = 72)
ldx #$0c ; Default status color is light grey (+2 = 74)
sta WSYNC ; Rest of previous line
sty PF0
sty PF1
sty PF2
stx COLUPF
stx COLUP0
stx COLUP1
dey
ldx #$f0
sta WSYNC
sta WSYNC
sta WSYNC
stx PF0 ; Fill playfield completely
sty PF1
sty PF2
ldy #$01
sty CTRLPF ; Symmetric PF
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
dey
sty PF0 ; Clear playfield again
sty PF1
sty PF2
ldy #$08 ; 32 lines (for letters; 8, 16, 8)
* sta WSYNC
dey
bne -
ldy #$06
* lda (high_nybble), y
sta GRP0
lda (low_nybble), y
sta GRP1
sta WSYNC
sta WSYNC
dey
bpl -
iny
sty GRP0
sty GRP1
ldy #$0A
* sta WSYNC
dey
bne -
;; Top border (12 lines)
lda #$03
sta PF1
lda #$ff
sta PF2
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
ldx #$00
stx PF1
stx PF2
ldx #$02 ; Turn on walls (the missiles)
stx ENAM0
stx ENAM1
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta PF2
lda curcol
sta COLUPF
;; Color blob (96 lines)
ldx #96
* sta WSYNC
dex
bne -
;; Bottom border (12 lines)
stx PF2
lda #$0c
sta COLUPF
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
stx ENAM0 ; Turn off walls (the missles)
stx ENAM1
lda #$03
sta PF1
lda #$ff
sta PF2
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
sta WSYNC
stx PF1
stx PF2
ldx #$08
* sta WSYNC
dex
bne -
; Turn on VBLANK, do 30 lines of Overscan
lda #$02
sta VBLANK
ldy #30
* sta WSYNC
dey
bne -
jmp frame ; And the frame is done, back to VSYNC.
;;; Graphical data. Notice that we have to start not on a page
;;; boundary, but with all graphics in each group on one page.
.advance $FF01
pf0_left:
.byte $e0,$20,$20,$20,$e0
pf1_left:
.byte $77,$54,$54,$54,$74
pf2_left:
.byte $ae,$6a,$ea,$aa,$ee
pf0_right:
.byte $00,$00,$00,$00,$00
pf1_right:
.byte $4e,$48,$4c,$48,$ee
pf2_right:
.byte $27,$24,$27,$21,$77
;; We don't need a digits_high. It's always $FF!
digits_low:
.byte <digit_0, <digit_1, <digit_2, <digit_3
.byte <digit_4, <digit_5, <digit_6, <digit_7
.byte <digit_8, <digit_9, <digit_a, <digit_b
.byte <digit_c, <digit_d, <digit_e, <digit_f
digit_0:
.byte $3c,$66,$66,$76,$6e,$66,$3c
digit_1:
.byte $7e,$18,$18,$18,$38,$18,$18
digit_2:
.byte $7e,$60,$30,$0c,$06,$66,$3c
digit_3:
.byte $3c,$66,$06,$1c,$06,$66,$3c
digit_4:
.byte $06,$06,$7f,$66,$1e,$0e,$06
digit_5:
.byte $3c,$66,$06,$06,$7c,$60,$7e
digit_6:
.byte $3c,$66,$66,$7c,$60,$66,$3c
digit_7:
.byte $18,$18,$18,$18,$0c,$66,$7e
digit_8:
.byte $3c,$66,$66,$3c,$66,$66,$3c
digit_9:
.byte $3c,$66,$06,$3e,$66,$66,$3c
digit_a:
.byte $66,$66,$66,$7e,$66,$3c,$18
digit_b:
.byte $7c,$66,$66,$7c,$66,$66,$7c
digit_c:
.byte $3c,$66,$60,$60,$60,$66,$3c
digit_d:
.byte $78,$6c,$66,$66,$66,$6c,$78
digit_e:
.byte $7e,$60,$60,$78,$60,$60,$7e
digit_f:
.byte $60,$60,$60,$78,$60,$60,$7e
;;; Interrupt vectors.
.advance $FFFA
.word reset, reset, reset

View File

@ -9,37 +9,42 @@
.space counter 1
.text
.org $FF00
.org $F800
reset: `clean'start
; Initialize the player sprites.
; We're going to use quad-sized players for our
; letters. With 160 color clocks, and letters
; twenty clocks wide, and a 68 color clock HBLANK,
; we want to place the two letters at color clock
; 68+80-20-2 = 126 and 68+80+2 = 150. Those
; translate to cycle counts 42 and 50.
; letters, and each one is 5 notional pixels wide.
; When we write RESP*, the start cycle after WSYNC
; determines the pixel it appears at with the
; formula 3N - 54 (minimum 1). Cycles 36 and 44
; get us close. We end up 4 pixels too far left.
; While we wait, we set up our sprites to be
; quad-sized and initialize the independent
; color-counters for each sprite.
sta WSYNC ; 3
lda #$07 ; 5
sta NUSIZ0 ; 7
sta NUSIZ1 ; 10
lda #$20 ; 13
ldy #5 ; 15
sta WSYNC
lda #$07 ; +2= 2
sta NUSIZ0 ; +3= 5
sta NUSIZ1 ; +3= 8
lda #$20 ; +2=10
ldy #5 ; +2=12
* dey
bne - ; 20-25-30-35-39
sta RESP0 ; 42 - color clock 126
sta col'0 ; 45
eor #$80 ; 47
sta RESP1 ; 50 - color clock 150
bne - ; 17-22-27-32-36
sta RESP0 ; +3=39 (P0 at pixel 54)
sta col'0 ; +3=42
eor #$80 ; +2=44
sta RESP1 ; (P1 at pixel 78)
sta col'1
sta temp
lda #$C0 ; HMOVE us 4 right
sta HMP0
sta HMP1
sta WSYNC
sta HMOVE
frame: `vertical'sync
lda #43
@ -117,7 +122,7 @@ frame: `vertical'sync
dey ; loop.
bne -
; Clear out the player graphics,,,
; Clear out the player graphics...
lda #$00
sta GRP0
sta GRP1

View File

@ -86,9 +86,14 @@ Section "Ophis" SEC01
File "..\..\examples\structuredemo.oph"
File "..\..\examples\fibonacci.oph"
File "..\..\examples\kinematics.oph"
SetOutPath "$INSTDIR\examples\hi_stella"
File "..\..\examples\hi_stella\hi_stella.oph"
File "..\..\examples\hi_stella\README.txt"
# Remove the older copies of hi_stella if needed
Delete "$INSTDIR\examples\hi_stella\hi_stella.oph"
Delete "$INSTDIR\examples\hi_stella\README.txt"
RMDir "$INSTDIR\examples\hi_stella"
SetOutPath "$INSTDIR\examples\stella"
File "..\..\examples\stella\hi_stella.oph"
File "..\..\examples\stella\colortest.oph"
File "..\..\examples\stella\README.txt"
SetOutPath "$INSTDIR\examples\hello_nes"
File "..\..\examples\hello_nes\hello_prg.oph"
File "..\..\examples\hello_nes\hello_chr.oph"
@ -159,8 +164,9 @@ Section Uninstall
Delete "$INSTDIR\ophis.exe"
Delete "$INSTDIR\ophismanual.pdf"
Delete "$INSTDIR\README.txt"
Delete "$INSTDIR\examples\hi_stella\hi_stella.oph"
Delete "$INSTDIR\examples\hi_stella\README.txt"
Delete "$INSTDIR\examples\stella\hi_stella.oph"
Delete "$INSTDIR\examples\stella\colortest.oph"
Delete "$INSTDIR\examples\stella\README.txt"
Delete "$INSTDIR\examples\hello_nes\hello_prg.oph"
Delete "$INSTDIR\examples\hello_nes\hello_chr.oph"
Delete "$INSTDIR\examples\hello_nes\hello_ines.oph"
@ -180,7 +186,7 @@ Section Uninstall
Delete "$STARTMENU\Manual.lnk"
RMDir "$SMPROGRAMS\$ICONS_GROUP"
RMDir "$INSTDIR\examples\hi_stella"
RMDir "$INSTDIR\examples\stella"
RMDir "$INSTDIR\examples\hello_nes"
RMDir "$INSTDIR\examples"
RMDir "$INSTDIR\platform"