base:making_stable_raster_routines
no way to compare when less than two revisions
Differences
This shows you the differences between two versions of the page.
— | base:making_stable_raster_routines [2015-04-17 04:32] (current) – created - external edit 127.0.0.1 | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== Making stable raster routines (C64 and VIC-20) ====== | ||
+ | |||
+ | |||
+ | by Marko Makela | ||
+ | |||
+ | |||
+ | ====== Preface ====== | ||
+ | |||
+ | |||
+ | Too many graphical effects, also called raster effects, have been | ||
+ | coded in a very sloppy way. For instance, if there are any color bars | ||
+ | on the screen in a game or demo, the colors often jitter a bit, | ||
+ | e.g. they are not stable. | ||
+ | virtually any demo crash by hitting the Restore key, or at least cause | ||
+ | visual distortions on the screen. | ||
+ | |||
+ | As late as a year ago I still hadn't coded a stable raster interrupt | ||
+ | routine myself. | ||
+ | video chip timing details together with my German friend Andreas | ||
+ | Boose. | ||
+ | it came to the hardware, but he was the only of us who had written a | ||
+ | stable raster routine. | ||
+ | used the same double-interrupt idea as Andreas used in his routine. | ||
+ | |||
+ | After a couple of errors my routine worked, and I understood how it | ||
+ | works exactly. | ||
+ | from demo people: They often code by instinct; by patching the routine | ||
+ | until it works, without knowing exactly what is happening. | ||
+ | demos often rely on weird things, like crash if the memory is not | ||
+ | initialized properly.) | ||
+ | |||
+ | In this article, I document two methods of creating stable raster | ||
+ | routines on Commodore computers. | ||
+ | computers, not only Commodores, but raster effects are very rarely | ||
+ | seen on other computers. | ||
+ | |||
+ | |||
+ | ====== Background ====== | ||
+ | |||
+ | What are raster effects? | ||
+ | screen appearance while it is being drawn. | ||
+ | the screen color to white in the top of the screen, and to black in | ||
+ | the middle of the screen. | ||
+ | top half is white and bottom half black. | ||
+ | implemented with interrupt routines that are executed synchronized | ||
+ | with the screen refresh. | ||
+ | |||
+ | The video chip on the Commodore 64 and many other videochips have a | ||
+ | special interrupt feature called the Raster interrupt. | ||
+ | generate an IRQ in the beginning of a specified raster line. On other | ||
+ | computers, like the VIC-20, there is no Raster interrupt, but you can | ||
+ | generate the interrupts with a timer, provided that the timer and the | ||
+ | videochip are clocked from the same source. | ||
+ | |||
+ | Even if the processor gets an interrupt signal at the same position on | ||
+ | each video frame, it won't always be executing the first instruction | ||
+ | of the interrupt routine at the same screen position. | ||
+ | machine instructions can take 2 to 9 machine cycles to execute, and if | ||
+ | the main program contains instructions of very varying lengths, the | ||
+ | beginning position of the interrupt can jump between 7 different | ||
+ | positions. | ||
+ | when doing serious effects. | ||
+ | |||
+ | Also, executing the interrupt sequence will take 7 additional cycles, | ||
+ | and the interrupt sequence will only start after the current | ||
+ | instruction if the interrupt arrived at least two cycles before the | ||
+ | end of the current instruction. | ||
+ | arrives while interrupts are disabled and the processor is just | ||
+ | starting to execute a CLI instruction. | ||
+ | jump to the interrupt right after the CLI, but it will execute the | ||
+ | next instruction before jumping to it. This is natural, since the CLI | ||
+ | takes only two cycles. | ||
+ | equation, and actually out of the scope of this article. | ||
+ | |||
+ | How to synchronize a raster interrupt routine? | ||
+ | check the current screen position and delay appropriately many cycles. | ||
+ | There are several ways of doing this, some of which are very awful and | ||
+ | inefficient. | ||
+ | know are busy-waiting several raster lines and polling the raster line | ||
+ | value, or using the Light pen feature, which will fail if the user | ||
+ | presses the fire button on Joystick port 1. Here I will present two | ||
+ | ways, both very elegant in my opinion. | ||
+ | |||
+ | |||
+ | ====== Using an auxiliary timer ====== | ||
+ | |||
+ | |||
+ | On the VIC-20, there is no Raster interrupt feature in the video chip. | ||
+ | All you can do is to use a timer for generating raster interrupts. | ||
+ | And if you use two timers running at a constant phase difference, you | ||
+ | can get full synchronization. | ||
+ | interrupt, and the second timer, the auxiliary timer, tells the raster | ||
+ | routine where it is running. | ||
+ | timer also for the checking, but the code will look nicer in the way I | ||
+ | will be presenting now. Besides, you can use the auxiliary timer idea | ||
+ | even when real raster interrupts are available. | ||
+ | |||
+ | The major drawback of using an auxiliary timer is initializing it. | ||
+ | The initialization routine must synchronize with the screen, that is, | ||
+ | wait for the beginning of the wanted raster line. To accomplish this, | ||
+ | the routine must first wait for a raster line that occurs a bit | ||
+ | earlier. | ||
+ | |||
+ | < | ||
+ | LDA #value | ||
+ | loop CMP raster | ||
+ | BNE loop | ||
+ | </ | ||
+ | |||
+ | One round of this loop will take 4+3=7 cycles to execute, assuming | ||
+ | that absolute addressing is being used. The loop will be finished if | ||
+ | the raster register contains the wanted value while the processor | ||
+ | reads it on the last cycle of the CMP instruction. | ||
+ | register can actually have changed already on the first cycle of the | ||
+ | BNE instruction on the previous run of the loop, that is 7 cycles | ||
+ | earlier! | ||
+ | |||
+ | Because of this, the routine must poll the raster register for several | ||
+ | raster lines, always consuming one cycle more if the raster register | ||
+ | changed too early. | ||
+ | cycles, a loop of 7 raster register value changes would do, but I made | ||
+ | the loop a bit longer in my VIC-20 routine. | ||
+ | I was too lazy to make it work only with 7 rounds.) | ||
+ | |||
+ | After the initialization routine is fully synchronized the screen, it | ||
+ | can set up the timer(s) and interrupts and exit. The auxiliary timer | ||
+ | in my VIC-20 demo routine is several dozens of cycles after the | ||
+ | primary timer, see the source code for comments. | ||
+ | that the auxiliary timer will be at least 0 when it is being read in | ||
+ | the raster routine. | ||
+ | as the auxiliary timer reads, however at most 15 cycles. | ||
+ | |||
+ | |||
+ | ====== Using double raster interrupt ====== | ||
+ | |||
+ | |||
+ | On the Commodore 64, I have never seen the auxiliary timer scheme | ||
+ | being used. Actually I haven' | ||
+ | probably the first one who made a stable raster interrupt routine on | ||
+ | the VIC-20. | ||
+ | standard on the C64 side. | ||
+ | |||
+ | The double interrupt method is based entirely on the Raster interrupt | ||
+ | feature of the video chip. In the first raster interrupt routine, the | ||
+ | program sets up another raster interrupt on a further line, changes | ||
+ | the interrupt vector and enables interrupts. | ||
+ | |||
+ | In the place where the second raster interrupt will occur, there will | ||
+ | be 2-byte instructions in the first interrupt routine. | ||
+ | the beginning of the next raster interrupt will be off at most by one | ||
+ | cycle. | ||
+ | can do it right, why wouldn' | ||
+ | |||
+ | At the beginning of the second raster interrupt routine, you will read | ||
+ | the raster line counter register at the point where it is about to | ||
+ | change. | ||
+ | possibilities: | ||
+ | change on the next cycle. | ||
+ | register changed one cycle too early or not, and delay a cycle when | ||
+ | needed. | ||
+ | |||
+ | Of course, somewhere in your second raster interrupt routine you must | ||
+ | restore the original raster interrupt position and set the interrupt | ||
+ | vector to point to the first interrupt routine. | ||
+ | |||
+ | |||
+ | ====== Applying in practice ====== | ||
+ | |||
+ | |||
+ | I almost forgot my complaints about demos crashing when you actively | ||
+ | hit the Restore key. On the VIC-20, you can disable NMI interrupts | ||
+ | generated by the Restore key, and on the C64, you can generate an NMI | ||
+ | interrupt with the CIA2 timer and leave the NMI-line low, so that no | ||
+ | further high-to-low transitions will be recognized on the line. The | ||
+ | example programs demonstrate how to do this. | ||
+ | |||
+ | So far, this article has been pretty theoretical. | ||
+ | results in practice, you must definitely know how many CPU clock | ||
+ | cycles the video chip consumes while drawing a scan line. This is | ||
+ | fairly easy to measure with a timer interrupt, if you patch the | ||
+ | interrupt handler so that it changes the screen color on each run. | ||
+ | Set the timer interval to LINES*COLUMNS cycles, where LINES is the | ||
+ | amount of raster lines and COLUMNS is your guess for the amount of | ||
+ | clock cycles spent in a raster line. | ||
+ | |||
+ | If your guess is right, the color will always be changed in the same | ||
+ | screen position (neglecting the 7-cycle jitter). | ||
+ | timer, remember that the timers on the 6522 VIA require 2 cycles for | ||
+ | re-loading, and the ones on the 6526 CIA need one extra cycle. | ||
+ | trying different timer values until you the screen color changes at | ||
+ | one fixed position. | ||
+ | |||
+ | Commodore used several different values for LINES and COLUMNS on its | ||
+ | videochips. | ||
+ | exactly 50 or 60 Hertz, but they didn't hesitate to claim that their | ||
+ | computers comply with the PAL-B or NTSC-M standards. | ||
+ | tables I have gathered some information of some Commodore video chips. | ||
+ | |||
+ | < | ||
+ | NTSC-M systems: | ||
+ | |||
+ | Chip Crystal | ||
+ | Host ID freq/ | ||
+ | ------ | ||
+ | VIC-20 | ||
+ | C64 | ||
+ | C64 | ||
+ | |||
+ | Later NTSC-M video chips were most probably like the 6567R8. | ||
+ | that the processor clock is a 14th of the crystal frequency on all | ||
+ | NTSC-M systems. | ||
+ | |||
+ | PAL-B systems: | ||
+ | |||
+ | Chip Crystal | ||
+ | Host ID freq/ | ||
+ | ------ | ||
+ | VIC-20 | ||
+ | C64 | ||
+ | |||
+ | On the PAL-B VIC-20, the crystal frequency is simultaneously the dot | ||
+ | clock, which is BTW a 4th of the crystal frequency used on the C64. | ||
+ | On the C64, the crystal frequency is divided by 18 to generate the | ||
+ | processor clock, which in turn is multiplied by 8 to generate the | ||
+ | dot clock. | ||
+ | |||
+ | The basic timings are the same on all 6569 revisions, and also on | ||
+ | any later C64 and C128 video chips. | ||
+ | values were the same on the C16 videochip TED as well. | ||
+ | </ | ||
+ | |||
+ | Note that the dot clock is 4 times the processor clock on the VIC-20, | ||
+ | and 8 times that on the C64. That is, one processor cycle is half a | ||
+ | character wide on the VIC-20, and a full character on a C64. I don't | ||
+ | have exact measurements of the VIC-20 timing, but it seems that while | ||
+ | the VIC-20 videochips draw the characters on the screen, it first | ||
+ | reads the character code, and then, on the following video cycle, the | ||
+ | appearance on the current character line. There are no bad lines, | ||
+ | like on the C64, where the character codes (and colors) are fetched on | ||
+ | every 8th raster line. | ||
+ | |||
+ | Those ones who got upset when I said that Commodore has never managed | ||
+ | to make a fully PAL-B or NTSC-M compliant 8-bit computer should take a | ||
+ | closer look at the " | ||
+ | you, calculate the raster line rate and the screen refresh rate from | ||
+ | the values in the table and see that they don't comply with the | ||
+ | standards. | ||
+ | frequency by the amount of cycles per line. To get the screen refresh | ||
+ | rate, divide that frequency by the amount of raster lines. | ||
+ | |||
+ | |||
+ | ====== The Code ====== | ||
+ | |||
+ | OK, enough theory and background. | ||
+ | one for the VIC-20 and one for the C64. In order to fully understand | ||
+ | them, you need to know the exact execution times of NMOS 6502 | ||
+ | instructions. | ||
+ | processor core, except the C65 prototype, which used a inferior CMOS | ||
+ | version with all nice poorly-documented features removed.) | ||
+ | check the 64doc document, available on my WWW pages at | ||
+ | http:// | ||
+ | ftp.funet.fi:/ | ||
+ | request. | ||
+ | |||
+ | Also, I have written a complete description of the video timing on the | ||
+ | 6567R56A, 6567R8 and 6569 video chips, which could maybe be turned | ||
+ | into another C=Hacking article. | ||
+ | in English and partially in German. | ||
+ | from ftp.funet.fi as / | ||
+ | copies of the German part (screen resolution, sprite disturbance | ||
+ | measurements, | ||
+ | |||
+ | The code is written for the DASM assembler, or more precisely for a | ||
+ | extended ANSI C port of it made by Olaf Seibert. | ||
+ | cross-assembler is available at ftp.funet.fi in / | ||
+ | |||
+ | First the raster demo for the VIC-20. | ||
+ | $9004 register contains the upper 8 bits of the raster counter. | ||
+ | this register changes only on every second line. I have tested the | ||
+ | program on my 6561-101-based VIC-20, but not on an NTSC-M system. | ||
+ | |||
+ | It was hard to get in contact with NTSC-M VIC-20 owners. | ||
+ | Dallmann, who has a NTSC-M VIC-20, although he lives in Germany, ran | ||
+ | my test to determine the amount of cycles per line and lines per frame | ||
+ | on the 6560-101. | ||
+ | partially broken, and because of this, this program did not work on | ||
+ | his computer. | ||
+ | it almost worked. | ||
+ | the display should be stable on an NTSC-M system, too. But the actual | ||
+ | raster effect, six 16*16-pixel boxes centered at the top border, are | ||
+ | very likely to be off their position. | ||
+ | |||
+ | < | ||
+ | processor 6502 | ||
+ | |||
+ | NTSC = 1 | ||
+ | PAL = 2 | ||
+ | |||
+ | ;SYSTEM = NTSC ; 6560-101: 65 cycles per raster line, 261 lines | ||
+ | SYSTEM | ||
+ | |||
+ | #if SYSTEM & PAL | ||
+ | LINES = 312 | ||
+ | CYCLES_PER_LINE = 71 | ||
+ | #endif | ||
+ | #if SYSTEM & NTSC | ||
+ | LINES = 261 | ||
+ | CYCLES_PER_LINE = 65 | ||
+ | #endif | ||
+ | TIMER_VALUE = LINES * CYCLES_PER_LINE - 2 | ||
+ | |||
+ | .org $1001 ; for the unexpanded Vic-20 | ||
+ | |||
+ | ; The BASIC line | ||
+ | |||
+ | basic: | ||
+ | .word 0$ ; link to next line | ||
+ | .word 1995 ; line number | ||
+ | .byte $9E ; SYS token | ||
+ | |||
+ | ; SYS digits | ||
+ | |||
+ | .if (* + 8) / 10000 | ||
+ | .byte $30 + (* + 8) / 10000 | ||
+ | .endif | ||
+ | .if (* + 7) / 1000 | ||
+ | .byte $30 + (* + 7) % 10000 / 1000 | ||
+ | .endif | ||
+ | .if (* + 6) / 100 | ||
+ | .byte $30 + (* + 6) % 1000 / 100 | ||
+ | .endif | ||
+ | .if (* + 5) / 10 | ||
+ | .byte $30 + (* + 5) % 100 / 10 | ||
+ | .endif | ||
+ | .byte $30 + (* + 4) % 10 | ||
+ | 0$: | ||
+ | .byte 0,0,0 ; end of BASIC program | ||
+ | |||
+ | start: | ||
+ | lda #$7f | ||
+ | sta $912e ; disable and acknowledge interrupts | ||
+ | sta $912d | ||
+ | sta $911e ; disable NMIs (Restore key) | ||
+ | |||
+ | ; | ||
+ | sync: | ||
+ | ldx #28 ; wait for this raster line (times 2) | ||
+ | 0$: | ||
+ | cpx $9004 | ||
+ | bne 0$ ; at this stage, the inaccuracy is 7 clock cycles | ||
+ | ; the processor is in this place 2 to 9 cycles | ||
+ | ; after $9004 has changed | ||
+ | ldy #9 | ||
+ | bit $24 | ||
+ | 1$: | ||
+ | ldx $9004 | ||
+ | txa | ||
+ | bit $24 | ||
+ | #if SYSTEM & PAL | ||
+ | ldx #24 | ||
+ | #endif | ||
+ | #if SYSTEM & NTSC | ||
+ | bit $24 | ||
+ | ldx #21 | ||
+ | #endif | ||
+ | dex | ||
+ | bne *-1 ; first spend some time (so that the whole | ||
+ | cmp $9004 ; loop will be 2 raster lines) | ||
+ | bcs *+2 ; save one cycle if $9004 changed too late | ||
+ | dey | ||
+ | bne 1$ | ||
+ | ; now it is fully synchronized | ||
+ | ; 6 cycles have passed since last $9004 change | ||
+ | ; and we are on line 2(28+9)=74 | ||
+ | |||
+ | ;initialize the timers | ||
+ | timers: | ||
+ | lda #$40 ; enable Timer A free run of both VIAs | ||
+ | sta $911b | ||
+ | sta $912b | ||
+ | |||
+ | lda #< | ||
+ | ldx #> | ||
+ | sta $9116 ; load the timer low byte latches | ||
+ | sta $9126 | ||
+ | |||
+ | #if SYSTEM & PAL | ||
+ | ldy #7 ; make a little delay to get the raster effect to the | ||
+ | dey ; right place | ||
+ | bne *-1 | ||
+ | nop | ||
+ | nop | ||
+ | #endif | ||
+ | #if SYSTEM & NTSC | ||
+ | ldy #6 | ||
+ | dey | ||
+ | bne *-1 | ||
+ | bit $24 | ||
+ | #endif | ||
+ | |||
+ | stx $9125 ; start the IRQ timer A | ||
+ | ; 6560-101: 65 cycles from $9004 change | ||
+ | ; 6561-101: 77 cycles from $9004 change | ||
+ | ldy #10 ; spend some time (1+5*9+4=55 cycles) | ||
+ | dey ; before starting the reference timer | ||
+ | bne *-1 | ||
+ | stx $9115 ; start the reference timer | ||
+ | |||
+ | pointers: | ||
+ | lda #< | ||
+ | sta $314 | ||
+ | lda #>irq | ||
+ | sta $315 | ||
+ | lda #$c0 | ||
+ | sta $912e ; enable Timer A underflow interrupts | ||
+ | rts ; return | ||
+ | |||
+ | irq: | ||
+ | ; irq (event) | ||
+ | ; pha ; 3 | ||
+ | ; txa ; 2 | ||
+ | ; pha ; 3 | ||
+ | ; tya ; 2 | ||
+ | ; pha ; 3 | ||
+ | ; tsx ; 2 | ||
+ | ; lda $0104, | ||
+ | ; and #xx ; 2 | ||
+ | ; beq ; 3 | ||
+ | ; jmp ($314) | ||
+ | ; --- | ||
+ | ; 38 to 45 cycles delay at this stage | ||
+ | |||
+ | lda $9114 ; get the NMI timer A value | ||
+ | ; (42 to 49 cycles delay at this stage) | ||
+ | ; sta $1e00 ; uncomment these if you want to monitor | ||
+ | ; ldy $9115 ; the reference timer on the screen | ||
+ | ; sty $1e01 | ||
+ | cmp #8 ; are we more than 7 cycles ahead of time? | ||
+ | bcc 0$ | ||
+ | pha ; yes, spend 8 extra cycles | ||
+ | pla | ||
+ | and #7 ; and reset the high bit | ||
+ | 0$: | ||
+ | cmp #4 | ||
+ | bcc 1$ | ||
+ | bit $24 ; waste 4 cycles | ||
+ | and #3 | ||
+ | 1$: | ||
+ | cmp #2 ; spend the rest of the cycles | ||
+ | bcs *+2 | ||
+ | bcs *+2 | ||
+ | lsr | ||
+ | bcs *+2 ; now it has taken 82 cycles from the beginning of the IRQ | ||
+ | |||
+ | effect: | ||
+ | ldy #16 ; perform amazing video effect | ||
+ | lda $900f | ||
+ | tax | ||
+ | eor #$f7 | ||
+ | 0$: | ||
+ | sta $900f | ||
+ | stx $900f | ||
+ | sta $900f | ||
+ | stx $900f | ||
+ | sta $900f | ||
+ | stx $900f | ||
+ | sta $900f | ||
+ | stx $900f | ||
+ | sta $900f | ||
+ | stx $900f | ||
+ | sta $900f | ||
+ | stx $900f | ||
+ | pha | ||
+ | pla | ||
+ | #if SYSTEM & PAL | ||
+ | pha | ||
+ | pla | ||
+ | nop | ||
+ | #endif | ||
+ | #if SYSTEM & NTSC | ||
+ | bit $24 | ||
+ | #endif | ||
+ | nop | ||
+ | dey | ||
+ | bne 0$ ; end of amazing video effect | ||
+ | |||
+ | jmp $eabf ; return to normal IRQ | ||
+ | </ | ||
+ | |||
+ | And after you have recovered from the chock of seeing a VIC-20 | ||
+ | program, here is an example for the C64. It does also something | ||
+ | noteworthy; it removes the side borders on a normal screen while | ||
+ | displaying all eight sprites. | ||
+ | bad lines, and the bad lines look pretty bad. But I could use the | ||
+ | program for what I wanted: I measured the sprite distortions on all | ||
+ | videochip types I had at hand. (FYI: the sprites 0-2 get distorted at | ||
+ | the very right of the screen, and the sprites 6 and 7 are invisible at | ||
+ | the very left of the screen. | ||
+ | size controls to witness these effects.) | ||
+ | |||
+ | This program is really robust, it installs itself nicely to the | ||
+ | interrupt routine chain. | ||
+ | itself. | ||
+ | the original interrupt routine address. :-) | ||
+ | |||
+ | The code also relies on the page boundaries in being where they are. | ||
+ | The cycles are counted so that the branches " | ||
+ | cycles. | ||
+ | instructions, | ||
+ | When coding the routine, I noticed again how stupid assembly coding | ||
+ | can be, especially conditional assembling. | ||
+ | monitor you have far better control on page boundaries. | ||
+ | might wonder why I disable the Restore key in a subroutine at the end | ||
+ | and not in the beginning of the program. | ||
+ | long that it would have affected the " | ||
+ | didn't want to risk the modified programs working on all three | ||
+ | different videochip types on the first try. | ||
+ | |||
+ | |||
+ | In the code, there are some comments that document the video timing, | ||
+ | like this one: | ||
+ | < | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; ^ now we are here | ||
+ | </ | ||
+ | |||
+ | The two vertical bars " | ||
+ | (63 cycles per line), they are not present. | ||
+ | cycles per line, there is one additional cycle on this position, and | ||
+ | the 6567R8 has two additional cycles there. | ||
+ | |||
+ | The numbers 0 through 7 are sprite pointer fetches (from the end of | ||
+ | the character matrix, e.g. the text screen), the " | ||
+ | sprite image fetches, the " | ||
+ | graphics fetches. | ||
+ | On the processor timing line, the " | ||
+ | free bus, and " | ||
+ | unless it is performing write cycles. | ||
+ | |||
+ | < | ||
+ | processor 6502 | ||
+ | |||
+ | ; Select the video timing (processor clock cycles per raster line) | ||
+ | CYCLES = 65 ; 6567R8 and above, NTSC-M | ||
+ | ;CYCLES = 64 ; 6567R5 6A, NTSC-M | ||
+ | ;CYCLES = 63 ; 6569 (all revisions), PAL-B | ||
+ | |||
+ | cinv = $314 | ||
+ | cnmi = $318 | ||
+ | raster = 52 ; start of raster interrupt | ||
+ | m = $fb ; zero page variable | ||
+ | |||
+ | .org $801 | ||
+ | basic: | ||
+ | .word 0$ ; link to next line | ||
+ | .word 1995 ; line number | ||
+ | .byte $9E ; SYS token | ||
+ | |||
+ | ; SYS digits | ||
+ | |||
+ | .if (* + 8) / 10000 | ||
+ | .byte $30 + (* + 8) / 10000 | ||
+ | .endif | ||
+ | .if (* + 7) / 1000 | ||
+ | .byte $30 + (* + 7) % 10000 / 1000 | ||
+ | .endif | ||
+ | .if (* + 6) / 100 | ||
+ | .byte $30 + (* + 6) % 1000 / 100 | ||
+ | .endif | ||
+ | .if (* + 5) / 10 | ||
+ | .byte $30 + (* + 5) % 100 / 10 | ||
+ | .endif | ||
+ | .byte $30 + (* + 4) % 10 | ||
+ | |||
+ | 0$: | ||
+ | .byte 0,0,0 ; end of BASIC program | ||
+ | |||
+ | start: | ||
+ | jmp install | ||
+ | jmp deinstall | ||
+ | |||
+ | install: | ||
+ | jsr restore | ||
+ | checkirq: | ||
+ | lda cinv ; check the original IRQ vector | ||
+ | ldx cinv+1 | ||
+ | cmp #<irq1 | ||
+ | bne irqinit | ||
+ | cpx #>irq1 | ||
+ | beq skipinit | ||
+ | irqinit: | ||
+ | sei | ||
+ | sta oldirq | ||
+ | stx oldirq+1 | ||
+ | lda #<irq1 | ||
+ | ldx #>irq1 | ||
+ | sta cinv ; set the new interrupt vector | ||
+ | stx cinv+1 | ||
+ | skipinit: | ||
+ | lda #$1b | ||
+ | sta $d011 ; set the raster interrupt location | ||
+ | lda #raster | ||
+ | sta $d012 | ||
+ | ldx #$e | ||
+ | clc | ||
+ | adc #3 | ||
+ | tay | ||
+ | lda #0 | ||
+ | sta m | ||
+ | 0$: | ||
+ | lda m | ||
+ | sta $d000, | ||
+ | adc #24 | ||
+ | sta m | ||
+ | tya | ||
+ | sta $d001, | ||
+ | dex | ||
+ | dex | ||
+ | bpl 0$ | ||
+ | lda #$7f | ||
+ | sta $dc0d ; disable timer interrupts | ||
+ | sta $dd0d | ||
+ | ldx #1 | ||
+ | stx $d01a ; enable raster interrupt | ||
+ | lda $dc0d ; acknowledge CIA interrupts | ||
+ | lsr $d019 ; and video interrupts | ||
+ | ldy #$ff | ||
+ | sty $d015 ; turn on all sprites | ||
+ | cli | ||
+ | rts | ||
+ | |||
+ | deinstall: | ||
+ | sei ; disable interrupts | ||
+ | lda #$1b | ||
+ | sta $d011 ; restore text screen mode | ||
+ | lda #$81 | ||
+ | sta $dc0d ; enable Timer A interrupts on CIA 1 | ||
+ | lda #0 | ||
+ | sta $d01a ; disable video interrupts | ||
+ | lda oldirq | ||
+ | sta cinv ; restore old IRQ vector | ||
+ | lda oldirq+1 | ||
+ | sta cinv+1 | ||
+ | bit $dd0d ; re-enable NMI interrupts | ||
+ | cli | ||
+ | rts | ||
+ | |||
+ | ; Auxiliary raster interrupt (for syncronization) | ||
+ | irq1: | ||
+ | ; irq (event) | ||
+ | ; pha ; 3 | ||
+ | ; txa ; 2 | ||
+ | ; pha ; 3 | ||
+ | ; tya ; 2 | ||
+ | ; pha ; 3 | ||
+ | ; tsx ; 2 | ||
+ | ; lda $0104, | ||
+ | ; and #xx ; 2 | ||
+ | ; beq ; 3 | ||
+ | ; jmp ($314) | ||
+ | ; --- | ||
+ | ; 38 to 45 cycles delay at this stage | ||
+ | lda #<irq2 | ||
+ | sta cinv | ||
+ | lda #>irq2 | ||
+ | sta cinv+1 | ||
+ | nop ; waste at least 12 cycles | ||
+ | nop ; (up to 64 cycles delay allowed here) | ||
+ | nop | ||
+ | nop | ||
+ | nop | ||
+ | nop | ||
+ | inc $d012 ; At this stage, $d012 has already been incremented by one. | ||
+ | lda #1 | ||
+ | sta $d019 ; acknowledge the first raster interrupt | ||
+ | cli ; enable interrupts (the second interrupt can now occur) | ||
+ | ldy #9 | ||
+ | dey | ||
+ | bne *-1 ; delay | ||
+ | nop ; The second interrupt will occur while executing these | ||
+ | nop ; two-cycle instructions. | ||
+ | nop | ||
+ | nop | ||
+ | nop | ||
+ | oldirq = * + 1 ; Placeholder for self-modifying code | ||
+ | jmp * ; Return to the original interrupt | ||
+ | |||
+ | ; Main raster interrupt | ||
+ | irq2: | ||
+ | ; irq (event) | ||
+ | ; pha ; 3 | ||
+ | ; txa ; 2 | ||
+ | ; pha ; 3 | ||
+ | ; tya ; 2 | ||
+ | ; pha ; 3 | ||
+ | ; tsx ; 2 | ||
+ | ; lda $0104, | ||
+ | ; and #xx ; 2 | ||
+ | ; beq ; 3 | ||
+ | ; jmp (cinv) | ||
+ | ; --- | ||
+ | ; 38 or 39 cycles delay at this stage | ||
+ | lda #<irq1 | ||
+ | sta cinv | ||
+ | lda #>irq1 | ||
+ | sta cinv+1 | ||
+ | ldx $d012 | ||
+ | nop | ||
+ | #if CYCLES - 63 | ||
+ | #if CYCLES - 64 | ||
+ | nop ; 6567R8, 65 cycles/line | ||
+ | bit $24 | ||
+ | #else | ||
+ | nop ; 6567R56A, 64 cycles/line | ||
+ | nop | ||
+ | #endif | ||
+ | #else | ||
+ | bit $24 ; 6569, 63 cycles/line | ||
+ | #endif | ||
+ | cpx $d012 ; The comparison cycle is executed CYCLES or CYCLES+1 cycles | ||
+ | ; after the interrupt has occurred. | ||
+ | beq *+2 ; Delay by one cycle if $d012 hadn't changed. | ||
+ | ; Now exactly CYCLES+3 cycles have passed since the interrupt. | ||
+ | dex | ||
+ | dex | ||
+ | stx $d012 ; restore original raster interrupt position | ||
+ | ldx #1 | ||
+ | stx $d019 ; acknowledge the raster interrupt | ||
+ | ldx #2 | ||
+ | dex | ||
+ | bne *-1 | ||
+ | nop | ||
+ | nop | ||
+ | lda #20 ; set the amount of raster lines-1 for the loop | ||
+ | sta m | ||
+ | ldx #$c8 | ||
+ | irqloop: | ||
+ | ldy #2 | ||
+ | dey | ||
+ | bne *-1 ; delay | ||
+ | dec $d016 ; narrow the screen (exact timing required) | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; ^ now we are here | ||
+ | stx $d016 ; expand the screen | ||
+ | #if CYCLES - 63 | ||
+ | #if CYCLES - 64 | ||
+ | bit $24 ; 6567R8 | ||
+ | #else | ||
+ | nop ; 6567R56A | ||
+ | #endif | ||
+ | #else | ||
+ | nop ; 6569 | ||
+ | #endif | ||
+ | dec m | ||
+ | bmi endirq | ||
+ | clc | ||
+ | lda $d011 | ||
+ | sbc $d012 | ||
+ | and #7 | ||
+ | bne irqloop | ||
+ | ; because the page boundary is crossed. | ||
+ | badline: | ||
+ | dec m | ||
+ | nop | ||
+ | nop | ||
+ | nop | ||
+ | nop | ||
+ | dec $d016 | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; ^ we are here | ||
+ | stx $d016 | ||
+ | ; | ||
+ | ; | ||
+ | ; | ||
+ | ; ^ ^^- we are here (6569) | ||
+ | ; | \- or here (6567R56A) | ||
+ | ; \- or here (6567R8) | ||
+ | ldy #2 | ||
+ | dey | ||
+ | bne *-1 | ||
+ | nop | ||
+ | nop | ||
+ | #if CYCLES - 63 | ||
+ | #if CYCLES - 64 | ||
+ | nop ; 6567R8, 65 cycles/line | ||
+ | nop | ||
+ | nop | ||
+ | #else | ||
+ | bit $24 ; 6567R56A, 64 cycles/line | ||
+ | #endif | ||
+ | #else | ||
+ | nop ; 6569, 63 cycles/line | ||
+ | #endif | ||
+ | dec m | ||
+ | bpl irqloop | ||
+ | endirq: | ||
+ | jmp $ea81 ; return to the auxiliary raster interrupt | ||
+ | |||
+ | restore: | ||
+ | lda cnmi | ||
+ | ldy cnmi+1 | ||
+ | pha | ||
+ | lda #< | ||
+ | sta cnmi | ||
+ | lda #>nmi | ||
+ | sta cnmi+1 | ||
+ | ldx #$81 | ||
+ | stx $dd0d ; Enable CIA 2 Timer A interrupt | ||
+ | ldx #0 | ||
+ | stx $dd05 | ||
+ | inx | ||
+ | stx $dd04 ; Prepare Timer A to count from 1 to 0. | ||
+ | ldx #$dd | ||
+ | stx $dd0e ; Cause an interrupt. | ||
+ | nmi = * + 1 | ||
+ | lda #$40 ; RTI placeholder | ||
+ | pla | ||
+ | sta cnmi | ||
+ | sty cnmi+1 | ||
+ | rts | ||
+ | </ | ||
base/making_stable_raster_routines.txt · Last modified: 2015-04-17 04:32 by 127.0.0.1