Interrupts & Multitasking

Introduction

This is a long overdue article about how to use interrupts with BV Forth. The basic mechanism has been in BVF for a while but it has never been publicised and so probably not used. The LPC2xxx range of ARM microcontrollers has a vast mechanism to choose from when deciding on an interrupt scheme. BVF uses the vectored interrupt scheme but not in a way that you might expect.

Multitasking is an extension of an interrupt scheme in that a round robin scheduler can be created by using an interrupt from a timer. Not the cleverest of multitasking schemes but non the less it works and works well.

The resources for this article are in in this menu to the left and the source code is written for BVF version 1.205.

Approach

There are several ways to tackle interrupts when using a high level language. The simplest method is to give direct access to the interrupt vectors. Upon interrupt, all the memory and key system variables required are swapped out and the interrupt can be service, if the service is required in high level then another virtual machine is required to service the interrupt, at the end all of this has to be undone. This is a considerable overhead for this level of processor and does not bring with it any benefits.

What is needed is a simple, easy to use method that does not consume too much processor resource. The method used, I have used before in other languages (TCB). It provides a simple flexible scheme and is possible because all Forth words end with a word called EXIT. This is the key to the interrupt scheme used, the process is as follows:

  1. An interrupt is generated, this is handled by low level code and places a request in an interrupt queue, the interrupt is then cleared.
  2. Exit checks to see if there is an interrupt request before leaving the currently executing word, if there is it executes the word before leaving.

There are two main disadvantages with this scheme 1) only Forth words work with the interrupt mechanism and 2) there is a delay between requesting the interrupt and it being carried out that cannot be determined. The first item is a limitation when using low level words, under normal circumstances this will not happen as all words eventually end up with a Forth word. In fact KEY and KEY? have been written as Forth words rather than low level words for the benefit of this scheme.

The second limitation may or may not be important depending in the application. A Forth word on the BV511 executes in about 1uS, this is a big generalisation as all words do different things but 10,000,000 FOR NEXT takes about 10 seconds. Once an interrupt request is made, say by an external interrupt then the current executing word must get to exit and run the interrupt. This process can take from a few microseconds to some other value, the other value is indeterminate but an educated guess would be somewhat less than 1ms.

It would be interesting to see if a Sony IR remote control could be decoded using this interrupt scheme as it has a time base of 550uS.

How it Works

At the start of ram, normally 0x4000000 there are two words followed by a further 28 words (words=32 bit). There are used for the interrupts and HERE starts after those, normally at 0x40000088, new words are compile starting at this address.

When an interrupt occurs a low level routine stores a flag at the first address, calculates what caused the interrupt (timer, external etc) and stores that number at the same address. The second address is not now used but shows what the last interrupt was, this may be useful for debugging. It is important to realise that this is simply a request and not an actual interrupt yet. The low level routine also clears the interrupt so that it is not called again unless directed to do so by the user.

EXIT checks the first word and if flagged looks at the number and executes the word given as an offset by the number. In other words if the number is 4 it will execute the forth word in the interrupt table.

[x] &40000000 (&0 ) ?
[x] &40000004 (&0 ) ?
[00] &40000008 (&5010 ) DEFINT
[01] &4000000C (&5010 ) DEFINT
[02] &40000010 (&5010 ) DEFINT
[03] &40000014 (&5010 ) DEFINT
[04] &40000018 (&40000898 ) T0INT
[05] &4000001C (&5010 ) DEFINT
[06] &40000020 (&5010 ) DEFINT
[07] &40000024 (&5010 ) DEFINT
[08] &40000028 (&5010 ) DEFINT
[09] &4000002C (&5010 ) DEFINT
[10] &40000030 (&5010 ) DEFINT
[11] &40000034 (&5010 ) DEFINT
[12] &40000038 (&5010 ) DEFINT
[13] &4000003C (&5010 ) DEFINT
[14] &40000040 (&5010 ) DEFINT
[15] &40000044 (&40000A6C ) P3INT
[16] &40000048 (&5010 ) DEFINT
[17] &4000004C (&5010 ) DEFINT
[18] &40000050 (&5010 ) DEFINT
[19] &40000054 (&5010 ) DEFINT
[20] &40000058 (&5010 ) DEFINT
[21] &4000005C (&5010 ) DEFINT
[22] &40000060 (&5010 ) DEFINT
[23] &40000064 (&5010 ) DEFINT
[24] &40000068 (&5010 ) DEFINT
[25] &4000006C (&5010 ) DEFINT
[26] &40000070 (&5010 ) DEFINT
[27] &40000074 (&5010 ) DEFINT
[28] &40000078 (&5010 ) DEFINT

This is the table that is at the start of RAM as produced by INTR. The first number in square brackets is the offset, the second number is the actual address, the third number in round brackets is the execution address of the word and the last item is the word that will be executed. The position of the words in the table are IMPORTANT these relate to the bit positions given in the VIC registers, see chapter 5 of the LPC user handbook. As an example position 4 [offset 4] relates to timer 0 which is bit 4 in the VIC registers. The point of this is that the low level routine that leaves a flag behind for EXIT calculates the offset from the type of interrupt it receives and EXIT uses this offset to execute the word at that offset.

Setting Up and Interrupt

This is a two stage process, first the table above needs an entry to the word that is executed when an interrupt takes place and second to arrange for the actual interrupt itself.

External Interrupt Example

The basic words for using the interrupt scheme is in the interrups.flb library. This should be extended by your own code. An example of doing this can be found in timer0-example.fth. All this does is to cause text to be printed to the screen every 15 seconds or so.

All of the interrupt schemes need three parts:

  1. Something to set up the interrupt in the first place and activate the hardware, this is done with setT0
  2. Something that the actual interrupt does when it gets interrupted, this is done by T0int
  3. Something to initialise the interrupt table with the word (T0int) required, this is done by T0start

To use the example, download timer0-example.fth and load it into the BV511 using BV Terminal 3, the interrupt library will be picked up from this web site. Use look to see the list of interrupt vectors in RAM before and after T0start.

You can see from this that the code field address of T0int has been placed in slot 4, slot 4 is timer 0 interrupt so that when the timer interrupts it is this word that will get executed

To recap, creating an interrupt involves the following steps:

  1. Create a word that runs when an interrupt is called, this may need to reset or clear flags in the VIC register.
  2. Place the execution address of this word in the table using INT> or INT>>. The offset you use for this will determine what kind of interrupt it is.
  3. Create a word to set up the registers so that an interrupt will occur as required.

CFA

It may be worthwhile spending a little time talking about Code Field Addresses (CFA). The CFA is the address that gets executed in order to run the word. The CFA can be obtained in 3 (probably more) ways:

  1. using ' e.g. ' here
  2. using find e.g. s" here" find
  3. using ['] e.g. ['] here

Try the first one, here just simply returns the next free dictionary space, so ' here execute will do the same as typing here. The second two can be used within a word, find takes the address given to it and searches the dictionary for that word. It will return a value on the type of word and success. The ['] is slightly more complex in that at RUN time (when the word has been invoked, rather than being compiled) it places the CFA of the following word on the stack, just what is needed for int>>.

The CFA is used extensively for interrupts and multitasking as it is this address that can be easily manipulated and executed.

Multitasking & Scheduling

It is just a short step from interrupts to create a multitasking environment. This can be as easy or complex as required. The technique used here is to have a list of CFA in memeory and executye them each time timer 0 fires. The words in the schedule libraries (rr-sed & b-shed) are mostly concerned with manipulating this list.

The actual scheduler is presented with a list of addresses, i.e

[&40000120]
[&40000350]
[&0140]
[&0]
[&0]
.
.

This list can be added to using s-add, like this ' my-word s-add, or words can be removed by using s-del. At start up the list is set to all 0, s-clr makes sure this is the case and should be used in start up code. A 0 address indicates to the scheduler that this is the end of the table. Another refinement has been added in that tasks can be paused, not removed form the table but ignored by the tasker. The pause works by setting bit 0 to a 1. This is a none address on the ARM so words will never have an address ending in 1 (thumb code is not used). Using ps. the word is listed in the table with (S) in front.

To see an example of using a tasker, download the example (shed-example) and load it using BVT. All it does is flash an LED connected to p1.16 on and off in the background. Other words can be added very easily by using s-add. As previously mentioned there are two almost identical schedulers, they differ only in what happens when timer 0 interrupts which is taken care of by T0int.

rr-shed: The T0int of this file will execute only one word each time it is called, when it gets called again it will move onto the next word in a round robin fashion.

b-shed: The T0int of this word will execute all of the words in the table each time it gets called.

Both of these are there to illustrate what can be done. It would now be a short step to introduce priorities and other interesting schemes. Have fun.