Overflow interrupts on FTM in quadrature mode

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Overflow interrupts on FTM in quadrature mode

1,383 Views
BoiO
Contributor I

Hello everyone,

For my project I want to use a frdm-K64F to read in quadrature encoder pulses. I have been using mbed to abstract most of the lower level code, however I have found that it has no implementation of hardware quadrature decoding, necessitating lower level code to facilitate this. Most of the implemented code is working, the only issue remaining is that the timer overflow interrupt seems to make the system hang.

For the K64F, flex timer 1 (FTM1) and FTM2 support quadrature decoding. For the code below FTM2 is used as its inputs are available directly on the headers of the frdm board, to which the A and B phase signals are connected (PTB18 and PTB19 respectively).

The code is confirmed to work properly for counting the encoder up and down, but locks up and goes into an infinite loop when a timer overflow occurs. If I don't enable interrupts (remove FTM2->SC |= FTM_SC_TOIE(1);) the code simply loops back from 64 to 0, and visa versa.
My steps for enabling the interrupts are:

  1. Enable interrupts for FTM2: FTM2->SC |= FTM_SC_TOIE(1);
  2. Tell the NVIC to handle FTM2 interrupts: NVIC_EnableIRQ(FTM2_IRQn);
  3. Handle the interrupt by overriding the weak callback function: void FTM2_IRQHandler(void){ .... }

I suspect that either I am missing an initialization step of the interrupt, or that somehow using the IRQHandler is not working well with mbed. Of course I am leaning more towards the former than the latter. Am I missing something? Or is there a mistake I am making?

For completion sake, I am using mbed studio with mbed OS 6.8.0 on the FRDM-K64F.

 

#include "mbed.h"

const unsigned int CONST_ENC_MOD_SIZE = 65;

static BufferedSerial           pc(USBTX, USBRX);

void FTM2_IRQHandler(void);

// main() runs in its own thread in the OS
int main()
{
    pc.set_baud(115200);
    // Register reference: https://os.mbed.com/questions/77426/Why-cant-I-access-FTM2-MODE-on-the-FRDM-/
    // https://www.nxp.com/docs/en/application-note/AN5142.pdf
    // RF chapter 40 for FTM modules
    //
    // In the reference manual of k64F, page 1100 [40.4.25 QUadrature Decoder mode]
    //                                  page 142 [3.8.2.1 (FTM) Instantiation information] -> FTM1 and FTM2 are used for quadrature decoders
    //                                  page 249 [10.3.1 K64 Signal Multiplexing and Pin assignments] -> pin connections for the timers
    //
    // This gives the following possible pins for the two timers (A; B), tqfp 100
    // FTM1
    // (PTB0, PTB1) on ALT6 function (PCR MUX)
    // (PTA12, PTA13) on ALT7 function (PCR MUX)
    //
    // FTM2
    // (PTB18, PTB19) on ALT6 function (PCR mux)

    printf("Initializing...\n");

    // Enable the clock for FTM
    SIM->SCGC6 |= SIM_SCGC6_FTM2_MASK;
    // Enable the counter
    FTM2->MODE |= FTM_MODE_FTMEN_MASK;
    // Enable the counter to run in the BDM mode
    FTM2->CONF |= FTM_CONF_BDMMODE(3);
    // Load the modulo register (max is 2^16 -1, 65535)
    FTM2->MOD = CONST_ENC_MOD_SIZE;
    // Load initial value into the timer register
    FTM2->CNTIN = 0; // Note that writing a value to the 'count' register doesn't updata it, but instead updates it with the initial value.
    // Config quadrature mode
    FTM2->QDCTRL |= FTM_QDCTRL_QUADEN_MASK;
    // Start the timer clock with src as external (RF P 993)
    FTM2->SC |= FTM_SC_CLKS(3); // Clock source as external
    FTM2->SC |= FTM_SC_TOIE(1); // Enable interrupts on overflow
    // Configuring the input pins
    PORTB->PCR[18] = PORT_PCR_MUX(6); // Select pin alt. function, quadrature mode FTM2 CHA
    PORTB->PCR[19] = PORT_PCR_MUX(6); // Select pin alt. function, quadrature mode FTM2 CHB

    //NVIC->ISER[FTM2_IRQn / 32] |= (1 << (FTM2_IRQn% 32));

    NVIC_EnableIRQ(FTM2_IRQn);


    while (true) {
        wait_us(200000); // 200ms wait
        uint32_t cnt = FTM2->CNT;
        printf("%d\n", cnt);
    }
}


void FTM2_IRQHandler(void){
    
    if(FTM2->CNT > (CONST_ENC_MOD_SIZE/2)){ // Negative overflow
        printf("Negative overflow\n");
        // TODO handle overflow
    }else{ // Positive overflow
        printf("Positive overflow\n");
        // TODO handle overflow
    }

    FTM2->SC &= ~FTM_SC_TOF(1); // Clear timer overflow bit
}

 

Additionally I have tried to override the interrupt vector:

 

NVIC_SetVector(FTM2_IRQn, (uint32_t)&FTM2_IRQHandler);

 

This sadly just results in the MCU throwing an error (flashing red led)

Labels (1)
0 Kudos
Reply
6 Replies

1,374 Views
BoiO
Contributor I

Some more information that might give a hint as to what is going on:

If I go into the debugger and 'pause' on the system freeze, it indicated to be in 'WDOG_EWM_IRQHandler' which points at that there is an unhandled interrupt. At the very least, it does confirm that an interrupt is triggered.

I have tried the following names for the ISR, none of which seem to work:

 

// This seems to be the correct default, as it also has mentions in the rest of the mbed codebase
void FTM2_IRQHandler(void){ .... } 
// The following 4 are name variations that are used in various FTM implementations on the internet
void FTM2_ISR(void){ .... } 
void ftm2_isr(void){ .... } 
void flexTimer2Isr(void){ .... }  
// Seems to be the ISR to catch unhandled interrupts, doesn't work either
void DefaultISR(void){ .... } 

 

Am I correct in assuming that FTM2_IRQHandler(){} is the default interrupt handler? And if so, what is going wrong that this ISR is not being called?

Alternatively, how can I override this function in the interrupt vectors without causing the mcu to crash and burn?

UPDATE:
The above code for overwriting the interrupt vector did work, the mcu crashed because of the printf code inside the ISR (understandably). So for now the problem is solved, I am however still curious as to what the default IRQ handler is.

0 Kudos
Reply

1,368 Views
myke_predko
Senior Contributor III

@BoiO 

The big red flag for me is the use of "printf" in your interrupt handler.  "printf" generally uses a lot of resources and you have to be careful in its use.  As a rule of thumb, I never use them in interrupt handlers.  

Have you tried your code without the IRQ printfs?  If you want to see what's happening, I'd recommend that you use LEDs instead - but don't flash them (ie turn them on, delay and then turn them off) in your IRQ.  You want to keep your IRQs as small as possible and execute in the absolute minimum amount of time.  

0 Kudos
Reply

1,362 Views
BoiO
Contributor I

I was fully aware of the bad practice of putting printf/large code in the IRQ, I put it there merely as a debugging tool as I only expected the interrupt to trigger once. I didn't expect it to break as it did, so I suppose that is a lesson learned the hard way.

Using LEDs was indeed what I did afterwards, and how I figured out that the code worked if I remapped the interrupt vector to the function.

Just to try out I tried removing the remapping ( NVIC_SetVector(FTM2_IRQn, (uint32_t)&FTM2_IRQHandler); ) whilst using just LEDs instead of printf, but that does not change anything.

So far the only mystery left, seems to be what the default interrupt handler is for the FTM interrupts, as it does not seem to be FTM2_IRQHandler(){}

0 Kudos
Reply

1,355 Views
myke_predko
Senior Contributor III

@BoiO 

Thanx for the additional information, the only suggestion that I would make is enabling the Channel Interrupt Enable ("CHnIE" in the RM) and the Fault Interrupt Enable ("FAULTIE").  In your handler, check the CHnF and FAULTF bits to see if you're getting an unexpected interrupt (you can do that within the debugger or with LEDs).  

As far as I can tell, you've set the interrupt handler correctly - although it never hurts to take a look at the interrupt table in Flash to make sure that the "weak" vector has been overwritten with a pointer to your handler.  

Good luck!

0 Kudos
Reply

1,346 Views
BoiO
Contributor I

Thank you for your suggestion on the CHnIE and FAULTIE interrupts, I will enable those as well. Although it is not very critical in my application to handle faults properly, as a hard reset can always be done and the system is only active for very short time periods.

As for the default interrupt handler not functioning properly, I suppose it is just a peculiarity of mbed. At least there is a 'workaround' that fixes it.

0 Kudos
Reply

1,344 Views
myke_predko
Senior Contributor III

Hey @BoiO 

I think at sometime in an application's development, API return codes should be checked - you might not feel that they're critical or think that the user can reset the application without too much inconvenience, but these assumptions may turn out to be (disasterously) wrong in the future.  

What I typically do is make the API return code check part of the Debug execution (When :SDK_DEBUGCONSOLE" is equal to zero) and then, when I go into production, I build without the check and fault notification.  I just grabbed the following from something I'm working on right now:

#if (2 != SDK_DEBUGCONSOLE)
if (kStatus_Success != (status = DSPI_MasterTransferEDMA(OLED_DSPI_MASTER_BASEADDR
, &oledDSPIEDMAMasterHandle
, &actualOLEDSPIXfer))) {
PRINTF("\nOLEDDSPI_MasterHalfTransferEDMA Error: %i"
, status);
vTaskSuspend(NULL);
}
#else
DSPI_MasterTransferEDMA(OLED_DSPI_MASTER_BASEADDR
, &oledDSPIEDMAMasterHandle
, &actualOLEDSPIXfer);
#endif

It's always interesting when applications that seem to be running correctly and there doesn't seem to be any reason to check on it's operation to discover that there is a problem.  

I'm not saying that this will be the magic bullet for your application but it does give a level of confidence that everything is working properly.  

Keep us updated as to your progress,

myke

 

 

0 Kudos
Reply