|
||
INTERRUPTS
See INTERRUPT
JUMP TABLE for interrupt addresses, AVOID INTERRUPTS WHEN POSSIBLEInexperienced real-time programmers often think that interrupts are essential, for example that an interrupt should always occur every time a particular event happens. This might be necessary but interrupts are more difficult to debug and mutual interference of different interrupts due to inadequate safeguards may only show up problems in your program years after the design. The advice to someone who is still learning real-time design must be to avoid all interrupts on whatever microprocessor. In some safety-critical applications, for example in certain railway uses, interrupts in programs are banned. Each job is different, however, and there are occasions where you must use interrupts. Having said that, the TDS9092 has 10 possible interrupts and they can all be used in one program if you wish! A program written in assembler or high level Forth can be tied to each. TDS9092 is particularly suitable for applications where interrupts are necessary and the next section may help you decide. EXAMPLE PROBLEMThis example demonstrates how an application apparently needing interrupts could probably be done better without them. A particular application needs a keyboard and LCD display. The system counts items on a production line conveyor belt and shows the number on the LCD. One solution is to have a main program for the LCD and keyboard and to interrupt the TDS9092 when an item arrives. The essence of the main program is:
: DISPLAY #ITEMS @ ( get number of items ' LCDEMIT CFA 'EMIT ! ( revector EMIT to LCD 0AT 6 U.R ( show qty on LCD ' <EMIT> CFA 'EMIT ! ; ( back to serial port : WORK ( word executed at power-up INITIALISE BEGIN BEGIN DISPLAY NEWKEY -DUP UNTIL PROCESSKEY AGAIN ;
Here we will not concern ourselves with the detail of PROCESSKEY or INITIALISE . Perhaps a certain key will cause the number of items counted in the variable #ITEMS to be sent down the serial link to another computer for example. The interrupt program is:
: TALLY 1 #ITEMS +! RETURN;
This increments a variable #ITEMS when an external interrupt arrives and then we return from interrupt back to the main program WORK shown above. It remains to establish WORK as the main, or foreground, task and TALLY as the background task. The former is done using the word SET as shown in the section STAND-ALONE SYSTEMS and the latter, of concern to us here, is done with ASSIGN . Include the following, most conveniently done at the end of the program listing:
IRQ1 ASSIGN TALLY ( associates TALLY with the ( interrupt IRQ1 at this address SET WORK ( alters cold start parameters to ( cause power-on entry into WORK
Note that they must be in this order; SET is always the last line of a system that will be put in PROM. Having seen the outline of how to approach this counting problem using interrupts, note that it might have been possible to do it without them:
: TALLY ?ITEM IF 1 #ITEMS +! THEN ; : WORK INITIALISE BEGIN BEGIN DISPLAY TALLY NEWKEY -DUP UNTIL PROCESSKEY AGAIN ;
This works because the program spends most of its time in the middle BEGIN . UNTIL loop. As long as PROCESSKEY does not take longer than the time between items arriving on the conveyor the second, non-interrupt, solution would be the better one. The inner BEGIN . AGAIN can be called a 'flash around loop' because you design it to be as quick as possible, diverting to do other functions, or part of a function, for only short times. Interrupts can be written either in assembly language or, as above, in high level Forth. These are considered in turn. EXTERNAL INTERRUPTSNote that IRQ1 and IRQ2 are level operated; an interrupt will occur if the input is low. This means that your negative going input pulse must be narrow, in fact shorter than the execution time of the interrupt, otherwise you'll get more interrupts when the first one finishes. On the other hand NMI is edge operated (negative edge) and has no such restriction. INTERRUPTS USING ASSEMBLY LANGUAGEThe Forth system ROM contains vectors to internal RAM at which you can put your own jump table. There are three bytes each to hold machine-code jump instructions and the full INTERRUPT JUMP TABLE is available. This table is at the start of the user's RAM/PROM area and so once set will be blown into the application PROM along with the program. It is instructive to look at this area in a TDS9092 after power-up. HEX 0800 20 DUMP gives:
0800 3B 3B 3B 3B 3B 3B 7E E8 4E 3B 3B 3B 7E E8 A3 3B 0810 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 80 0
The jump table is mainly preset to 3B, which is the Return From Interrupt RTI instruction. Two interrupt jumps are pre-configured and these are marked in the Interrupt Jump Table. We can see them in the above memory dump. Each jump has three bytes. The first is 7E and the next two give the absolute address that will be jumped to when the interrupt occurs. When setting the jump table you must make sure that this address will start a string of machine code and that eventually an RTI instruction will cause a return from interrupt. One preset interrupt is the TRAP which gives the message CRASHED MACHINE if the microprocessor loses control, and the other is TOI timer 1 overflow interrupt which is set to keep time and date. You are free to set up the jump table as needed, including re-assignment of these two. Next is an example of an assembler interrupt routine. Apart from being an example, it is a useful one to know. In the 63B01Y0 microprocessor there is a free-running counter which overflows every 53.33ms. This routine maintains a variable which is incremented on each counter overflow. Together the two give a 32-bit timer with a resolution of about 814ns and a full range of about 58 minutes. It can be extended further if needed. This also shows the use of the 6301 Forth assembler. Note that the address to jump to is the Parameter Field Address of the assembly language interrupt routine. This is readily found with ' as seen below. If in difficulty with an interrupt written in assembler see if it is possible to start with the example below and then change it to meet your needs.
( _ASTIMER.TDS) DECIMAL 88 USER %TIMER ( a 16-bit extension of timer 1 CODE +TIME ( Interrupting word which increments ( %TIMER $08 A LDA, $09 LDX, ( clear timer 1 overflow ( flag %TIMER LDX, INX, %TIMER STX, ( increment ( variable RTI, ?CSP SMUDGE ( return from interrupt : INITIALISE DIS ( disable interrupts $8 C@ $4 OR $8 C! ( enable overflow interrupts 0 %TIMER ! 0 $09 ! ( clear timer EIS ; ( general interrupt enable : TEST ( to demonstrate the extended timer INITIALISE CR BEGIN $09 @ %TIMER @ ( - double word time 20 D.R 100MS ( print 10 times per second ?TERMINAL ( stop on ctrl+C UNTIL ; $7E TOI C! ' +TIME TOI 1+ ! ( interrupt table entry
Note that apart from the general interrupt enable EIS , the particular interrupt must also have its enable bit set. In the above example this is bit 2 of address 8. INTERRUPTS WRITTEN IN HIGH LEVEL FORTHTo clearly show the difference between interrupts in assembly language and Forth the same example above is now coded in Forth:
( _HITIMER.TDS) DECIMAL 88 USER %TIMER ( a 16-bit extension of timer 1 : +TIME ( interrupting word which increments %TIMER $08 C@ $09 @ 2DROP ( clear timer 1 overflow flag 1 %TIMER +! ( increment variable RETURN; ( return from interrupt : INITIALISE DIS ( disable interrupts $8 C@ $4 OR $8 C! ( enable overflow interrupt 0 %TIMER ! 0 $09 ! ( clear timer EIS ; ( general interrupt enable TOI ASSIGN +TIME ( set up task switch : TEST ( to demonstrate the extended timer INITIALISE CR BEGIN $09 @ %TIMER @ ( - double word time 20 D.R 100 MS ( print 10 times per second ?TERMINAL ( stop on ctrl+C UNTIL ;
This is easy as we saw in the example at the beginning of this section because the words ASSIGN and RETURN; do all the work for you. To create an interrupt in high level Forth:
q Write the interrupt routine as a normal Forth word and debug it. q
Now change the semicolon to RETURN; so that it has this form: q Use ASSIGN to set up the interrupt. q Create an initialising word which enables the interrupt, both a particular and general interrupt enable.
The word ASSIGN works by compiling a headerless word which saves Forth 'registers' on the stack. A new return stack base is then allocated in the free space above the old one. The Code Field Address (cfa) of the interrupting Forth word is put in the Interpreter Pointer of the Forth inner interpreter and we jump back to Forth. A completely 'new' Forth system has now been created for the duration of the interrupt. ASSIGN also sets the entry in the interrupt jump table. The return from interrupt performed by RETURN; is simpler, we just pull all the old data back off the stack and replace it in the Forth 'registers' before executing a return from interrupt instruction (RTI) which puts us back in the original Forth system exactly where we left it. |