Electronic – Lockup when executing relocated ISR in ARM Cortex-M0+

armfreescaleinterruptskinetis

I've been writing a small program for my Freescale Kinetis KE04Z which runs in RAM in order to program the flash of the device. I've been using SWD to place the program in RAM and run it. All was going well (blinking LEDs as a test, etc) until I tried to start the periodic interrupt timer (PIT module) in order to confirm the speed of the microcontroller by flashing an LED with some specific timing (the flash controller manual says that if the speed is wrong then it could destroy the flash). I don't have an oscilloscope available at the moment, otherwise I would just ditch the PIT, write the gpio pin toggle register (PTOR) over and over again, and measure the period of the resulting square wave.

I have in my startup section the following assembly which moves the VTOR to my interrupt vector table:

ldr r0, =0xE000ED08
ldr r1, =__interrupt_vector_table
str r1,[r0]

__interrupt_vector_table points to the beginning of the RAM section where I have placed my interrupt vector table (0x1fffff00 for the record). The code can be found here. The interrupt table and startup code listing is here.

In my main method I have the following:

SIM->SCGC |= SIM_SCGC_PIT_MASK; //turn on PIT clock
PIT->MCR = 0; //activate PIT
PIT->CHANNEL[0].LDVAL = 1.2e6; //100mS tick
PIT->CHANNEL[0].TCTRL = PIT_TCTRL_TEN_MASK; //enable the timer
PIT->CHANNEL[0].TCTRL |= PIT_TCTRL_TIE_MASK; //enable the interrupt
NVIC->ISER[0] = 1 << 22; //enable interrupt in NVIC
__enable_irq(); //cpsie i

It was at this point where I started to run into problems. Once the PIT would go off, a lockup would occur according to the SIM_SRSID register when I read it via SWD. I have a couple thoughts on why this could happen:

  • The ISR runs, but never returns, causing the WDT to go off an initiate a reset, causing the VTOR to be reset to 0x0. Since the device is unprogrammed, the reset vector is 0xffffffff, which causes a lockup.
  • The ISR runs, smashes the registers, and prevents the main method from properly resetting the WDT, causing the same situation as above.
  • The interrupt goes off, but for some reason the NVIC is unable to run my ISR and initiates a HardFault. My hard fault code just loops forever, which would eventually cause the WDT to go off and etc.
  • My hand-written interrupt table is off-by-one and one of the default ISRs is getting executing and causing a WDT timeout (or I'm not enabling the correct ISR and the same thing is happening). The thing is, I've never been able to show that my default ISR code was running instead of my actual IRQ handler. I've done a few tests using writing memory locations to specific values and then reading them after the reset and the code to set those values has never executed.

I'm pretty sure it has to do with my ISR or the interrupt in general because once I comment out the line of code that sets PIT_TCTRL0.TIE the program works fine and doesn't lock up.

My ISR is really simple right now:

void PIT_CH0_IRQHandler(void)
{
    PIT->CHANNEL[0].TFLG = 0x1; //reset flags
}

Which compiles to:

2000001c <PIT_CH0_IRQHandler>:
2000001c: 2201       movs r2, #1
2000001e: 4b01       ldr r3, [pc, #4] ; (20000024 <PIT_CH0_IRQHandler+0x8>)
20000020: 611a       str r2, [r3, #16]
20000022: 4770       bx lr
20000024: 400370fc strdmi r7, [r3], -ip

I honestly don't know if this is right. There is no context saving going on or anything else that I've seen in other ISRs, but I've never looked in detail at ARM Cortext M0+ ISRs so for all I know the NVIC takes care of context saving.

My question is:

  • Is my ISR code, NVIC code, or VTOR relocating code the problem here?
  • Or perhaps is locating the VTOR to a location in RAM a bad thing and won't work properly?

This is my first time writing a program that runs in RAM and also my first time relocating the VTOR, so I don't have a lot of experience in these matters. I've done lots of interrupt code for a K20 before, but the VTOR was always at 0x0. If I've left anything out or need to supply additional information, just let me know.

Best Answer

I just wanted to post a followup since I fixed the problem. It doesn't match any existing answer, but that's because it was from an unexpected source.

I was thinking about how exactly the NVIC stores the context since that seems to be the moment that causes things to go nuts and I realized that it likely uses the stack.

I execute the following sequence in SWD when loading my program (its likely not the best way to do it, but its a work in progress):

  1. Enable debug in DHCSR
  2. Enable reset catch in DEMCR
  3. Clear any reset flag in DHCSR
  4. Use AIRCR to request a reset and wait for it to occur by polling DHCSR. The processor is now halted just after a reset occurs. The VTOR is now 0x0, register 15 now points to 0xffffffff since the flash (located at 0x0) is unprogrammed), ...and the stack pointer is also 0xffffffff (didn't realize this until now)
  5. Use the AHB-AP to load the program
  6. Set the VTOR to point to the new VTOR in my program. Set register 15 to the start of the reset routine in my code (whose address is found at VTOR+4).
  7. Unhalt the processor and let it run.

The processor would then run until the first interrupt. My realization was that since I had neglected to set the stack pointer during step 6 and it was still pointing to somewhere outside of RAM, the NVIC likely had nowhere to save the context and probably caused a fault. This would eventually let the WDT go off since it just loops endlessly in those routines which would cause the VTOR to reset back to 0x0, then attempting to execute 0xffffffff (since that's what is at address 0x4) which would result in a LOCKUP.

By simply setting the stack pointer (register 14) to the top of my stack during step 6 above I fixed my problem.