Interrupts - wedged code

Basic and Machine Language

Moderator: Moderators

Post Reply
Robbie
Vic 20 Dabbler
Posts: 84
Joined: Tue Aug 11, 2020 4:36 am
Location: England

Interrupts - wedged code

Post by Robbie »

Hello.

I've been having a mess with interrupts, and have managed to wedge my code in by 'poking' to address $0314-5.

This is letting me create at least some semblance of 'multi-tasking'.

Please can someone tell me if there any other methods of handling interrupts / event-driven stuff, or is that our lot?

Many thanks,
Robbie
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: Interrupts - wedged code

Post by Mike »

Wedging in code in ($0314) is a starting point for sure. This vector handles 'normal' IRQs (Interrupt Request(s)) on the 6502.

Normally, that IRQ is called 60 times per second (governed by Timer 1 in VIA #2), and if your wedge continues with JMP $EABF, the rest of the IRQ KERNAL can do its work as normal - keyboard processing and advancing the TI$ clock. You don't need to save the registers A, X and Y, as that has already been done before the original IRQ routine or your wedge are called.

You can enable other IRQ sources and change the interrupt frequency by reprogramming registers in the VIA #2 chip, but that's already advanced stuff. Details are found in the Programmer's Reference Guide.

There's a 'software interrupt', which is generated by the BRK instruction, and which is vectored at ($0316). The normal action is to cause a BASIC warmstart. Some tools, like MINIMON, use this to provide a breakpoint facility. Details again found in the PRG.

There's also the NMI (Non Maskable Interrupt), which normally processes the RESTORE key as one of its sources. The RS232 routines in the KERNAL are another 'customer'. The corresponding vector resides at ($0318) - be warned: getting an own NMI routine working is already very advanced stuff. Even the PRG isn't much help here.

For the beginning, tinkering around with the 60 ticks/second standard IRQ is a good start. If you have more detailed questions, don't hesitate to ask. You find an example here: Display of TI$ in border (unex. or +3K).

Greetings,

Michael
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: Interrupts - wedged code

Post by chysn »

Please can someone tell me if there any other methods of handling interrupts / event-driven stuff, or is that our lot?
That's great for doing things periodically. Also, you can intercept incoming commands (in direct mode or BASIC) with the IGONE vector at $0308 and $0309.

You can also catch the RESTORE key with the NMI vector at $0318 and $0319.

Another interesting thing is redirection of the output vector at $0326 and $0327. I've used this to do things like set multicolor colors for character strings, or redirect the output to memory.

If you take a look at a memory map, you can see all kinds of vectors in RAM that you can play with.
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
Robbie
Vic 20 Dabbler
Posts: 84
Joined: Tue Aug 11, 2020 4:36 am
Location: England

Re: Interrupts - wedged code

Post by Robbie »

It never ceases to amaze me just how much is going on with such a 'simple computer'.

I can see how IGONE and the other vectors nearby would come in handy for creating wAx.

I'm right now reading the PRG chapter on VIA chips, but can't find anything about how to access them, or how they signal an interrupt to the processor. If, for example, if a timer sends a interrupt, where would that appear? On the schematic, the 6522s IRQ pins connect to pin 4 on the 6502, but what the means code-wise...?

Ahhhh... $9110-$911F: https://vic-20.appspot.com/docs/viausage.txt

Is there an interrupt vector for VIAs as for the other stuff though?
Last edited by Robbie on Sun Dec 20, 2020 4:30 pm, edited 1 time in total.
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: Interrupts - wedged code

Post by Mike »

Robbie wrote:If, for example, a timer sends a interrupt, where would that appear?
When an interrupt is triggered by the VIA chip, the corresponding bit in the IFR (Interrupt Flag Register) is set.

The current instruction is finished, and then the 6502 stacks the PC and the status register and enters the KERNAL interrupt handler, vectored over ($FFFE). On this VIC-20, this is a fixed address and points to a routine that checks for either a IRQ or a BRK instruction and then calls the IRQ server with JMP ($0314) or JMP ($0316). The NMI is similarly entered over ($FFFA) and then a JMP ($0318) calls the actual NMI routine.

The KERNAL takes care to acknowledge (i.e., clear) the interrupt, as long as you 'just' wedge in a routine and continue at $EABF. If you replace the KERNAL IRQ handler and/or use different interrupt sources, you need to check which one of them has triggered, and clear only the corresponding bit in the IFR. Which interrupts are active is held in the Interrupt Enable Register (IER) - but as I said, that's advanced stuff.

The PRG is extremely vague in that regard. You'd better take a sharp look into the datasheet of the 6522 for this. Also any good ROM listing is your friend here.
Is there an interrupt vector for VIAs as for the other stuff though?
The VIAs *are* the main source for IRQs and NMIs on the VIC-20.

Besides this, cartridges can also issue IRQs and NMIs, but how to handle those is entirely the business of the cartridge firmware. You definitely don't want to mess around with cartridge registers without good reason.

On the C64, also the VIC-II chip can act as interrupt source (raster beam, sprite collisions, lightpen), but that isn't the case for the VIC-I in the VIC-20.
Robbie
Vic 20 Dabbler
Posts: 84
Joined: Tue Aug 11, 2020 4:36 am
Location: England

Re: Interrupts - wedged code

Post by Robbie »

Mike wrote: Sun Dec 20, 2020 4:30 pm The PRG is extremely vague in that regard. You'd better take a sharp look into the datasheet of the 6522 for this.
Thanks Mike. I did have a look through the 6522 datasheet a couple of weeks ago, and it went over my head. Now that I've done a bit more reading it might make more sense.

The Interrupt Flag Register and Interrupt Enable Register are exactly what I've been trying to find, but somehow overlooked. I shall go and hunt down more info on those and get reading.

I just found this tutorial on 6502 interrupts which seems really useful for my level of understanding, and perhaps for others who may come across this thread at a later date: http://6502.org/tutorials/interrupts.html
User avatar
srowe
Vic 20 Scientist
Posts: 1340
Joined: Mon Jun 16, 2014 3:19 pm

Re: Interrupts - wedged code

Post by srowe »

Robbie wrote: Sun Dec 20, 2020 4:37 pm I just found this tutorial on 6502 interrupts which seems really useful for my level of understanding, and perhaps for others who may come across this thread at a later date: http://6502.org/tutorials/interrupts.html
That is a very good article, there are some specifics to the VIC that need to be considered, e.g. registers are saved to the stack before the IRQ vector is called, but not the NMI vector. It's also takes some care to make use of memory (especially zero page) without interfering with the normal working of the system.
User avatar
R'zo
Vic 20 Nerd
Posts: 514
Joined: Fri Jan 16, 2015 11:48 pm

Re: Interrupts - wedged code

Post by R'zo »

Mike wrote: Sun Dec 20, 2020 2:10 pm
There's also the NMI (Non Maskable Interrupt), which normally processes the RESTORE key as one of its sources. The RS232 routines in the KERNAL are another 'customer'. The corresponding vector resides at ($0318) - be warned: getting an own NMI routine working is already very advanced stuff. Even the PRG isn't much help here.
I've been trying to understand how to run NMIs and I have been finding documentation on the subject confusing. If I understand what your saying then they key to programming an nmi is to hijack a vector and point it at your own code. Since the nmi does not have a preset user vector then you just steal one from an already existing routine.
R'zo
I do not believe in obsolete...
User avatar
srowe
Vic 20 Scientist
Posts: 1340
Joined: Mon Jun 16, 2014 3:19 pm

Re: Interrupts - wedged code

Post by srowe »

R'zo wrote: Thu Dec 24, 2020 9:56 pm I've been trying to understand how to run NMIs and I have been finding documentation on the subject confusing. If I understand what your saying then they key to programming an nmi is to hijack a vector and point it at your own code. Since the nmi does not have a preset user vector then you just steal one from an already existing routine.
The NMI vector is at $0318, you intercept it in the same way as the IRQ vector*. Handling it has some significant differences:

1) Your code is responsible for saving and restoring register state
2) NMI is edge triggered. This means your code *must* handle *all* sources of interrupt as the vector will not be called again until all pending conditions are cleared.

* There's no entirely safe way of replacing the vector, an NMI could occur between writing the two bytes.
The Geek on Skates
Vic 20 Drifter
Posts: 33
Joined: Fri Jul 12, 2019 6:11 am
Website: http://www.geekonskates.com
Occupation: Code Monkey

Re: Interrupts - wedged code

Post by The Geek on Skates »

Granted I'm still basically a noob, and I mainly program in C or BASIC, but one thing I do when I need "multi-tasking" is to have a "step" function. So for example, one thing I'm doing in the game I'm building is something like this:

Code: Select all

uint8_t musicTimer = 0;   // A timer causing a delay between steps, so the notes don't all play at once
uint8_t musicsTep = 0;  // An index, indicating the next step in the song
uint8_t music[] = { 135, 147, 159 };  // This is the actual song,
#define SONG_LENGTH 3  // Just because my "song" here is only 3 notes

void stepMusic() {
    // Update the timer
    musicTimer++;
    if (musicTimer < 255) return;
    musicTimer = 0;
    
    // Move on to the next step
    musicStep++;
    if (musicStep == SONG_LENGTH) {
    	// musicStep = 0;	// if I want the song to loop; I could just do "return" if I want it to stop.
    }
    
    // And play the next step
    POKE(36876U, music[musicStep]);
}

int main() {
    while(true) {
    	handlePlayerInput();	// These 3 functions just demonstrate
    	aiLogic();				// How the main game loop works.
    	updateGraphics();		// No need to actually implement them here lol
    	stepMusic();			// And here we tell it to "take a step" in my music loop
    }
    return 0;
}
This is working well in my game (which is for the unexpanded VIC-20), and honestly the game still runs so fast that I have to throw other delays in my loops to make them playable. Now this does have one major drawback: If there's too much going on in one of the other functions, it can slow down the music. I haven't experienced this yet in the game I'm building now, but I've seen it in C64 games I've built in the past. I've even seen it on the C64 Mario port, and the original Legend of Zelda; too many sprites can slow down the music. But it is a viable alternative to interrupts IMO. Sorry, my 6502 Assembly knowledge is limited to about a dozen instructions, and I have no idea what this code would look like; I know it can't do "functions" as such, but it can do subroutines (and my functions above don't return anything so that works). And I'm sure you could find memory addresses to STA the timer and index (and sound track if using it for music).

PS: Not to go off-topic (too much :D), but how did you guys learn to handle interrupts? Just because what I'm doing works doesn't mean it's the best way, and I can still do inline Assembly with cc65 so this is something I've been wanting to learn. I can post in another thread if u like lol
User avatar
srowe
Vic 20 Scientist
Posts: 1340
Joined: Mon Jun 16, 2014 3:19 pm

Re: Interrupts - wedged code

Post by srowe »

The Geek on Skates wrote: Thu Jan 14, 2021 7:22 pm PS: Not to go off-topic (too much :D), but how did you guys learn to handle interrupts? Just because what I'm doing works doesn't mean it's the best way, and I can still do inline Assembly with cc65 so this is something I've been wanting to learn. I can post in another thread if u like lol
Mostly by looking at other people's code and then trying write my own. In the old days that was very frustrating, any mistake results in a lockup. Today being able to run code in an emulator lets you explore the result of a hang and determine the cause.
I'd recommend learning more about the 6502 and machine code in general, it'll help remove some of the mystery about what's possible and why you need to perform certain actions. The hardest area is the workings of the VIA, there are details I've only just got to fully understand relatively recently.
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: Interrupts - wedged code

Post by chysn »

The Geek on Skates wrote: Thu Jan 14, 2021 7:22 pm Now this does have one major drawback: If there's too much going on in one of the other functions, it can slow down the music. I haven't experienced this yet in the game I'm building now, but I've seen it in C64 games I've built in the past. I've even seen it on the C64 Mario port, and the original Legend of Zelda; too many sprites can slow down the music. But it is a viable alternative to interrupts IMO.
If that framework seems to work at all, it's by accident or luck. If your music player steps every n iterations (255 in your example), what happens if the introduction of a new element adds processor cycles to the main loop? If you add another enemy element to display and move around, you could easily add 100 processor cycles to execution. This means 100 times n extra processor cycles need to be run before the next note. 25500 at 1MHz is ~1/50s, which will certainly get noticeable after a few additional elements.

You can, like you say, try to compensate by scattering other delay loops around. But that's certainly not a viable alternative to interrupt-driven code, especially in the unexpanded VIC, in which concise code is the difference between doing what you want and cutting features.

The value of the interrupt is that it doesn't much matter (within reason) how long graphics and AI routines take. Things that need to happen at a regular time interval, like music, will march politely along at the proper tempo, and you can forget about such things in the rest of your development.

And anyway, you're already on the right track. Your stepMusic() subroutine will look exactly the same whether it's called from an interrupt service routine or a main loop. You'll want to count to a smaller number than 255 (the default IRQ is every 1/60s), but everything else will be the same.

Side note: Watch out for buffer overread here:

Code: Select all

if (musicStep == SONG_LENGTH) {
and consider >=, which will work whether you return or reset.
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
Post Reply