Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ARM Assembly - Basic Interrupt Handling

I'm currently messing around with some ARM Assembler on the lpc2378, I've wrote a loop to control a furnace's temperature, however; I believe I need to implement some kind of interrupt handling to complete my project.

When the application runs, it enters a loop, which waits for Button_1 input, the loop then carries on and goes through various stages but it cannot reach wait for Button_2 input for the application to function.

So a couple of questions here, how exactly does the interrupt handler work? and how could I implement it into my application.

Here is my Button_1 code:

;=========================================================================
; Wait for BUT1 to be pressed
;=========================================================================
WaitBUT1
  STMFD r13!,{r0,r5,r14}   ; Push r0, r5 and LR

WaitForBUT1Pressed
    ldr r0, = IO0PIN            ; Address of FIO0PIN register 
    ldr r1, [r0]                ; Read FIO0PIN in to r1
    ands r1, r1, # B1_MASK      ; Mask out BUT1
    beq BUT1Pressed             ; Exit LED toggle loop if button is pressed
    B WaitForBUT1Pressed
BUT1Pressed

  LDMFD r13!,{r0,r5,r14}   ; Pop r0, r5 and LR
  mov pc, r14              ; Put link register back into PC

and my Button_2 code:

;=========================================================================
; Wait for BUT2 to be pressed
;=========================================================================
WaitBUT2
  STMFD r13!,{r0,r5,r14}   ; Push r0, r5 and LR

WaitForBUT2Pressed
    ldr r0, = IO0PIN            ; Address of FIO0PIN register 
    ldr r1, [r0]                ; Read FIO0PIN in to r1
    ands r1, r1, # B2_MASK      ; Mask out BUT1
    beq BUT2Pressed             ; Exit LED toggle loop if button is pressed
    B WaitForBUT2Pressed
BUT2Pressed

  LDMFD r13!,{r0,r5,r14}   ; Pop r0, r5 and LR
  mov pc, r14              ; Put link register back into PC

and also my furnace control loop:

LoopStart

   BL WaitBUT1 ; wait until button 1 is pressed
   BL heaterOn ; turn heater on
   BL systemLedOn ; turn system LED on
   BL readTemp ; Check ADC for temp
   BL checkTemp ; Count down, check ADC for temp
   CMP r3, #5 ; Compare timer with delay
   BGT errorVal
   SUBS r4, r2, r7  ;Performs r7 = r4 - r2 and sets condition register
   BEQ LoopStart ; if equal nothing to do
   BGT overTemp ; r7 < 0 case
   BL errorLedOn
   BL heaterOn
   BL FanOff
   B LoopStart

overTemp
   BL errorLedOn
   BL heaterOff
   BL FanOn
   B LoopStart


   BL WaitBUT2
   BL FanOff
   BL errorLedOff
   BL systemLedOff
   BL heaterOff

       B LoopStart

Thanks in advance.

like image 484
user1080390 Avatar asked May 18 '12 03:05

user1080390


1 Answers

If you dont have it already you need to get the ARM ARM (ARM Architectural Reference Manual). there used to be only one but there are so many cores now they had to split it up for the different architectures. http://infocenter.arm.com along the left side ARM Architecture, then Reference Manuals, then ARMv5, yes I know you have an ARMv4 (ARM7TDMI). The ARMv5 manual is what used to be, THE manual.

It will cover the exception vectors and such.

You probably already know that on boot, the instruction at address 0x00000000 is executed. For interrupts the instruction at address 0x00000018 is called.

.globl _start
_start:
    b   reset     ;@ 0x00000000 reset
    b   handler   ;@ 0x00000004 undefined instruction
    b   handler   ;@ 0x00000008 software interrupt swi
    b   handler   ;@ 0x0000000C prefetch abort
    b   handler   ;@ 0x00000010 data abort
    b   handler   ;@ 0x00000014 dont know
    b    irq_handler   ;@ 0x00000018 irq
    b   handler   ;@ 0x000000

From the ARM ARM you should also see that the registers are banked, in particular r13 the stack pointer, there is a separate stack pointer when in interrupt mode. So on boot, when you are setting up your normal stack you also want to setup some space for the interrupt stack.

reset:
;@//mov r0, #(PSR_IRQ_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS)
mov r0,#0xD2
msr cpsr_c, r0
ldr sp, =0xD600C000

;@//mov r0, #(PSR_SVC_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS)
mov r0,#0xD3
msr cpsr_c, r0
ldr sp, =0xD600B000

at some point you need to enable the interrupt in the cpsr. you might wait on this until you have enabled whatever interrupt in whatever peripheral (in the chip, outside the core).

;@ SVC MODE, IRQ ENABLED, FIQ DIS
mov r0,#0x53
msr cpsr_c, r0

You will need to read the manual for the lpc part to understand how to enable the interrupt, as well as how to clear the interrupt when it happens.

The first thing you need to do in the interrupt handler (the code you branch to at address 0x18) is preserve the shared (not banked) registers on the stack so that you dont mess them up. If you dont when you return to the code that was interrupted the registers will have changed and that code may not work. Since your project appears to be in ASM it is possible for you to use some registers in the application and reserve some regsiters only for the isr and not have to setup the stack. If you are going to use bl anywhere in the handler you need to save lr on the stack.

At some point your handler needs to clear the interrupt in the peripheral so it doesnt fire again until the next interrupt, gotta read the lpc manual for that. Yes when I say lpc I mean NXP formerly Philips...

The last thing is if you have pushed lr, pop it, and pop other registers if you had pushed them, then use this exact instruction and the arm will return and switch modes back to the mode that was interrupted (switching to the use of those banked registers).

subs pc,lr,#4

Note you might also need, and it is generally a good idea with ARM, to get the TRM (Techincal Reference Manual) for the specific core, in your case the ARM7TDMI-S. On the infocenter page, left side go to ARM7 Processors, not ARMv7 under architecture but lower where it has ARM11, ARM9, ARM7.

Buttons can be messy with interrupts as they bounce and cause many edges on the I/O pin. Looking at your other post I dont see where you would need an interrupt. You should in general avoid interrupts unless otherwise necessary. they are messy in general. There are designs that you cant avoid using one, certainly. And there are event driven designs where all of your code is an interrupt handler and there is nothing in the application but startup code and an infinite loop (or some sort of sleep).

You can use interrupts to debounce, for example, if you setup a timer interrupt and sample the I/O pins for the buttons in the interrupt, you avoid a fair amount of the bouncing, but you also risk the chance of missing a button press. Just because the button I/O pin is asserted two interrupts in a row that is not two presses, you change a state variable somewhere, and when the button is released you change the state variable. edge changes on that state variable are of interest to the application. when the button goes from unpressed to pressed, do something, if it goes from pressed to unpressed, dont do anything, wait for it to change from unpressed to pressed as an example.

You can also use the same timer interrupt to start or sample the adc.

Since you have not solved your other problem which is also mentioned here, I wouldnt go near interrupts. Figure that problem out first, then decide if there is a feature you need that requires an interrupt. Like even/regular sampling of the adc.

that should be more than enough to get you started. If you do try interrupts DO NOT try them as part of this application, create a completely separate application, for example an led blinker. change the state of the led in the interrupt handler, and/or change a global variable or register in the handler and have the forground watch for that and change the led as a result. Always divide your problems/learning exercises into individual pieces, solve those then glue them together add one piece, test, add one piece, test.

like image 177
old_timer Avatar answered Nov 09 '22 05:11

old_timer