Vol 2 Issue 7
Advanced raster interrupt
programming
or: How do I get more than 16 colours at
once
by Udo of TEX
The problem: To get more than 16 colours on the screen at once, you must change the color palette 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 system waits for the Vertical Blank Interrupt before the palette is set. For example, we'd have to change the colour palette in the middle of the screen to be able to use 16 other colours in the lower half of the screen. And, of course, we'd have to put the previous colour palette after the VBL so that the upper half of the screen uses the first 16 colours 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 colour 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 interrupt vector, so that the interrupt routines 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 interrupt mask $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 colours 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, takes 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 preceeding line and the colours can be changed while the electron beam is going back to the beginning of the next line. If the colours 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 four 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 | ;interrupt vektor |
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 colour palette |
movem.l | 2(a6),d4-d7/a0-a2 | ;Load colours 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 colour 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 starts flickering, the colours 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 colours the electron beam is already too far to prevent flickering. The colours 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:
One tolerates the flickering and does nothing about it (absolutely out of the question in our demos!)
One switches off the hazardous interrupts
(like we did in our TEX Demos 1-3)
Remark: Hazardous are Timer C, that are
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 distance 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.
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: 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)
This text was published in the Atari ST diskmag "ST News" and is used by kind permission of Richard Karsmakers. Source for this article: http://www.st-news.com |