Sunday, January 8, 2023

Clock Library Test

Clock Test

Test program for my clock library. The library code runs a real time clock that adjusts for the NES's ~60.1 frames/second framerate. A clock can be updated once per frame to count about 1/60th of a second, but for a more accurate clock, consider the following:

	60.098811862348404716732985230828 frames/sec
	= 0.01663926405550947066275456212502 seconds/frame
	= 1.663926405550947066275456212502 hundredths seconds/frame
	fractional part = 0.663926405550947066275456212502
	fractional part of 65536 ($10000) = 0.663926405550947066275456212502 * 65536
	= 43,511.080914186866935428298342531
	drop the decimal part: = 43,511

The clock routine is as follows:

FRACTIONAL_CONSTANT = 43511 ; -------------------------------------------------------------------------------------------- ; execute every NMI for a real time clock counter func clockTick mb fractional[ 0 ] = fractional[ 0 ] + #<FRACTIONAL_CONSTANT mb fractional[ 1 ] = fractional[ 1 ] +c #>FRACTIONAL_CONSTANT ; add 1 to hundredths every frame plus any carry: mb hundredths = hundredths +c #1 ; a still holds hundredths if ( a >= #100 ) mb hundredths = a -c #100 ; carry will be set inc seconds if ( lda seconds = #60 ) ldx #0 stx seconds inc minutes if ( lda minutes = #60 ) stx minutes inc hours endif endif endif rts endfunc

The main routine:

; -------------------------------------------------------------------------------------------- ; File: mainGameLoopState ; -------------------------------------------------------------------------------------------- ; this State is one level above rootState ; -------------------------------------------------------------------------------------------- ; required headers: .include "config.h" .include "header/memory.h" .include "header/funcCall.h" .include "header/ca65hl.h" ; -------------------------------------------------------------------------------------------- ; Define State: identifyState "mainGameLoopState" loadState "mainGameLoopState" ; -------------------------------------------------------------------------------------------- ; Constants for State: ; -------------------------------------------------------------------------------------------- ; Headers for State: ; standard library .include "header/string.h" .include "header/nmi.h" .include "header/controller.h" .include "header/condes.h" .include "header/nes_constants.h" .include "header/ppu.h" .include "header/clock.h" ; -------------------------------------------------------------------------------------------- ; Code segment for State: .segment "CODE" ; -------------------------------------------------------------------------------------------- ; global memory settings: resBSS clockRunning .byte ; -------------------------------------------------------------------------------------------- ; Imports: ; -------------------------------------------------------------------------------------------- ; Exports: declarefunc nmiEnd ; -------------------------------------------------------------------------------------------- ; States: declarefunc mainGameLoopState ; -------------------------------------------------------------------------------------------- ; Forward Declarations: .pushseg .segment "RODATA" palette: .byte $01,$36,$05,$16,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F cursorPos: .byte 0, 3, 6, 9 .popseg func mainGameLoopState resBSS timeStr .byte 4 ; 3 byte string plus terminator resBSS cursor .byte ; position as 0 to 3 resBSS i .byte ; loop counter mb cursor = #0 mb clockRunning = #1 call nmi::waitForNMI ; wait for vblank to write palette call ppu::setBackgroundPalette, { &palette } jPrintRenderMode off jPrintSetMaxCol 32 jPrintSetCursor 9, 6 jPrint "Clock Test!" jPrint jPrint jPrint jPrint "00:00:00:00" jPrintSetCursor 1, 14 jPrint "Press START to Pause" jPrint "Press UP/DOWN to change value" jPrint "Press A to reset value" jPrintEnd PPU_RENDER_ON repeat ; draw the time: ; i = 0 -> hours ; i = 1 -> minutes ; i = 2 -> seconds ; i = 3 -> hundredths for ( mb i := #3, !N, dec i ) ; i: 3 to 0 ldx i call str::byteToDecimal, { clock::time[ x ], &timeStr } ldx i : mb a = cursorPos[ x ] + #9 call str::print, { a , #10, &(timeStr + 1) } ; only print the last 2 digits next ; draw cursor jPrintRenderMode on jPrintSetCursor 10, 11 jPrint " " ; create a blank buffer jPrintEnd ldy cursor txa ; x holds vramBufferIndex mb x = a + cursorPos[ y ] mb ppu::vramBuffer[ x ] = #$1F ; arrow symbol ; process input: lda controller::pressedNew : tay ; store a copy of buttons in y to quickly restore a ldx cursor if ( !zero && a & #BUTTON_LEFT ) dex elseif ( tya : a & #BUTTON_RIGHT && x < #3 ) inx elseif ( tya : a & #BUTTON_START ), !Z inc clockRunning elseif ( tya : a & #BUTTON_A ) mb clock::time[ x ] = #0 elseif ( tya : a & #BUTTON_UP && clock::time[ x ] <> #59 ), Z inc clock::time[ x ] elseif ( tya : a & #BUTTON_DOWN && clock::time[ x ] ), !Z dec clock::time[ x ] endif stx cursor call nmi::waitForNMI forever endfunc ; -------------------------------------------------------------------------------------------- func nmiEnd call controller::readPort, { #0 } call controller::autoRepeatButtons, { #0, #$FF } ; allow all to auto repeat if ( clockRunning & #1 ) call clock::clockTick endif rts endfunc ; --------------------------------------------------------------------------------------------

Get the ROM here.