Overview
Synchronise your game to the display refresh by polling the vertical position register. When VPOS reaches zero, you’re at the start of a new frame. This provides consistent 50Hz timing on PAL Amigas (60Hz on NTSC) without using interrupts.
Code
; =============================================================================
; VBLANK GAME LOOP - AMIGA
; Poll-based vertical blank synchronisation
; Taught: Game 1 (Signal), Unit 2
; CPU: Variable | Memory: ~40 bytes
; =============================================================================
CUSTOM equ $dff000
VPOSR equ $004 ; Vertical position register (offset)
section code,code_c
start:
lea CUSTOM,a5 ; Custom chip base in A5
; --- System takeover ---
move.w #$7fff,INTENA(a5) ; Disable all interrupts
move.w #$7fff,INTREQ(a5) ; Clear pending interrupts
move.w #$7fff,DMACON(a5) ; Disable all DMA
; --- Your setup code here ---
bsr init_game
; --- Install copper list ---
lea copperlist,a0
move.l a0,COP1LC(a5)
move.w d0,COPJMP1(a5)
; --- Enable DMA ---
move.w #$83a0,DMACON(a5) ; Master + copper + sprites + bitplanes
; === MAIN LOOP ===
mainloop:
bsr.s wait_vblank
bsr read_input
bsr update_game
bsr update_graphics
bra.s mainloop
; Wait for vertical blank (VPOS = 0)
wait_vblank:
move.l #$1ff00,d1 ; Mask for vertical position
.wait:
move.l VPOSR(a5),d0 ; Read vertical position
and.l d1,d0 ; Isolate VPOS bits
bne.s .wait ; Loop until VPOS = 0
rts
; Placeholder routines
init_game:
rts
read_input:
rts
update_game:
rts
update_graphics:
rts
Complete example with exit handling:
mainloop:
bsr.s wait_vblank
bsr read_joystick
bsr move_player
bsr update_sprite
; Check left mouse button for exit
btst #6,$bfe001
bne.s mainloop ; Loop if not pressed
; Exit - restore system and return to CLI
bra cleanup
Trade-offs
| Aspect | Cost |
|---|---|
| CPU | Busy-waits during VBlank wait |
| Memory | ~20 bytes for wait routine |
| Limitation | Wastes CPU cycles while waiting |
When to use: Simple games, demos, when you don’t need background processing.
When to avoid: Complex games that need to use wait time productively. Use interrupts instead.
How It Works
- The VPOSR register contains the current vertical beam position
- Bits 8-16 hold the vertical line number (0-311 on PAL)
- When VPOS = 0, the beam is at the top of the screen (VBlank)
- Poll until VPOS reaches 0, then run your frame logic
Frame Budget
On a PAL Amiga at 50Hz:
- 20ms per frame
- At 7.09MHz (68000), approximately 140,000 cycles per frame
- VBlank itself is ~25 scanlines (~1.6ms)
If your game logic exceeds the frame budget, it will skip frames and slow down.
System Takeover Pattern
Always disable system interrupts and DMA before taking over:
move.w #$7fff,INTENA(a5) ; Disable interrupts
move.w #$7fff,INTREQ(a5) ; Clear pending
move.w #$7fff,DMACON(a5) ; Disable DMA
Writing $7fff with bit 15 clear disables those bits.
Then enable only what you need:
move.w #$83a0,DMACON(a5) ; Enable master + copper + sprites + bitplanes
Writing with bit 15 set enables those bits.
Related
Patterns: Joystick Reading, Copper List Basics
Vault: Commodore Amiga