Alright, at the risk of beating a dead horse, I'm going to announce what I believe to be the real condition, based on a lot of testing with various delays, mode lines, and interrupting methods:
>> The last cycle of the seven-cycle IRQ/BRK sequence must land on exactly cycle 10 to block an NMI. <<
The following sequence should accomplish this once, regardless of any DMA (assuming no intervening interrupts and no display list or P/M DMA on the scan line):STA WSYNC ;clear existing DMA cyclesSTA WSYNC ;sync to cycle 105PHA ;*, 105, 106PLA ;107, 108, 109, 110PHA ;111, 112, 113PLA ;0, 1, 2, 3BRK ;4, 5, 6, 7, 8, 9, 10
- You must ensure that the starting cycle for your delay is known exactly. STA WSYNC in uncontrolled conditions is not enough as you still have three cycles of uncertainty: a possible DMA cycle immediately after STA WSYNC, a possible playfield DMA cycle at 105, and a possible refresh DMA cycle at 106.
- For each cycle of DMA in cycles 0-8 (missile, display list, players, display list LMS) you must initiate the delay or interrupt sequence one cycle earlier for it to hit at the right time.
- Playfield DMA cycles starting at cycle 12 don't matter.
- The instruction pattern at the beginning of the IRQ routine doesn't matter.
- If you are initiating an immediate IRQ by CLI with the IRQ line active, the 6502 will execute one more instruction before initiating the IRQ sequence.
- If you are initiating an immediate IRQ by writing IRQEN with a POKEY interrupt active, the 6502 will execute two more instructions before initiating the IRQ sequence, at least if the next instruction is short (two cycles).
- If you are strobing STIMER to sync a 16-bit, 1.79MHz timer (atariksi's example), the write needs to happen 7 cycles in advance of the desired interrupt sequence start. This implies that all timers roll over immediately after STIMER instead of waiting one period.
What all of this also implies is that you cannot reliably avoid this problem with a 64KHz clock. The reason is that, while you can align the clock itself to fire the IRQ on an odd cycle, you're still screwed if instruction completion delays the 6502's interrupt response so that it lands on an even boundary. Therefore, either the 15KHz or the 1.79MHz clocks are the way to go for interference-free POKEY timers. (Which, embarrassingly, is the way the OP was doing it before I sent him off on this particular tangent by suggesting the 64KHz clock. Oops.)
There is one pertinent case that I haven't tested, which is if playfield DMA is occurring on cycle 10. This can only occur on a mode 2-7 line with wide playfield fetch that has been shortened to one scan line. It's difficult to test this case, though.
How do you figure 10 cycles on my example: NOP, CLI, STA STIMER.
Also, at 1.79Mhz if the divisor is 8-bits, the formula is 1789790/(A+4) instead of 1789790(A+7). Here's modified version using just 8-bit divisor-- this also locks up the system reset on 400/800:
;*** ATIRQ400.asm: Atari Timer IRQ measured to exact cycles so no WSYNC is required.
;*** 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 divisor
;*** is 114-7 for 16-bit counter and 114-4 for 8-bit counter.
;*** 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. Reset key can still be
;*** detected by location 54287; just the reset vector will not be called. In this program, the pink bar will turn
;*** blue when reset is pressed (switching value from 95 -> 127).
TIMERFREQLSB = 53760
TIMERFREQMSB = 53762
WSYNC = 54282
VCOUNT = 54283
DOSVEC = 10
CASINI = 2
WARMSTART = 58484
VMIRQ = 534 ;hardware irq ptr
ORG = 600h
DB 0,3 ;# of sectors to load 1..255
StartAdr: Lda #MyReset,L
MyReset: Lda #2
Lda #0 ;no VBIs nor DLIs for maximum performance
Sta 53774 ;disable all IRQs
Sta 54272 ;turn off screen
;Sta 53775 ;turn off serial/KB/15&63Khz CLK /1.79Mhz CLK is always on
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 SelfModifyThis+1 ;Remove 5 cycles from background task (for Jmp )
ROMVectorSet: Lda #64 ;Bit 6 = channel 1 @1.79Mhz, bit 5 = channel 3 @1.79Mhz
Sta 53768 ;bit 4 = channel 1+2 to 16-bit divisor, bit 3 = channel 3+4 to 16-bit
Lda #114-4 ;lsb 114-7 or 57-7 for two irqs/scanline
Sta 53760 ;timer freq = 1789790/[A+7] or [A+4]
Lda #1 ;1,2,4=timer interrupts
Sta 53774 ;enable IRQ #2
;Sta 53248 ;unrem to show sprite using 0 CPU cycles and 0 DMA cycles
;Stx 53775 ;resetting SKCTL does not have any effect on syslock
Ldy #1 ;load ack parameters
NotMidScr: Cmp VCOUNT
Bne NotMidScr ;CF=1 when A=65
;Nop ;make no difference on syslock
Nop ;doing 3 cycles or less doesn't lock system reset
Sta 53769 ;start timer counter
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 53770 ;No longer random if bits 0,1 of 53775 = 0
Sta 53762 ;make construction site noise
Lda 54287 ;reads 95 when no reset pressed and 127 when reset pressed
;Lda #64 ;which of these two instruction to rem out depends on IRQ routine total cycles
SelfModifyThis: Jmp Idle1 ;For Atari 400/800, this jmp and following NOP (5 cycles) will be removed since ROM vector not used
;*** H/W does jmp  (7 cycles) and XL/XE OS does a CLD and JMP  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
;Sta 53771 ;POTGO
Inc 53274 ;change register (like color for example)
;Sta 53774 ;send ack to timer irq (optimized with Inc = 6 cycles)
;Inc 53774 ;Rol/Asl/Inc (6 cycles) for Ack using Timer #4 (only Inc for Timer #2)
;nop ;unpublished lines (Inc and Nop)-- used stx/sty instead-- replace
; inc with rol and reset causes temporary disturbance before resuming normal screen
Sty 53774 ;do ack
Ror 53274 ;change register (like color for example)
;Pla ;4 cycles
Rti ;6 cycles
;LastOffset: DW 2e0h,2e1h,ORG