0% found this document useful (0 votes)
21 views13 pages

c64 Interrupts Eng Part06

Uploaded by

olivier bernhard
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
21 views13 pages

c64 Interrupts Eng Part06

Uploaded by

olivier bernhard
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 13

RetroProgramming Italia - RP Italia presents :

Interrupts C64 tutorial – Part 6


(Raster Interrupts 3)
By
Attilio Capuozzo – Founder of RetroProgramming Italia – RP Italia
Facebook Group
& Antonio Savona – C64 GameCoder & DemoCoder

with the collaboration of


Olivier Bernhard - External collaborator "RetroProgramming
Italia - RP Italia".

We will now propose a slightly modified version of the interrupt routine


presented in part 5, which is reproduced below for the convenience of readers:
Let's briefly summarize what this routine does:
- When the raster beam reaches the raster line $08, a raster interrupt
is triggered and the associated interrupt handler (named irq00)
performs the following operations:

• Changes the color of the screen edges and the display area to
white
• Schedules a new raster interrupt at raster line $96 with irq01
as associated interrupt handler.
- When the raster beam reaches the raster line $96, a raster interrupt
is triggered and the associated interrupt handler (named irq01)
performs the following operations:

• Changes the color of the screen edges and the display area to
black
• Schedules a new raster interrupt at raster line $08 with irq00
as associated interrupt handler.

The result that we obtain is the separation of the screen (border +


display window) in two zones of color, one upper white and the other
lower black, separation which occurs in a fixed way at the screen line
$96 (150).
We could now decide to change the raster line where the raster
interrupt irq01 is triggered, at each new scan of the screen.
This way, the raster interrupt irq00 will always be triggered at raster
line $08 while the raster interrupt irq01 will be triggered at a raster
line that is no longer fixed, but variable, within a certain range in
order to produce an animation effect.
This is what we are going to do by showing how with few modifications
we can introduce this different behavior:
In the modified program, we first defined as the first interrupt
handler irq00 followed by irq01 (contrary to what happened in the
original routine). Also note that we have eliminated the constant
IRQSPLIT because, as we said, the irq01 no longer latches at a fixed
position.
Let's now look at the changes made to the raster interrupt handler
irq00:

In register $d012, i.e. the 8 least significant bits of frame register $d012-
$d011, we no longer load the constant IRQSPLIT as a frame line comparison
parameter for irq01, but we read the values of the irq_table created with
the .fill directive of Kick assembler :

.fill 128,144.0 + 64.0 * sin(i * PI * 2 / 128.0)

Let's try to understand how this directive works:

.fill 128,144.0 + 64.0 * sin(i * PI * 2 / 128.0)

This means that we will generate 128 values (bytes)

.fill 128,144.0 + 64.0 * sin(i * PI * 2 / 128.0)

This determines how each of the 128 values is calculated. Each of the 128
values is indexed by i, with i varying from 0 to 127 (128 values).

i=0 value= 144.0 + 64.0 * sin(0 * PI * 2 / 128.0) = 144

i=1 value= 144.0 + 64.0 * sin(1 * PI * 2 / 128.0) = 147

i=2 value= 144.0 + 64.0 * sin(2 * PI * 2 / 128.0) = 150

i=3 value= 144.0 + 64.0 * sin(3 * PI * 2 / 128.0) = 153

...

...

i=127 value= 144.0 + 64.0 * sin(127 * PI * 2 / 128.0) = 141


we obtain the table below:
irq_table :
.byte 144,147,150,153,156,160,163,166,168,171,174,177,180,182,185,187
.byte 189,191,193,195,197,199,200,202,203,204,205,206,207,207,208,208
.byte 208,208,208,207,207,206,205,204,203,202,200,199,197,195,193,191
.byte 189,187,185,182,180,177,174,171,168,166,163,160,156,153,150,147
.byte 144,141,138,135,132,128,125,122,120,117,114,111,108,106,103,101
.byte 99 , 97, 95, 93, 91, 89, 88, 86, 85 ,84, 83, 82, 81, 81, 80, 80
.byte 80 , 80, 80, 81, 81, 82, 83, 84, 85 ,86, 88, 89, 91, 93, 95, 97
.byte 99 ,101,103,106,108,111,114,117,120,122,125,128,132,135,138,141

In the end, this directive allows us to simply calculate 128 raster line
values that vary between 144+64 and 144-64 according to the sine function
indicated above.

Now let's see how to define, in the code, the raster line for the raster
interrupt irq01 using the "SELF MODIFYING CODE" technique.

At the first execution of the irq00, on line 45 of the code, we load with
the immediate addressing the value 127 in the X register of the 6510.

On line 46, we load a value from the irq_table into Y using the contents of
the X-register as an index (absolute indexed addressing) and on the next
line, we store the value of the table in $d012 to define the next raster
interrupt served by the interrupt handler irq01.

on line 48, we find one of the 2 key instructions of the "SELF MODIFYING
CODE": dec idx+1.

The idx label corresponds to the address where the opcode of the ldx
instruction on line 45 is located; the next byte, idx+1, contains the value
127.

Thus, with the decrementing performed on line 48, we only decrement by 1 the
index X of the array starting with the value 127.

As long as the content of the address idx+1 is >=0, we continue the execution
of the program starting from line 52 (BPL instruction at line 49).
When the index - following the successive decrements - reaches the value $00,
an additional "DEC" causes a change in the value contained in the idx+1
location which then becomes equal to $ff ($00-$01=$ff). From then on, the
condition of the conditional jump bpl (line 49) will no longer be verified
since the MSB of the location will be fixed and consequently change, the N
flag of the status register setting it to 1.

Remember that the status register flag N (also called sign bit) does nothing
more than copy the status of the most significant bit (MSB) of the location
concerned, whether the content is a complement to 2 (signed) or an unsigned
number.

To bring the table index back to 127, on line 50, we load 127 into the
Accumulator, whose contents is then stored in the idx+1 address at line 51.

So, line 51 uses again the "SELF MODIFYING CODE".

We could also have not used the "SELF MODIFYING CODE" technique, but in this
case, we would have had to define an additional variable in memory to store
the value of the index of the irq_table, which would have lost 1 byte. The
code would be as follows:

Although the savings in this particular example may seem minimal and perhaps
negligible - we remind readers that our objective here is essentially didactic
- there are many other cases in which the above technique proves to be
fundamental to obtain savings in terms of memory and cpu cycles (compact and
faster code).
Here are some screenshots of the execution of the modified routine :
The complete code of the modified program is below:

.pc = $c000 "main"


sei
lda #$7f
sta $dc0d
sta $dd0d
bit $dc0d
bit $dd0d
lda #$01
sta $d01a
lda #$08
sta $d012
lda #$1b
sta $d011
lda #<irq00
sta $0314
lda #>irq00
sta $0315
cli
rts

irq01:
lda #0
ldx $d012
!: cpx $d012
beq !-
sta $d020
sta $d021
lda #<irq00
sta $0314
lda #>irq00
sta $0315
lda #$08
sta $d012
lsr $d019
jmp $ea31

irq00:
lda #$01
sta $d020
sta $d021

lda #<irq01
sta $0314
lda #>irq01
sta $0315

idx: ldx #127


ldy irq_table,x
sty $d012
dec idx + 1
bpl !ok+
lda #127
sta idx + 1
!ok:

lsr $d019

jmp $ea81

irq_table:
.fill 128,144.0 + 64.0 * sin(i * PI * 2 / 128.0)
Before showing other examples of application, we think it is necessary to
clarify some theoretical aspects encountered previously.

When we dealt with the memory accesses made by the VIC, in particular for
displaying text or graphics, we mentioned the accesses to the video memory
for reading Screen Codes (at least in Text Mode).

On this occasion, we have used the terms "screen memory" and "video matrix"
as synonyms for "video memory".

Nevertheless, based on the memory mapping of Commodore 64 (Fig. 1), it is


necessary to differentiate between these two terms.

Figure 1 : Difference between screen memory and video matrix (extract from "Mappa di Memoria del C64")

In particular, the screen memory is a 1K (1024 byte) area of RAM with an


address range from $0400 to $07ff and also includes the 8 SPRITES data
pointers mapped to addresses $07f8 to $07ff.

The video matrix represents the first 1000 bytes of screen memory (default
from $0400 to $07e7) where, in text mode, 1-byte screen codes are stored.
These are indexes or "pointers" to a "Character Set" which contains the binary
description of the shape of all the characters that can be displayed on the
screen (display window).

The "Character Set" can be in ROM if we use the standard C64 characters, in
which case we will use the term "Character Generator ROM" (or more briefly
"Char ROM") or, in the case of user-defined characters, in RAM memory; in
the latter case, we can speak of "User-Defined Character RAM".

The original data sheet of the graphic chip of the VIC-II indicates the above-
mentioned character set with the term "Character Base".

In practice, however, both terms - screen memory and video matrix - can in
principle be considered equivalent and therefore interchangeable.
Still on the subject of the additional memory accesses made by the VIC during
a BAD LINE, we said that in addition to the accesses to the video matrix,
the VIC also reads the color information - 4-bit color code, Fig. 2 - of the
color RAM between the $d800 and $dbff addresses.

Figure 2: C64's 16-color palette (Color nibble)

The "color RAM" is a fixed memory area that cannot be moved by the user,
unlike the screen memory.

It should be noted that accesses to color RAM do not subtract CPU clock cycles
because the VIC-II has four data pins (i.e., the VIC's 12-bit data bus pins)
connected directly to the color RAM chip (Fig. 3).

Figure 3: VIC-II data pins 35 to 38 (DB11 to DB8) connected to "color RAM".


This way, the VIC-II can always read the color RAM, whether or not it is in
its 16K address space (14-bit VIC address bus).

The color code will be stored in the four most significant bits of the VIC's
data bus (DB8 to DB11) in both Text Mode - Standard (also called Hires or
Normal or Monocolor), Multicolor and Extended Background (ECM) - and Bitmap
Multicolor mode.

The remaining 8 bits of the graphics chip's data bus (DB0 to DB7) can contain
either the value of the screen code - in Text mode - or, in Bitmap mode,
additional color information (Figs. 4, 5 and 6).

Figure 1: source of colors in text mode

Figure 5: Color source in Extended Background text mode (or ECM: Extended Color Mode). Bits D6 and D7 in the
figure refer to the 2 most significant bits of the character code.

Figure 6: Color source in Bitmap mode


Let's now show a first example of an application where we make a simple
horizontal scrolling of the text with a raster interrupt.

As usual, the source code is accompanied by detailed explanatory comments


that greatly facilitate understanding. Other comments are reported after the
source of the Assembly routine.

// Scroll Text
// Horizontal text scrolling (HSCroll) Register of $d016 of VIC (bit 0-2)
// By Attilio Capuozzo for "RetroProgramming Italia - RP Italia".
// KickAssembler Version

* = $c000

.label NMIRoutine = $fec1 // The address of the last instruction of the default NMI Handler ($fe47) corresponding to the RTI instruction.

Sei // temporary deactivation of maskable IRQ-type interrupts


lda #<IRQRoutine // Changing the IRQ vector of the CINV RAM ($0314-$0315) pointing to the system IRQ manager by default ($ea31)
sta $0314
lda #>IRQRoutine
sta $0315
lda #$7f // 127 dec-->%01111111 = Binary mask to reset the MSB of the ICR of the 2 CIAs and to set the first 7 bits
// ( Deactivation of all interruption sources )
sta $dc0d // deactivation of the IRQ interrupts of the CIA 1 and in particular the system interrupt, i.e. the IRQ interrupt of Timer A
// (triggered every 1/60 seconds), to prevent the display from flickering.
sta $dd0d // deactivation of all CIA2 NMI interrupts
lda $dc0d // Removal of any previous Interrupt Request Indicator (IRQ) from the CIA1 (Reading resets the register)
lda $dd0d // Removal of any previous Interrupt Request Indicator (IRQ) from the CIA2 (Reading resets the register)
lda #$00 // First raster interrupt at line $00
sta $d012 // storage of the value in the raster register
sta $fb // Horizontal Scroll Counter Variable (stored in Zero Page)
lda #$1b // Decimal (27)
sta $d011 // Reset the MSB of $d011, i.e. bit 8 of raster register $d012-$d011
lda #$01
sta $d01a // enable raster interrupt signals from VIC
cli // Resetting the interrupt disable indicator, i.e. reactivating IRQ interrupts (maskable interrupts).
ldx #<NMIRoutine // We load in the NMINV vector ($0318-$0319), in the little-endian low byte (LSB)-high byte (MSB) format, the $fec1 address
ldy #>NMIRoutine // which corresponds to the last instruction, the RTI, of the default NMI Handler ($fe47).
stx $0318 // With this trick, we "deactivate" the use of the key combination RUN STOP + RESTORE.
sty $0319 // with which the user could force the exit of the program.
rts
IRQRoutine:
asl $d019 // Acknowledgement of the raster interrupt
bit $d012 // We copy the MSB (bit 7 or more significant) of $d012 into flag N of the status register.
bmi nextRaster // If MSB is set, then the Rasterline is >127, then we switch to the "nextRaster" label to handle raster line 154 (9a)
ldx $fb // Load the value of the Horizontal Scroll Counter variable into X
inx // The value of X is increased (ONLY the first time the reading of the first value in the table is skipped, i.e. 8).
cpx #$0d+1 // If X=14 ($d0e=$d0d+1) then we have already read the last value from the scrollValue table (14 values).
bne scrolling // If we are not at the end of the table, we jump to the "scrolling" label WITHOUT resetting the counter.
ldx #$00 // We reset the counter to zero if we have reached the last value in the table.

scrolling: // Raster Interrupt Management Label at line 0 (upper half of the screen with "smooth" horizontal scrolling effect)
lda scrollValue,x // Reading the horizontal scroll value of the table
sta $d016 // Store the value of horizontal scrolling in bits 0-2 (values from 0 to 7) of $d016.
stx $fb // Save the counter in memory
lda #$9a // raster line of the next raster interrupt: line 154 (9a)
sta $d012
jmp testTimerAInterrupt

nextRaster: // Managing the lower half of the screen at the raster line 154 (9a)
lda #$08
sta $d016 // We load the default value (08) in $d016 which corresponds to the absence of Horizontal Scrolling (bit 0-2=0).
lda #$00 // raster line of the next raster interrupt: line $00
sta $d012

testTimerAInterrupt:
lda $dc0d // Let's check if a system interrupt request has been generated in the meantime (IRQ of Timer A of CIA1).
and #$01 // So let's check if bit 0 (LSB) of $dc0d is set(=1)
beq exitPullStack // If an IRQ from Timer A is NOT signalled (i.e. bit 0=0 of $dc0d), we exit normally.
jmp $ea31 // otherwise (bit 0=1 of $dc0d) we make a JMP to the system interrupt handler ($ea31) for the Keyboard Scan etc.

exitPullStack:
pla // The following instructions occupy 6 bytes and are the equivalent of Jmp $ea81 or Jmp $febc
tay // the entry points (pointing to the last 6 bytes) of the system interrupt handler (IRQ-->$ea31 and NMI-->$fe47)
pla // Extraction of the stack from the Y, X and A registers + return from interrupt
tax // Pushing to the stack of the 6510's 3 data registers is performed by the system's main IRQ interrupt handler
pla // mapped to ROM address $ff48 and vectorized by the $fffe-$ffff locations of the Kernal ROM (last 8K of the C64 memory card).
rti // The main system IRQ interrupt handler ($ff48) is executed BEFORE the system IRQ handler ($ea31) vectorized in RAM
// by $0314-$0315 (CINV).

scrollValue: // Table of the 14 horizontal scroll values of register $d016 from (0 to 7) + 8 (default value of register $d016)
.byte 8,9,10,11,12,13,14,15
.byte 14,13,12,11,10,9
First of all, we note the trick used in the Setup to disable the forced exit
of the program by pressing the RUN/STOP and RESTORE keys simultaneously:

ldx #<NMIRoutine
ldy #>NMIRoutine
stx $0318
sty $0319

Remember that by pressing the RESTORE key, you trigger an NMI interrupt.

By loading in the NMI Vector ($0318-$0319) the address $fec1 - corresponding


to the NMIRoutine label declared at the top of the Routine (.label directive)
- we make our NMI Handler execute only one RTI instruction which is equivalent
to ignoring the RESTORE key!

$fec1 corresponds, in fact, to the address of the last instruction (the RTI)
of the System NMI Handler which starts at position $fe47 and is vectorized
by $0318-$0319.

At the beginning of the IRQ Handler IRQRoutine, just after the ACK of the
IRQ Raster Interrupt, we use the BIT (Bit Test) instruction to test the high
bit (MSB) of $d012 via a BMI (Branch if MInus) conditional jump instruction:

bit $d012
bmi nextRaster

We know from the ISA (Instruction Set Architecture) documentation of the 6510
that the BIT instruction performs a volatile AND between the accumulator and
a memory location, i.e. it does not store the result of the above logical
operation in register A.

In addition, the instruction also copies bits 7 and 6 of the operand - the
memory location - into the N (negative) and V (oVerflow) flags, respectively,
of the status register.

And it is this feature that allows us to execute the branching of the next
instruction (BMI), with a test on flag N of the status register.

In testTimerAInterrupt, we read the contents of $dc0d, the Interrupt Control


Register (ICR) of the CIA1, to check whether an IRQ interrupt request has
been generated by the Timer A of the CIA1 - the system interrupt is usually
triggered every 1/60 seconds - and then possibly serve it via a JuMP at $ea31,
i.e. the system's IRQ manager:

testTimerAInterrupt:
lda $dc0d
and #$01

We would like to point out that although all CIA1 interrupt sources were
previously disabled in the:

lda #$7f
sta $dc0d
the interrupt flag of Timer A - bit 0 of $dc0d - will also be set in case
the interrupt condition occurs (Underflow of Timer A of CIA 1 as already
seen) but the request will NOT be served and the IRQ interrupt will NOT be
triggered.

The last point we intend to raise is the following:

exitPullStack:
pla
tay
pla
tax
pla
rti

The above instructions which, in order, retrieve from the stack the 3 data
registers Y, X and A of the 6510 (followed by a RTI) correspond to the last
6 instructions of one of the 2 system interrupt handlers - IRQ or NMI - and
are often replaced by an equivalent JMP $ea81 or JMP $febc.

That's all folks!

Attilio Capuozzo – Founder of "RetroProgramming Italia – RP


Italia Facebook" Group
& Antonio Savona – C64 GameCoder & DemoCoder
with the collaboration of
Olivier Bernhard - External collaborator "RetroProgramming
Italia - RP Italia".

You might also like