Jump to content
Sheddy

How can POKEY IRQ Timers mess up NMI timing?

Recommended Posts

I've been doing some experimenting, but I'm at a loss to understand what's going on here:

Try the two programs in the attached zip file on a real XL/XE machine. press the joystick fire button!

 

1) s15.xex program. This works as expected. Nice colours down the screen and a raygun sound when firing

2) s64.xex program. Same, except the colours glitch and shift when firing.

 

program 1 syncs an IRQ exactly to a scan line using the ~15KHz clock (using 1.79MHz also works).

program 2 is using the ~64KHz Pokey clock. This doesn't sync exactly to a scan line (2 cycles out every line)

 

But why is program 2 causing such a big problem? The DLI's are NMI's (Non Maskable) so should always run at the correct time regardless of what the IRQ is doing? They are obviously not though. Note that this behaviour doesn't show up on any emulators I've tried, even those that supposedly have cycle exact POKEY emulation. But it happens on my PAL 800XL and 130XE. I thought I was beginning to understand the POKEY timers, but now I'm not sure :(

 

Maybe if an IRQ and NMI happen at exactly the same time the NMI doesn't get done? (I'd always heard to the contrary though)

 

Here's the code. Yes, I know it can be made faster, but it is only an example. Unfortunately I have passed the 64KHz code off as a good way to do sample playback before, but it's not much good if it doesn't work on real hardware.

 

; IRQ sample playback
; NB XL/XE only

ATARI=0
.include "equates.asm"

; variables

*=$f0

sample 		.ds 2
sample_nybble	.ds 1
sample_value	.ds 1
sample_size	.ds 1
table_index	.ds 1
trigger		.ds 1


*=$02e0

.byte <run,>run			; initial run address

; main code

*=$2000

run
lda #0
sta NMIEN			; turn off NMI's
sei
sta IRQEN			; disable IRQ's
lda #128+32+16+2		; bit 7 on - ram at $5000, bits 4&5 on - 130XE compatible
sta PORTB			; bit 1 on - disable os & rom, bit 0 off - disable basic  
lda #<nmi			; setup custom NMI handler
sta $fffa
lda #>nmi
sta $fffa+1
lda #<irq			; setup custom IRQ handler
sta $fffe
lda #>irq
sta $fffe+1
lda #<dlist			; set up a display list
sta DLISTL
lda #>dlist
sta DLISTH
lda #2+32			; enable normal width screen+screen dma
sta DMACTL
lda #128+64
sta NMIEN			; turn on NMI's - DLI's and VBI
cli				; enable IRQ's	
ldx #0
run1	txa
asl a
sta colour_table,x		; create a table of colours
dex
bne run1

; initialize sample play irq

play	jsr init			; initialize the sample to play
wait1	lda TRIG0			; any other stuff can go here, but...
beq wait1			; just wait until fire button is pressed
wait2	lda TRIG0
bne wait2
beq play

; end of main code

init
sei
lda #<sample_start
sta sample
lda #>sample_start
sta sample+1
lda #[>sample_end->sample_start]
sta sample_size			; sample size in 256 byte pages
lda #0 ;1			; 0=POKEY 64KHz, 1=15KHz 
sta AUDCTL			
lda #15	;3			; ~64KHz clock 16 = ~4Khz timer, ~15KHz clock 4 = ~4KHz
sta AUDF1			; in timer 1
lda #$f0			; test - no polycounters + volume only
sta AUDC1
lda #1
sta IRQEN			; enable timer 1
lda #0
sta sample_nybble		; initialize nybble
ldx #$f8
stx sample_value		; an initial sample value (with no polycounters + volume only bit)
lda #$70
wait3	cmp VCOUNT
bne wait3			; sync to a scanline
lda #0
sta SKCTL
lda #3
sta SKCTL			; test - reset pokey and polycounters
sta STIMER			; start timers
cli
rts

; IRQ

irq
pha				; save a
lda sample_value
sta AUDC1			; play sample ASAP to minimise DMA lag
tya
pha				; save y
ldy #0
sty IRQEN			; reset interrupt
lda #1
sta IRQEN			; re-enable only timer 1
eor sample_nybble		; switch between 0 and 1 (right and left nybble)
sta sample_nybble
beq irq1
lda (sample),y
lsr a
lsr a
lsr a
lsr a				; left nybble of sample
bpl irq2			; always branch
irq1	lda (sample),y
and #$0f			; right nybble of sample
inc sample			; next lo byte of sample
bne irq2
inc sample+1			; next hi byte of sample
dec sample_size			; check if at end of sample
bne irq2			; branch if not
tya
sta IRQEN			; end of sample - reset interrupt
beq irq3			; always branch
irq2	ora #$f0			; no polycounters+volume only bit 
irq3	sta sample_value		; save sample to play next irq
pla
tay
pla				; restore y and a
rti

; NMI

nmi
pha
bit NMIST			; check for cause of interrupt
bvs vbi 			; branch if a VBI

; DLI

dli
txa
pha
ldx table_index
lda colour_table,x
sta WSYNC
sta COLBK
inc table_index
pla
tax
pla
rti

; VBI

; critical vbi

vbi
sta NMIRES			; reset interrupt
txa
pha				; save x
tya
pha
lda #0
sta table_index			; reset table index
sta COLBK
;	tsx
;	lda $104,x
;	and #$04
;	bne vbiexit			; exit VBI if VBI interrupted an IRQ
;	cli				; allow IRQ to interrupt from now on

; deferred VBI here

vbiexit	pla
tay
pla
tax
pla				; restore x and a
rti

; display list with some random DLIS

*=$2400

dlist
.byte $70,$70,$70
.byte $8D+$40,<screen_start,>screen_start
.byte $8D,$D,$8D,$8D,$8D,$8D,$8D,$8D,$8D,$8D
.byte $8D,$8D,$8D,$8D,$8D,$8D,$8D,$8D,$8D,$8D
.byte $8D,$D,$D,$8D,$8D,$D,$8D,$8D,$8D,$8D
.byte $8D,$D,$8D,$8D,$8D,$8D,$8D,$8D,$8D,$8D
.byte $8D,$D,$8D,$8D,$8D,$8D,$D,$8D,$8D,$8D
.byte $8D,$D,$8D,$D,$8D,$8D,$D,$8D,$8D,$8D
.byte $8D,$D,$8D,$8D,$8D,$D,$8D,$8D,$8D,$8D
.byte $8D,$D,$8D,$8D,$D,$8D,$8D,$8D,$8D,$8D
.byte $8D,$D,$8D,$D,$8D,$8D,$8D,$8D,$8D,$8D
.byte $8D,$D,$D,$D,$D
.byte $41,<dlist,>dlist

; sample data

*=$2800

; NB start on a 256 byte page boundary
; sample will stop playing at nearest page boundary if not a multiple of 256 bytes

sample_start
.incbin "raygun44.raw"		; 4KHz, 4-bit raw sample data
sample_end

; screen data

*=$8000

screen_start

; colour table

*=$9000

colour_table

samples.zip

Share this post


Link to post
Share on other sites

In another thread, atariksi claimed you could mask System Reset (NMI) on 400/800 - I thought it was impossible without resorting to crashing the CPU but it isn't.

 

I think it comes down to a couple of things:

- Antic only holds the NMI line at (low) activate level for a certain period. If you can distract the CPU long enough, it can be missed.

- IIRC, the 6502 itself has a "bug" such that if an NMI and IRQ occur at the same time, the NMI can actually be ignored.

 

IRQs are level triggered, ie IRQ sources tend to hold the IRQ line low until the CPU services the interrupt and takes action to disable it.

NMIs are edge triggered, ie they rely on the CPU acting upon a high->low transition of the NMI input. For example, on the C64 there's the old trick of masking out the RESTORE key. Normally it's not maskable in any way, but it can be disabled by triggering a Timer NMI, and having a custom routine that does an RTI without resetting the NMI status on the CIA.

 

I haven't tried your program, will see if I can give it a go later. For your scanline exact IRQ, it might be desirable to set it up to occur at a different time so that it doesn't clash with DLIs.

Share this post


Link to post
Share on other sites

Is there any chance that the IRQ service is introducing a longer delay than the usual instructions that are running? Here's the internals of an IRQ request:

 

figure_1.gif

 

A sequential IRQ/DLI can take up to 13 cycles to get to the first instruction of the DLI. Is that more latency than your code is designed to deal with?

Share this post


Link to post
Share on other sites

lda #$70
wait3 cmp VCOUNT
bne wait3; sync to a scanline
lda #0
sta SKCTL
lda #3
sta SKCTL; test - reset pokey and polycounters
sta STIMER

 

That's not an exact align there for the timer, it could drift a couple of cycles either way.

 

I'd change it a bit. Put the reset SKCTL and poly stuff before the wait loop. Do a STA WSYNC immediately before the STA STIMER. That should make the IRQ occur earlier in the scanline, might be enough to stop if interfering with the NMIs.

 

If that don't work, I'd try different delay loops after the WSYNC until you hit the sweet spot.

Share this post


Link to post
Share on other sites
...

sta PORTB ; bit 1 on - disable os & rom, bit 0 off - disable basic

...

Your "rem" statement is wrong. That may explain the problem.

 

>program 1 syncs an IRQ exactly to a scan line using the ~15KHz clock (using 1.79MHz also works).

>program 2 is using the ~64KHz Pokey clock. This doesn't sync exactly to a scan line (2 cycles out every line)

 

64Khz should be out of sync since clocks are as follows:

 

1.789773 is color burst/2.

15.7Khz is exactly 1.789773/114.

64Khz is exactly 1.789773/28 (they would have to divide by 28.5 to get it divide evenly into scanline cycles).

 

>But why is program 2 causing such a big problem? The DLI's are NMI's (Non Maskable) so should always run at the correct time regardless of what the IRQ is doing? They are obviously not though. Note that this behaviour doesn't show up on any emulators I've tried, even those that supposedly have cycle exact POKEY emulation. But it happens on my PAL 800XL and 130XE. I thought I was beginning to understand the POKEY timers, but now I'm not sure :(

 

Actually, even at 1.79Mhz it should NOT work (cause glitches). You may just have tried a case where it was phase shifted enough to not cause conflict.

Share this post


Link to post
Share on other sites
lda #$70
wait3 cmp VCOUNT
bne wait3; sync to a scanline
lda #0
sta SKCTL
lda #3
sta SKCTL; test - reset pokey and polycounters
sta STIMER

 

That's not an exact align there for the timer, it could drift a couple of cycles either way.

 

I'd change it a bit. Put the reset SKCTL and poly stuff before the wait loop. Do a STA WSYNC immediately before the STA STIMER. That should make the IRQ occur earlier in the scanline, might be enough to stop if interfering with the NMIs.

 

If that don't work, I'd try different delay loops after the WSYNC until you hit the sweet spot.

 

Sta STIMER will reset timers to zero for IRQ; what is initializing SKCTL accomplishing? True, he should base things on WSYNC so he knows the EXACT cycle when things start but his problem is the 64Khz mode since that's misaligned with scanline rate anyway. He needs to use some divisor (prime # or something) so that DLI triggerring and IRQ triggerring cycles never overlap assuming that's the entire issue here.

Share this post


Link to post
Share on other sites

I wouldn't worry about SKCTL... maybe he just wants a known sequence of Random numbers for whatever reason.

 

I'm going to have to play around with some of this stuff... although I suspect our answer lies in how the 6502 does Interrupt processing, I'm sure there's a known issue where it can ignore an NMI under certain circumstances.

Share this post


Link to post
Share on other sites
lda #$70
wait3 cmp VCOUNT
bne wait3; sync to a scanline
lda #0
sta SKCTL
lda #3
sta SKCTL; test - reset pokey and polycounters
sta STIMER

 

That's not an exact align there for the timer, it could drift a couple of cycles either way.

 

I'd change it a bit. Put the reset SKCTL and poly stuff before the wait loop. Do a STA WSYNC immediately before the STA STIMER. That should make the IRQ occur earlier in the scanline, might be enough to stop if interfering with the NMIs.

 

If that don't work, I'd try different delay loops after the WSYNC until you hit the sweet spot.

 

Sta STIMER will reset timers to zero for IRQ; what is initializing SKCTL accomplishing? True, he should base things on WSYNC so he knows the EXACT cycle when things start but his problem is the 64Khz mode since that's misaligned with scanline rate anyway. He needs to use some divisor (prime # or something) so that DLI triggerring and IRQ triggerring cycles never overlap assuming that's the entire issue here.

 

yes, sorry, starting timers with the 64KHz clock at a certain point in a scanline is a red herring to the main concern. It should drift 2 cycles every scan line. The code is only in there because it was enough to help avoid the NMI's with the 15KHz and I just left it in there when changing the code to use the 64KHz clock.

 

the reset SKCTL was just a test to see if polycounters made any difference to timers (if they do it is subtle)

 

The main concern is why IRQ's can affect NMI's so badly? Keeping IRQ's from triggering at/near NMI's avoids the problem, but doesn't explain the problem. I don't think the IRQ latency can be it if the 15KHz works. If the NMI's aren't actually being ignored, maybe they are being delayed by the IRQ if they trigger at the same time? I'm still tending towards thinking Rybags is correct and this is a 6502C "feature". It is a shame if that is the case, as even with it getting interrupted by NMI's, sample playing with 64KHz clock sounds pretty good on real hardware, and only needs 1 channel.

 

Why do you think the CPU based POKEY clock shouldn't work Atariksi? It can trigger at a specific point in the scan line as per the 15KHz clock. Or do you just mean the lax timing when starting the timer (no WSYNC etc.)?

Edited by Sheddy

Share this post


Link to post
Share on other sites

Just tried a couple of things, kinda distantly related.

 

NMIST ($D40F) will reflect a requested NMI whether that NMI is enabled or not, e.g. you can setup a DList with DLIs in it and it will still be triggered within Antic and reflected in NMIST - of course, the 6502 isn't signalled.

This is already known... technique has been described before "DLIs in polling mode". In polling mode, we typically store to NMIRES to clear the pending DLI.

 

Despite NMIST reflecting a requested DLI, by enabling DLIs in NMIEN, the pending DLI will not trigger an NMI to the 6502... (ed) tested this just then, even a tight loop waiting for NMIST to trigger a DLI, then re-enabling DLIs won't trigger it.

 

Despite NMIST holding a pending DLI, it seems a VBlank NMI resets that bit, ie - the OS won't get confused and JMP ($0200) instead of processing the VBI.

Edited by Rybags

Share this post


Link to post
Share on other sites

I wonder what it would do on an 800 without a 6502C. I see that you've taken over the FFFx vectors, though.

Edited by Bryan

Share this post


Link to post
Share on other sites
I wonder what it would do on an 800 without a 6502C (if the PORTB stuff was removed).

 

yes, that would be interesting. I did try the code with the OS and it has the same problem. I'll post it so someone can give it a go.

Share this post


Link to post
Share on other sites

I'd get my 'scope onto the NMI line and see what's going on internally, but I've virtually trashed one machine in the last 24 hours... don't want to risk another just yet.

Share this post


Link to post
Share on other sites

I took out the sta WSYNC to see where the DLI was being serviced. It's about 1/3 of the way across (well, that's where the COLBK change is)- unless you're playing sound- then it scoots around.

Edited by Bryan

Share this post


Link to post
Share on other sites
lda #$70
wait3 cmp VCOUNT
bne wait3; sync to a scanline
lda #0
sta SKCTL
lda #3
sta SKCTL; test - reset pokey and polycounters
sta STIMER

 

That's not an exact align there for the timer, it could drift a couple of cycles either way.

 

I'd change it a bit. Put the reset SKCTL and poly stuff before the wait loop. Do a STA WSYNC immediately before the STA STIMER. That should make the IRQ occur earlier in the scanline, might be enough to stop if interfering with the NMIs.

 

If that don't work, I'd try different delay loops after the WSYNC until you hit the sweet spot.

 

Sta STIMER will reset timers to zero for IRQ; what is initializing SKCTL accomplishing? True, he should base things on WSYNC so he knows the EXACT cycle when things start but his problem is the 64Khz mode since that's misaligned with scanline rate anyway. He needs to use some divisor (prime # or something) so that DLI triggerring and IRQ triggerring cycles never overlap assuming that's the entire issue here.

 

yes, sorry, starting timers with the 64KHz clock at a certain point in a scanline is a red herring to the main concern. It should drift 2 cycles every scan line. The code is only in there because it was enough to help avoid the NMI's with the 15KHz and I just left it in there when changing the code to use the 64KHz clock.

 

the reset SKCTL was just a test to see if polycounters made any difference to timers (if they do it is subtle)

 

The main concern is why IRQ's can affect NMI's so badly? Keeping IRQ's from triggering at/near NMI's avoids the problem, but doesn't explain the problem. I don't think the IRQ latency can be it if the 15KHz works. If the NMI's aren't actually being ignored, maybe they are being delayed by the IRQ if they trigger at the same time? I'm still tending towards thinking Rybags is correct and this is a 6502C "feature". It is a shame if that is the case, as even with it getting interrupted by NMI's, sample playing with 64KHz clock sounds pretty good on real hardware, and only needs 1 channel.

 

Why do you think the CPU based POKEY clock shouldn't work Atariksi? It can trigger at a specific point in the scan line as per the 15KHz clock. Or do you just mean the lax timing when starting the timer (no WSYNC etc.)?

 

The CPU based clock (1.79Mhz) works as long as it's phase shifted away from the DLI. The 15Khz cannot be phase shifted from the tests that I did. Even if you delay STIMER, the 15Khz interrupt seems to occur at exact same spot on the scanline. Enclosed below is the IRQ-based code that locks out the System Reset NMI on Atari 400/800-- but it doesn't work if I use 15Khz timer regardless of phase shift. By the way, I see you are optimizing code by writing directly to ROM vector area, but then you mine as well do STA ZP1 and STX ZP2 rather than to PHA, TXA/PHA and reverse for exiting from DLI.

 

;*** ATIRQ400.asm: Atari Timer IRQ measured to exact cycles so no WSYNC is required by Krishna Software Inc.

;*** This plays music on voice #1 and uses IRQ timer interrupt on timer #4. Voice #0 is for constant tone.

;*** There are three CLKs which can be used by either 8 or 16 bit divisors as controlled by 53768.

;*** The 15.6999Khz CLK and 63.921Khz CLK can be turned off via 53775 but 1.78979Mhz CLK is always on.

;*** The 15.6999Khz CLK is 1.78979Mhz/114 and gives the scanline interrupt at end of scanline.

;*** The 63.921Khz CLK is 1.78979Mhz/28 and does not divide evenly into 29868 cycles/frame so cannot be used

;*** for color clock accurate or scanline accurate interrupts. This program also attempts to write IRQ vector

;*** directly at 65534/65535 by disabling ROM for XL/XE series (should have no effect on Atari 400/800).

;*** This program locks out SYSTEM RESET on Atari 400/800 where it's a NMI.

TIMERFREQLSB = 53760

TIMERFREQMSB = 53762

WSYNC = 54282

VCOUNT = 54283

 

DOSVEC = 10

CASINI = 2

WARMSTART = 58484

VMIRQ = 534 ;hardware irq ptr

 

ORG = 600h

;DW 0FFFFh

;DW StartAdr

;DW LastOffset-1

DB 0,3 ;# of sectors to load 1..255

DW ORG

DW StartAdr

Rts

StartAdr: Lda #MyReset,L

Sta CASINI

Lda #MyReset,H

Sta CASINI+1

Lda #0

Sta 580

Lda #2

Sta 9

Jmp WARMSTART

MyReset: Lda #2

Sta 9

Lda #MyReset,L

Sta CASINI

Lda #MyReset,H

Sta CASINI+1

Sei

Lda #0 ;no VBIs nor DLIs for maximum performance

Sta 54286

Sta 53774 ;disable all IRQs

Sta 54272 ;turn off screen

Sta 54017 ;disable ROM and BASIC for XL/XE series via PORTB

Lda #TimerTwoIRQ,L ;general IRQ routine but we use only for timer #2

Sta VMIRQ

Ldy #TimerTwoIRQ,H

Sty VMIRQ+1

Sta 65534

Sty 65535

Cpy 65535

Beq ROMVectorSet

Lda #IdleLoop,L

Sta SelfModifyThis+1 ;Remove 5 cycles from background task (for Jmp [534])

Lda #IdleLoop,H

Sta SelfModifyThis+2

ROMVectorSet: Lda #40 ;+40 for join channels 3,4; +80 for channels 1+2 @1.79Mhz

Sta 53768 ;join channels at 1.79 Mhz

Lda #160

Sta 53760

Lda #143

Sta 53761

Lda #175

Sta 53763

Lda #114-7 ;lsb 114-7 or 57-7 for two irqs/scanline

Sta 53764 ;timer #2 freq = 1789790/[A+1]

Lda #0 ;msb for rate divisor A

Sta 53766

Lda #4 ;1,2,4=timer interrupts

Sta 53774 ;enable IRQ #2

Lda #128

;Sta 53248 ;unrem to show sprite using 0 CPU cycles and 0 DMA cycles

Lda #144

;Sta 53249

Lda #255

Sta 53261

Sta 53262

Sta 53256

Sta 53257

Lda #32

Sta 53275

Lda #10

Sta 53266

Lda #12

Sta 53267

Ldx #0

Ldy #4 ;load ack parameters

Lda #65

NotMidScr: Cmp VCOUNT

Bne NotMidScr ;CF=1 when A=65

Sta WSYNC

Nop

;Nop

CLI

Sta 53769 ;start timer counter

;CLI

;Nop

;Nop

;Nop

;Nop

;Lda #34

;Sta 54272

IdleLoop: ;(29868 cycles in NTSC frame - 9*262 for Refresh = 27510 cycles so to keep the IRQ

; stable, cycles must be aligned to 27510 including IRQ routine). For PAL frame, use

; 312*114 = 35568 - 312*9 = 32760 cycles.)

;(27510 factors to 2*5*7*3*131)

 

Lda #0

Sta 53274

Lda 1535

;Lda 53770 ;No longer random if bits 0,1 of 53775 = 0

ROL

Sta 53274

;Rol

Sta 53762 ;make construction site noise

Lda #48

Sta 53274

Lda #64 ;which of these two instruction to rem out depends on IRQ routine total cycles

;Sta 53274

Lda #96

Sta 53274

Lda #128

Sta 53274

Lda #144

Sta 53274

Lda #160

Sta 53274

Lda #176

Sta 53274

SelfModifyThis: Jmp Idle1 ;For Atari 400/800, this jmp and following NOP (5 cycles) will be removed since ROM vector not used

Idle1: Nop

Jmp IdleLoop

 

;*** H/W does jmp [65534] (7 cycles) and XL/XE OS does a CLD and JMP [534] for 2+5 = 7 cycles.

;*** Below routine 32 cycles+7+5 = 44 cycles on old OS (like on Atari 400/800).

;*** Optimized by using X/Y registers and not using it in main routine so PHA/PLA not needed.

TimerTwoIRQ: ;Pha ;3 cycles

;Lda #255

;Sta 53771 ;POTGO

;Nop

Inc 53274 ;change register (like color for example)

;Lda #0

;Sta 53774

;Lda #4

;Sta 53774 ;send ack to timer irq

Stx 53774

Sty 53774 ;do ack

Inc 1535

;Nop

;Lda #96

Ror 53274 ;change register (like color for example)

;Pla ;4 cycles

Rti ;6 cycles

 

;LastOffset: DW 2e0h,2e1h,ORG

Share this post


Link to post
Share on other sites
I'd get my 'scope onto the NMI line and see what's going on internally, but I've virtually trashed one machine in the last 24 hours... don't want to risk another just yet.

 

ha, ha! sounds like you've been having fun. what are you up to now? (the interlace discovery is very cool BTW)

 

I took out the sta WSYNC to see where the DLI was being serviced. It's about 1/3 of the way across (well, that's where the COLBK change is)- unless you're playing sound- then it scoots around.

 

interesting. no way should the sound affect when the DLI is serviced that much

 

here's the code using the OS, which should also work on an 800. Programs in the zip as before. If anyone can try and see if the colours glitch and wobble with s64.xex on a real 800 it'd be much appreciated.

 

ATARI=0
.include "equates.asm"

; variables

*=$f0

sample 		.ds 2
sample_nybble	.ds 1
sample_value	.ds 1
sample_size	.ds 1
table_index	.ds 1


*=$02e0

.byte <run,>run			; initial run address

; main code

*=$2000

run
sei
lda #0
sta NMIEN			; turn off NMI's
sta POKMSK
sta IRQEN			; disable IRQ's
lda #<irq			; use immediate IRQ vector (no other OS IRQ's will happen)
sta VIMIRQ
lda #>irq
sta VIMIRQ+1
lda #<dlist			; set up a display list
sta SDLSTL
lda #>dlist
sta SDLSTH
lda #<vbi			; set up an immediate VBI
sta VVBLKI
lda #>vbi
sta VVBLKI+1
lda #<dli			; set up a DLI
sta VDSLST
lda #>dli
sta VDSLST+1
lda #128+64			; turn on NMI's - DLI's and VBI
sta NMIEN
cli				; enable IRQ's	
ldx #0
run1	txa
asl a
sta colour_table,x		; create a table of colours
dex
bne run1

; initialize sample play irq

play	jsr init			; initialize the sample to play
wait1	lda TRIG0			; any other stuff can go here, but...
beq wait1			; just wait until fire button is pressed
wait2	lda TRIG0			; 
bne wait2
beq play

; end of main code

init
lda #<sample_start
sta sample
lda #>sample_start
sta sample+1
lda #[>sample_end->sample_start]
sta sample_size			; sample size in 256 byte pages
lda #0				; ~64KHz POKEY clock
;	lda #1				; ~15KHz POKEY clock	
sta AUDCTL
lda #15				; ~64KHz clock divided by 16 = ~4Khz
;	lda #3				; ~15KHz clock divided by 4 = ~4KHz
sta AUDF1			; in timer 1
lda #0
sta sample_nybble		; initialize nybble
sta AUDC1
lda #1
sta IRQEN			; enable timer 1
sta POKMSK
ldx #$18
stx sample_value		; an initial sample value (with volume only bit)
lda #$70
wait3	cmp VCOUNT
bne wait3			; sync to a scanline (only relevant when 15KHz)
stx STIMER			; start timers
rts

; IRQ

irq
pha				; save a
lda sample_value
sta AUDC1			; play sample ASAP to minimise DMA lag
tya
pha				; save y
ldy #0
lda #1
sty IRQEN			; reset interrupt
sta IRQEN			; re-enable only timer 1
eor sample_nybble		; switch between 0 and 1 (right and left nybble)
sta sample_nybble
beq irq1
lda (sample),y
lsr a
lsr a
lsr a
lsr a				; left nybble of sample
bpl irq2			; always branch
irq1	lda (sample),y
and #$0f			; right nybble of sample
inc sample			; next lo byte of sample
bne irq2
inc sample+1			; next hi byte of sample
dec sample_size			; check if at end of sample
bne irq2			; branch if not
tya
sta IRQEN			; end of sample - reset interrupt
sta POKMSK
beq irq3			; always branch
irq2	ora #$10			; turn on volume only bit
irq3	sta sample_value		; save sample to play next irq
pla
tay
pla				; restore y and a
rti

; DLI

dli
pha
txa
pha
ldx table_index
lda colour_table,x
sta WSYNC
sta COLBK
inc table_index
pla
tax
pla
rti

; VBI

vbi
lda #0
sta table_index			; reset table index
sta COLBK
jmp SYSVBV			; 58463 - do system vertical blank processes

; display list

*=$2400

dlist
.byte $70,$70,$70
.byte $8D+$40,<screen_start,>screen_start
.byte $8D,$D,$8D,$8D,$8D,$8D,$8D,$8D,$8D,$8D
.byte $8D,$8D,$8D,$8D,$8D,$8D,$8D,$8D,$8D,$8D
.byte $8D,$D,$D,$8D,$8D,$D,$8D,$8D,$8D,$8D
.byte $8D,$D,$8D,$8D,$8D,$8D,$8D,$8D,$8D,$8D
.byte $8D,$D,$8D,$8D,$8D,$8D,$D,$8D,$8D,$8D
.byte $8D,$D,$8D,$D,$8D,$8D,$D,$8D,$8D,$8D
.byte $8D,$D,$8D,$8D,$8D,$D,$8D,$8D,$8D,$8D
.byte $8D,$D,$8D,$8D,$D,$8D,$8D,$8D,$8D,$8D
.byte $8D,$D,$8D,$D,$8D,$8D,$8D,$8D,$8D,$8D
.byte $8D,$D,$D,$D,$D
.byte $41,<dlist,>dlist

; sample data

*=$2800

; NB start on a 256 byte page boundary
; sample will stop playing at nearest page boundary if not a multiple of 256 bytes

sample_start
.incbin "raygun44.raw"		; 4KHz, 4-bit raw sample data
sample_end

; screen data

*=$8000

screen_start

; colour table

*=$9000

colour_table

samplesOS.zip

Share this post


Link to post
Share on other sites
The CPU based clock (1.79Mhz) works as long as it's phase shifted away from the DLI. The 15Khz cannot be phase shifted from the tests that I did. Even if you delay STIMER, the 15Khz interrupt seems to occur at exact same spot on the scanline. Enclosed below is the IRQ-based code that locks out the System Reset NMI on Atari 400/800-- but it doesn't work if I use 15Khz timer regardless of phase shift. By the way, I see you are optimizing code by writing directly to ROM vector area, but then you mine as well do STA ZP1 and STX ZP2 rather than to PHA, TXA/PHA and reverse for exiting from DLI.

 

OK, I see what you mean now. (As an aside, according to Altirra emu author Avery Lee, when POKEY is initialized should relate to where that spot on the scanline is for 15KHz as there is nothing else that particularly syncs it)

I don't understand how your code disables reset NMI yet, but it could well be related to the glitches I'm seeing.

Yes, no hardcore optimizes have been done! purely example code.

Edited by Sheddy

Share this post


Link to post
Share on other sites

I've done something and I've made the DLIs miss... I think even VBI gets clobbered at times.

 

The 16 KHz Timing should be able to be aligned to any cycle... I don't see why not. Pokey has no idea whatsoever what HCOUNT is, only Antic and GTIA bother to keep track.

 

Remember too, emulators don't do Timers properly. Don't trust them for this, or any hardcore stuff for that matter.

 

Another possibility though... could it be the case that it's important during what part of the scanline you actually switch AUDCTL to the 16 KHz mode, irrespective of what you do with STIMER down the track?

Share this post


Link to post
Share on other sites

If NMI's are vulnerable, then wouldn't disk loading cause missed DLI's and VBI's? Do we have an example of a stable DLI-filled screen during loads?

Share this post


Link to post
Share on other sites

atariksi: what Assembler, debugger, monitor were you testing with?

 

I'm loading stuff up here in Atari Asm/Ed...

 

I modified my own stuff, and came to the conclusion your theory was right.

But... try something like this:

 

  lda $d20a
 eor seed; seed = just some number you choose
 tax
 eor #$7f
 tay
 lda #0
 sta $d20f; put POKEY in INIT state
wait1  dey
 bne wait1
 lda #3
 sta $d20f; POKEY back to normal
 txa
 eor #$44
 tay
wait2
 dey
 bne wait2
wait3
 dex
 bne wait3
 lda #1
 sta $d208; voices in 16 KHz mode
 sta $d209; STIMER... don't really need it.

 

OK, there's probably some stuff in there not needed, but I'm able to randomize where the IRQs occur during a scanline with something like this.

Share this post


Link to post
Share on other sites
The CPU based clock (1.79Mhz) works as long as it's phase shifted away from the DLI. The 15Khz cannot be phase shifted from the tests that I did. Even if you delay STIMER, the 15Khz interrupt seems to occur at exact same spot on the scanline. Enclosed below is the IRQ-based code that locks out the System Reset NMI on Atari 400/800-- but it doesn't work if I use 15Khz timer regardless of phase shift. By the way, I see you are optimizing code by writing directly to ROM vector area, but then you mine as well do STA ZP1 and STX ZP2 rather than to PHA, TXA/PHA and reverse for exiting from DLI.

 

OK, I see what you mean now. (As an aside, according to Altirra emu author Avery Lee, when POKEY is initialized should relate to where that spot on the scanline is for 15KHz as there is nothing else that particularly syncs it)

I don't understand how your code disables reset NMI yet, but it could well be related to the glitches I'm seeing.

Yes, no hardcore optimizes have been done! purely example code.

 

On the slower clocks when you hit STIMER doesn't matter much - it doesn't reset the base clock. The timer will count down only when the base clock changes. I didn't understand this until recently.

Share this post


Link to post
Share on other sites

DLIs aren't used during disk loads all that much... they can tend to upset SIO. Only XL/XE OS allows them too.

 

Missed VBIs aren't critical, although of course if there's DLIs running that rely on counters etc being reset, then you might get problems.

 

I think with the method I've employed, we should be able to work out where our 16 KHz IRQs occur... that way we could avoid conflict.

Of course, we can scrap all this random/seed loop stuff - I think it's probably a case of putting POKEY into 64 Khz mode, wait a little bit, then go into INIT state, do a known wait to a blank scanline then wait a little more. Then put Pokey into normal operation, finally switch to 15 KHz mode.

Share this post


Link to post
Share on other sites
here's the code using the OS, which should also work on an 800. Programs in the zip as before. If anyone can try and see if the colours glitch and wobble with s64.xex on a real 800 it'd be much appreciated.

 

EDIT:

 

Okay, ran it on my 800 (Standard Rockwell R6502) and the glitches still happen.

Edited by Bryan

Share this post


Link to post
Share on other sites

I think I've got the whole situation worked out...

 

Since the OS does it's clear chipset register routine at cold/warmstart, we'd likely have a situation where Pokey always gets initialized and put back into normal mode "in sync" with the same spot on the scanline.

 

If you go through the OS, you see a loop similar to:

 

LDA #0

TAX

loop: sta $d000,x

sta $d200,x

sta $d400,x

inx

bne loop

 

etc.

 

During the loop of course, we're hitting $D40A. Further into the warmstart, the OS puts Pokey back into normal mode. I would suspect that in possibly 100% of cases, that always occurs at the same place in a scanline. It might be the case though, that with different revisions of the OS that the alignment would be slightly different.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...