Thursday 27 of March, 2008
SAM7 Advanced interrupt controller in emforth.
Programming the priority interrupt controller has been a low priority. So far emforth and related projects have not used interrupts.
Getting the controller working was relatively easy but I've had a few frustrating days getting the last duck in line so I could return from interrupt without crashing the system. The last duck was a missing "S" bit in the last instruction of the interrupt routine. None of the dozens of web pages I read explained this but ultimately it was in the SAM7 datasheet.
My initial goal is simply to flash a led (or backlight) as an interrupt task triggered but the real time timet (RTT). I have already used the RTT in other projects and the RTT.FTH include file and setup code was used in the charger demo.
There are plenty of sites explaining that the magic instruction to make it all work (in regular assembler) is ldr pc,[pc,#-0xF20]
This instruction is placed into address H'18 and is executed when an IRQ occurs. The same trick is used for the fast interrupt (FIQ) as well.
The result of this instruction being executed - is the value in the interrupt vector register (AIC_IVR) is loaded into the program counter and the interrupt service routine is called.
The hex code for the instruction is E51FFF20.
I added this line of code to the startup routine of EMF V1.76 ...
E51FFF20 200018 ! // set up vectored interrupt
Because ram hasn't been remapped yet I have to use 200018 as the address.
The RTT is a system device and must use interrupt source one.
To place the address of my test routine into source vector one I say...
' CODEINT AIC_AVC1 ! // codeint is my service routine and AIC_AVC1 = FFFFF084
Mode register1 (FFFFF084) is set to h'21 like this.
21 AIC_SMR1 !
This means priority 1 and positive edge triggered. The final version will be level triggered or I suspect there'd be trouble if a second system device fires at the same time.
But for now it is baby steps to get one interrupt call and return working.
We also have to enable to interrupt source in the AIC by saying..
2 AIC_IECR !
I also have to write to AIC_EOICR to clear some pending interrupt after reset ...
0 AIC_EOICR !
One site suggested doing this up to 32 times on reset to clear all possible pending interrupts.
A simple do-nothing interrupt routine looks like this.
CODE CODEINT { R0 } PUSH AIC_EOICR # R0 MOV, R0 R0 STR, { R0 } POP 4 # LR PC [S] SUB, // Adjust return value into PC END-CODE
My working register (R0) is saved and retrieved from the IRQ stack.
AIC_EOICR is written to - to clear the interrupt in the AIC but the RTT is not accessed so there will be no more interrupts.
AIC_EOICR can be wrriten with any value. The address of AIC_EOICR is FFFFF130 - this can't be loaded with a single ARM instruction - later I will show a work around this to keep the AIC_EOICR write a two instruction sequence.
The last instruction differs from the usual LR to PC move for two reasons.
Firstly the address in LR is wrong after an interrupt and has to be adjusted.
Secondly the 'S' bit of the instruction must be set so the CPSR is restored by copying SPSR into it - this returns us the the correct mode (ie system) and re-enabled the interrupt.
It also restored any changes the service routine made to CPSR.
We will usually want to stack LR (the IQR version that is) so we can nest subroutine or possibly interrupts.
One way is the adjust LR before stacking it so it can be popped straight into the PC.
Note the S bit is again used to copy SPSR into CPSR after the popping is done.
IE we are popping from the IRQ stack and changing CPU mode (and therefore stack) after pops are done.
CODE CODEINT 4 # LR LR SUB, // Adjust return value { LR } PUSH { R0 } PUSH AIC_EOICR # R0 MOV, R0 R0 STR, { R0 } POP { PC } [U] [!] [S] LDM, END-CODE
Higher level handlers.
Now the basics of code level handlers seems to be sorted out it is time to work on handlers in forth.
: :INT CREATE E24EE004 , // DEC LR BY 4 E92D4000 , // { LR } PUSH E92D003F , // PUSH R0-R5 E3E00EFF , // FF0 # R0 MVN, LOADS FFFFF00F E5800121 , // AIC_EOICR FFFFF00F - # R0 R0 [P] STR, ] ; COMPILER : ;INT 0 MODE ! ( Set mode to interp ) E8BD003F , // POP R0-R5 E8FD8000 , // POPPC WITH [S] ; 'IMMEDIATE ;INT
The instructions used are very similar to the CODEINT example above. One minor difference is the working regs r0 to r5 are saved. Note the data pointers etc are not and it is assumed the routines leave them as they found them. The routines may used the system data stack but must remove any values placed there before exiting.
The other difference is how AIC_EOICR is accessed.
To load the value of this register address (FFFFF130) requires multiple instructions or a load from a memory location.
I've seen others load the AIC base address (FFFFF000) and then use a store with an offset of h'130 to write to it.
If there is a way to load FFFFF000 in one go I couldn't figure it out.
However it is possible to load FFFFF00F with the instruction - FF0 # R0 MVN,
With this as a base address FFFFF130 can be reached with an offset of h'121
It could be also done other ways like loading zero and using a negative offset.
Blink blink blink
The led blink interrupt is working - it looks like this.
VARIABLE FSTATE :INT MYINT 100 RTTALARM +! // set new alarm time - note this is a RTT register RTTSR @ DROP // Clear alarm int - note this is a RTT register FSTATE @ // check led state and flip IF LEDON 0 FSTATE ! ELSE LEDOFF 1 FSTATE ! THEN ;INT
Seeing I only have one system interrupt set - I can be lazy and assume any interrupt comes from the RTT alarm. If I were to add say a PIT interrupt I'd have to identify the interrupt source in the handler.
As before I point the vector to the routine.
' MYINT AIC_AVC1 !
And this word sets it all up and run it.
: INTON 0 AIC_EOICR ! RESETRTT // reset the RTT 1 ALMIENBIT LSHIFT RTTMODE @ OR RTTMODE ! // set the mode RTTVAL @ 400 + RTTALARM ! // set the first alarm for about 1 second from now 01 AIC_SMR1 ! // set the AIC mode for source 1 2 AIC_IECR ! // enable source 1 ;
I tested the code on both a EX256 and a MT256 and found I have the add a slight delay to the MT256 version to make it work. This was just "1 DROP" added to the beginning of the service routine.
I'm not sure if this is a AIC or RTT problem yet.
It doesn't seem to matter where the delay goes, this suggests it is an AIC timing issue.
This test code is small and fast and the only interrupt running - so there is no point is nesting interrupts. When the need arises nesting will be added. There are plenty or examples online of how to do this.