ADVANCED RASTER INTERRUPT PROGRAMMING - OR: HOW DO I GET MORE THAN 16 COLORS AT ONCE by Udo from TEX The problem: To get more than 16 colors at once on the screen at once, you must change the color palette somewhere somewhere in the screen. This can be done with Xbios function 6 (setpalette) because this waits for the electron beam to prevent flickering. The operating systems waits for the Vertical Blank Interrupt before the palette is set. For example, we'd have to change the color palette in the middle of the screen to be able to use 16 other colors in the lower half of the screen. And, of course, we'd have to put the previous color palette after the VBL so that the upper half of the screen uses the first 16 colors again. So that the screensplitting is done with each setup of the screen (50 or 60 times per second) without the main program being harassed with it, we have to use Interrupt techniques. The Atari has three possibilities therefore: The Level 2 Interrupt, the Level 4 Interrupt and the Level 6 Interrupt. All three methods have in common that Level 4 is used to set the upper color palette and to initialise the data for the further interrupt procedure. Installation of a Level 4 interrupt: instal4: move.l $70,old4+2 ;mark old vector move.l #new4,$70 ;set new vector rts This routine has to be executed in supervisor mode (e.g. by use of Xbios 38, Supexec). The new Level 4 routine: new4: ;here are the seperate initialise routine old4: jmp $000000 ;after old4+2 comes the old interruptvector, so that the interruptroutines from the OS are still being executed All program examples were written for the SEKA assembler, and can be re-written for use with other assemblers without much trouble (a ';' denotes a comment). Raster Interrupts with Level 2: This interrupt is usually disabled through interruptmask $0300 of the Processor Status Register (sr), because it is caused by the electron beam going back to the beginning of a line (15625 times per second). When the interrupt is cleared, is halts the main program (the biggest disadvantage of this method). For raster programming, one installs a Level 2 routine, that makes the variegated screen together with the Level 4 routine: instal: ;should again be executed from ;supervisor mode move.l $70,old4+2 move.l #new4,$70 move.l $68,old2 move.l #new2,$68 rts wert = 50 old2: dc.l 0 zeile: dc.w 0 pal_o: blk.w 16,0 pal_u: blk.w 16,0 new4: movem.l d0-d7/a0-a1,-(sp) move.w #wert,zeile ;set in which line the palette ;should be switched move.l #pal_o,a0 ;upper palette move.l #$ff8240,a1 ;palette register movem.l (a0),d0-d7 ;load colors in registers movem.l d0-d7,(a1) ;set colors movem.l (sp)+,d0-d7/a0-a1 old4: jmp $00000 new2: move.l d0,-(sp) move.w zeile,d0 subq.w #1,d0 ;decrease counter move.w d0,zeile bne no2 ;not yet zero, then nothing movem.l d1-d7/a0-a1,-(sp) move.l #pal_u,a0 ;palette below move.l #$ff8240,a1 ;palette registers movem.l (a0),d0-d7 ;load colors in registers movem.l d0-d7,(a1) ;set colors movem.l (sp)+,d1-d7/a0-a1 no2: move.l (sp)+,d0 rte To switch off the interrupt, you have to put back the old values of the vectors back to $68 and $70. The disadvantage of this method is that both the level 6 and the level 2 interrupt have priority above the level 2 interrupt and thus can block it or even interrupt it. This causes the colors not to be switched on the same lines all the times, which creates a flickering effect (just like in the "Gauntlet" text when there's no titles music to be heard). When you block the interrupt level 6, you cannot use the mouse or the keyboard, and you can't even perform disk operations! Raster Interrupts with Level 4: Because you can create stable raster interrupts with this technique that, however, take up such a lot of time so that the main program almost stops completely (and the level 6 interrupt has to be blocker as well), I would just like to mention the possibility of this method but not explaining it in more detail (I have discovered this possibility in the 42-Crew demo): After the switch-off of the level 6 interrupt, the level 4 routine is modified in such a way that it reads memory locations $ff8205/6/7 after being called. In these locations, it is possible to read the current position of the Video Address Pointers (the address that the video chip momentarily uses to fetch its screen data from). It now waits until a certain value is reached. Is it the first value of a line, it is reached at the end of the preceding line and the colors can be changed while the electron beam is going back to the beginning of the next line. If the colors in line 180 should be changed, the program has to wait until this line is reached, which takes 90% of the available time! Raster Interrupts with Level 6: The Level 6 Interrupt is controlled by a peripheral chip that can recognise 16 different interrupts and gives these to the processor as a Level 6 interrupt. In the Atari, the following are used: RS232, Keyboard, Centronics busy, monochrome monitor detect and Timer C (one of 4 timers). Timer B can now count screen lines, where the difference with the Level 2 interrupt lies in the fact that only the screen lines are counted that are displayed and not the number of times the electron beam goes back to the beginning of a line! Because of this fact, one cannot use stable rasters in the border (in our "Super-Neo-Demo-Show" there is no lower border for the video chip). But now for practical proceedings; the installations of the Timer B: instal6: move.l #new6,$120 ;interruptvektor or.b #1,$fffa07 ;enable Timer B or.b #1,$fffa13 move.l $70,old4+2 ;divert level 4 move.l #new4,$70 move.b #0,$fffa1b ;Timer B stop rts If this routine was called from supervisor mode, than the 'new6' routine waits for the first call. This is reached when the level 4 interrupt sets a counter value and thus starts Timer B. Timer B then decreases that value by one each time an end of a screen line is reached, until zero is reached. When that happens, an interrupt is executed (if you change the palette only once, this will happen 50 or 60 times per second instead of 15625 times for the level 2 interrupt). The new level 4 routine: new4: movem.l d0-d7/a0-a1,-(sp) move.b #0,$fffa1b ;Timer stop move.b #wert,$fffa21 ;Counter value move.b #8,$fffa1b ;Timer start move.l #pal_o,a0 ;Upper palette move.l #$ff8240,a1 movem.l (a0),d0-d7 movem.l d0-d7,(a1) ;Set colors movem.l (sp)+,d0-d7/a0-a1 old4: jmp $000000 And the Timer B routine: new6: movem.l d0/d3-d7/a0-a6,-(sp) move.w #$fa21,a4 ;Takes $fffa21 -> a4 clr.b -6(a4) ;Timer B stop move.b #240,(a4) ;Pseudo value (see remark 1) move.b #8,-(a4) ;Timer B start move.l #pal_u,a6 ;Pointer on color palette movem.l 2(a6),d4-d7/a0-a2 ;Load colors in registers move.w #$8240,a5 move.w 30(a6),d3 move.b (a4),d0 ;Trick 1! (See remark 2) wait: cmp.b (a4),d0 beq wait movem.l d4-d7/a0-a2,2(a5) ;Trick 2! (See remark 2) move.w d3,30(a5) move.w (a6),(a5) movem.l (sp)+,d0/d3-d7/a0-a6 bclr #0,$fffa0f ;Clear interrupt again rte Remark 1: In this example, the color palette is to be changed only once. That's why a value is written to the counter register that is never reached (maximum 200). Remark 2: So that really nothing start flickering, the colors have to be set in the border and during the time needed for the electron beam to get back to a beginning of a line. The interrupt is executed at the beginning of the right border, but as soon as the processor reaches the actual command to set the colors the electron beam is already too far to prevent flickering. The colors have to be set at the beginning of the border - and as fast as possible (Trick 2 - the movem.l is the fastest). Trick 1 reads the Timer B counter and waits until it changes - in other words, it waits until the end of a line is reached. Because it now still waits until the next line, this way is only suitable to change the color palette at every second line. Problematical aspects: The MFP interrupts are reported to the processor as level 6 interrupts and are not mutually interrupted! If system interrupts are still installed, this can cause quite some problems. To prevent this, there are three possibilities: 1) One tolerates the flickering and does nothing about it (absolutely out of the question in our Demos!) 2) One switches off the hazardous interrupts (like we did in our TEX DEMOS 1-3) 3) One programs the interrupts in such a way that they can interrupt themselves (be careful!) (This is what we did in our Super-Neo-Demo-Show) Remark for 2): Hazardous are Timer C, that is simply turned off. When doing that, the keyboard repeat as well as the possibility to load with GEMDOS are blocked. Because keyboard checks through GEMDOS are still possible, the program can still react, turn off the raster interrupts and turn on the other interrupts again before going on with the actual program. Add thereto the complete sample program, that rescues the starting values in 'hblon', sets the raster interrupts and then waits for a key to be pressed. After pressing 'ESC', the raster interrupt is switched off and the starting values are set back. The diatance values specify the distances between the current and the previous raster interrupt. A number of additional raster interrupts can be inserted as long as you make sure the distance values are not larger than 199. Of course, enough color palettes have to be present. Remark to 3): To create the possibility to load, it is possible to give priority to an interrupt by software. To give priority to the raster interrupt, for example, you have to take care that Timer C can be interrupted. This can be done the following way (initialising of the other interrupt routines has to be done like I explained earlier): newtimc: ;New Timer C routine tst.w flagtc ;test Timer C flag bne noirq move.w #1,flagtc ;Set flag to prevent second call move.w #$2500,sr bclr #5,$fffa11 ;Clear ISR bit to enable level 6 move.l #hier,-(sp) ;To operating system after 'hier:' move.w sr,-(sp) jmp $000000 ;This is where the old Timer C vector ;has to go hier: ;From the OS from here on clr.w flagtc ;Clear flag noirq: bclr #5,$fffa11 ;Interrupt End rte With this method, flickering can be prevented. The Operating System doesn't like this, however, and takes revenge with a lot of read errors (depending on computer, drive and disk, this can vary between 15% and 60%). This is why our "Super-Neo-Demo-Show" keeps on reading until 32128 bytes were able to be read. Because this is not always possible, I would advise the second method to prevent flickering (Quote: " luckily there are only few raster interrupts to manage in our game.... movem.l.. swap.. rte.. aaarghh...")! P.S.: Because GEM really doesn't like it when many raster interrupts are present on the screen (because of reasons unknown), the program crashed when moving the mouse. Help: Start the program as a .TOS file or send a command to the keyboard processor ($12 = Turn off the mouse). Who knows exactly why .PRG files crash, should please write to us on the address mentioned below: For questions and support you can also contact us through the following address: -TEX- Postfach 1322 D-6702 Bad Drkheim 1 West Germany Have fun while trying the routines! Udo (TEX) Originally published in ST NEWS Volume 2 Issue 7. * Advanced raster interrupt programming * by UDO from TEX * This source is for K-SEKA 1.5 x move.l #0,-(sp) supervisor on move.w #$20,-(sp) trap #1 addq.l #6,sp move.l d0,savereg move.l #$78000,a7 * line_a $a mouse off move.w #4,-(sp) get old resolution trap #14 addq.l #2,sp move.w d0,oldrez move.l #$ff8240,a0 save old palette move.l #oldpal,a1 movem.l (a0),d0-d7 movem.l d0-d7,(a1) bsr prepare prepare screen bsr hblon switch hbl on bsr sub1 wait on 'ESC' bsr hbloff switch hbl off goon move.l #oldpal,a0 set palette move.l #$ff8240,a1 movem.l (a0),d0-d7 movem.l d0-d7,(a1) move.w oldrez,-(sp) set resolution move.l #-1,-(sp) move.l #-1,-(sp) move.w #5,-(sp) trap #14 add.l #12,sp * line_a $9 mouse on move.l savereg,-(sp) supervisor off move.w #$20,-(sp) trap #1 addq.l #6,sp clr.l -(sp) terminate trap #1 oldrez dc.w 0 savereg dc.l 0 oldpal dcb.w 16,0 ;----------------------------- hblon move.l $120.w,oldtb save all registers move.l $118.w,oldkey move.l $118.w,newkey2+2 move.l $70.w,old4 move.l $70.w,new4b+2 move.b $fffa07,old07 move.b $fffa09,old09 move.b $fffa0b,old0b move.b $fffa0d,old0d move.b $fffa0f,old0f move.b $fffa11,old11 move.b $fffa13,old13 move.b $fffa15,old15 move.b $fffa1b,old1b move.b $fffa21,old21 move.l #contr,a0 and.b #$df,$fffa09 and.b #$fe,$fffa07 move.b (a0)+,d0 cmp.b #21,d0 bne noinst move.l #newtb,$120.w install new vectors move.l #new4,$70.w move.l #newkey,$118.w or.b #1,$fffa07 allow timer b interrupt or.b #1,$fffa13 noinst rts hbloff move.w sr,-(sp) move.w #$2700,sr move.b contr+1,d0 cmp.b #4,d0 bne noex move.b old07,$fffa07 restore all registers move.b old09,$fffa09 move.b old0b,$fffa0b move.b old0d,$fffa0d move.b old0f,$fffa0f move.b old11,$fffa11 move.b old13,$fffa13 move.b old15,$fffa15 move.b old1b,$fffa1b move.b old21,$fffa21 move.l oldtb,$120.w restore vectors move.l oldkey,$118.w move.l old4,$70.w noex move.w (sp)+,sr rts old4 dc.l 0 oldtb dc.l 0 oldkey dc.l 0 old07 dc.b 0 old09 dc.b 0 old0b dc.b 0 old0d dc.b 0 old0f dc.b 0 old11 dc.b 0 old13 dc.b 0 old15 dc.b 0 old1b dc.b 0 old21 dc.b 0 new4 *new level 4 interrupt movem.l d0-d7,-(sp) move.b #0,$fffa1b stop timer move.b distanz,$fffa21 set data register move.b #8,$fffa1b start timer movem.l palette,d0-d7 set first palette movem.l d0-d7,$ff8240 move.l palstart,pal set start values move.l disstart,dis addq.l #1,dis add.l #32,pal movem.l (sp)+,d0-d7 new4b jmp $000000 newkey *keyboard don't disturb!!! move.w #$2500,sr newkey2 jmp $000000 newtb clr.b $fffa1b timer stop movem.l d0/d3-d7/a0-a6,-(sp) move.l dis,a0 move.w #$fa21,a4 move.b (a0)+,(a4) set next distance move.b #8,-6(a4) move.l a0,dis move.l pal,a6 prepare palette movem.l 2(a6),d4-d7/a0-a2 move.w #$8240,a5 move.w 30(a6),d3 move.b (a4),d0 wait on next right border wait cmp.b (a4),d0 beq wait movem.l d4-d7/a0-a2,2(a5) fill registers as fast as you can move.w d3,30(a5) move.w (a6),(a5) add.l #32,pal movem.l (sp)+,d0/d3-d7/a0-a6 bclr #0,$fffa0f end of interrupt rte palstart dc.l palette disstart dc.l distanz contr dc.b 21,4,15,06 pal dc.l 0 dis dc.l 0 distanz dc.b 19,20,20,20,20,20,20,20,240 even palette dc.w $000,$001,$002,$003,$004,$005,$006,$007 *0 dc.w $010,$011,$012,$013,$014,$015,$016,$017 dc.w $020,$021,$022,$023,$024,$025,$026,$027 *1 dc.w $030,$031,$032,$033,$034,$035,$036,$037 dc.w $040,$041,$042,$043,$044,$045,$046,$047 *2 dc.w $050,$051,$052,$053,$054,$055,$056,$057 dc.w $060,$061,$062,$063,$064,$065,$066,$067 *3 dc.w $070,$071,$072,$073,$074,$075,$076,$077 dc.w $700,$701,$702,$703,$704,$705,$706,$707 *4 dc.w $710,$711,$712,$713,$714,$715,$716,$717 dc.w $720,$721,$722,$723,$724,$725,$726,$727 *5 dc.w $730,$731,$732,$733,$734,$735,$736,$737 dc.w $740,$741,$742,$743,$744,$745,$746,$747 *6 dc.w $750,$751,$752,$753,$754,$755,$756,$757 dc.w $760,$761,$762,$763,$764,$765,$766,$767 *7 dc.w $770,$771,$772,$773,$774,$775,$776,$777 dc.w $000,$700,$730,$750,$770,$470,$070,$075 *8 dc.w $077,$057,$027,$007,$507,$707,$704,$777 ;----------------------------- prepare move.w #0,-(sp) set low res move.l #-1,-(sp) move.l #-1,-(sp) move.w #5,-(sp) trap #14 add.l #12,sp move.l $44e,a1 get screenaddress move.l #data,a0 move.w #199,d0 loop *fill screen movem.l (a0),d1-d7/a2-a4 movem.l d1-d7/a2-a4,(a1) movem.l 40(a0),d1-d7/a2-a4 movem.l d1-d7/a2-a4,40(a1) movem.l 80(a0),d1-d7/a2-a4 movem.l d1-d7/a2-a4,80(a1) movem.l 120(a0),d1-d7/a2-a4 movem.l d1-d7/a2-a4,120(a1) add.l #160,a1 dbf d0,loop rts * fill pattern data dc.w $0000,$0000,$0000,$0000,$0FFF,$0000,$0000,$0000 dc.w $FF00,$00FF,$0000,$0000,$000F,$FFFF,$0000,$0000 dc.w $FFFF,$FFFF,$0000,$0000,$0000,$0000,$FFFF,$0000 dc.w $0FFF,$0000,$FFFF,$0000,$FF00,$00FF,$FFFF,$0000 dc.w $000F,$FFFF,$FFFF,$0000,$FFFF,$FFFF,$FFFF,$0000 dc.w $0000,$0000,$0000,$FFFF,$0FFF,$0000,$0000,$FFFF dc.w $FF00,$00FF,$0000,$FFFF,$000F,$FFFF,$0000,$FFFF dc.w $FFFF,$FFFF,$0000,$FFFF,$0000,$0000,$FFFF,$FFFF dc.w $0FFF,$0000,$FFFF,$FFFF,$FF00,$00FF,$FFFF,$FFFF dc.w $000F,$FFFF,$FFFF,$FFFF,$FFFF,$FFFF,$FFFF,$FFFF *----------------------------- sub1 *wait on 'ESC' move.w #7,-(sp) trap #1 addq.l #2,sp swap d0 cmp.b #1,d0 bne sub1 rts