diff --git a/pvsneslib/include/snes/input.h b/pvsneslib/include/snes/input.h index d4c8278f..0c897b93 100644 --- a/pvsneslib/include/snes/input.h +++ b/pvsneslib/include/snes/input.h @@ -27,9 +27,12 @@ ---------------------------------------------------------------------------------*/ -/*! \file input.h - \brief input support. -*/ +/*! + * \file input.h + * \brief input support. + * + * The inputs are automatically read by the \ref VBlank-ISR on non-lag frames. + */ #ifndef SNES_INPUT_INCLUDE #define SNES_INPUT_INCLUDE @@ -37,7 +40,7 @@ #include #include -/*! \file +/*! \brief common values for pad input. common values that can be used to test auto pad. @@ -59,7 +62,7 @@ typedef enum KEYPAD_BITS KEY_Y = BIT(14), //!< pad Y button. } KEYPAD_BITS; -/*! \file +/*! \brief common values for SuperScope input. */ //! enum values for the SuperScope buttons and flags. @@ -73,9 +76,9 @@ typedef enum SUPERSCOPE_BITS SSC_NOISE = BIT(8), //!< superscope NOISE flag. } SUPERSCOPE_BITS; -extern u16 pad_keys[2]; -extern u16 pad_keysold[2]; -extern u16 pad_keysrepeat[2]; +extern u16 pad_keys[5]; //!< current pad value +extern u16 pad_keysold[5]; //!< previous pad value +extern u16 pad_keysdown[5]; //!< newly pressed down pad keys extern u8 snes_mplay5; /*! \brief 1 if MultiPlay5 is connected */ extern u8 snes_mouse; /*! \brief 1 if Mouse is going to be used */ @@ -147,59 +150,48 @@ extern u16 scope_sinceshot; /*! \brief Number of frames elapsed since last shot */ #define REG_JOYxLH(a) (((vuint16 *)0x4218)[(a)]) -/*! \fn scanPads() - \brief Wait for pad ready and read pad values in. -*/ -void scanPads(void); - /*! \fn padsCurrent(value) \brief Return current value of selected pad - \param value Address of the pad to use (0 or 1 to 4 if multiplayer 5 connected) + \param value pad index to use (0-1 or 0-4 if multiplayer 5 connected) \return unsigned short of the current pad value */ // unsigned short padsCurrent(u16 value); #define padsCurrent(value) (pad_keys[value]) -/*! \fn padsDown(u16 value) +/*! \fn padsDown(value) \brief Return value of down keys for selected pad - \param value Address of the pad to use (0 or 1 to 4 if multiplayer 5 connected) - \return unsigned short of the current pad value + \param value pad index to use (0-1 or 0-4 if multiplayer 5 connected) + \return unsigned short of the newly pressed down keys (0 -> 1 transition) */ -unsigned short padsDown(u16 value); +// unsigned short padsDown(u16 value); +#define padsDown(value) (pad_keysdown[value]) /*! \fn padsUp(u16 value) \brief Return value of up keys for selected pad - \param value Address of the pad to use (0 or 1 to 4 if multiplayer 5 connected) - \return unsigned short of the current pad value + \param value pad index to use (0-1 or 0-4 if multiplayer 5 connected) + \return unsigned short of the released keys (1 -> 0 transition) */ unsigned short padsUp(u16 value); /*! \fn padsClear(u16 value) \brief Clear internal variables for selected pad - \param value Address of the pad to use (0 or 1 to 4 if multiplayer 5 connected) + \param value pad index to use (0-1 or 0-4 if multiplayer 5 connected) */ void padsClear(u16 value); /*! \fn detectMPlay5(void) \brief Check if MultiPlayer5 is connected and populate snes_mplay5 (0 or 1 for connected) -*/ -void detectMPlay5(void); -/*! \fn scanMPlay5() - \brief Wait for multiplayer5 pads ready and read pad values in. + \b CAUTION: REG_WRIO ($4201) must not be written to while MultiPlayer5 is active. + (Bit 7 of REG_WRIO must be set when Auto Joy reads the controllers, shortly after the VBlank Period starts.) */ -void scanMPlay5(void); +void detectMPlay5(void); /*! \fn detectMouse(void) \brief Check if Mouse is connected and populate snes_mouse (0 or 1 for connected) */ void detectMouse(void); -/*! \fn mouseRead(void) - \brief Wait for mouse ready and read mouse values in. -*/ -void mouseRead(void); - /*! \fn mouseSpeedChange(u8 port) \brief Set mouse hardware speed (populate mouseSpeed[] first). \param port Specify wich port to use (0-1) @@ -211,33 +203,4 @@ void mouseSpeedChange(u8 port); */ void detectSuperScope(void); -/*! \fn scanScope(void) - \brief Nintendo SHVC Scope BIOS version 1.00 - Quickly disassembled and commented by Revenant on 31 Jan 2013 - - This assembly uses xkas v14 syntax. It probably also assembles with bass, if there's - any such thing as good fortune in the universe. - - How to use the SHVC Super Scope BIOS: - (all variables are two bytes) - - 1: Set "HoldDelay" and "RepDelay" for the button hold delay and repeat rate - - 2: "jsr GetScope" or "jsl GetScopeLong" once per frame - - 3: Read one of the following to get the scope input bits (see definitions below): - - ScopeDown (for any flags that are currently true) - - ScopeNow (for any flags that have become true this frame) - - ScopeHeld (for any flags that have been true for a certain length of time) - - ScopeLast (for any flags that were true on the previous frame) - - 3a: If the bits read from ScopeNow indicate a valid shot, or if the Cursor button - is being pressed, then read "ShotH"/"ShotV" to adjust for aim, or read - "ShotHRaw"/"ShotVRaw" for "pure" coordinates - - 3c: at some point, set "CenterH"/"CenterV" equal to "ShotHRaw"/"ShotVRaw" - so that the aim-adjusted coordinates are "correct" -*/ -void scanScope(void); - -#endif // SNES_PADS_INCLUDE \ No newline at end of file +#endif // SNES_PADS_INCLUDE diff --git a/pvsneslib/include/snes/interrupt.h b/pvsneslib/include/snes/interrupt.h index 9ee477b0..34d6d67e 100644 --- a/pvsneslib/include/snes/interrupt.h +++ b/pvsneslib/include/snes/interrupt.h @@ -27,15 +27,89 @@ ---------------------------------------------------------------------------------*/ -/*! \file interrupt.h - \brief snes interrupt support. -*/ +/*! + * \file interrupt.h + * \brief snes interrupt support. + * + *

VBlank ISR \anchor VBlank-ISR

+ * + * PVSnesLib includes a Vertical Blank Interrupt Service Routine (VBlank ISR or NMI ISR) that will + * do the following actions (in this order) at the start of the Vertical Blanking Period when + * VBlank interrupts are enabled: + * - Transfer #oamMemory to the PPU OAM on non lag-frames. + * - Call #nmi_handler. + * - Increment #snes_vblank_count. + * - Read inputs from the controller ports on non lag-frames (see input.h). + * - Increment #lag_frame_counter on lag-frames. + * + * A lag-frame is when the execution time between two WaitForVBlank() calls exceeds a 50/60Hz + * display frame. By testing for lag-frames, the VBlank ISR will not transfer a partially + * populated #oamMemory to the PPU OAM. + * + * Lag-frames are determined by the #vblank_flag variable, which is set on WaitForVBlank() and + * cleared in the VBlank ISR. + * + * Inputs are only read on non lag-frames to prevent the input state from unexpectedly changing in + * the middle of the main loop (potentially causing a heisenbug). Code that loops until a button + * is pressed must call WaitForVBlank() within the loop otherwise it will loop forever. + * + * The #nmi_handler function pointer (set using nmiSet()) is called on \b every VBlank interrupt + * (even during force-blank / setScreenOff()). + * To prevent glitches, #nmi_handler's should either: + * - Test #vblank_flag and only update the PPU registers/memory if the #vblank_flag is set. + * - Use locks or flags on every buffer/queue to prevent partially populated data from being transferred to the PPU. + * + */ #ifndef SNES_INTERRUPT_INCLUDE #define SNES_INTERRUPT_INCLUDE #include +/** + * \brief VBlank ISR flag. + * + * Used to detect lag-frames in the VBlank ISR. + * + * This variable is set to a truthy (non-zero) value in WaitForVBlank() + * and cleared in the NMI ISR after the call to nmi_handler. + * + * vblank_flag can be used in a custom nmi_handler to detect lag frames. Within nmi_handler: + * - If vblank_flag is truthy (non-zero), the nmi_handler was called at the end of the frame. + * - If vblank_flag is 0, the nmi_handler was called in the middle of a lag frame. + * + * \b CAUTION: This variable SHOULD NOT be changed outside of WaitForVBlank() + */ +extern u8 vblank_flag; + +/** + * \brief Lag-frame counter. + * + * This variable is incremented on every VBlank Interrupt that occurs during a lag-frame. + * + * \b CAUTION: The lag frame counter cannot tell the difference between a lag-frame and + * force-blank setup loading graphics to the PPU. + * + * \c lag_frame_counter can be modified. This is useful in development builds to measure the + * amount of lag in a level by resetting \c lag_frame_counter on level load and printing + * \c lag_frame_counter on a pause screen or at the end of the level. + */ +extern u16 lag_frame_counter; + +/** + * \brief VBlank routine + * + * This function is called on \b every VBlank interrupt by the \ref VBlank-ISR "VBlank Interrupt Service Routine". + * + * \b CAUTION: Writes to \c nmi_handler are not atomic and can cause a crash if a VBlank + * Interrupt occurs in the middle of the \c nmi_handler write. Use nmiSet() or disable NMI + * interrupts when modifying \c nmi_handler. + * + * Assembly note: This function pointer will be called with a non-zero Direct Page register + * to prevent \c nmi_handler from clobbering the tcc imaginary registers. + * + * \see \ref VBlank-ISR, nmiSet() + */ extern void *nmi_handler; /** \brief VBlank NMI Enable (0=Disable, 1=Enable) (Initially disabled on reset) */ @@ -140,20 +214,30 @@ The H/V-IRQ flag in Bit7 of TIMEUP, Port 4211h gets set when the V-Counter gets #define REG_HVBJOY (*(vuint8 *)0x4212) /** - * \brief - * Add a handler for the given interrupt mask.
+ * \brief Sets the #nmi_handler (VBlank routine). * - * Specify the handler to use for the nmi interrupt.
- * \param handler - * Address of the function to use as an interrupt service routine
-*/ -#define nmiSet(handler) nmi_handler = handler; + * This function will also disable any active IRQ interrupts, enable VBlank interrupts and enable Joypad Auto-Read. + * + * \param vblankRoutine the function to call on every VBlank (NMI) interrupt.
+ * + * \b CAUTION: \p vblankRoutine is called on \b every VBlank interrupt. + * #vblank_flag can be used to determine if \p vblankRoutine was called during a lag-frame. + * + * \b CAUTION: This function will override the default #nmi_handler. + * If you are using consoleDrawText(), you will need to call consoleVblank() inside \p vblankRoutine. + * + * \see \ref VBlank-ISR, #nmi_handler + */ +void nmiSet(void (*vblankRoutine)(void)); /** - * \brief - * Wait for vblank interrupt
+ * \brief Waits for a VBlank interrupt. + * + * Sets the #vblank_flag and pauses execution until the #vblank_flag is cleared (by the \ref VBlank-ISR). + * + * \b CAUTION: This function will loop forever if VBlank interrupts are disabled. * - * Waits for a vertical blank interrupt
+ * Assembly note: This function will not modify the A/X/Y registers and can be called with an 8 or 16 bit .ACCU/.INDEX. */ void WaitForVBlank(void); diff --git a/pvsneslib/include/snes/sprite.h b/pvsneslib/include/snes/sprite.h index 1eb79160..fabdb600 100644 --- a/pvsneslib/include/snes/sprite.h +++ b/pvsneslib/include/snes/sprite.h @@ -109,7 +109,12 @@ Size bit (Small/Large) can be defined in OBSEL Register (Port 2101h).
*/ extern t_sprites oambuffer[128]; /*!< \brief current sprite buffer for dynamic engine */ -extern u8 oamMemory[128 * 4 + 8 * 4]; /*!< \brief to address oam table low and high */ +/** + * \brief to address oam table low and high + * + * This buffer is automatically transferred to the PPU OAM by the \ref VBlank-ISR on non lag-frames. + */ +extern u8 oamMemory[128 * 4 + 8 * 4]; /*! \def REG_OBSEL \brief Object Size and Object Base (W) diff --git a/pvsneslib/include/snes/video.h b/pvsneslib/include/snes/video.h index d9b67a1d..a525e3a6 100644 --- a/pvsneslib/include/snes/video.h +++ b/pvsneslib/include/snes/video.h @@ -337,6 +337,8 @@ void setMode(u8 mode, u8 size); /*! \fn setScreenOn(void) \brief Put screen On. + + Calls WaitForVBlank() before enabling the screen to flush VBlank buffers/queues and minimise glitches. */ void setScreenOn(void); diff --git a/pvsneslib/source/consoles.asm b/pvsneslib/source/consoles.asm index c2b3cd67..fe66252e 100644 --- a/pvsneslib/source/consoles.asm +++ b/pvsneslib/source/consoles.asm @@ -418,11 +418,12 @@ consoleVblank: consoleInit: php + sep #$20 + + lda.b #0 ; Disable interrupts to prevent the VBlank ISR + sta.l REG_NMITIMEN ; from modifying VRAM, PPU Registers or variables. + rep #$20 - lda.w #:consoleVblank ; Put current handler to our function - sta.l nmi_handler + 2 - lda.w #consoleVblank - sta.l nmi_handler lda.w #0 ; Begin counting vblank sta.w snes_vblank_count @@ -496,10 +497,14 @@ consoleInit: lda #TXT_VRAMOFFSET sta txt_vram_offset - plb + ; Set nmi_handler, enable VBlank interrupts, enable joypad auto-read. + pea :consoleVblank + pea consoleVblank + jsl nmiSet + pla + pla - lda #INT_VBLENABLE | INT_JOYPAD_ENABLE ; enable NMI, enable autojoy - sta.l REG_NMITIMEN + plb plp rtl diff --git a/pvsneslib/source/crt0_snes.asm b/pvsneslib/source/crt0_snes.asm index ac1efcb0..3d4f9dc1 100644 --- a/pvsneslib/source/crt0_snes.asm +++ b/pvsneslib/source/crt0_snes.asm @@ -1,6 +1,8 @@ .include "hdr.asm" -.RAMSECTION ".registers" BANK 0 SLOT 1 PRIORITY 1 +; tcc imaginary registers must start at address $00:0000 to ensure the NMI ISR writes to +; the correct addresses when the Direct Page Register is `tcc__registers_nmi_isr`. +.RAMSECTION ".registers" BANK 0 SLOT 1 ORGA 0 FORCE PRIORITY 1000 tcc__registers dsb 0 tcc__r0 dsb 2 tcc__r0h dsb 2 @@ -25,18 +27,21 @@ tcc__f3 dsb 2 tcc__f3h dsb 2 move_insn dsb 4 ; 3 bytes mvn + 1 byte rts move_backwards_insn dsb 4 ; 3 bytes mvp + 1 byte rts -nmi_handler dsb 4 - -tcc__registers_irq dsb 0 -tcc__regs_irq dsb 48 +.ENDS + + +; `tcc__registers_nmi_isr` should be page-aligned to prevent a `D.l != 0` direct-page cycle penalty. +.RAMSECTION ".vblank_imagingary_registers" BANK 0 SLOT 1 ALIGN 0x100 -snes_vblank_count dsb 2 ; 2 bytes to count number of vblank -snes_vblank_count_svg dsb 2 ; same thing for saving purpose -snes_frame_count dsb 2 ; 2 bytes for frame counter inside loop -snes_frame_count_svg dsb 2 ; same thing for saving purpose +; Imaginary registers for the NMI ISR. +; Used to prevent the VBlank interrupts from clobbering the tcc imaginary registers (`tcc__registers`). +; MUST NOT be used for IRQ interrupts unless you know for certain IRQ and NMI interrupts will not overlap. +; MUST be >= sizeof(tcc imaginary registers). +tcc__registers_nmi_isr dsb 48 .ENDS + ; sections "globram.data" and "glob.data" can stay here in the file ; because we are using wla-65816 -d switch to disable WLA's ability to calculate A-B where A and B are labels. ; If you remove the -d switch, move those two sections to the very end of the source file, then WLA cannot calculate SECTIONEND_glob.data-SECTIONSTART_glob.data and it should be delayed for WLALINK to calculate @@ -218,88 +223,6 @@ tcc__snesinit: .ENDS -; Needed to satisfy interrupt definition in "Header.inc". -.SECTION ".vblank" SEMIFREE ORG ORG_0 - -.accu 16 -.index 16 -.16bit - -VBlank: -.ifdef FASTROM - jml FVBlank - -FVBlank: -.endif - rep #$30 - phb - phd - phx - phy - pha - ; set data bank register to bss section - pea $7e7e - plb - plb - - ; Refresh pad values - sep #$20 - lda snes_mplay5 - beq + - jsl scanMPlay5 - bra cvbloam -+ - lda snes_mouse - beq + - jsl mouseRead - lda mouseConnect - and mouseConnect + 1 ; If both ports have a mouse plugged, it will skip pad controller reading - bne cvbloam -+ - jsl scanPads - lda snes_sscope - beq cvbloam - jsl scanScope - -cvbloam: - ; Put oam to screen if needed - rep #$20 ; A 16 bits - lda.w #$0000 - sta.l $2102 ; OAM address - lda.w #$0400 - sta.l $4370 ; DMA type CPU -> PPU, auto inc, $2104 (OAM write) - lda.w #$0220 - sta.l $4375 ; DMA size (220 = 128*4+32 - - lda #oamMemory.w - sta.l $4372 ; DMA address = oam memory - sep #$20 - lda #:oamMemory - sta.l $4374 ; DMA address bank = oam memory - - lda.b #$80 ; DMA channel 7 1xxx xxxx - sta.l $420b - - rep #$20 - - ; Count frame number - inc.w snes_vblank_count - - lda.w #tcc__registers_irq - tad - lda.l nmi_handler - sta.b tcc__r10 - lda.l nmi_handler + 2 - sta.b tcc__r10h - jsl tcc__jsl_r10 - pla - ply - plx - pld - plb - RTI - -.ENDS .SECTION ".start" SEMIFREE ORG ORG_0 @@ -323,12 +246,17 @@ fast_start: jsr tcc__snesinit + sep #$20 + + stz.w vblank_flag + rep #$30 ; all registers 16-bit ; direct page points to register set lda.w #tcc__registers tad + ; This nmi_handler write is safe. Interrupts are disabled by `tcc__snesinit` lda.w #EmptyNMI sta.b nmi_handler lda.w #:EmptyNMI @@ -381,6 +309,8 @@ fast_start: stz.b tcc__r0 stz.b tcc__r1 + stz.w lag_frame_counter + stz.w snes_vblank_count stz.w snes_vblank_count_svg stz.w snes_frame_count diff --git a/pvsneslib/source/input.asm b/pvsneslib/source/input.asm index bc3aea79..2a1737e2 100644 --- a/pvsneslib/source/input.asm +++ b/pvsneslib/source/input.asm @@ -30,15 +30,16 @@ .equ REG_JOY1L $4218 .equ REG_JOY2L $421A +.equ REG_JOY4L $421E .equ REG_OPHCT $213C ; Horizontal scanline location .BASE $00 .RAMSECTION ".reg_pads" BANK 0 SLOT 1 -pad_keys dsb 10 ; 5 pads , 16 bits reg -pad_keysold dsb 10 ; 5 pads , 16 bits reg -pad_keysrepeat dsb 10 ; 5 pads , 16 bits reg +pad_keys dsb 10 ; 5 pads , 16 bits reg +pad_keysold dsb 10 ; 5 pads , 16 bits reg +pad_keysdown dsb 10 ; 5 pads , 16 bits reg snes_mplay5 db ; 1 if MultiPlayer5 is connected mp5read db ; for multiplayer5 plug test @@ -119,160 +120,57 @@ connect_st dsb 2 .ENDS -.BASE BASE_0 -.SECTION ".pads0_text" SUPERFREE - -;--------------------------------------------------------------------------------- -; void scanPads(void) -scanPads: - php - phb - phy - - sep #$20 ; change bank address to 0 - lda.b #$0 - pha - plb - - rep #$20 ; copy joy states #1&2 - ldy pad_keys - sty pad_keysold - ldy pad_keys+2 - sty pad_keysold+2 - --: lda REG_HVBJOY ; wait until joypads are ready - lsr - bcs - - - lda REG_JOY1L ; read joypad register #1 - bit #$0F ; catch non-joypad input - beq + ; (bits 0-3 should be zero) - sep #$20 - lda #$0 - rep #$20 -+: sta pad_keys ; store 'current' state - eor pad_keysold ; compute 'down' state from bits that - and pad_keys ; have changed from 0 to 1 - sta pad_keysrepeat ; - - lda REG_JOY2L ; read joypad register #2 - bit #$0F ; catch non-joypad input - beq + ; (bits 0-3 should be zero) - sep #$20 - lda #$0 - rep #$20 -+: sta pad_keys+2 ; store 'current' state - eor pad_keysold+2 ; compute 'down' state from bits that - and pad_keys+2 ; have changed from 0 to 1 - sta pad_keysrepeat+2 ; - - ply - plb - plp - rtl -.ENDS - +.BASE BASE_0 .SECTION ".pads1_text" SUPERFREE ;--------------------------------------------------------------------------------- ; void padsClear(unsigned short value) padsClear: php - phb + rep #$30 phx - sep #$20 ; change bank address to 0 - lda.b #$0 - pha - plb + lda 7,s ; value argument + cmp #5 + bcs + + asl + tax - rep #$20 - lda 8,s ; get value - pha - plx - - sep #$20 - lda #$0 - sta pad_keys,x - sta pad_keysold,x - sta pad_keysrepeat,x + lda #$0 + sta.l pad_keys,x + sta.l pad_keysold,x + sta.l pad_keysdown,x + + plx - plb plp rtl .ENDS -.SECTION ".pads2_text" SUPERFREE - -;--------------------------------------------------------------------------------- -; unsigned short padsDown(unsigned short value) -; return (pad_keys[value] & ~pad_keysold[value]); -padsDown: - php - phb - phx - - sep #$20 ; change bank address to 0 - lda.b #$0 - pha - plb - - rep #$20 - lda 8,s ; get value - pha - plx - - lda pad_keysold,x - eor #$FFFF - sta.w tcc__r0 - - lda pad_keys,x - and.w tcc__r0 - sta.w tcc__r0 - - plx - plb - plp - rtl - -.ENDS .SECTION ".pads3_text" SUPERFREE ;--------------------------------------------------------------------------------- -;unsigned short padsUp(unsigned short value) { -; return (pad_keys[value] ^ pad_keysold[value]) & (~pad_keys[value]); +;unsigned short padsUp(u16 value) padsUp: php - phb + rep #$30 phx - sep #$20 ; change bank address to 0 - lda.b #$0 - pha - plb - - rep #$20 - lda 8,s ; get value - pha - plx - - lda pad_keys,x - eor #$FFFF - sta.w tcc__r0 + lda 7,s ; value argument + asl + tax - lda pad_keys,x - eor.w pad_keysold,x - and.w tcc__r0 - sta.w tcc__r0 + ; return pad_keysold[value] & (~pad_keys[value]); + lda.l pad_keys,x + eor.w #0xFFFF + and.l pad_keysold,x + sta.b tcc__r0 - plx - plb + plx plp rtl - .ENDS @@ -346,275 +244,8 @@ nomplay5: .ENDS -.SECTION ".padsm51_text" SUPERFREE - -;--------------------------------------------------------------------------------- -; void scanMPlay5(void) -scanMPlay5: - php - phb - phy - - sep #$20 ; change bank address to 0 - lda.b #$0 - pha - plb - - rep #$20 ; copy joy states #1->5 - ldy pad_keys - sty pad_keysold - ldy pad_keys+2 - sty pad_keysold+2 - ldy pad_keys+4 - sty pad_keysold+4 - ldy pad_keys+6 - sty pad_keysold+6 - ldy pad_keys+8 - sty pad_keysold+8 - --: lda REG_HVBJOY ; wait until joypads are ready - lsr - bcs - - - sep #$20 - lda.b #$80 ; enable iobit to read data - sta.w REG_WRIO - - lda.b #$1 - sta.w REG_JOYA ; do stobe on/off - stz.w REG_JOYA - - rep #$20 - ldy #16 -getpad1data: ; get all 16 bits pad1 data serialy - lda.w REG_JOYA - lsr a ; put bit0 into carry - rol.w pad_keys ; pad 1 data - dey - bne getpad1data - - ldy #16 -getpad23data: ; get all 16 bits pad2&3 data serialy - lda.w REG_JOYB - lsr a ; put bit1 into carry - rol.w pad_keys+2 ; pad 2 data - lsr a ; put bit1 into carry - rol.w pad_keys+4 ; pad 3 data - dey - bne getpad23data - - sep #$20 - stz.w REG_WRIO ; to allow read for other pads - - rep #$20 - ldy #16 -getpad45data: ; get all 16 bits pad2&3 data serialy - lda.w REG_JOYB - lsr a ; put bit1 into carry - rol.w pad_keys+6 ; pad 4 data - lsr a ; put bit1 into carry - rol.w pad_keys+8 ; pad 5 data - dey - bne getpad45data - - lda pad_keys - eor pad_keysold ; compute 'down' state from bits that - and pad_keys ; have changed from 0 to 1 - sta pad_keysrepeat ; - lda pad_keys+2 - eor pad_keysold+2 - and pad_keys+2 - sta pad_keysrepeat+2 - lda pad_keys+4 - eor pad_keysold+4 - and pad_keys+4 - sta pad_keysrepeat+4 - lda pad_keys+6 - eor pad_keysold+6 - and pad_keys+6 - sta pad_keysrepeat+6 - lda pad_keys+8 - eor pad_keysold+8 - and pad_keys+8 - sta pad_keysrepeat+8 - - sep #$20 - lda.b #$80 ; enable iobit for next frame - sta.w REG_WRIO - - ply - plb - plp - rtl - -.ENDS - -.SECTION ".padsscop_text" SUPERFREE - -;--------------------------------------------------------------------------------- -; Nintendo SHVC Scope BIOS version 1.00 -; Quickly disassembled and commented by Revenant on 31 Jan 2013 -; -; This assembly uses xkas v14 syntax. It probably also assembles with bass, if there's -; any such thing as good fortune in the universe. -; -; How to use the SHVC Super Scope BIOS: -; (all variables are two bytes) -; -; 1: Set "HoldDelay" and "RepDelay" for the button hold delay and repeat rate -; -; 2: "jsr GetScope" or "jsl GetScopeLong" once per frame -; -; 3: Read one of the following to get the scope input bits (see definitions below): -; - ScopeDown (for any flags that are currently true) -; - ScopeNow (for any flags that have become true this frame) -; - ScopeHeld (for any flags that have been true for a certain length of time) -; - ScopeLast (for any flags that were true on the previous frame) -; -; 3a: If the bits read from ScopeNow indicate a valid shot, or if the Cursor button -; is being pressed, then read "ShotH"/"ShotV" to adjust for aim, or read -; "ShotHRaw"/"ShotVRaw" for "pure" coordinates -; -; 3c: at some point, set "CenterH"/"CenterV" equal to "ShotHRaw"/"ShotVRaw" -; so that the aim-adjusted coordinates are "correct" -;--------------------------------------------------------------------------------- -; void scanScope(void) -scanScope: - phb - phk - plb - jsr GetScope - plb - rtl - -GetScope: - php - sep #$20 - - lda REG_STAT78 ; Has the PPU counter been latched? - and.b #$40 ; If not, don't get the scanline location - beq NoShot - - lda REG_OPHCT ; Get the horizontal scanline location (bits 0-7) - sta scope_shoth - sta scope_shothraw - - lda REG_OPHCT ; Get the horizontal scanline location (bit 8) - and.b #$01 - sta scope_shoth+1 - sta scope_shothraw+1 - lda REG_OPVCT ; Get the vertical scanline location (bits 0-7) - sta scope_shotv - sta scope_shotvraw - - lda REG_OPVCT ; Get the vertical scanline location (bit 8) - and.b #$01 - sta scope_shotv+1 - sta scope_shotvraw+1 - - rep #$20 - lda scope_centerh ; Factor in the horizontal offset factor - clc - adc scope_shoth - sta scope_shoth - - lda scope_centerv ; Factor in the vertical offset factor - clc - adc scope_shotv - sta scope_shotv - - stz scope_sinceshot ; update number of frames since last shot - bra GetInput ; (what happens if 65536 frames elapse between shots?) - -NoShot: - inc scope_sinceshot - -; Wait for valid joypad input -GetInput: - sep #$20 - --: lda REG_HVBJOY - and.b #$01 - bne - - - rep #$20 - lda REG_JOY2L ; Get joypad 2 input - sta scope_port2down ; using a typical method to separate frame input / total input - eor scope_port2last - and scope_port2down - sta scope_port2now - lda scope_port2down - sta scope_port2last - - lda scope_port2down ; Check if the controller in port 2 is a Super Scope. - and.w #$0CFF ; For a 16-bit auto joypad read, bits 0-7 should be always 1 - cmp.w #$00FF ; and bits 10-11 should be always 0. - bne NoScope - - lda scope_sinceshot ; has a shot already happened this frame? - beq GetButtons ; If so, then only pay attention to the pause button bit - - lda scope_port2down ; Check which already-held buttons are still held - and scope_last - sta scope_last - - lda scope_port2down ; Check pause button held status - and.w #$1000 - sta scope_down - - lda scope_port2now ; Check pause button pressed status - and.w #$1000 - sta scope_now - - plp ; return from input check - rts - -GetButtons: - lda scope_port2down ; Get button status when NOT paused - sta scope_down - eor scope_last - and scope_port2down - sta scope_now - sta scope_held - - lda scope_port2down ; if no bits are set on port 2, don't check for "held buttons". - beq NotHolding - - cmp scope_last ; else if the bits aren't the same as last frame, don't check either. - bne NotHolding - - dec scope_tohold ; if a certain number of frames have elapsed with the same buttons - bne NotHeld ; held down, consider them "officially held". - - lda scope_port2down - sta scope_held - - lda scope_repdelay ; set the remaining delay to the repeat value - sta scope_tohold - bra NotHeld - -NotHolding: - lda scope_holddelay ; set the remaining delay to the normal value - sta scope_tohold - -NotHeld: - lda scope_port2down - sta scope_last - - plp ; return from input check - rts - -NoScope: - stz scope_port2down ; If no scope is connected, zero out all inputs - stz scope_port2now - stz scope_down - stz scope_now - stz scope_held - stz snes_sscope ; and lib flag - - plp ; return from input check - rts +.SECTION ".padsdetectsuperscope_text" SUPERFREE ;--------------------------------------------------------------------------------- ; detectSuperScope(void) @@ -648,146 +279,9 @@ detectSuperScope: .ENDS -;--------------------------------------------------------------------------------- - -;* mouse_read - -;--------------------------------------------------------------------------------- - -;* If this routine is called every frame, then the mouse status will be set -;* to the appropriate registers. -;* INPUT -;* None (Mouse key read automatically) -;* OUTPUT -;* Connection status (mouse_con) D0=1 Mouse connected to Joyl -;* D1=1 Mouse connected to Joy2 -;* Switch (mousePressed,1) D0=left switch turbo -;* D1=right switch turbo -;* Switch (mouseButton,1) D0=left switch trigger -;* D1=right switch trigger -;* Mouse movement (ball) value -;* (mouse_x) D7=0 Positive turn, D7=1 Negative turn -;* D6-D0 X movement value -;* (mouse_y) D7=0 Positive turn, D7=1 Negative turn -;* D6-D0 X movement value - -;--------------------------------------------------------------------------------- - -.SECTION ".mouse_text" SUPERFREE - -;--------------------------------------------------------------------------------- -; void mouseRead(void) -mouseRead: - php - sep #$30 - phb - phx - phy - - lda #$00 ; Set Data Bank to 0 - pha - plb - -_10: - lda REG_HVBJOY - and #$01 - bne _10 ; Automatic read ok? - - ldx #$01 - lda REG_JOY2L ; Joy2 - jsr mouse_data - - lda connect_st+1 - beq _20 - - jsr speed_change - stz connect_st+1 - -_20: - dex - lda REG_JOY1L ; Joy1 - jsr mouse_data - - lda connect_st - beq _30 - - jsr speed_change - stz connect_st - -_30: - lda mouseConnect - ora mouseConnect+1 - bne + - stz snes_mouse ; Disable mouse flag if no mouse connected - -+: - ply - plx - plb - plp - rtl - -mouse_data: - - sta tcc__r0 ; (421A / 4218 saved to reg0) - and.b #$0F - cmp.b #$01 ; Is the mouse connected? - beq _m10 - - stz mouseConnect,x ; No connection. - - stz mouseButton,x - stz mousePressed,x - stz mouse_x,x - stz mouse_y,x - - rts - -_m10: - lda mouseConnect,x ; When mouse is connected, speed will change. - bne _m20 ; Previous connection status - ; (mouse.com judged by lower 1 bit) - lda #$01 ; Connection check flag on - sta mouseConnect,x - sta connect_st,x - rts - -_m20: - rep #$10 - ldy #16 ; Read 16 bit data. - sep #$10 - -_m30: - lda REG_JOYA,x - - lsr a - rol mouse_x,x - rol mouse_y,x - dey - bne _m30 - - stz mousePressed,x - - rol tcc__r0 - rol mousePressed,x - rol tcc__r0 - rol mousePressed,x ; Switch turbo - - lda mousePressed,x - eor mouse_sb,x ; Get switch trigger - bne _m40 - - stz mouseButton,x - - rts - -_m40: - lda mousePressed,x - sta mouseButton,x - sta mouse_sb,x - - rts +; Must be in bank 0, used by _MouseRead in the VBlank ISR. +.SECTION ".mousespeedchange_text" SEMIFREE BANK 0 ;--------------------------------------------------------------------------------- ; void mouseSpeedChange(u8 port) @@ -805,7 +299,7 @@ mouseSpeedChange: lda 8,s ; Set port tax - jsr speed_change + jsr @speed_change ply plx @@ -813,7 +307,13 @@ mouseSpeedChange: plp rtl -speed_change: + +; Called by _MouseRead in the Vblank ISR +; X = 0 or 1 +; DB = 0 +.ACCU 8 +.INDEX 8 +@speed_change: php sep #$30 @@ -869,6 +369,11 @@ _s30: plp rts +.ENDS + + +.SECTION ".detectmouse_text" SUPERFREE + ;--------------------------------------------------------------------------------- ; detectMouse(void) detectMouse: @@ -900,4 +405,4 @@ detectMouse: plp rtl -.ENDS \ No newline at end of file +.ENDS diff --git a/pvsneslib/source/interrupts.asm b/pvsneslib/source/interrupts.asm deleted file mode 100644 index da78dd83..00000000 --- a/pvsneslib/source/interrupts.asm +++ /dev/null @@ -1,66 +0,0 @@ -;--------------------------------------------------------------------------------- -; -; Copyright (C) 2013-2020 -; Alekmaul -; -; This software is provided 'as-is', without any express or implied -; warranty. In no event will the authors be held liable for any -; damages arising from the use of this software. -; -; Permission is granted to anyone to use this software for any -; purpose, including commercial applications, and to alter it and -; redistribute it freely, subject to the following restrictions: -; -; 1. The origin of this software must not be misrepresented; you -; must not claim that you wrote the original software. If you use -; this software in a product, an acknowledgment in the product -; documentation would be appreciated but is not required. -; 2. Altered source versions must be plainly marked as such, and -; must not be misrepresented as being the original software. -; 3. This notice may not be removed or altered from any source -; distribution. -; -;--------------------------------------------------------------------------------- - -.BASE BASE_0 -.SECTION ".interrupts0_text" SUPERFREE - -;--------------------------------------------------------------------------- -WaitForVBlank: - wai - rtl - -; old version still here for memory purpose -; pha -; php -; sep #$20 -;-: -; lda.l REG_RDNMI -; bmi - -;-: -; lda.l REG_RDNMI -; bpl - -; plp -; pla -; rtl - - -.ENDS - -.SECTION ".interrupts1_text" SUPERFREE - -;--------------------------------------------------------------------------- -; void WaitNVBlank(u16 ntime) -WaitNVBlank: - php - - sep #$20 - lda 5,s -- wai - dea - bne - - - plp - rtl - -.ENDS diff --git a/pvsneslib/source/libc.asm b/pvsneslib/source/libc.asm index aa982d3e..a54a917c 100644 --- a/pvsneslib/source/libc.asm +++ b/pvsneslib/source/libc.asm @@ -474,7 +474,6 @@ exitl4: .include "consoles.asm" .include "dmas.asm" .include "input.asm" -.include "interrupts.asm" .include "lzsss.asm" .include "maps.asm" .include "objects.asm" @@ -483,3 +482,4 @@ exitl4: .include "sounds.asm" .include "sprites.asm" .include "videos.asm" +.include "vblank.asm" diff --git a/pvsneslib/source/vblank.asm b/pvsneslib/source/vblank.asm new file mode 100644 index 00000000..551ecf06 --- /dev/null +++ b/pvsneslib/source/vblank.asm @@ -0,0 +1,822 @@ +;--------------------------------------------------------------------------------- +; +; Copyright (C) 2013-2024 +; Alekmaul - DigiDwrf +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any +; damages arising from the use of this software. +; +; Permission is granted to anyone to use this software for any +; purpose, including commercial applications, and to alter it and +; redistribute it freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you +; must not claim that you wrote the original software. If you use +; this software in a product, an acknowledgment in the product +; documentation would be appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and +; must not be misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source +; distribution. +; +;--------------------------------------------------------------------------------- + + +; Using lowram segment (Work-RAM address $0000-$1FFF) for VBlank variables so they can be accessed with a DB of $00. +.BASE $00 +.RAMSECTION ".vblank_lowram" BANK 0 SLOT 1 PRIORITY 1 + +vblank_flag dsb 1 + +nmi_handler dsb 4 + +lag_frame_counter dsb 2 ; Number of lag frames encountered (can be externally modified) + +snes_vblank_count dsb 2 ; Incremented every VBlank interrupt + +.ENDS + + +; Needed to ensure VBlank ISR is in bank zero so the 65816 can access it when a NMI interrupt occurs. +.BASE BASE_0 +.SECTION ".vblank_isr" SEMIFREE ORGA $8000 BASE BASE_0 BANK 0 + + +;; Scan and read the joypads +;; +;; REQUIRES: Auto-Joypad enabled. +;; REQUIRES: REG_WRIO bit 7 set BEFORE Vblank starts if a Multitap/Mp5 is connected. +;; +;; REQUIRES: Auto-Joypad has finished reading the controllers. +;; (This macro DOES NOT contain a REG_HVBJOY test/spinloop.) +;; +;; ACCU 8 +;; INDEX 16 +;; DB = 0 +;; D = tcc__registers_nmi_isr (NOT ZERO) +.MACRO _ScanPads + ldy pad_keys ; copy joy states #1&2 + sty pad_keysold + ldy pad_keys+2 + sty pad_keysold+2 + + ; The code assumes Joypad Auto-Read is not active. + ; This is enforced by a REG_HVBJOY spinloop in the VBlank ISR. + + rep #$20 + + lda REG_JOY1L ; read joypad register #1 + bit #$0F ; catch non-joypad input + beq + ; (bits 0-3 should be zero) + lda.w #$0 ++ + sta pad_keys ; store 'current' state + eor pad_keysold ; compute 'down' state from bits that + and pad_keys ; have changed from 0 to 1 + sta pad_keysdown ; + + lda REG_JOY2L ; read joypad register #2 + bit #$0F ; catch non-joypad input + beq + ; (bits 0-3 should be zero) + lda.w #$0 ++ + sta pad_keys+2 ; store 'current' state + eor pad_keysold+2 ; compute 'down' state from bits that + and pad_keys+2 ; have changed from 0 to 1 + sta pad_keysdown+2 ; + + sep #$20 +.ENDM + + +;--------------------------------------------------------------------------------- + + +;; Scan and read the last 3 controllers on a Multitap or MP5 connected to Port 2. +;; +;; NOTE: Does not read pads 0 & 1. Use `ScanPads` to read pads 0 & 1. +;; +;; REQUIRES: Joypad Auto-Read enabled. +;; REQUIRES: REG_WRIO bit 7 set BEFORE Vblank starts. +;; +;; REQUIRES: Auto-Joypad has finished reading the controllers. +;; (This macro DOES NOT contain a REG_HVBJOY test/spinloop.) +;; +;; ACCU 8 +;; INDEX 16 +;; DB = 0 +;; D = tcc__registers_nmi_isr (NOT ZERO) +.MACRO _ScanMPlay5 + ; Using the multitap reading protocol from the SNES Development Wiki + ; https://snes.nesdev.org/wiki/Multitap + +.function __pad_n(array, index) (array + index * 2) + + ; Save old pad state + ; pads 0 & 1 are read by ScanPads. + ldy.w __pad_n(pad_keys, 2) + sty.w __pad_n(pad_keysold, 2) + + ldy.w __pad_n(pad_keys, 3) + sty.w __pad_n(pad_keysold, 3) + + ldy.w __pad_n(pad_keys, 4) + sty.w __pad_n(pad_keysold, 4) + + + sep #$20 +.ACCU 8 + + ; The code assumes Joypad Auto-Read is not active. + ; This is enforced by a REG_HVBJOY spinloop in the VBlank ISR. + + + ; Pads 3 & 4 must be manually read using the REG_JOYB register. + ; + ; The strobe/latch pin does not need to be toggled. + ; All 4 controllers on the mutlitap share the latch pin. + + ; Switch multitap to the second pair of controllers + stz.w REG_WRIO + + ; Initialise pad_keys to 1 so a `rol` outputs carry set after 8 `rol` instructions + ; A = 1 + sta.w __pad_n(pad_keys, 4) + sta.w __pad_n(pad_keys, 4) + 1 + + ; Read high byte of pads 3/4 + - + lda.w REG_JOYB + lsr + rol.w __pad_n(pad_keys, 3) + 1 + lsr + rol.w __pad_n(pad_keys, 4) + 1 + bcc - + + ; Read low byte of pads 3/4 + - + lda.w REG_JOYB + lsr + rol.w __pad_n(pad_keys, 3) + lsr + rol.w __pad_n(pad_keys, 4) + bcc - + + rep #$30 +.ACCU 16 +.INDEX 16 + + ; Pads 0 & 1 are read by ScanPads. + + ; Read & process pad 2 from Auto-Joy + lda.w REG_JOY4L + bit.w #$0f + beq + + ; Not a standard controller + lda.w #0 + + + sta.w __pad_n(pad_keys, 2) + eor.w __pad_n(pad_keysold, 2) + and.w __pad_n(pad_keys, 2) + sta.w __pad_n(pad_keysdown, 2) + + ; Process pads 3 & 4 + .REPEAT 2 INDEX _I + .REDEFINE @p = 3 + _I + .ASSERT @p < 5 + + lda.w __pad_n(pad_keys, @p) + bit.w #$0f + beq + + ; Not a standard controller + lda.w #0 + sta.w __pad_n(pad_keys, @p) + + + eor.w __pad_n(pad_keysold, @p) + and.w __pad_n(pad_keys, @p) + sta.w __pad_n(pad_keysdown, @p) + .ENDR + + + sep #$20 +.ACCU 8 + + ; Switch multitap back to the first pair of controllers + ; Ensures Auto-Joy will read pads 2/3 on the next VBlank. + lda.b #$80 + sta.w REG_WRIO +.ENDM + + + +;--------------------------------------------------------------------------------- +; Nintendo SHVC Scope BIOS version 1.00 +; Quickly disassembled and commented by Revenant on 31 Jan 2013 +; +; This assembly uses xkas v14 syntax. It probably also assembles with bass, if there's +; any such thing as good fortune in the universe. +; +; How to use the SHVC Super Scope BIOS: +; (all variables are two bytes) +; +; 1: Set "HoldDelay" and "RepDelay" for the button hold delay and repeat rate +; +; 2: "jsr GetScope" or "jsl GetScopeLong" once per frame +; +; 3: Read one of the following to get the scope input bits (see definitions below): +; - ScopeDown (for any flags that are currently true) +; - ScopeNow (for any flags that have become true this frame) +; - ScopeHeld (for any flags that have been true for a certain length of time) +; - ScopeLast (for any flags that were true on the previous frame) +; +; 3a: If the bits read from ScopeNow indicate a valid shot, or if the Cursor button +; is being pressed, then read "ShotH"/"ShotV" to adjust for aim, or read +; "ShotHRaw"/"ShotVRaw" for "pure" coordinates +; +; 3c: at some point, set "CenterH"/"CenterV" equal to "ShotHRaw"/"ShotVRaw" +; so that the aim-adjusted coordinates are "correct" +;--------------------------------------------------------------------------------- + +;; Read the SuperScope from controller port 2 and PPU counter. +;; +;; REQUIRES: Auto-Joypad enabled. +;; REQUIRES: Auto-Joypad has finished reading the controllers. +;; (This subroutine DOES NOT contain a REG_HVBJOY test/spinloop.) +;; +;; DB = 0 +;; D = tcc__registers_nmi_isr (NOT ZERO) +.ACCU 8 +.INDEX 16 +_GetScope: + php + sep #$20 + + lda REG_STAT78 ; Has the PPU counter been latched? + and.b #$40 ; If not, don't get the scanline location + beq @NoShot + + lda REG_OPHCT ; Get the horizontal scanline location (bits 0-7) + sta scope_shoth + sta scope_shothraw + + lda REG_OPHCT ; Get the horizontal scanline location (bit 8) + and.b #$01 + sta scope_shoth+1 + sta scope_shothraw+1 + + lda REG_OPVCT ; Get the vertical scanline location (bits 0-7) + sta scope_shotv + sta scope_shotvraw + + lda REG_OPVCT ; Get the vertical scanline location (bit 8) + and.b #$01 + sta scope_shotv+1 + sta scope_shotvraw+1 + + rep #$20 + lda scope_centerh ; Factor in the horizontal offset factor + clc + adc scope_shoth + sta scope_shoth + + lda scope_centerv ; Factor in the vertical offset factor + clc + adc scope_shotv + sta scope_shotv + + stz scope_sinceshot ; update number of frames since last shot + bra @GetInput ; (what happens if 65536 frames elapse between shots?) + +@NoShot: + inc scope_sinceshot + +; Wait for valid joypad input +@GetInput: + ; The code assumes Joypad Auto-Read is not active. + ; This is enforced by a REG_HVBJOY spinloop in the VBlank ISR. + + rep #$20 + lda REG_JOY2L ; Get joypad 2 input + sta scope_port2down ; using a typical method to separate frame input / total input + eor scope_port2last + and scope_port2down + sta scope_port2now + lda scope_port2down + sta scope_port2last + + lda scope_port2down ; Check if the controller in port 2 is a Super Scope. + and.w #$0CFF ; For a 16-bit auto joypad read, bits 0-7 should be always 1 + cmp.w #$00FF ; and bits 10-11 should be always 0. + bne @NoScope + + lda scope_sinceshot ; has a shot already happened this frame? + beq @GetButtons ; If so, then only pay attention to the pause button bit + + lda scope_port2down ; Check which already-held buttons are still held + and scope_last + sta scope_last + + lda scope_port2down ; Check pause button held status + and.w #$1000 + sta scope_down + + lda scope_port2now ; Check pause button pressed status + and.w #$1000 + sta scope_now + + plp ; return from input check + rts + +@GetButtons: + lda scope_port2down ; Get button status when NOT paused + sta scope_down + eor scope_last + and scope_port2down + sta scope_now + sta scope_held + + lda scope_port2down ; if no bits are set on port 2, don't check for "held buttons". + beq @NotHolding + + cmp scope_last ; else if the bits aren't the same as last frame, don't check either. + bne @NotHolding + + dec scope_tohold ; if a certain number of frames have elapsed with the same buttons + bne @NotHeld ; held down, consider them "officially held". + + lda scope_port2down + sta scope_held + + lda scope_repdelay ; set the remaining delay to the repeat value + sta scope_tohold + bra @NotHeld + +@NotHolding: + lda scope_holddelay ; set the remaining delay to the normal value + sta scope_tohold + +@NotHeld: + lda scope_port2down + sta scope_last + + plp ; return from input check + rts + +@NoScope: + stz scope_port2down ; If no scope is connected, zero out all inputs + stz scope_port2now + stz scope_down + stz scope_now + stz scope_held + stz snes_sscope ; and lib flag + + plp ; return from input check + rts + + + +;--------------------------------------------------------------------------------- + +;* mouse read + +;--------------------------------------------------------------------------------- + +;* If this routine is called every frame, then the mouse status will be set +;* to the appropriate registers. +;* INPUT +;* None (Mouse key read automatically) +;* OUTPUT +;* Connection status (mouse_con) D0=1 Mouse connected to Joyl +;* D1=1 Mouse connected to Joy2 +;* Switch (mousePressed,1) D0=left switch turbo +;* D1=right switch turbo +;* Switch (mouseButton,1) D0=left switch trigger +;* D1=right switch trigger +;* Mouse movement (ball) value +;* (mouse_x) D7=0 Positive turn, D7=1 Negative turn +;* D6-D0 X movement value +;* (mouse_y) D7=0 Positive turn, D7=1 Negative turn +;* D6-D0 X movement value + +;--------------------------------------------------------------------------------- + + +;; Read the mouse values +;; +;; REQUIRES: Auto-Joypad enabled. +;; REQUIRES: Auto-Joypad has finished reading the controllers. +;; (This subroutine DOES NOT contain a REG_HVBJOY test/spinloop.) +;; +;; DB = 0 +;; D = tcc__registers_nmi_isr (NOT ZERO) +.ACCU 8 +.INDEX 16 +_MouseRead: + sep #$30 + + ; The code assumes Joypad Auto-Read is not active. + ; This is enforced by a REG_HVBJOY spinloop in the VBlank ISR. + + ldx #$01 + lda REG_JOY2L ; Joy2 + jsr _MouseData + + lda connect_st+1 + beq @_20 + + jsr mouseSpeedChange@speed_change + stz connect_st+1 + +@_20: + dex + lda REG_JOY1L ; Joy1 + jsr _MouseData + + lda connect_st + beq @_30 + + jsr mouseSpeedChange@speed_change + stz connect_st + +@_30: + + lda mouseConnect + ora mouseConnect+1 + bne + + stz snes_mouse ; Disable mouse flag if no mouse connected + ++: + rep #$10 + rts + + +;; Read the mouse values from a single controller port. +;; +;; IN: A = REG_JOY1L or REG_JOY2L +;; IN: X = 0 or 1 +;; +;; DB = 0 +;; D = tcc__registers_nmi_isr (NOT ZERO) +.accu 8 +.index 8 +_MouseData: + + sta tcc__r0 ; (421A / 4218 saved to reg0) + and.b #$0F + cmp.b #$01 ; Is the mouse connected? + beq @_m10 + + stz mouseConnect,x ; No connection. + + stz mouseButton,x + stz mousePressed,x + stz mouse_x,x + stz mouse_y,x + + rts + +@_m10: + lda mouseConnect,x ; When mouse is connected, speed will change. + bne @_m20 ; Previous connection status + ; (mouse.com judged by lower 1 bit) + lda #$01 ; Connection check flag on + sta mouseConnect,x + sta connect_st,x + rts + +@_m20: + rep #$10 + ldy #16 ; Read 16 bit data. + sep #$10 + +@_m30: + lda REG_JOYA,x + + lsr a + rol mouse_x,x + rol mouse_y,x + dey + bne @_m30 + + stz mousePressed,x + + rol tcc__r0 + rol mousePressed,x + rol tcc__r0 + rol mousePressed,x ; Switch turbo + + lda mousePressed,x + eor mouse_sb,x ; Get switch trigger + bne @_m40 + + stz mouseButton,x + + rts + +@_m40: + lda mousePressed,x + sta mouseButton,x + sta mouse_sb,x + + rts + + + +;--------------------------------------------------------------------------------- + +.accu 16 +.index 16 +.16bit + + +;; Vertical Blank Interrupt Service Routine (NMI ISR) +;; +VBlank: +.ifdef FASTROM + jml FVBlank + +FVBlank: +.endif + + rep #$38 ; 16 bit A, 16 bit I, decimal mode disabled + + ; Push CPU registers to the stack. + ; A/X/Y must be saved in 16-bit mode (**before** switching to an 8 bit index) + phb + phd + phx + phy + pha + + ; Direct Page and DB are unknown. Set them here. + + ; Using a different Direct Page Register value to prevent the `nmi_handler` call + ; from clobbering tcc imaginary registers. + lda.w #tcc__registers_nmi_isr + tad +; D = tcc__registers_nmi_isr + + pea $7e80 + plb +; DB = $80 + + + ; Whenever this ISR is modified, the `VBlank ISR` section in `interrupt.h` MUST ALSO be updated. + + + ; Using 16 bit A so the `stz $2102` below clears a 16-bit register. + sep #$10 +.ACCU 16 +.INDEX 8 + + ldx.w vblank_flag + beq + + ; Transfer oamMemory to OAM + stz.w $2102 ; OAM address (word register) + + lda.w #$0400 + sta.w $4370 ; DMA type CPU -> PPU, auto inc, $2104 (OAM write) + + lda.w #$0220 + sta.w $4375 ; DMA size (0x220 = 128*4+32) + + lda.w #oamMemory.w + sta.w $4372 ; DMA address = oam memory + + ldx.b #:oamMemory + stx.w $4374 ; DMA address bank = oam memory + + ldx.b #$80 + stx.w $420b ; DMA channel 7 1xxx xxxx + + + + rep #$30 +.ACCU 16 +.INDEX 16 + plb +; DB = $7e + + jsl __JumpTo_nmi_handler + + + ; This marks the end of the Vertical Blanking Period critical code + + + ; Count frame number + inc.w snes_vblank_count + + ; Refresh pad values + sep #$20 + + ; Set Data Bank to access joypad registers and lowram + lda.b #0 + pha + plb + ; DB = 0 + + lda.w vblank_flag + bne @ReadInputs + ; The VBlank interrupt occurred in a lag frame + ; Do not read inputs. + + ldx.w lag_frame_counter + inx + bne + + dex + + + stx.w lag_frame_counter + + jmp @EndReadInputs + + @ReadInputs: + ; Not in a lag frame + ; Read inputs + + stz.w vblank_flag + + ; Wait until the Joypad Auto-Read has finished. + ; (done here so HVBJOY is only tested in one spot) + lda.b #1 + - + bit.w REG_HVBJOY + bne - + + lda snes_mplay5 + bne @ScanMp5 + + lda snes_mouse + beq + + jsr _MouseRead + + ; If both ports have a mouse plugged, it will skip pad controller reading + lda.w mouseConnect + and.w mouseConnect + 1 + bne @EndReadInputs + + + lda snes_sscope + beq + + jsr _GetScope + bra @EndReadInputs + + + @ScanPads: + _ScanPads + +@EndReadInputs: + + ; Restore CPU registers + rep #$30 + + pla + ply + plx + pld + plb + + rti + + + +@ScanMp5: + _ScanMPlay5 + jmp @ScanPads + + +;; Long jump to `nmi_handler` +;; +;; ACCU 16 +;; INDEX 16 +;; DB = $7e +;; D = tcc__registers_nmi_isr +__JumpTo_nmi_handler: + ; The `JML [addr]` instruction will always read the new program counter from Bank 0 + jml [nmi_handler] + +.ENDS + + +;--------------------------------------------------------------------------- + +.BASE BASE_0 +.SECTION ".nmiSet_text" SUPERFREE + +; void nmiSet(void (*vblankRoutine)(void)); +; +; DB access lowram +nmiSet: + php +.DEFINE _stack_arg_offset 5 + + sep #$20 +.ACCU 8 + + ; Disable interrupts. + ; Prevents a crash if a VBlank Interrupt occurs in the middle of the two `nmi_handler` writes. + lda #0 + sta.l REG_NMITIMEN + + rep #$20 +.INDEX 16 + lda _stack_arg_offset,s + sta.w nmi_handler + + lda _stack_arg_offset + 2,s + sta.w nmi_handler + 2 + + + sep #$20 +.ACCU 8 + ; Reset the NMI flag. + ; Prevents an NMI interrupt from erroneously activating on the REG_NMITIMEN write. + lda.l REG_RDNMI + + ; Enable VBlank Interrupts and Joypad Auto-Read. + lda #INT_VBLENABLE | INT_JOYPAD_ENABLE + sta.l REG_NMITIMEN + +.UNDEFINE _stack_arg_offset + plp + rtl + +.ENDS + + +;--------------------------------------------------------------------------- + +.BASE BASE_0 +.SECTION ".waitforvblank_text" SUPERFREE + +; WaitForVBlank(void); +; +; KEEP: A, X, Y (documented in interrupt.h) +; A unknown +; I unknown +; DB unknown +; D unknown +WaitForVBlank: + php + sep #$20 +.ACCU 8 + + pha + + ; MUST NOT modify X, Y or high-byte of A + ; This behaviour is documented in interrupt.h and used by `video.asm`. + + lda #1 + sta.l vblank_flag + + - + wai + lda.l vblank_flag + bne - + + pla + plp + rtl + +.ENDS + + +.SECTION ".waitforthreevblanks_text" SUPERFREE + +; WaitForVBlankTiles3(void); +; +; Used by `setMosaicEffect`. +; +; KEEP: A, X, Y +; A unknown +; I unknown +; DB unknown +; D unknown +WaitForThreeVBlanks: + ; MUST NOT modify X, Y or A + jsl WaitForVBlank + jsl WaitForVBlank + jml WaitForVBlank + +.ENDS + + +.SECTION ".waitnvblank_text" SUPERFREE + +;--------------------------------------------------------------------------- +; void WaitNVBlank(u16 ntime) +WaitNVBlank: + php + + sep #$20 + + lda 5,s + - + pha + jsl WaitForVBlank + pla + dea + bne - + + plp + rtl + +.ENDS + diff --git a/pvsneslib/source/videos.asm b/pvsneslib/source/videos.asm index 7ff977bf..ff5ba243 100644 --- a/pvsneslib/source/videos.asm +++ b/pvsneslib/source/videos.asm @@ -101,6 +101,17 @@ m7_md DSB (225-64)*3 ; 483 bytes .ENDS + +; getFPScounter() variables +.RAMSECTION ".getfpscounter_lowram" BANK 0 SLOT 1 + +snes_vblank_count_svg dsb 2 ; for comparing snes_vblank_count +snes_frame_count dsb 2 ; 2 bytes for frame counter inside loop +snes_frame_count_svg dsb 2 ; same thing for saving purpose + +.ENDS + + .BASE BASE_0 .SECTION ".videos0_text" SUPERFREE @@ -125,7 +136,8 @@ setFadeEffect: ldx.b #$0 -: - wai + jsl WaitForVBlank + ; A,X,Y unchanged txa sta.l REG_INIDISP inx @@ -142,7 +154,8 @@ setFadeEffect: _fadeouteffect: ldx.b #$F -: - wai + jsl WaitForVBlank + ; A,X,Y unchanged txa sta.l REG_INIDISP dex @@ -176,7 +189,9 @@ setFadeEffectEx: ldx.b #$0 -: lda.b 10,s --- wai +-- + jsl WaitForVBlank + ; A,X,Y unchanged dea bne -- @@ -197,7 +212,9 @@ _sfeex1: ldx.b #$F -: lda.b 10,s --- wai +-- + jsl WaitForVBlank + ; A,X,Y unchanged dea bne -- @@ -231,9 +248,9 @@ setMosaicEffect: lda #$00 ldx.w #$0 -: - wai - wai - wai + jsl WaitForThreeVBlanks + ; A,X,Y unchanged + ora 8,s ; Enable effect for BG in parameters sta.l REG_MOSAIC clc @@ -254,9 +271,9 @@ _mosaicouteffect: lda #$F0 ldx.w #$0 --: wai - wai - wai +-: + jsl WaitForThreeVBlanks + ; A,X,Y unchanged ora 8,s ; Enable effect for BG in parameters sta.l REG_MOSAIC @@ -282,9 +299,14 @@ setScreenOn: php sep #$20 - lda #$f - wai + ; Calling WaitForVBlank to: + ; * Flush any unsent VBlank ISR/routine buffers/queues (fixes an uninitialized OAM glitch). + ; * Ensure the input/pad state is up-to-date when `setScreenOn()` returns. + ; * Prevent screen tearing and a single frame glitch that occurs when the screen is enabled mid-frame. + jsl WaitForVBlank + + lda #$f sta.l REG_INIDISP plp diff --git a/snes-examples/audio/effects/effects.c b/snes-examples/audio/effects/effects.c index 2404b61b..e0a2da47 100644 --- a/snes-examples/audio/effects/effects.c +++ b/snes-examples/audio/effects/effects.c @@ -62,8 +62,7 @@ int main(void) consoleDrawText(7, 14, "Effect: tada"); while (1) { - // Refresh pad values and test key a (without repeating sound if still pressed) - scanPads(); + // Test key a (without repeating sound if still pressed) if (padsCurrent(0) & KEY_A) { if (keyapressed == 0) diff --git a/snes-examples/audio/effectsandmusic/effectsandmusic.c b/snes-examples/audio/effectsandmusic/effectsandmusic.c index 4e9f5b98..01a0d52c 100644 --- a/snes-examples/audio/effectsandmusic/effectsandmusic.c +++ b/snes-examples/audio/effectsandmusic/effectsandmusic.c @@ -68,8 +68,7 @@ int main(void) consoleDrawText(5, 17, "Effect: tada"); while (1) { - // Refresh pad values and test key a (without repeating sound if still pressed) - scanPads(); + // Test key a (without repeating sound if still pressed) if (padsCurrent(0) & KEY_A) { if (keyapressed == 0) diff --git a/snes-examples/audio/tada/tada.c b/snes-examples/audio/tada/tada.c index 23b750e0..42af406e 100644 --- a/snes-examples/audio/tada/tada.c +++ b/snes-examples/audio/tada/tada.c @@ -53,8 +53,7 @@ int main(void) // Wait for nothing :D ! while (1) { - // Refresh pad values and test key a (without repeating sound if still pressed) - scanPads(); + // Test key a (without repeating sound if still pressed) if (padsCurrent(0) & KEY_A) { if (keyapressed == 0) diff --git a/snes-examples/graphics/Backgrounds/Mode1ContinuosScroll/Mode1ContinuosScroll.c b/snes-examples/graphics/Backgrounds/Mode1ContinuosScroll/Mode1ContinuosScroll.c index 2a85d2a9..70a69a2a 100644 --- a/snes-examples/graphics/Backgrounds/Mode1ContinuosScroll/Mode1ContinuosScroll.c +++ b/snes-examples/graphics/Backgrounds/Mode1ContinuosScroll/Mode1ContinuosScroll.c @@ -76,32 +76,29 @@ character player1; // keep the joypad commands unsigned short pad0; -// handle a mutex to avoid refresh background while we are changing it -unsigned char bg_mutex; - // control where the background needs to be updated background bgInfo; // set background 1 to be updated void updateBG1(u8 *pgfx, u16 adrspr, int size) { - bg_mutex = 1; // to avoid vbl during queue management + // Safe to write to `bgInfo` - `myconsoleVBlank` will not read from `bgInfo` on lag-frames. + bgInfo.bg1.adrgfxvram = adrspr; bgInfo.bg1.gfxoffset = pgfx; bgInfo.bg1.size = size; bgInfo.refreshBG1 = true; - bg_mutex = 0; // to avoid vbl during queue management } // set background 2 to be updated void updateBG2(u8 *pgfx, u16 adrspr, int size) { - bg_mutex = 1; // to avoid vbl during queue management + // Safe to write to `bgInfo` - `myconsoleVBlank` will not read from `bgInfo` on lag-frames. + bgInfo.bg2.adrgfxvram = adrspr; bgInfo.bg2.gfxoffset = pgfx; bgInfo.bg2.size = size; bgInfo.refreshBG2 = true; - bg_mutex = 0; // to avoid vbl during queue management } void updatePos(character *p, unsigned short pad) @@ -184,19 +181,17 @@ void handleScrollSub(character *p, scroll *s) // interruption of vblank (send information to vram via DMA) void myconsoleVblank() { - - dmaCopyOAram((unsigned char *)&oamMemory, 0, 0x220); - - if (bg_mutex == 0) + // `vblank_flag` is set if the main-loop has finished the frame (not a lag-frame). + // `vblank_flag` is also used to confirm it is safe to read from `bgInfo`. + // (must not write to `vblank_flag` in this function). + if (vblank_flag) { if (bgInfo.refreshBG1 == true) { dmaCopyVram(bgInfo.bg1.gfxoffset, bgInfo.bg1.adrgfxvram, bgInfo.bg1.size); bgInfo.refreshBG1 = false; } - } - if (bg_mutex == 0) - { + if (bgInfo.refreshBG2 == true) { dmaCopyVram(bgInfo.bg2.gfxoffset, bgInfo.bg2.adrgfxvram, bgInfo.bg2.size); @@ -226,7 +221,6 @@ int main(void) bgInitTileSet(2, &BG3_tiles, &BG3_pal, 0, (&BG3_tiles_end - &BG3_tiles), 16 * 4, BG_4COLORS, 0x4000); // queue BG1 and BG2 to be updated in the vblank interruption - bg_mutex = 0; updateBG1(&BG1_map, 0x0000, 2048); updateBG2(&BG2_map, 0x0000 + 2048, 2048); @@ -277,7 +271,6 @@ int main(void) // Wait for nothing :P while (1) { - scanPads(); pad0 = padsCurrent(0); // update character pos updatePos(&player1, pad0); diff --git a/snes-examples/graphics/Backgrounds/Mode1Scroll/Mode1Scroll.c b/snes-examples/graphics/Backgrounds/Mode1Scroll/Mode1Scroll.c index 7be49a56..0719b373 100644 --- a/snes-examples/graphics/Backgrounds/Mode1Scroll/Mode1Scroll.c +++ b/snes-examples/graphics/Backgrounds/Mode1Scroll/Mode1Scroll.c @@ -47,9 +47,6 @@ int main(void) // no move currently move = 0; - // Refresh pad values - scanPads(); - // Get current #0 pad pad0 = padsCurrent(0); diff --git a/snes-examples/graphics/Effects/Fading/Fading.c b/snes-examples/graphics/Effects/Fading/Fading.c index fc615489..d37a6dc6 100644 --- a/snes-examples/graphics/Effects/Fading/Fading.c +++ b/snes-examples/graphics/Effects/Fading/Fading.c @@ -13,6 +13,15 @@ extern char palette, palette_end; extern char map, map_end; //--------------------------------------------------------------------------------- + +// NOTE: Does not pause execution if a pad 0 key is currently pressed. +void WaitForKey() { + while (padsCurrent(0) == 0) + { + WaitForVBlank(); + } +} + int main(void) { // Copy tiles to VRAM @@ -27,8 +36,7 @@ int main(void) bgSetDisable(2); setScreenOn(); - // Wait for a key - while (!padsCurrent(0)); + WaitForKey(); // Now just play with effects :P while (1) @@ -36,22 +44,22 @@ int main(void) // Fade out so light to black setFadeEffectEx(FADE_OUT,12); WaitForVBlank(); - while (!padsCurrent(0)); + WaitForKey(); // Fade in now so black to light setFadeEffectEx(FADE_IN,24); WaitForVBlank(); - while (!padsCurrent(0)); + WaitForKey(); // Fade out so light to black setFadeEffect(FADE_OUT); WaitForVBlank(); - while (!padsCurrent(0)); + WaitForKey(); // Fade in now so black to light setFadeEffect(FADE_IN); WaitForVBlank(); - while (!padsCurrent(0)); + WaitForKey(); } return 0; -} \ No newline at end of file +} diff --git a/snes-examples/graphics/Effects/MosaicShading/MosaicShading.c b/snes-examples/graphics/Effects/MosaicShading/MosaicShading.c index 43b5d07e..faea7de1 100644 --- a/snes-examples/graphics/Effects/MosaicShading/MosaicShading.c +++ b/snes-examples/graphics/Effects/MosaicShading/MosaicShading.c @@ -13,6 +13,15 @@ extern char palette, palette_end; extern char map, map_end; //--------------------------------------------------------------------------------- + +// NOTE: Does not pause execution if a pad 0 key is currently pressed. +void WaitForKey() { + while (padsCurrent(0) == 0) + { + WaitForVBlank(); + } +} + int main(void) { // Copy tiles to VRAM @@ -27,9 +36,7 @@ int main(void) bgSetDisable(2); setScreenOn(); - // Wait for a key - while (!padsCurrent(0)) - ; + WaitForKey(); // Now just play with effects :P while (1) @@ -37,26 +44,22 @@ int main(void) // Fade out so light to black setFadeEffect(FADE_OUT); WaitForVBlank(); - while (!padsCurrent(0)) - ; + WaitForKey(); // Fade in now so black to light setFadeEffect(FADE_IN); WaitForVBlank(); - while (!padsCurrent(0)) - ; + WaitForKey(); // Now do some big pixels setMosaicEffect(MOSAIC_OUT, MOSAIC_BG1); WaitForVBlank(); - while (!padsCurrent(0)) - ; + WaitForKey(); // And now restore screen to normal setMosaicEffect(MOSAIC_IN, MOSAIC_BG1); WaitForVBlank(); - while (!padsCurrent(0)) - ; + WaitForKey(); } return 0; -} \ No newline at end of file +} diff --git a/snes-examples/graphics/Sprites/DynamicSprite/DynamicSprite.c b/snes-examples/graphics/Sprites/DynamicSprite/DynamicSprite.c index 2f3c29c1..f19c09b6 100644 --- a/snes-examples/graphics/Sprites/DynamicSprite/DynamicSprite.c +++ b/snes-examples/graphics/Sprites/DynamicSprite/DynamicSprite.c @@ -27,34 +27,27 @@ spritequeue sprqueue[16]; // Max 16 entries in queue //--------------------------------------------------------------------------------- void myconsoleVblank(void) { - u8 *pgfx; - u16 padrgfx; + if (vblank_flag) { + u8 *pgfx; + u16 padrgfx; - // Refresh pad values - scanPads(); - - // if tile sprite queued - if (spr_queue != 0xff) - { - if (spr_mutex == 0) - { // if we have finished adding things - // copy memory to vram (2 tiles of the 16x16 sprites) - while (spr_queue != 0xff) - { - pgfx = sprqueue[spr_queue].gfxoffset; - padrgfx = sprqueue[spr_queue].adrgfxvram; - dmaCopyVram(pgfx, padrgfx, 8 * 4 * 2); - dmaCopyVram(pgfx + 8 * 4 * 16, padrgfx + 8 * 4 * 8, 8 * 4 * 2); - spr_queue--; + // if tile sprite queued + if (spr_queue != 0xff) + { + if (spr_mutex == 0) + { // if we have finished adding things + // copy memory to vram (2 tiles of the 16x16 sprites) + while (spr_queue != 0xff) + { + pgfx = sprqueue[spr_queue].gfxoffset; + padrgfx = sprqueue[spr_queue].adrgfxvram; + dmaCopyVram(pgfx, padrgfx, 8 * 4 * 2); + dmaCopyVram(pgfx + 8 * 4 * 16, padrgfx + 8 * 4 * 8, 8 * 4 * 2); + spr_queue--; + } } } } - - // Put oam to screen if needed - dmaCopyOAram((unsigned char *)&oamMemory, 0, 0x220); - - // count vbls - snes_vblank_count++; } //--------------------------------------------------------------------------------- @@ -121,4 +114,4 @@ int main(void) WaitForVBlank(); } return 0; -} \ No newline at end of file +} diff --git a/snes-examples/random/random.c b/snes-examples/random/random.c index 6cb86f76..1e2d8f36 100644 --- a/snes-examples/random/random.c +++ b/snes-examples/random/random.c @@ -39,11 +39,11 @@ int main(void) while (1) { - pad0 = padsCurrent(0); consoleDrawText(6, 12, "RANDOM NUMBER=%04x", rand() & 0xFFFF); - while (pad0 == 0) + WaitForVBlank(); + + while (padsCurrent(0) == 0) { - pad0 = padsCurrent(0); WaitForVBlank(); } }