Adding support for interrupts in your programs

When I got back to CPC development in 2008, I wondered at the time how could I achieve to create a slow graphical effect (taking several VBLs) without caring about the music needing to be played 50 times per second. My first attempt was to split the effect along all the VBLs but it quickly became quite complex to maintain.The solution of course for this problem was to use interrupts.

Download

Disclaimer

I'm still learning. In none of the cases I consider myself as an expert wih low-level or system programming on the Amstrad CPC. If you encounter mistakes in this article, please report them to me I will be glad to fix it.While this topic is actually a complex one, the aim is to keep it as accessible as possible to everyone. I won't enter details but go instead directly to the info.

What are interrupts ?

When Z80 execute code, it basically treats evaluate current instruction, execute it, then go to next line of the program, evalute current instruction, execute it,... and does that indefinitively.Z80 has a mechanism to temporarily pause the "go to next line of the program" process. Instead of going next line, it can actually go to a previously-specified line of the program. Once that secondary block of lines to be executed is treated, then the Z80 will execute back the program from interrupted position and execution will continue as if nothing actually occured. That secondary block of lines being executed is called an interrupt.

How interrupts works on Amstrad CPC

On Amstrad CPC, an interrupt occurs 300 times per second (allowing 6 interrupts per VBL).By default, interrupts are enabled. 300 times per second, the Z80 will internally execute the following instructions : di call &38When Amstrad CPC is started up, we see that at &38 adress there is this instruction : jp &B941&B941 adress is part of Amstrad CPC firmware code, that will do internal management of the CPC.To implement our own interrupts, the idea is to redirect the jump from &B941 adress to our own code !Really, it's as simple as that :di ld hl, interrupt_callback ld ( &39 ), hl ei jp $ ; infinite loopinterrupt_callback: ei retThe function interrupt_callback will get called 6 times per VBL.

Digging around the interrupt : disabling/enabling interrupts

di/ei instructions are used to disable/enable interrupts. If an interrupt has to be executed but the Z80 is currently inside a di/ei block, no wonders : the interrupt's execution will be delayed till the ei instruction will be executed.

Digging around the interrupt : preserving state

As mentioned before, to execute an interrupt, the Z80 will actually pause currently executed code, then execute the interrupt, then get the program back on track. Unfortunately, if interrupt's code change values of registers, when leaving the interrupt the registers will be kept as is and that will probably make the program crashes once getting back to interrupted code.A possible solution for that is to keep registers values using stack (don't forget secondary register set too) :interrupt_callback: push af push bc push de push hl push ix push iy exx ex af, af' push af push bc push de push hl

Digging around the interrupt : stack considerations

Be aware in previous example values are stored on stack ; but also, the "internal call" from Z80 to interrupt will make the return adress being pushed to stack (keep in mind that RET is equals to POP HL/JP (HL) - with "HL" preserved).Basically, if your main program being interrupted also makes use of stack it could lead to a conflict. If your program really needs stack usage, simply use DI/EI to disable/enable interrupts for this part of code (which has to try to be as small as possible). That said, because of these considerations about stack usage, it's generally a good idea to let the interrupt code runs its own stack. Example :interrupt_callback: ld ( previous_stack + 1 ), sp ld sp, interrupt_stack_start

Digging around the interrupt : managing 6 interrupts

It's nice to get code called at 300Hz, but it could be more practical to use 50Hz-based code (for a music player, by example). For that, the trick is to implement 6 distincts interrupt code. When the main interrupt code will be called, we will actually increment a small counter, that will be used to jump to the right interrupt code to be executed. That counter will be reseted to zero each 6 times.This is a possible implementation for that, our different interrupt implementation will actually change background/border colors :interrupt_callback:

setColor: ld bc, &7f00 out (c), c out (c), a ld bc, &7f10 out (c), c out (c), a ret

interrupt_0: ld a, COLOR0 jp setColor

interrupt_1: ld a, COLOR1 jp setColor

interrupt_2: ld a, COLOR2 jp setColor

interrupt_3: ld a, COLOR3 jp setColor

interrupt_4: ld a, COLOR4 jp setColor

interrupt_5: ld a, COLOR5 jp setColor

Digging around the interrupt : correct setup

As a reminder, we setup the interrupts like that : di ld hl, interrupt_callback ld ( &39 ), hl eiThis tells that our first interrupt will be starting from next 300Hz interrupt code. It's more convenient from a developer perspective to always consider interrupt_0 to be called as first interrupt, interrupt_1 as second one, etc.For that, I added to the main interrupt code a small "VBL" timer to make sure interrupt_0 is always executed at right position. This can be accomplished like that :ld b,#f5antiVBL:in a,(c)rrajr c,antiVBLVBL:in a,(c)rrajr nc,VBLld b, &f5 in a, ( c ) rrca jr nc, skipInitFirst ; here this is 1st interrupt of VBLskipInitFirst: ; this is not 1st interrupt of VBL

Wrapping up everything : the source

The source is Maxam-compatible, you can directly copy/paste this code in WinAPE emulator if you want (this source is also available as a file at the beginning of this article) :

setColor: ld bc, &7f00 out (c), c out (c), a ld bc, &7f10 out (c), c out (c), a ret

interrupt_0: ld a, COLOR0 jp setColor

interrupt_1: ld a, COLOR1 jp setColor

interrupt_2: ld a, COLOR2 jp setColor

interrupt_3: ld a, COLOR3 jp setColor

interrupt_4: ld a, COLOR4 jp setColor

interrupt_5: ld a, COLOR5 jp setColor

Conclusion

This is the end of this very technical article. There are still rooms for improvements (especially the setup thing to "sync" with the VBL) but the aim of this article was to present you the basics. With that, you are now able to use interrupts in your own programs the easy way !

Powered by Create your own unique website with customizable templates.