Can't get __disable_irq() to work on Teensy LC

Status
Not open for further replies.
Hi! I am trying to learn how to set and use interrupt routines in the Teensy-LC (specifcally, ftm0_isr()). I would like to try disabling interrupts completely (for the sake of learning). I set up the FTM0 to interrupt and execute the ftm0_isr routine, but when I call __disable_irq(), the ISR still happens. Is there something I am doing wrong or not understanding about __disable_irq()?

I am using Teensyduino 1.22 with Arduino 1.6.3. The code below should set up the FTM0 ISR to flash the LED on pin 13 repeatedly, but then stop all interrupts (via __disable_irq()). However, for me, the LED continues to flash. And yes, I am doing this the hard way, because I'd like to learn more about interrupts in the Cortex-M0+ :)

Code:
// Page numbers refer to the KL26 datasheet

const uint8_t LED_PIN = 13;
volatile uint8_t led = 0;

void setup() {
  
  // Set up the LED and set it low initially
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, led);
  
  // The order of setting the TPMx_SC, TPMx_CNT, and TPMx_MOD
  // seems to matter. You must clear _SC, set _CNT to 0, set _MOD
  // to the desired value, then you can set the bit fields in _SC.
  
  // Clear TPM0_SC register (p. 572)
  FTM0_SC = 0;
  
  // Reset the TPM0_CNT counter (p. 574)
  FTM0_CNT = 0;
  
  // Set overflow value (modulo) (p.574)
  FTM0_MOD = 0xFFFF;
  
  // Set TPM0_SC register (p. 572)
  // Bits | Va1ue | Description
  //  8   |    0  | DMA: Disable DMA
  //  7   |    1  | TOF: Clear Timer Overflow Flag
  //  6   |    1  | TOIE: Enable Timer Overflow Interrupt
  //  5   |    0  | CPWMS: TPM in up counting mode
  // 4-3  |   01  | CMOD: Counter incrememnts every TPM clock
  // 2-0  |  111  | PS: Prescale = 128
  FTM0_SC = 0b011001111;
  
  // Nested Vector Interrupt Controller (NVIC) (p. 57)
  // Also: Chapter 4.2 of the Generic User Guide
  // Our FTM0 interrupt number is 17 (as per kinetis.h). We can
  // use that to set up our interrupt vector and priority.
 
  // Set the urgency of the interrupt. Lower numbers mean higher
  // urgency (they will happen first). Acceptable values are
  // 0, 64, 128, and 192. Default is 128. We set the priority
  // (2nd byte) in the register for the FTM0 interrupt (&E000_E410) 
  // to 64.
  NVIC_SET_PRIORITY(IRQ_FTM0, 64);
  
  // Enable the interrupt vector. In this case, we want to execute
  // the ISR (named "ftm0_isr()" for Teensy) every time TPM0 
  // overflows. We set bit 17 of &E000_E100.
  NVIC_ENABLE_IRQ(IRQ_FTM0);
  // Same as: NVIC_ISER0 |= (1 << 17);
  
  // Try disabling all interrupts. The LED should stop flashing.
  __disable_irq();
}

void loop() {
  // Do nothing
  delay(1000);
}

// "ftm0_isr" is an interrupt vector defined for the Teensy
void ftm0_isr(void) {
  
  // First, we need to clear the timer and channel flags so that
  // the interrupt will happen again. We can take care of the
  // rest of the interrupt after that.
  
  // Write a 1 to the TOF bit to clear the timer overflow flag
  FTM0_SC |= (1 << 7);
  
  // Write a 1 to the CHF bit to clear the channel flag
  FTM0_C4SC |= (1 << 7);
  
  // Our interrupt: Toggle the LED
  led = ~led;
  digitalWrite(LED_PIN, led);
}
 
Just a quick look at your code and I am not exactly sure what is happening but some thoughts.

From the Reference Manual pg 780:

The TOF bit is cleared by
reading the SC register while TOF is set and then writing a 0 to TOF bit.

So first of all you should be writing a 0, and then you need to read the FTM0_SC register before you can clear it, and I'm not sure whether

Code:
FTM0_SC |= (1 << 7);

implies a read. In this case the interrupt would not be getting cleared and what could be happening is if an interrupt is pending when you enable the FTM interrupt you could be just entering and re-entering the interrupt routine and never getting to __disable _irq() statement. As a quick test you could try switching the order of __disable and NVIC_ENABLE. And if that changes things, verify your setup is doing what you hope it is.

Have fun learning.

-TLB
 
Thanks for the reply! Which manual are you looking at? I grabbed the KL26 Reference Manual from PJRC (https://www.pjrc.com/teensy/KL26P121M48SF4RM.pdf), and it doesn't talk about reading the SC register first or writing 0 to the TOF bit. From p. 573:

Timer Overflow Flag

Set by hardware when the TPM counter equals the value in the MOD register and increments. Writing a 1
to TOF clears it. Writing a 0 to TOF has no effect.

If another LPTPM overflow occurs between the flag setting and the flag clearing, the write operation has
no effect; therefore, TOF remains set indicating another overflow has occurred. In this case a TOF
interrupt request is not lost due to a delay in clearing the previous TOF.

0 TPM counter has not overflowed.
1 TPM counter has overflowed.

I tried placing __disable_irq() before NVIC_ENABLE, and it did not affact anything. Any thoughts?
 
So much for backward compatibility! I realized after I posted, I was looking at the the Teensy3.1 manual. Doesn't make me think very nice thoughts about the designers of this part, switching the polarity of this bit. Makes one wonder where else they have done something else like this.

From my experience with the FTM interrupts on Teensy 3.1 :

Trace thru the Teensyduino main() to see what order everything is called. I don't use Teensyduino, having my own C++ main(), so don't know the details of Teensyduino (looked it up once quite a while ago). The initialization does set the FTM timers, and I think especially FTM0. I was only using FTM1 and FTM2 (only ones with Quadrature Decode). I did have to go back and re-initialize some timer registers that were set in teensy.c. So there definitely can be some conflicts.

Try putting __disable_irq() in your loop() function instead. If there is nothing going on between the end of setup() and the beginning of loop() it should be equivalent.

Try noInterrupts(). This should be the same as __disable_irq().

Good Luck.

TLB
 
Yeah, I'm not sure why they changed that.

From what I can tell, they are using __disable_irq() in the Teensyduino functions (e.g. micros()). I've tried both __disable_irq() and noInterrupt() in both setup and loop, and neither have any effect. noInterrupt() seems to compile just fine, but it doesn't seem to do much. :(
 
delay() calls mircros() and micros() calls __disable_irq() followed by __enable_irq()

The delay function, and millis and micros, uses systick counter values and
thoose need interrupts active to happen. So without interrupts delay will wait forever.

So you can simply remove the call to delay in your loop, and the program should execute
the isr exactly once.
 
Lots of places use __enable_irq(), so you can NOT use __disable_irq() as a long-term way to turn off interrupts. Well, not unless you write all the code and don't call functions from Teensyduino or anything else. This isn't a hardware limitation, it's purely a software limitation. The convention used in many, many places is __disable_irq(), with only a very small amount of code, followed by unconditional __enable_irq().

If you want to turn an interrupt off long-term, use NVIC_DISABLE_IRQ(). Or you can turn off the interrupt within the peripheral itself, but the NVIC way lets you leave the peripheral running and able to request interrupts, but disables the interrupt controller from servicing it.

....switching the polarity of this bit. Makes one wonder where else they have done something else like this.

Indeed, the timers look very similar, with all the same registers (or at least the useful subset), but there are some important differences. The polarity of writing to clear the interrupt status is the most critical issue.

The other annoying difference is reconfiguring the timer mode bits. On Teensy 3.1, you can simply write the new setting. On Teensy-LC, you must first write 0, and either wait or read it until it reads back as zero, and then you can write the new setting. At startup, Teensyduino configures all 10 channels as PWM, for analogWrite. If you want to use the timer channels in other modes like compare or input capture, you must go through this special reconfigure step.

Yet another small difference is the clock source. On Teensy 3.1, it's F_BUS. On Teensy-LC, it's F_PLL/2.

At least these last 2 differences have a good purpose. The timers on Teensy-LC are designed to be able to run with a clock that's asynchronous to the processor & bus clock & memory clock. The hardware supports some amazing low-power features, which don't really fit into the Arduino paradigm very well, like running the CPU at a slow clock or even stopped, but the timers run from a fast clock.

But there's really no excuse for changing the interrupt clear polarity. Unfortunately, that's the way Freescale made the silicon.
 
Status
Not open for further replies.
Back
Top