1
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ Look at the vector table entries, whether the actual entries are odd addresses. The T-bit must be set or you get a fault. \$\endgroup\$ – Turbo J Sep 9 '15 at 16:32
  • \$\begingroup\$ I did check that. They are all odd (for example, 0x2000001c is listed as 0x2000001d in the table). \$\endgroup\$ – Los Frijoles Sep 9 '15 at 16:34
3
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

This is not a complete answer and I'm not an expert on assembly, but some points I already checked (and might help you):

The layout of your vector table is fine. The positioning of your relocated vector table is also fine (a multiple of 256). The code you use to relocate the vector table to the VTOR register seems fine as well, it's the right address you use.

The context saving (pushing stuff on the stack) on interrupt entry is done by the Cortex-M core automatically, so there won't be code for that in the compiled version.

In the reference manual (page 119), they actually say that relocating the vector table to ram is fine:

The CM0+ core adds support for a programmable Vector Table Offset Register (VTOR ) to relocate the exception vector table. This device supports booting from internal flash and RAM.

This device supports booting from internal flash with the reset vectors located at addresses 0x0 (initial SP_main), 0x4 (initial PC), and RAM with relocating the exception vector table to RAM.

I'm also not aware of any pitfalls or special procedures necessary to do the relocation.

I've checked the peripheral description and the NVIC mapping and your ISR code. The only thing I can find which you do differently than described is:

PIT->CHANNEL[0].TCTRL = PIT_TCTRL_TEN_MASK; //enable the timer
PIT->CHANNEL[0].TCTRL |= PIT_TCTRL_TIE_MASK; //enable the interrupt

They do it the other way round, first enable the interrupt and then enable the timer. They also recommend to clear the TIF flag before enabling the timer.

The only other thing I can find which is probably not causing an issue now is that you have an implicit conversion from double to a unsigned 32-bit integer going on, which isn't the cleanest (can give some strange behaviour on some occasions):

PIT->CHANNEL[0].LDVAL = 1.2e6; //100mS tick

I'd say 1200000U is not really better readable but it's safe that you actually get the value you want in your register.

Can you disable the WDT and halt the execution of your locked up controller to see where it ends up? That's the approach I use to find out what's going on, also the stack can be very helpful to see where you are coming from in that situation.

\$\endgroup\$
  • \$\begingroup\$ That instruction isn't really an instruction. That's just simply "0x400370fc" which is the address to the peripheral which is loaded using ldr r3, [pc, #4]. Using -D on objdump (I use -D so I can see more sections) makes it show up as an instruction even though it isn't. \$\endgroup\$ – Los Frijoles Sep 9 '15 at 22:49
  • \$\begingroup\$ @LosFrijoles ah yes of course, should have seen that, haven't looked at the hex code, it would have been clear as Thumb are 16 bit instructions. And it's after the unconditional return, so wouldn't get executed anyway. - I've also edited my answer to reflect my latest research, I'm unable to find an obvious mistake, sorry. \$\endgroup\$ – Arsenal Sep 10 '15 at 7:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.